diff --git a/README.md b/README.md index ae785f82..f90d1434 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [Atlantis](https://www.runatlantis.io/) is tool which provides unified workflow for collaborating on Terraform through GitHub, GitLab and Bitbucket Cloud. +> [!CAUTION] > Before using Atlantis and the code in this repository, please make sure that you have read and understood the security implications described in [the official Atlantis documentation](https://www.runatlantis.io/docs/security.html). ## Usage @@ -20,7 +21,8 @@ The Atlantis module creates all resources required to run Atlantis on AWS Fargat module "atlantis" { source = "terraform-aws-modules/atlantis/aws" - name = "atlantis" + name = "atlantis" + vpc_id = "vpc-1234556abcdef" # ECS Container Definition atlantis = { @@ -48,6 +50,8 @@ module "atlantis" { # ECS Service service = { + subnet_ids = ["subnet-xyzde987", "subnet-slkjf456", "subnet-qeiru789"] + task_exec_secret_arns = [ "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes256-7g8H9i", "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes192-4D5e6F", @@ -57,11 +61,13 @@ module "atlantis" { AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" } } - service_subnets = ["subnet-xyzde987", "subnet-slkjf456", "subnet-qeiru789"] - vpc_id = "vpc-1234556abcdef" # ALB - alb_subnets = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + alb = { + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + } + + # ACM certificate_domain_name = "example.com" route53_zone_id = "Z2ES7B9AZ6SHAE" @@ -80,7 +86,8 @@ The Atlantis module creates most of resources required to run Atlantis on AWS Fa module "atlantis" { source = "terraform-aws-modules/atlantis/aws" - name = "atlantis" + name = "atlantis" + vpc_id = "vpc-1234556abcdef" # Existing cluster create_cluster = false @@ -117,6 +124,8 @@ module "atlantis" { # ECS Service service = { + subnet_ids = ["subnet-xyzde987", "subnet-slkjf456", "subnet-qeiru789"] + task_exec_secret_arns = [ "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes256-7g8H9i", "arn:aws:secretsmanager:eu-west-1:111122223333:secret:aes192-4D5e6F", @@ -126,8 +135,6 @@ module "atlantis" { AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" } } - service_subnets = ["subnet-xyzde987", "subnet-slkjf456", "subnet-qeiru789"] - vpc_id = "vpc-1234556abcdef" tags = { Environment = "dev" @@ -210,8 +217,8 @@ module "atlantis" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | ~> 5.0 | +| [terraform](#requirement\_terraform) | >= 1.10 | +| [aws](#requirement\_aws) | >= 6.19 | ## Providers @@ -221,11 +228,11 @@ No providers. | Name | Source | Version | |------|--------|---------| -| [acm](#module\_acm) | terraform-aws-modules/acm/aws | 5.0.0 | -| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 9.1.0 | -| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | 5.11.0 | -| [ecs\_service](#module\_ecs\_service) | terraform-aws-modules/ecs/aws//modules/service | 5.11.0 | -| [efs](#module\_efs) | terraform-aws-modules/efs/aws | 1.3.1 | +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | 6.1.1 | +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 10.2.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | 6.7.0 | +| [ecs\_service](#module\_ecs\_service) | terraform-aws-modules/ecs/aws//modules/service | 6.7.0 | +| [efs](#module\_efs) | terraform-aws-modules/efs/aws | 2.0.0 | ## Resources @@ -235,30 +242,26 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [alb](#input\_alb) | Map of values passed to ALB module definition. See the [ALB module](https://github.com/terraform-aws-modules/terraform-aws-alb) for full list of arguments supported | `any` | `{}` | no | -| [alb\_https\_default\_action](#input\_alb\_https\_default\_action) | Default action for the ALB https listener | `any` |
{
"forward": {
"target_group_key": "atlantis"
}
}
| no | +| [alb](#input\_alb) | Map of values passed to ALB module definition. See the [ALB module](https://github.com/terraform-aws-modules/terraform-aws-alb) for full list of arguments supported |
object({
# Load Balancer
access_logs = optional(object({
bucket = string
enabled = optional(bool, true)
prefix = optional(string)
}))
connection_logs = optional(object({
bucket = string
enabled = optional(bool, true)
prefix = optional(string)
}))
drop_invalid_header_fields = optional(bool, true)
enable_cross_zone_load_balancing = optional(bool, true)
enable_deletion_protection = optional(bool, true)
enable_http2 = optional(bool, true)
enable_waf_fail_open = optional(bool)
enable_zonal_shift = optional(bool, true)
idle_timeout = optional(number)
internal = optional(bool)
ip_address_type = optional(string)
name = optional(string)
preserve_host_header = optional(bool)
security_groups = optional(list(string), [])
subnet_ids = optional(list(string), [])

# Listener(s)
default_port = optional(number, 80)
default_protocol = optional(string, "HTTP")
https_listener_ssl_policy = optional(string, "ELBSecurityPolicy-TLS13-1-2-2021-06")
https_default_action = optional(any, {
forward = {
target_group_key = "atlantis"
}
})
https_listener = optional(any, {})
listeners = optional(any, {})

# Target Group(s)
target_groups = optional(any, {})

# Securtity Group(s)
create_security_group = optional(bool, true)
security_group_name = optional(string)
security_group_use_name_prefix = optional(bool, true)
security_group_description = optional(string)
security_group_ingress_rules = optional(map(object({
name = optional(string)
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
})),
# Default
{
http = {
from_port = 80
cidr_ipv4 = "0.0.0.0/0"
}
https = {
from_port = 443
cidr_ipv4 = "0.0.0.0/0"
}
}
)
security_group_egress_rules = optional(
map(object({
name = optional(string)
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
})),
# Default
{
all = {
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}
}
)
security_group_tags = optional(map(string), {})

# Route53 Record(s)
route53_records = optional(map(object({
zone_id = string
name = optional(string)
type = string
evaluate_target_health = optional(bool, true)
})))

# WAF
associate_web_acl = optional(bool, false)
web_acl_arn = optional(string)

tags = optional(map(string), {})
})
| `{}` | no | | [alb\_security\_group\_id](#input\_alb\_security\_group\_id) | ID of an existing security group that will be used by ALB. Required if `create_alb` is `false` | `string` | `""` | no | -| [alb\_subnets](#input\_alb\_subnets) | List of subnets to place ALB in. Required if `create_alb` is `true` | `list(string)` | `[]` | no | | [alb\_target\_group\_arn](#input\_alb\_target\_group\_arn) | ARN of an existing ALB target group that will be used to route traffic to the Atlantis service. Required if `create_alb` is `false` | `string` | `""` | no | -| [atlantis](#input\_atlantis) | Map of values passed to Atlantis container definition. See the [ECS container definition module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/container-definition) for full list of arguments supported | `any` | `{}` | no | -| [atlantis\_gid](#input\_atlantis\_gid) | GID of the atlantis user | `number` | `1000` | no | -| [atlantis\_uid](#input\_atlantis\_uid) | UID of the atlantis user | `number` | `100` | no | +| [atlantis](#input\_atlantis) | Map of values passed to Atlantis container definition. See the [ECS container definition module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/container-definition) for full list of arguments supported |
object({
uid = optional(string, 100)
gid = optional(string, 1000)

command = optional(list(string))
cpu = optional(number, 2048)
dependsOn = optional(list(object({
condition = string
containerName = string
})))
disableNetworking = optional(bool)
dnsSearchDomains = optional(list(string))
dnsServers = optional(list(string))
dockerLabels = optional(map(string))
dockerSecurityOptions = optional(list(string))
entrypoint = optional(list(string))
environment = optional(list(object({
name = string
value = string
})), [])
environmentFiles = optional(list(object({
type = string
value = string
})))
extraHosts = optional(list(object({
hostname = string
ipAddress = string
})))
firelensConfiguration = optional(object({
type = string
options = optional(map(string))
configFile = optional(object({
type = string
content = string
}))
}))
healthCheck = optional(object({
command = optional(list(string), [])
interval = optional(number, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
}))
hostname = optional(string)
image = optional(string, "ghcr.io/runatlantis/atlantis:latest")
linuxParameters = optional(object({
capabilities = optional(object({
add = optional(list(string))
drop = optional(list(string))
}))
devices = optional(list(object({
containerPath = optional(string)
hostPath = optional(string)
permissions = optional(list(string))
})))
initProcessEnabled = optional(bool)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}))
logConfiguration = optional(object({
logDriver = optional(string)
options = optional(map(string))
secretOptions = optional(list(object({
name = string
valueFrom = string
})))
}))
memory = optional(number, 4096)
memoryReservation = optional(number)
mountPoints = optional(list(object({
containerPath = optional(string)
readOnly = optional(bool)
sourceVolume = optional(string)
})))
port = optional(number, 4141)
privileged = optional(bool, false)
readonlyRootFilesystem = optional(bool, false)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool, true)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
}),
# Default
{
enabled = true
}
)
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number, 30)
stopTimeout = optional(number, 120)
user = optional(string, "atlantis")
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})))
workingDirectory = optional(string)

# CloudWatch Log Group
enable_cloudwatch_logging = optional(bool, true)
create_cloudwatch_log_group = optional(bool, true)
cloudwatch_log_group_use_name_prefix = optional(bool, true)
cloudwatch_log_group_retention_in_days = optional(number, 14)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_kms_key_id = optional(string)
})
| `{}` | no | | [certificate\_arn](#input\_certificate\_arn) | ARN of certificate issued by AWS ACM. If empty, a new ACM certificate will be created and validated using Route53 DNS | `string` | `""` | no | | [certificate\_domain\_name](#input\_certificate\_domain\_name) | Route53 domain name to use for ACM certificate. Route53 zone for this domain should be created in advance. Specify if it is different from value in `route53_zone_name` | `string` | `""` | no | -| [cluster](#input\_cluster) | Map of values passed to ECS cluster module definition. See the [ECS cluster module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/cluster) for full list of arguments supported | `any` | `{}` | no | +| [cluster](#input\_cluster) | Map of values passed to ECS cluster module definition. See the [ECS cluster module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/cluster) for full list of arguments supported |
object({
# Cluster
name = optional(string)
configuration = optional(object({
execute_command_configuration = optional(object({
kms_key_id = optional(string)
log_configuration = optional(object({
cloud_watch_encryption_enabled = optional(bool)
cloud_watch_log_group_name = optional(string)
s3_bucket_encryption_enabled = optional(bool)
s3_bucket_name = optional(string)
s3_kms_key_id = optional(string)
s3_key_prefix = optional(string)
}))
logging = optional(string, "OVERRIDE")
}))
managed_storage_configuration = optional(object({
fargate_ephemeral_storage_kms_key_id = optional(string)
kms_key_id = optional(string)
}))
}),
# Default
{
execute_command_configuration = {
log_configuration = {
cloud_watch_log_group_name = "placeholder" # will use CloudWatch log group created by module
}
}
}
)
setting = optional(list(object({
name = string
value = string
})),
# Default
[{
name = "containerInsights"
value = "enabled"
}]
)

# Cloudwatch log group
create_cloudwatch_log_group = optional(bool, true)
cloudwatch_log_group_retention_in_days = optional(number, 90)
cloudwatch_log_group_kms_key_id = optional(string)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_tags = optional(map(string), {})

# Capacity providers
default_capacity_provider_strategy = optional(
map(object({
base = optional(number)
name = optional(string) # Will fall back to use map key if not set
weight = optional(number)
})),
# Default
{
FARGATE = {
weight = 100
}
}
)
})
| `{}` | no | | [cluster\_arn](#input\_cluster\_arn) | ARN of an existing ECS cluster where resources will be created. Required when `create_cluster` is `false` | `string` | `""` | no | | [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | | [create\_alb](#input\_create\_alb) | Determines whether to create an ALB or not | `bool` | `true` | no | | [create\_certificate](#input\_create\_certificate) | Determines whether to create an ACM certificate or not. If `false`, `certificate_arn` must be provided | `bool` | `true` | no | | [create\_cluster](#input\_create\_cluster) | Whether to create an ECS cluster or not | `bool` | `true` | no | | [create\_route53\_records](#input\_create\_route53\_records) | Determines whether to create Route53 `A` and `AAAA` records for the loadbalancer | `bool` | `true` | no | -| [efs](#input\_efs) | Map of values passed to EFS module definition. See the [EFS module](https://github.com/terraform-aws-modules/terraform-aws-efs) for full list of arguments supported | `any` | `{}` | no | +| [efs](#input\_efs) | Map of values passed to EFS module definition. See the [EFS module](https://github.com/terraform-aws-modules/terraform-aws-efs) for full list of arguments supported |
object({
name = optional(string)

# File System
availability_zone_name = optional(string)
creation_token = optional(string)
performance_mode = optional(string)
encrypted = optional(bool, true)
kms_key_arn = optional(string)
provisioned_throughput_in_mibps = optional(number)
throughput_mode = optional(string)
lifecycle_policy = optional(object({
transition_to_ia = optional(string)
transition_to_archive = optional(string)
transition_to_primary_storage_class = optional(string)
}))
protection = optional(object({
replication_overwrite = optional(string)
}))

# File System Policy
attach_policy = optional(bool, true)
bypass_policy_lockout_safety_check = optional(bool)
source_policy_documents = optional(list(string), [])
override_policy_documents = optional(list(string), [])
policy_statements = optional(any, {})
deny_nonsecure_transport = optional(bool, true)
deny_nonsecure_transport_via_mount_target = optional(bool, true)

# Mount targets
mount_targets = optional(map(object({
ip_address = optional(string)
ip_address_type = optional(string)
ipv6_address = optional(string)
region = optional(string)
security_groups = optional(list(string), [])
subnet_id = string
})),
# Default
{}
)

# Security Group
create_security_group = optional(bool, true)
security_group_name = optional(string)
security_group_use_name_prefix = optional(bool, true)
security_group_description = optional(string)
security_group_ingress_rules = optional(any, {})

# Access Point(s)
access_points = optional(any, {})
})
| `{}` | no | | [enable\_efs](#input\_enable\_efs) | Determines whether to create and utilize an EFS filesystem | `bool` | `false` | no | | [name](#input\_name) | Common name to use on all resources created unless a more specific name is provided | `string` | `"atlantis"` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | | [route53\_record\_name](#input\_route53\_record\_name) | Name of Route53 record to create ACM certificate in and main A-record. If null is specified, var.name is used instead. Provide empty string to point root domain name to ALB. | `string` | `null` | no | | [route53\_zone\_id](#input\_route53\_zone\_id) | Route53 zone ID to use for ACM certificate and Route53 records | `string` | `""` | no | -| [service](#input\_service) | Map of values passed to ECS service module definition. See the [ECS service module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/service) for full list of arguments supported | `any` | `{}` | no | -| [service\_subnets](#input\_service\_subnets) | List of subnets to place ECS service within | `list(string)` | `[]` | no | +| [service](#input\_service) | Map of values passed to ECS service module definition. See the [ECS service module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/service) for full list of arguments supported |
object({
capacity_provider_strategy = optional(map(object({
base = optional(number)
capacity_provider = string
weight = optional(number)
})))
deployment_circuit_breaker = optional(object({
enable = bool
rollback = bool
}))
enable_ecs_managed_tags = optional(bool, true)
force_new_deployment = optional(bool, true)
health_check_grace_period_seconds = optional(number)
launch_type = optional(string, "FARGATE")
load_balancer = optional(any, {})
name = optional(string)
assign_public_ip = optional(bool, false)
security_group_ids = optional(list(string), [])
subnet_ids = optional(list(string), [])
platform_version = optional(string)
propagate_tags = optional(string)
timeouts = optional(object({
create = optional(string)
delete = optional(string)
update = optional(string)
}))
triggers = optional(map(string))
wait_for_steady_state = optional(bool)

# Service IAM Role
create_iam_role = optional(bool, true)
iam_role_arn = optional(string)
iam_role_name = optional(string)
iam_role_use_name_prefix = optional(bool, true)
iam_role_path = optional(string)
iam_role_description = optional(string)
iam_role_permissions_boundary = optional(string)
iam_role_tags = optional(map(string), {})
iam_role_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))

# Task Definition
create_task_definition = optional(bool, true)
task_definition_arn = optional(string)
container_definitions = optional(any, {})
cpu = optional(number, 2048)
ephemeral_storage = optional(object({
size_in_gib = number
}))
family = optional(string)
memory = optional(number, 4096)
requires_compatibilities = optional(list(string), ["FARGATE"])
runtime_platform = optional(
object({
cpu_architecture = optional(string)
operating_system_family = optional(string)
}),
# Default
{
operating_system_family = "LINUX"
cpu_architecture = "ARM64"
}
)
volume = optional(any, {})
task_tags = optional(map(string), {})

# Task Execution IAM Role
create_task_exec_iam_role = optional(bool, true)
task_exec_iam_role_arn = optional(string)
task_exec_iam_role_name = optional(string)
task_exec_iam_role_use_name_prefix = optional(bool, true)
task_exec_iam_role_path = optional(string)
task_exec_iam_role_description = optional(string)
task_exec_iam_role_permissions_boundary = optional(string)
task_exec_iam_role_tags = optional(map(string), {})
task_exec_iam_role_policies = optional(map(string), {})
task_exec_iam_role_max_session_duration = optional(number)

# Task Execution IAM Role Policy
create_task_exec_policy = optional(bool, true)
task_exec_ssm_param_arns = optional(list(string), [])
task_exec_secret_arns = optional(list(string), [])
task_exec_iam_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))

# Tasks - IAM role
create_tasks_iam_role = optional(bool, true)
tasks_iam_role_arn = optional(string)
tasks_iam_role_name = optional(string)
tasks_iam_role_use_name_prefix = optional(bool, true)
tasks_iam_role_path = optional(string)
tasks_iam_role_description = optional(string)
tasks_iam_role_permissions_boundary = optional(string)
tasks_iam_role_tags = optional(map(string), {})
tasks_iam_role_policies = optional(map(string), {})
tasks_iam_role_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))

# Security Group
create_security_group = optional(bool, true)
security_group_name = optional(string)
security_group_use_name_prefix = optional(bool, true)
security_group_description = optional(string)
security_group_ingress_rules = optional(any, {})
security_group_egress_rules = optional(any,
{
egress = {
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}
}
)
security_group_tags = optional(map(string), {})
})
| `{}` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | | [validate\_certificate](#input\_validate\_certificate) | Determines whether to validate ACM certificate using Route53 DNS. If `false`, certificate will be created but not validated | `bool` | `true` | no | | [vpc\_id](#input\_vpc\_id) | ID of the VPC where the resources will be provisioned | `string` | `""` | no | diff --git a/docs/UPGRADE-5.0.md b/docs/UPGRADE-5.0.md new file mode 100644 index 00000000..daef62ee --- /dev/null +++ b/docs/UPGRADE-5.0.md @@ -0,0 +1,140 @@ +# Upgrade from v4.x to v5.x + +Please consult the `examples` directory for reference example configurations. If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- Minimum supported version of Terraform AWS provider updated to `v6.19` to support the latest resources utilized +- Minimum supported version of Terraform updated to `v1.10` (min supported version for ACM module used within this module) +- The underlying `aws_security_group_rule` have been replaced with `aws_vpc_security_group_ingress_rule` and `aws_vpc_security_group_egress_rule` to allow for more flexibility in defining security group rules. +- The attributes used to construct the container definition(s) have been changed from HCL's norm of `snake_case` to `camelCase` to match the AWS API. There currently isn't a [resource nor data source for the container definition](https://github.com/hashicorp/terraform-provider-aws/issues/17988), so one is constructed entirely from HCL in the `container-definition` sub-module. This definition is then rendered as JSON when presented to the task definition (or task set) APIs. Previously, the variable names used were `snake_case` and then internally converted to `camelCase`. However, this does not allow for [using the `container-definition` sub-module on its own](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/147) due to the mismatch between casing. Its probably going to trip a few folks up, but hopefully we'll remove this for a data source in the future. +- `service.task_exec_ssm_param_arns` default of `["arn:aws:ssm:*:*:parameter/*"]` has been removed to prevent unintended permission grants. If you were relying on this default, you will need to explicitly set this variable in your configuration. +- `service.task_exec_secret_arns` default of `["arn:aws:secretsmanager:*:*:secret:*"]` has been removed to prevent unintended permission grants. If you were relying on this default, you will need to explicitly set this variable in your configuration. + +## Additional changes + +### Added + +- Support for `region` argument to specify the AWS region for the resources created if different from the provider region. + +### Modified + +- The ALB module used within this module has been updated to `v10.2.0` +- The ECS cluster and service modules used within this module have been updated to `v6.7.0` +- The ACM module used within this module has been updated to `v6.1.1` +- The EFS module used within this module has been updated to `v2.0.0` +- Variable definitions now contain detailed object types in place of the previously used `any` type + +### Removed + +- None + +### Variable and output changes + +1. Removed variables: + + - `atlantis_gid` -> is now `atlantis.group_id` within the `atlantis` object variable + - `atlantis_uid` -> is now `atlantis.user_id` within the `atlantis` object variable + - `alb_https_default_action` -> replaced by `alb.https_default_action` within the `alb` object variable + - `alb_subnets` -> replaced by `alb.subnet_ids` within the `alb` object variable + - `service_subnets` -> replaced by `service.subnet_ids` within the `service` object variable + - From the `alb` object variable: + - `customer_owned_ipv4_pool` + - `desync_mitigation_mode` + - `dns_record_client_routing_policy` + - `enable_tls_version_and_cipher_suite_headers` + - `enable_xff_client_port` + - `load_balancer_type` + - `xff_header_processing_mode` + - From the `service` object variable: + - `ignore_task_definition_changes` + - `alarms` + - `deployment_controller` + - `deployment_maximum_percent` - Atlantis only supports 1 running instance + - `deployment_minimum_healthy_percent` - Atlantis only supports 1 running instance + - `desired_count` - Atlantis only supports 1 running instance + - `enable_execute_command` + - `ordered_placement_strategy` + - `placement_constraints` + - `scheduling_strategy` + - `service_connect_configuration` + - `service_registries` + - `container_definition_defaults` + - `inference_accelerator` + - `ipc_mode` + - `pid_mode` + - `task_definition_placement_constraints` + - `proxy_configuration` + - `skip_destroy` + - `external_id` + - `scale` + - `force_delete` + - `wait_until_stable` + - `wait_until_stable_timeout` + - `enable_autoscaling` + - `autoscaling_min_capacity` + - `autoscaling_max_capacity` + - `autoscaling_policies` + - `autoscaling_scheduled_actions` + - From the `atlantis` object variable: + - `essential` - now always true + - `extra_hosts` + - `interactive` + - `links` + - `pseudo_terminal` + - `system_controls` + - From the `efs` object variable: + - `create_backup_policy` + - `enable_backup_policy` + - `create_replication_configuration` + - `replication_configuration_destination` + +2. Renamed variables: + + - `cluster.settings` -> `cluster.setting` (singular) + - `cluster.fargate_capacity_providers` -> replaced by `cluster.default_capacity_provider_strategy` + +3. Added variables: + + - `region` + +4. Removed outputs: + + - None + +5. Renamed outputs: + + - None + +6. Added outputs: + + - None + +## Upgrade Migrations + +### Diff of Before vs After + +```diff + module "atlantis" { + source = "terraform-aws-modules/atlantis/aws" +- version = "4.4.1" ++ version = "5.0.0" + +# Truncated for brevity, only the relevant changes shown + +- alb_subnets = module.vpc.public_subnets +alb = { ++ subnet_ids = module.vpc.public_subnets + ... +} + +- service_subnets = module.vpc.private_subnets +service = { ++ subnet_ids = module.vpc.private_subnets + ... +} +``` + +### State Move Commands + +None - the security group rules will be replaced on apply due to the change from `aws_security_group_rule` to `aws_vpc_security_group_ingress_rule` and `aws_vpc_security_group_egress_rule` diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index 0713a7e0..f1fb3158 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -19,8 +19,8 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | ~> 5.0 | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [aws](#requirement\_aws) | >= 6.19 | | [github](#requirement\_github) | >= 5.0 | | [random](#requirement\_random) | >= 3.0 | @@ -28,7 +28,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 5.0 | +| [aws](#provider\_aws) | >= 6.19 | | [random](#provider\_random) | >= 3.0 | ## Modules @@ -37,8 +37,8 @@ Note that this example may create resources which cost money. Run `terraform des |------|--------|---------| | [atlantis](#module\_atlantis) | ../../ | n/a | | [github\_repository\_webhooks](#module\_github\_repository\_webhooks) | ../../modules/github-repository-webhook | n/a | -| [secrets\_manager](#module\_secrets\_manager) | terraform-aws-modules/secrets-manager/aws | ~> 1.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [secrets\_manager](#module\_secrets\_manager) | terraform-aws-modules/secrets-manager/aws | ~> 2.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources diff --git a/examples/github-complete/main.tf b/examples/github-complete/main.tf index cb387c72..8381399f 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -34,9 +34,10 @@ locals { module "atlantis" { source = "../../" - name = local.name + name = local.name + vpc_id = module.vpc.vpc_id - # ECS + # ECS Container Definition atlantis = { environment = [ { @@ -64,7 +65,10 @@ module "atlantis" { ] } + # ECS Service service = { + subnet_ids = module.vpc.private_subnets + task_exec_secret_arns = [for sec in module.secrets_manager : sec.secret_arn] # Provide Atlantis permission necessary to create/destroy resources tasks_iam_role_policies = { @@ -74,14 +78,12 @@ module "atlantis" { # ALB alb = { + subnet_ids = module.vpc.public_subnets + # For example only enable_deletion_protection = false } - alb_subnets = module.vpc.public_subnets - service_subnets = module.vpc.private_subnets - vpc_id = module.vpc.vpc_id - # ACM certificate_domain_name = "${local.name}.${var.domain}" route53_zone_id = data.aws_route53_zone.this.id @@ -125,7 +127,7 @@ resource "random_password" "webhook_secret" { module "secrets_manager" { source = "terraform-aws-modules/secrets-manager/aws" - version = "~> 1.0" + version = "~> 2.0" for_each = { github-token = { @@ -137,16 +139,17 @@ module "secrets_manager" { } # Secret - name_prefix = each.key - recovery_window_in_days = 0 # For example only - secret_string = each.value.secret_string + name_prefix = each.key + recovery_window_in_days = 0 # For example only + secret_string_wo = each.value.secret_string + secret_string_wo_version = 2 tags = local.tags } module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr diff --git a/examples/github-complete/versions.tf b/examples/github-complete/versions.tf index 0b115f06..8427c067 100644 --- a/examples/github-complete/versions.tf +++ b/examples/github-complete/versions.tf @@ -1,17 +1,15 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.11" required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.0" + version = ">= 6.19" } - github = { source = "integrations/github" version = ">= 5.0" } - random = { source = "hashicorp/random" version = ">= 3.0" diff --git a/examples/github-separate/README.md b/examples/github-separate/README.md index a0720588..04812ad3 100644 --- a/examples/github-separate/README.md +++ b/examples/github-separate/README.md @@ -19,8 +19,8 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | ~> 5.0 | +| [terraform](#requirement\_terraform) | >= 1.11 | +| [aws](#requirement\_aws) | >= 6.19 | | [github](#requirement\_github) | >= 5.0 | | [random](#requirement\_random) | >= 3.0 | @@ -28,20 +28,20 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 5.0 | +| [aws](#provider\_aws) | >= 6.19 | | [random](#provider\_random) | >= 3.0 | ## Modules | Name | Source | Version | |------|--------|---------| -| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 9.1.0 | +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 10.2.0 | | [atlantis](#module\_atlantis) | ../../ | n/a | | [atlantis\_disabled](#module\_atlantis\_disabled) | ../../ | n/a | -| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | 5.6.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | 6.7.0 | | [github\_repository\_webhooks](#module\_github\_repository\_webhooks) | ../../modules/github-repository-webhook | n/a | -| [secrets\_manager](#module\_secrets\_manager) | terraform-aws-modules/secrets-manager/aws | ~> 1.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [secrets\_manager](#module\_secrets\_manager) | terraform-aws-modules/secrets-manager/aws | ~> 2.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources diff --git a/examples/github-separate/main.tf b/examples/github-separate/main.tf index 4d25ea28..35f89b4d 100644 --- a/examples/github-separate/main.tf +++ b/examples/github-separate/main.tf @@ -30,7 +30,8 @@ locals { module "atlantis" { source = "../../" - name = local.name + name = local.name + vpc_id = module.vpc.vpc_id # Existing cluster create_cluster = false @@ -41,7 +42,7 @@ module "atlantis" { alb_target_group_arn = module.alb.target_groups["atlantis"].arn alb_security_group_id = module.alb.security_group_id - # ECS + # ECS Container Definition atlantis = { environment = [ { @@ -70,7 +71,10 @@ module "atlantis" { fqdn = module.alb.dns_name } + # ECS Service service = { + subnet_ids = module.vpc.private_subnets + task_exec_secret_arns = [for sec in module.secrets_manager : sec.secret_arn] # Provide Atlantis permission necessary to create/destroy resources tasks_iam_role_policies = { @@ -78,9 +82,6 @@ module "atlantis" { } } - service_subnets = module.vpc.private_subnets - vpc_id = module.vpc.vpc_id - tags = local.tags } @@ -105,21 +106,21 @@ module "atlantis_disabled" { module "ecs_cluster" { source = "terraform-aws-modules/ecs/aws//modules/cluster" - version = "5.6.0" + version = "6.7.0" # Cluster - cluster_name = local.name - cluster_settings = { + name = local.name + setting = [{ name = "containerInsights" value = "enabled" - } + }] tags = local.tags } module "alb" { source = "terraform-aws-modules/alb/aws" - version = "9.1.0" + version = "10.2.0" name = local.name @@ -192,7 +193,7 @@ resource "random_password" "webhook_secret" { module "secrets_manager" { source = "terraform-aws-modules/secrets-manager/aws" - version = "~> 1.0" + version = "~> 2.0" for_each = { github-token = { @@ -204,16 +205,17 @@ module "secrets_manager" { } # Secret - name_prefix = each.key - recovery_window_in_days = 0 # For example only - secret_string = each.value.secret_string + name_prefix = each.key + recovery_window_in_days = 0 # For example only + secret_string_wo = each.value.secret_string + secret_string_wo_version = 2 tags = local.tags } module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr diff --git a/examples/github-separate/versions.tf b/examples/github-separate/versions.tf index 0b115f06..8427c067 100644 --- a/examples/github-separate/versions.tf +++ b/examples/github-separate/versions.tf @@ -1,17 +1,15 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.11" required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.0" + version = ">= 6.19" } - github = { source = "integrations/github" version = ">= 5.0" } - random = { source = "hashicorp/random" version = ">= 3.0" diff --git a/main.tf b/main.tf index 84317650..0eb5bf03 100644 --- a/main.tf +++ b/main.tf @@ -4,8 +4,6 @@ locals { try(var.atlantis.fqdn, module.alb.route53_records["A"].fqdn, null), module.alb.dns_name, ), "")}" - - atlantis_port = try(var.atlantis.port, 4141) } ################################################################################ @@ -29,36 +27,32 @@ locals { module "alb" { source = "terraform-aws-modules/alb/aws" - version = "9.1.0" + version = "10.2.0" + region = var.region create = var.create && var.create_alb # Load balancer - access_logs = lookup(var.alb, "access_logs", {}) - customer_owned_ipv4_pool = try(var.alb.customer_owned_ipv4_pool, null) - desync_mitigation_mode = try(var.alb.desync_mitigation_mode, null) - dns_record_client_routing_policy = try(var.alb.dns_record_client_routing_policy, null) - drop_invalid_header_fields = try(var.alb.drop_invalid_header_fields, true) - enable_cross_zone_load_balancing = try(var.alb.enable_cross_zone_load_balancing, true) - enable_deletion_protection = try(var.alb.enable_deletion_protection, true) - enable_http2 = try(var.alb.enable_http2, null) - enable_tls_version_and_cipher_suite_headers = try(var.alb.enable_tls_version_and_cipher_suite_headers, null) - enable_waf_fail_open = try(var.alb.enable_waf_fail_open, null) - enable_xff_client_port = try(var.alb.enable_xff_client_port, null) - idle_timeout = try(var.alb.idle_timeout, null) - internal = try(var.alb.internal, false) - ip_address_type = try(var.alb.ip_address_type, null) - load_balancer_type = try(var.alb.load_balancer_type, "application") - name = try(var.alb.name, var.name) - preserve_host_header = try(var.alb.preserve_host_header, null) - security_groups = try(var.alb.security_groups, []) - subnets = try(var.alb.subnets, var.alb_subnets) - xff_header_processing_mode = try(var.alb.xff_header_processing_mode, null) - timeouts = try(var.alb.timeouts, {}) + access_logs = var.alb.access_logs + connection_logs = var.alb.connection_logs + drop_invalid_header_fields = var.alb.drop_invalid_header_fields + enable_cross_zone_load_balancing = var.alb.enable_cross_zone_load_balancing + enable_deletion_protection = var.alb.enable_deletion_protection + enable_http2 = var.alb.enable_http2 + enable_waf_fail_open = var.alb.enable_waf_fail_open + enable_zonal_shift = var.alb.enable_zonal_shift + idle_timeout = var.alb.idle_timeout + internal = var.alb.internal + ip_address_type = var.alb.ip_address_type + load_balancer_type = "application" + name = try(coalesce(var.alb.name, var.name), "") + preserve_host_header = var.alb.preserve_host_header + security_groups = var.alb.security_groups + subnets = var.alb.subnet_ids # Listener(s) - default_port = try(var.alb.default_port, 80) - default_protocol = try(var.alb.default_protocol, "HTTP") + default_port = var.alb.default_port + default_protocol = var.alb.default_protocol listeners = merge( { http-https-redirect = { @@ -76,27 +70,26 @@ module "alb" { { port = 443 protocol = "HTTPS" - ssl_policy = try(var.alb.https_listener_ssl_policy, "ELBSecurityPolicy-TLS13-1-2-Res-2021-06") + ssl_policy = var.alb.https_listener_ssl_policy certificate_arn = var.create_certificate ? module.acm.acm_certificate_arn : var.certificate_arn }, - var.alb_https_default_action, - lookup(var.alb, "https_listener", {}) + var.alb.https_default_action, + var.alb.https_listener, ) }, - lookup(var.alb, "listeners", {}) + var.alb.listeners ) # Target group(s) target_groups = merge( { atlantis = { - name = var.name - protocol = "HTTP" - port = local.atlantis_port - create_attachment = false - target_type = "ip" - deregistration_delay = 10 - load_balancing_cross_zone_enabled = true + name = var.name + protocol = "HTTP" + port = var.atlantis.port + create_attachment = false + target_type = "ip" + deregistration_delay = 10 health_check = { enabled = true @@ -111,54 +104,32 @@ module "alb" { } } }, - lookup(var.alb, "target_groups", {}) + var.alb.target_groups ) # Security group - create_security_group = try(var.alb.create_security_group, true) - security_group_name = try(var.alb.security_group_name, var.name) - security_group_use_name_prefix = try(var.alb.security_group_use_name_prefix, true) - security_group_description = try(var.alb.security_group_description, null) + create_security_group = var.alb.create_security_group + security_group_name = try(coalesce(var.alb.security_group_name, var.name), "") + security_group_use_name_prefix = var.alb.security_group_use_name_prefix + security_group_description = var.alb.security_group_description vpc_id = var.vpc_id - security_group_ingress_rules = lookup(var.alb, "security_group_ingress_rules", - { - http = { - from_port = 80 - to_port = 80 - ip_protocol = "tcp" - cidr_ipv4 = "0.0.0.0/0" - } - https = { - from_port = 443 - to_port = 443 - ip_protocol = "tcp" - cidr_ipv4 = "0.0.0.0/0" - } - } - ) - security_group_egress_rules = lookup(var.alb, "security_group_egress_rules", - { - all = { - ip_protocol = "-1" - cidr_ipv4 = "0.0.0.0/0" - } - } - ) - security_group_tags = try(var.alb.security_group_tags, {}) + security_group_ingress_rules = var.alb.security_group_ingress_rules + security_group_egress_rules = var.alb.security_group_egress_rules + security_group_tags = var.alb.security_group_tags # Route53 record(s) route53_records = merge( { for k, v in local.route53_records : k => v if var.create_route53_records }, - lookup(var.alb, "route53_records", {}) + var.alb.route53_records ) # WAF - associate_web_acl = try(var.alb.associate_web_acl, false) - web_acl_arn = try(var.alb.web_acl_arn, null) + associate_web_acl = var.alb.associate_web_acl + web_acl_arn = var.alb.web_acl_arn tags = merge( - try(var.alb.tags, {}), - var.tags + var.tags, + var.alb.tags, ) } @@ -168,8 +139,9 @@ module "alb" { module "acm" { source = "terraform-aws-modules/acm/aws" - version = "5.0.0" + version = "6.1.1" + region = var.region create_certificate = var.create && var.create_certificate && var.create_alb domain_name = var.certificate_domain_name @@ -190,275 +162,238 @@ locals { containerPath = local.mount_path sourceVolume = "efs" readOnly = false - }] : try(var.atlantis.mount_points, []) - - # Ref https://github.com/terraform-aws-modules/terraform-aws-atlantis/issues/383 - deployment_maximum_percent = var.enable_efs ? 100 : 200 - deployment_minimum_healthy_percent = var.enable_efs ? 0 : 66 + }] : var.atlantis.mountPoints } module "ecs_cluster" { source = "terraform-aws-modules/ecs/aws//modules/cluster" - version = "5.11.0" + version = "6.7.0" + region = var.region create = var.create && var.create_cluster # Cluster - cluster_name = try(var.cluster.name, var.name) - cluster_configuration = try(var.cluster.configuration, {}) - cluster_settings = try(var.cluster.settings, { - name = "containerInsights" - value = "enabled" - } - ) + name = try(coalesce(var.cluster.name, var.name)) + configuration = var.cluster.configuration + setting = var.cluster.setting # Cloudwatch log group - create_cloudwatch_log_group = try(var.cluster.create_cloudwatch_log_group, true) - cloudwatch_log_group_retention_in_days = try(var.cluster.cloudwatch_log_group_retention_in_days, 90) - cloudwatch_log_group_kms_key_id = try(var.cluster.cloudwatch_log_group_kms_key_id, null) - cloudwatch_log_group_tags = try(var.cluster.cloudwatch_log_group_tags, {}) + create_cloudwatch_log_group = var.cluster.create_cloudwatch_log_group + cloudwatch_log_group_retention_in_days = var.cluster.cloudwatch_log_group_retention_in_days + cloudwatch_log_group_kms_key_id = var.cluster.cloudwatch_log_group_kms_key_id + cloudwatch_log_group_class = var.cluster.cloudwatch_log_group_class + cloudwatch_log_group_tags = var.cluster.cloudwatch_log_group_tags # Capacity providers - fargate_capacity_providers = try(var.cluster.fargate_capacity_providers, {}) + default_capacity_provider_strategy = var.cluster.default_capacity_provider_strategy tags = var.tags } module "ecs_service" { source = "terraform-aws-modules/ecs/aws//modules/service" - version = "5.11.0" + version = "6.7.0" + region = var.region create = var.create # Service - ignore_task_definition_changes = try(var.service.ignore_task_definition_changes, false) - alarms = try(var.service.alarms, {}) - capacity_provider_strategy = try(var.service.capacity_provider_strategy, {}) + capacity_provider_strategy = var.service.capacity_provider_strategy cluster_arn = var.create_cluster && var.create ? module.ecs_cluster.arn : var.cluster_arn - deployment_controller = try(var.service.deployment_controller, {}) - deployment_maximum_percent = try(var.service.deployment_maximum_percent, local.deployment_maximum_percent) - deployment_minimum_healthy_percent = try(var.service.deployment_minimum_healthy_percent, local.deployment_minimum_healthy_percent) - desired_count = try(var.service.desired_count, 1) - enable_ecs_managed_tags = try(var.service.enable_ecs_managed_tags, true) - enable_execute_command = try(var.service.enable_execute_command, false) - force_new_deployment = try(var.service.force_new_deployment, true) - health_check_grace_period_seconds = try(var.service.health_check_grace_period_seconds, null) - launch_type = try(var.service.launch_type, "FARGATE") + deployment_circuit_breaker = var.service.deployment_circuit_breaker + deployment_maximum_percent = 100 + deployment_minimum_healthy_percent = 0 + desired_count = 1 + enable_ecs_managed_tags = var.service.enable_ecs_managed_tags + enable_execute_command = false + force_new_deployment = var.service.force_new_deployment + health_check_grace_period_seconds = var.service.health_check_grace_period_seconds + launch_type = var.service.launch_type load_balancer = merge( { service = { - target_group_arn = var.create_alb && var.create ? module.alb.target_groups["atlantis"].arn : var.alb_target_group_arn + target_group_arn = var.create && var.create_alb ? module.alb.target_groups["atlantis"].arn : var.alb_target_group_arn container_name = "atlantis" - container_port = local.atlantis_port + container_port = var.atlantis.port } }, - lookup(var.service, "load_balancer", {}) + var.service.load_balancer ) - name = try(var.service.name, var.name) - assign_public_ip = try(var.service.assign_public_ip, false) - security_group_ids = try(var.service.security_group_ids, []) - subnet_ids = try(var.service.subnet_ids, var.service_subnets) - ordered_placement_strategy = try(var.service.ordered_placement_strategy, {}) - placement_constraints = try(var.service.placement_constraints, {}) - platform_version = try(var.service.platform_version, null) - propagate_tags = try(var.service.propagate_tags, null) - scheduling_strategy = try(var.service.scheduling_strategy, null) - service_connect_configuration = lookup(var.service, "service_connect_configuration", {}) - service_registries = lookup(var.service, "service_registries", {}) - timeouts = try(var.service.timeouts, {}) - triggers = try(var.service.triggers, {}) - wait_for_steady_state = try(var.service.wait_for_steady_state, null) + name = try(coalesce(var.service.name, var.name)) + assign_public_ip = var.service.assign_public_ip + security_group_ids = var.service.security_group_ids + subnet_ids = var.service.subnet_ids + platform_version = var.service.platform_version + propagate_tags = var.service.propagate_tags + timeouts = var.service.timeouts + triggers = var.service.triggers + wait_for_steady_state = var.service.wait_for_steady_state # Service IAM role - create_iam_role = try(var.service.create_iam_role, true) - iam_role_arn = try(var.service.iam_role_arn, null) - iam_role_name = try(var.service.iam_role_name, null) - iam_role_use_name_prefix = try(var.service.iam_role_use_name_prefix, true) - iam_role_path = try(var.service.iam_role_path, null) - iam_role_description = try(var.service.iam_role_description, null) - iam_role_permissions_boundary = try(var.service.iam_role_permissions_boundary, null) - iam_role_tags = try(var.service.iam_role_tags, {}) - iam_role_statements = lookup(var.service, "iam_role_statements", {}) + create_iam_role = var.service.create_iam_role + iam_role_arn = var.service.iam_role_arn + iam_role_name = var.service.iam_role_name + iam_role_use_name_prefix = var.service.iam_role_use_name_prefix + iam_role_path = var.service.iam_role_path + iam_role_description = var.service.iam_role_description + iam_role_permissions_boundary = var.service.iam_role_permissions_boundary + iam_role_tags = var.service.iam_role_tags + iam_role_statements = var.service.iam_role_statements # Task definition - create_task_definition = try(var.service.create_task_definition, true) - task_definition_arn = try(var.service.task_definition_arn, null) + create_task_definition = var.service.create_task_definition + task_definition_arn = var.service.task_definition_arn container_definitions = merge( { atlantis = { - command = try(var.atlantis.command, []) - cpu = try(var.atlantis.cpu, 1024) - dependencies = try(var.atlantis.dependencies, []) # depends_on is a reserved word - disable_networking = try(var.atlantis.disable_networking, null) - dns_search_domains = try(var.atlantis.dns_search_domains, []) - dns_servers = try(var.atlantis.dns_servers, []) - docker_labels = try(var.atlantis.docker_labels, {}) - docker_security_options = try(var.atlantis.docker_security_options, []) - enable_execute_command = try(var.atlantis.enable_execute_command, try(var.service.enable_execute_command, false)) - entrypoint = try(var.atlantis.entrypoint, []) + command = var.atlantis.command + cpu = var.atlantis.cpu + dependsOn = var.atlantis.dependsOn + disableNetworking = var.atlantis.disableNetworking + dnsSearchDomains = var.atlantis.dnsSearchDomains + dnsServers = var.atlantis.dnsServers + dockerLabels = var.atlantis.dockerLabels + dockerSecurityOptions = var.atlantis.dockerSecurityOptions + entrypoint = var.atlantis.entrypoint environment = concat( [ { name = "ATLANTIS_PORT" - value = local.atlantis_port + value = var.atlantis.port }, { name = "ATLANTIS_ATLANTIS_URL" value = local.atlantis_url }, ], - lookup(var.atlantis, "environment", []) + var.atlantis.environment ) - environment_files = try(var.atlantis.environment_files, []) - essential = try(var.atlantis.essential, true) - extra_hosts = try(var.atlantis.extra_hosts, []) - firelens_configuration = try(var.atlantis.firelens_configuration, {}) - health_check = try(var.atlantis.health_check, {}) - hostname = try(var.atlantis.hostname, null) - image = try(var.atlantis.image, "ghcr.io/runatlantis/atlantis:latest") - interactive = try(var.atlantis.interactive, false) - links = try(var.atlantis.links, []) - linux_parameters = try(var.atlantis.linux_parameters, {}) - log_configuration = lookup(var.atlantis, "log_configuration", {}) - memory = try(var.atlantis.memory, 2048) - memory_reservation = try(var.atlantis.memory_reservation, null) - mount_points = local.mount_points - name = "atlantis" - port_mappings = [{ + environmentFiles = var.atlantis.environmentFiles + essential = true + extraHosts = var.atlantis.extraHosts + firelensConfiguration = var.atlantis.firelensConfiguration + healthCheck = var.atlantis.healthCheck + hostname = var.atlantis.hostname + image = var.atlantis.image + linuxParameters = var.atlantis.linuxParameters + logConfiguration = var.atlantis.logConfiguration + memory = var.atlantis.memory + memoryReservation = var.atlantis.memoryReservation + mountPoints = local.mount_points + name = "atlantis" + portMappings = [{ name = "atlantis" - containerPort = local.atlantis_port - hostPort = local.atlantis_port + containerPort = var.atlantis.port protocol = "tcp" }] - privileged = try(var.atlantis.privileged, false) - pseudo_terminal = try(var.atlantis.pseudo_terminal, false) - readonly_root_filesystem = try(var.atlantis.readonly_root_filesystem, false) - repository_credentials = try(var.atlantis.repository_credentials, {}) - resource_requirements = try(var.atlantis.resource_requirements, []) - secrets = try(var.atlantis.secrets, []) - start_timeout = try(var.atlantis.start_timeout, 30) - stop_timeout = try(var.atlantis.stop_timeout, 120) - system_controls = try(var.atlantis.system_controls, []) - ulimits = try(var.atlantis.ulimits, []) - user = try(var.atlantis.user, "${var.atlantis_uid}:${var.atlantis_gid}") - volumes_from = try(var.atlantis.volumes_from, []) - working_directory = try(var.atlantis.working_directory, null) + privileged = var.atlantis.privileged + readonlyRootFilesystem = var.atlantis.readonlyRootFilesystem + repositoryCredentials = var.atlantis.repositoryCredentials + resourceRequirements = var.atlantis.resourceRequirements + restartPolicy = var.atlantis.restartPolicy + secrets = var.atlantis.secrets + startTimeout = var.atlantis.startTimeout + stopTimeout = var.atlantis.stopTimeout + user = try(coalesce(var.atlantis.user, "${var.atlantis.uid}:${var.atlantis.gid}")) + versionConsistency = "disabled" + volumesFrom = var.atlantis.volumesFrom + workingDirectory = var.atlantis.workingDirectory # CloudWatch Log Group - service = var.name - enable_cloudwatch_logging = try(var.atlantis.enable_cloudwatch_logging, true) - create_cloudwatch_log_group = try(var.atlantis.create_cloudwatch_log_group, true) - cloudwatch_log_group_use_name_prefix = try(var.atlantis.cloudwatch_log_group_use_name_prefix, true) - cloudwatch_log_group_retention_in_days = try(var.atlantis.cloudwatch_log_group_retention_in_days, 14) - cloudwatch_log_group_kms_key_id = try(var.atlantis.cloudwatch_log_group_kms_key_id, null) + service = try(coalesce(var.service.name, var.name)) + enable_cloudwatch_logging = var.atlantis.enable_cloudwatch_logging + create_cloudwatch_log_group = var.atlantis.create_cloudwatch_log_group + cloudwatch_log_group_use_name_prefix = var.atlantis.cloudwatch_log_group_use_name_prefix + cloudwatch_log_group_retention_in_days = var.atlantis.cloudwatch_log_group_retention_in_days + cloudwatch_log_group_class = var.atlantis.cloudwatch_log_group_class + cloudwatch_log_group_kms_key_id = var.atlantis.cloudwatch_log_group_kms_key_id }, }, - lookup(var.service, "container_definitions", {}) + var.service.container_definitions ) - container_definition_defaults = lookup(var.service, "container_definition_defaults", {}) - cpu = try(var.service.cpu, 1024) - ephemeral_storage = try(var.service.ephemeral_storage, {}) - family = try(var.service.family, null) - inference_accelerator = try(var.service.inference_accelerator, {}) - ipc_mode = try(var.service.ipc_mode, null) - memory = try(var.service.memory, 2048) - network_mode = try(var.service.network_mode, "awsvpc") - pid_mode = try(var.service.pid_mode, null) - task_definition_placement_constraints = try(var.service.task_definition_placement_constraints, {}) - proxy_configuration = try(var.service.proxy_configuration, {}) - requires_compatibilities = try(var.service.requires_compatibilities, ["FARGATE"]) - runtime_platform = try(var.service.runtime_platform, { - operating_system_family = "LINUX" - cpu_architecture = "X86_64" - }) - skip_destroy = try(var.service.skip_destroy, null) - volume = { for k, v in merge( - { + cpu = var.service.cpu + ephemeral_storage = var.service.ephemeral_storage + family = var.service.family + memory = var.service.memory + network_mode = "awsvpc" + requires_compatibilities = var.service.requires_compatibilities + runtime_platform = var.service.runtime_platform + track_latest = true + volume = merge( + { for k, v in { efs = { efs_volume_configuration = { file_system_id = module.efs.id transit_encryption = "ENABLED" authorization_config = { - access_point_id = try(module.efs.access_points["atlantis"].id, null) + access_point_id = try(module.efs.access_points["atlantis"].id, "") iam = "ENABLED" } } } - }, - lookup(var.service, "volume", {}) - ) : k => v if var.enable_efs } - task_tags = try(var.service.task_tags, {}) + } : k => v if var.enable_efs }, + var.service.volume + ) + task_tags = var.service.task_tags # Task execution IAM role - create_task_exec_iam_role = try(var.service.create_task_exec_iam_role, true) - task_exec_iam_role_arn = try(var.service.task_exec_iam_role_arn, null) - task_exec_iam_role_name = try(var.service.task_exec_iam_role_name, null) - task_exec_iam_role_use_name_prefix = try(var.service.task_exec_iam_role_use_name_prefix, true) - task_exec_iam_role_path = try(var.service.task_exec_iam_role_path, null) - task_exec_iam_role_description = try(var.service.task_exec_iam_role_description, null) - task_exec_iam_role_permissions_boundary = try(var.service.task_exec_iam_role_permissions_boundary, null) - task_exec_iam_role_tags = try(var.service.task_exec_iam_role_tags, {}) - task_exec_iam_role_policies = lookup(var.service, "task_exec_iam_role_policies", {}) - task_exec_iam_role_max_session_duration = try(var.service.task_exec_iam_role_max_session_duration, null) + create_task_exec_iam_role = var.service.create_task_exec_iam_role + task_exec_iam_role_arn = var.service.task_exec_iam_role_arn + task_exec_iam_role_name = var.service.task_exec_iam_role_name + task_exec_iam_role_use_name_prefix = var.service.task_exec_iam_role_use_name_prefix + task_exec_iam_role_path = var.service.task_exec_iam_role_path + task_exec_iam_role_description = var.service.task_exec_iam_role_description + task_exec_iam_role_permissions_boundary = var.service.task_exec_iam_role_permissions_boundary + task_exec_iam_role_tags = var.service.task_exec_iam_role_tags + task_exec_iam_role_policies = var.service.task_exec_iam_role_policies + task_exec_iam_role_max_session_duration = var.service.task_exec_iam_role_max_session_duration # Task execution IAM role policy - create_task_exec_policy = try(var.service.create_task_exec_policy, true) - task_exec_ssm_param_arns = try(var.service.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) - task_exec_secret_arns = try(var.service.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) - task_exec_iam_statements = lookup(var.service, "task_exec_iam_statements", {}) + create_task_exec_policy = var.service.create_task_exec_policy + task_exec_ssm_param_arns = var.service.task_exec_ssm_param_arns + task_exec_secret_arns = var.service.task_exec_secret_arns + task_exec_iam_statements = var.service.task_exec_iam_statements # Tasks - IAM role - create_tasks_iam_role = try(var.service.create_tasks_iam_role, true) - tasks_iam_role_arn = try(var.service.tasks_iam_role_arn, null) - tasks_iam_role_name = try(var.service.tasks_iam_role_name, null) - tasks_iam_role_use_name_prefix = try(var.service.tasks_iam_role_use_name_prefix, true) - tasks_iam_role_path = try(var.service.tasks_iam_role_path, null) - tasks_iam_role_description = try(var.service.tasks_iam_role_description, null) - tasks_iam_role_permissions_boundary = try(var.service.tasks_iam_role_permissions_boundary, null) - tasks_iam_role_tags = try(var.service.tasks_iam_role_tags, {}) - tasks_iam_role_policies = lookup(var.service, "tasks_iam_role_policies", {}) - tasks_iam_role_statements = lookup(var.service, "tasks_iam_role_statements", {}) - - # Task set - external_id = try(var.service.external_id, null) - scale = try(var.service.scale, {}) - force_delete = try(var.service.force_delete, null) - wait_until_stable = try(var.service.wait_until_stable, null) - wait_until_stable_timeout = try(var.service.wait_until_stable_timeout, null) + create_tasks_iam_role = var.service.create_tasks_iam_role + tasks_iam_role_arn = var.service.tasks_iam_role_arn + tasks_iam_role_name = var.service.tasks_iam_role_name + tasks_iam_role_use_name_prefix = var.service.tasks_iam_role_use_name_prefix + tasks_iam_role_path = var.service.tasks_iam_role_path + tasks_iam_role_description = var.service.tasks_iam_role_description + tasks_iam_role_permissions_boundary = var.service.tasks_iam_role_permissions_boundary + tasks_iam_role_tags = var.service.tasks_iam_role_tags + tasks_iam_role_policies = var.service.tasks_iam_role_policies + tasks_iam_role_statements = var.service.tasks_iam_role_statements # Autoscaling - enable_autoscaling = try(var.service.enable_autoscaling, false) - autoscaling_min_capacity = try(var.service.autoscaling_min_capacity, 1) - autoscaling_max_capacity = try(var.service.autoscaling_max_capacity, 10) - autoscaling_policies = try(var.service.autoscaling_policies, {}) - autoscaling_scheduled_actions = try(var.service.autoscaling_scheduled_actions, {}) + # Atlantis only supports a single instance + enable_autoscaling = false # Security Group - create_security_group = try(var.service.create_security_group, true) - security_group_name = try(var.service.security_group_name, null) - security_group_use_name_prefix = try(var.service.security_group_use_name_prefix, true) - security_group_description = try(var.service.security_group_description, null) - security_group_rules = merge( + create_security_group = var.service.create_security_group + security_group_name = var.service.security_group_name + security_group_use_name_prefix = var.service.security_group_use_name_prefix + security_group_description = var.service.security_group_description + security_group_ingress_rules = merge( { atlantis = { - type = "ingress" - from_port = local.atlantis_port - to_port = local.atlantis_port - protocol = "tcp" - source_security_group_id = var.create_alb ? module.alb.security_group_id : var.alb_security_group_id + from_port = var.atlantis.port + protocol = "tcp" + referenced_security_group_id = var.create_alb ? module.alb.security_group_id : var.alb_security_group_id } }, - lookup(var.service, "security_group_rules", { + var.service.security_group_ingress_rules + ) + security_group_egress_rules = merge( + { egress = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" } - }) + } ) - security_group_tags = try(var.service.security_group_tags, {}) + security_group_tags = var.service.security_group_tags tags = var.tags } @@ -469,91 +404,93 @@ module "ecs_service" { module "efs" { source = "terraform-aws-modules/efs/aws" - version = "1.3.1" + version = "2.0.0" + region = var.region create = var.create && var.enable_efs - name = try(var.efs.name, var.name) - - # File system - availability_zone_name = try(var.efs.availability_zone_name, null) - creation_token = try(var.efs.creation_token, var.name) - performance_mode = try(var.efs.performance_mode, null) - encrypted = try(var.efs.encrypted, true) - kms_key_arn = try(var.efs.kms_key_arn, null) - provisioned_throughput_in_mibps = try(var.efs.provisioned_throughput_in_mibps, null) - throughput_mode = try(var.efs.throughput_mode, null) - lifecycle_policy = try(var.efs.lifecycle_policy, {}) - - # File system policy - attach_policy = try(var.efs.attach_policy, true) - bypass_policy_lockout_safety_check = try(var.efs.bypass_policy_lockout_safety_check, null) - source_policy_documents = try(var.efs.source_policy_documents, []) - override_policy_documents = try(var.efs.override_policy_documents, []) - policy_statements = concat( - [{ - actions = [ - "elasticfilesystem:ClientMount", - "elasticfilesystem:ClientWrite", - ] - principals = [ - { - type = "AWS" - identifiers = [module.ecs_service.tasks_iam_role_arn] - } - ] - }], - lookup(var.efs, "policy_statements", []) + + name = try(coalesce(var.efs.name, var.name)) + + # File System + availability_zone_name = var.efs.availability_zone_name + creation_token = try(coalesce(var.efs.creation_token, var.name)) + performance_mode = var.efs.performance_mode + encrypted = var.efs.encrypted + kms_key_arn = var.efs.kms_key_arn + provisioned_throughput_in_mibps = var.efs.provisioned_throughput_in_mibps + throughput_mode = var.efs.throughput_mode + lifecycle_policy = var.efs.lifecycle_policy + protection = var.efs.protection + + # File System Policy + attach_policy = var.efs.attach_policy + bypass_policy_lockout_safety_check = var.efs.bypass_policy_lockout_safety_check + source_policy_documents = var.efs.source_policy_documents + override_policy_documents = var.efs.override_policy_documents + policy_statements = merge( + { + EFSMountWrite = { + actions = [ + "elasticfilesystem:ClientMount", + "elasticfilesystem:ClientWrite", + ] + principals = [ + { + type = "AWS" + identifiers = [module.ecs_service.tasks_iam_role_arn] + } + ] + } + }, + var.efs.policy_statements ) - deny_nonsecure_transport = try(var.efs.deny_nonsecure_transport, true) + deny_nonsecure_transport = var.efs.deny_nonsecure_transport + deny_nonsecure_transport_via_mount_target = var.efs.deny_nonsecure_transport_via_mount_target # Mount targets - mount_targets = lookup(var.efs, "mount_targets", {}) + mount_targets = var.efs.mount_targets - # Security group - create_security_group = try(var.efs.create_security_group, true) - security_group_name = try(var.efs.security_group_name, "${var.name}-efs-") - security_group_use_name_prefix = try(var.efs.security_group_use_name_prefix, true) - security_group_description = try(var.efs.security_group_description, null) - security_group_vpc_id = try(var.efs.security_group_vpc_id, var.vpc_id) - security_group_rules = merge( + # Security Group + create_security_group = var.efs.create_security_group + security_group_name = try(coalesce(var.efs.security_group_name, "${var.name}-efs-")) + security_group_use_name_prefix = var.efs.security_group_use_name_prefix + security_group_description = var.efs.security_group_description + security_group_vpc_id = var.vpc_id + security_group_ingress_rules = merge( { atlantis = { # relying on the defaults provdied for EFS/NFS (2049/TCP + ingress) - description = "NFS ingress from Atlantis" - source_security_group_id = module.ecs_service.security_group_id + description = "NFS ingress from Atlantis" + referenced_security_group_id = module.ecs_service.security_group_id } }, - lookup(var.efs, "security_group_rules", {}) + var.efs.security_group_ingress_rules ) - # Access point(s) + # Access Point(s) access_points = merge( { atlantis = { posix_user = { - gid = var.atlantis_gid - uid = var.atlantis_uid + gid = var.atlantis.gid + uid = var.atlantis.uid } root_directory = { path = local.mount_path creation_info = { - owner_gid = var.atlantis_gid - owner_uid = var.atlantis_uid + owner_gid = var.atlantis.gid + owner_uid = var.atlantis.uid permissions = "0750" } } } }, - lookup(var.efs, "access_points", {}) + var.efs.access_points ) - # Backup policy - create_backup_policy = try(var.efs.create_backup_policy, false) - enable_backup_policy = try(var.efs.enable_backup_policy, false) - - # Replication configuration - create_replication_configuration = try(var.efs.create_replication_configuration, false) - replication_configuration_destination = try(var.efs.replication_configuration_destination, {}) + # Backup Policy + create_backup_policy = false + enable_backup_policy = false tags = var.tags } diff --git a/modules/github-repository-webhook/README.md b/modules/github-repository-webhook/README.md index f7c2fca9..f9c7b16e 100644 --- a/modules/github-repository-webhook/README.md +++ b/modules/github-repository-webhook/README.md @@ -5,7 +5,7 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | +| [terraform](#requirement\_terraform) | >= 1.10 | | [github](#requirement\_github) | >= 5.0 | ## Providers diff --git a/modules/github-repository-webhook/versions.tf b/modules/github-repository-webhook/versions.tf index 51af6b4f..59c89df8 100644 --- a/modules/github-repository-webhook/versions.tf +++ b/modules/github-repository-webhook/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10" required_providers { github = { diff --git a/modules/gitlab-repository-webhook/README.md b/modules/gitlab-repository-webhook/README.md index e6ebc843..68ddb267 100644 --- a/modules/gitlab-repository-webhook/README.md +++ b/modules/gitlab-repository-webhook/README.md @@ -5,7 +5,7 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.1 | +| [terraform](#requirement\_terraform) | >= 1.10 | | [gitlab](#requirement\_gitlab) | >= 16.0 | ## Providers diff --git a/modules/gitlab-repository-webhook/versions.tf b/modules/gitlab-repository-webhook/versions.tf index d5e390fe..8700516a 100644 --- a/modules/gitlab-repository-webhook/versions.tf +++ b/modules/gitlab-repository-webhook/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.1" + required_version = ">= 1.10" required_providers { gitlab = { diff --git a/variables.tf b/variables.tf index f18e1867..3768eb5b 100644 --- a/variables.tf +++ b/variables.tf @@ -4,34 +4,22 @@ variable "create" { default = true } -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} -} - variable "name" { description = "Common name to use on all resources created unless a more specific name is provided" type = string default = "atlantis" } -variable "atlantis" { - description = "Map of values passed to Atlantis container definition. See the [ECS container definition module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/container-definition) for full list of arguments supported" - type = any - default = {} -} - -variable "atlantis_gid" { - description = "GID of the atlantis user" - type = number - default = 1000 +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null } -variable "atlantis_uid" { - description = "UID of the atlantis user" - type = number - default = 100 +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} } variable "vpc_id" { @@ -40,6 +28,137 @@ variable "vpc_id" { default = "" } +################################################################################ +# Atlantis +################################################################################ + +variable "atlantis" { + description = "Map of values passed to Atlantis container definition. See the [ECS container definition module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/container-definition) for full list of arguments supported" + type = object({ + uid = optional(string, 100) + gid = optional(string, 1000) + + command = optional(list(string)) + cpu = optional(number, 2048) + dependsOn = optional(list(object({ + condition = string + containerName = string + }))) + disableNetworking = optional(bool) + dnsSearchDomains = optional(list(string)) + dnsServers = optional(list(string)) + dockerLabels = optional(map(string)) + dockerSecurityOptions = optional(list(string)) + entrypoint = optional(list(string)) + environment = optional(list(object({ + name = string + value = string + })), []) + environmentFiles = optional(list(object({ + type = string + value = string + }))) + extraHosts = optional(list(object({ + hostname = string + ipAddress = string + }))) + firelensConfiguration = optional(object({ + type = string + options = optional(map(string)) + configFile = optional(object({ + type = string + content = string + })) + })) + healthCheck = optional(object({ + command = optional(list(string), []) + interval = optional(number, 30) + retries = optional(number, 3) + startPeriod = optional(number) + timeout = optional(number, 5) + })) + hostname = optional(string) + image = optional(string, "ghcr.io/runatlantis/atlantis:latest") + linuxParameters = optional(object({ + capabilities = optional(object({ + add = optional(list(string)) + drop = optional(list(string)) + })) + devices = optional(list(object({ + containerPath = optional(string) + hostPath = optional(string) + permissions = optional(list(string)) + }))) + initProcessEnabled = optional(bool) + maxSwap = optional(number) + sharedMemorySize = optional(number) + swappiness = optional(number) + tmpfs = optional(list(object({ + containerPath = string + mountOptions = optional(list(string)) + size = number + }))) + })) + logConfiguration = optional(object({ + logDriver = optional(string) + options = optional(map(string)) + secretOptions = optional(list(object({ + name = string + valueFrom = string + }))) + })) + memory = optional(number, 4096) + memoryReservation = optional(number) + mountPoints = optional(list(object({ + containerPath = optional(string) + readOnly = optional(bool) + sourceVolume = optional(string) + }))) + port = optional(number, 4141) + privileged = optional(bool, false) + readonlyRootFilesystem = optional(bool, false) + repositoryCredentials = optional(object({ + credentialsParameter = optional(string) + })) + resourceRequirements = optional(list(object({ + type = string + value = string + }))) + restartPolicy = optional(object({ + enabled = optional(bool, true) + ignoredExitCodes = optional(list(number)) + restartAttemptPeriod = optional(number) + }), + # Default + { + enabled = true + } + ) + secrets = optional(list(object({ + name = string + valueFrom = string + }))) + startTimeout = optional(number, 30) + stopTimeout = optional(number, 120) + user = optional(string, "atlantis") + volumesFrom = optional(list(object({ + readOnly = optional(bool) + sourceContainer = optional(string) + }))) + workingDirectory = optional(string) + + # CloudWatch Log Group + enable_cloudwatch_logging = optional(bool, true) + create_cloudwatch_log_group = optional(bool, true) + cloudwatch_log_group_use_name_prefix = optional(bool, true) + cloudwatch_log_group_retention_in_days = optional(number, 14) + cloudwatch_log_group_class = optional(string) + cloudwatch_log_group_kms_key_id = optional(string) + }) + default = {} + nullable = false +} + ################################################################################ # Load Balancer ################################################################################ @@ -64,64 +183,121 @@ variable "alb_security_group_id" { variable "alb" { description = "Map of values passed to ALB module definition. See the [ALB module](https://github.com/terraform-aws-modules/terraform-aws-alb) for full list of arguments supported" - type = any - default = {} -} + type = object({ + # Load Balancer + access_logs = optional(object({ + bucket = string + enabled = optional(bool, true) + prefix = optional(string) + })) + connection_logs = optional(object({ + bucket = string + enabled = optional(bool, true) + prefix = optional(string) + })) + drop_invalid_header_fields = optional(bool, true) + enable_cross_zone_load_balancing = optional(bool, true) + enable_deletion_protection = optional(bool, true) + enable_http2 = optional(bool, true) + enable_waf_fail_open = optional(bool) + enable_zonal_shift = optional(bool, true) + idle_timeout = optional(number) + internal = optional(bool) + ip_address_type = optional(string) + name = optional(string) + preserve_host_header = optional(bool) + security_groups = optional(list(string), []) + subnet_ids = optional(list(string), []) -variable "alb_https_default_action" { - description = "Default action for the ALB https listener" - type = any - default = { - forward = { - target_group_key = "atlantis" - } - } -} + # Listener(s) + default_port = optional(number, 80) + default_protocol = optional(string, "HTTP") + https_listener_ssl_policy = optional(string, "ELBSecurityPolicy-TLS13-1-2-2021-06") + https_default_action = optional(any, { + forward = { + target_group_key = "atlantis" + } + }) + https_listener = optional(any, {}) + listeners = optional(any, {}) -variable "alb_subnets" { - description = "List of subnets to place ALB in. Required if `create_alb` is `true`" - type = list(string) - default = [] -} + # Target Group(s) + target_groups = optional(any, {}) -variable "create_route53_records" { - description = "Determines whether to create Route53 `A` and `AAAA` records for the loadbalancer" - type = bool - default = true -} + # Securtity Group(s) + create_security_group = optional(bool, true) + security_group_name = optional(string) + security_group_use_name_prefix = optional(bool, true) + security_group_description = optional(string) + security_group_ingress_rules = optional(map(object({ + name = optional(string) + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(string) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })), + # Default + { + http = { + from_port = 80 + cidr_ipv4 = "0.0.0.0/0" + } + https = { + from_port = 443 + cidr_ipv4 = "0.0.0.0/0" + } + } + ) + security_group_egress_rules = optional( + map(object({ + name = optional(string) + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(string) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })), + # Default + { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + } + } + ) + security_group_tags = optional(map(string), {}) -################################################################################ -# ECS -################################################################################ + # Route53 Record(s) + route53_records = optional(map(object({ + zone_id = string + name = optional(string) + type = string + evaluate_target_health = optional(bool, true) + }))) -variable "create_cluster" { - description = "Whether to create an ECS cluster or not" - type = bool - default = true -} + # WAF + associate_web_acl = optional(bool, false) + web_acl_arn = optional(string) -variable "cluster_arn" { - description = "ARN of an existing ECS cluster where resources will be created. Required when `create_cluster` is `false`" - type = string - default = "" + tags = optional(map(string), {}) + }) + default = {} + nullable = false } -variable "cluster" { - description = "Map of values passed to ECS cluster module definition. See the [ECS cluster module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/cluster) for full list of arguments supported" - type = any - default = {} -} - -variable "service" { - description = "Map of values passed to ECS service module definition. See the [ECS service module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/service) for full list of arguments supported" - type = any - default = {} -} - -variable "service_subnets" { - description = "List of subnets to place ECS service within" - type = list(string) - default = [] +variable "create_route53_records" { + description = "Determines whether to create Route53 `A` and `AAAA` records for the loadbalancer" + type = bool + default = true } ################################################################################ @@ -164,6 +340,266 @@ variable "route53_record_name" { default = null } +################################################################################ +# ECS +################################################################################ + +variable "create_cluster" { + description = "Whether to create an ECS cluster or not" + type = bool + default = true +} + +variable "cluster_arn" { + description = "ARN of an existing ECS cluster where resources will be created. Required when `create_cluster` is `false`" + type = string + default = "" +} + +variable "cluster" { + description = "Map of values passed to ECS cluster module definition. See the [ECS cluster module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/cluster) for full list of arguments supported" + type = object({ + # Cluster + name = optional(string) + configuration = optional(object({ + execute_command_configuration = optional(object({ + kms_key_id = optional(string) + log_configuration = optional(object({ + cloud_watch_encryption_enabled = optional(bool) + cloud_watch_log_group_name = optional(string) + s3_bucket_encryption_enabled = optional(bool) + s3_bucket_name = optional(string) + s3_kms_key_id = optional(string) + s3_key_prefix = optional(string) + })) + logging = optional(string, "OVERRIDE") + })) + managed_storage_configuration = optional(object({ + fargate_ephemeral_storage_kms_key_id = optional(string) + kms_key_id = optional(string) + })) + }), + # Default + { + execute_command_configuration = { + log_configuration = { + cloud_watch_log_group_name = "placeholder" # will use CloudWatch log group created by module + } + } + } + ) + setting = optional(list(object({ + name = string + value = string + })), + # Default + [{ + name = "containerInsights" + value = "enabled" + }] + ) + + # Cloudwatch log group + create_cloudwatch_log_group = optional(bool, true) + cloudwatch_log_group_retention_in_days = optional(number, 90) + cloudwatch_log_group_kms_key_id = optional(string) + cloudwatch_log_group_class = optional(string) + cloudwatch_log_group_tags = optional(map(string), {}) + + # Capacity providers + default_capacity_provider_strategy = optional( + map(object({ + base = optional(number) + name = optional(string) # Will fall back to use map key if not set + weight = optional(number) + })), + # Default + { + FARGATE = { + weight = 100 + } + } + ) + }) + default = {} +} + +variable "service" { + description = "Map of values passed to ECS service module definition. See the [ECS service module](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/modules/service) for full list of arguments supported" + type = object({ + capacity_provider_strategy = optional(map(object({ + base = optional(number) + capacity_provider = string + weight = optional(number) + }))) + deployment_circuit_breaker = optional(object({ + enable = bool + rollback = bool + })) + enable_ecs_managed_tags = optional(bool, true) + force_new_deployment = optional(bool, true) + health_check_grace_period_seconds = optional(number) + launch_type = optional(string, "FARGATE") + load_balancer = optional(any, {}) + name = optional(string) + assign_public_ip = optional(bool, false) + security_group_ids = optional(list(string), []) + subnet_ids = optional(list(string), []) + platform_version = optional(string) + propagate_tags = optional(string) + timeouts = optional(object({ + create = optional(string) + delete = optional(string) + update = optional(string) + })) + triggers = optional(map(string)) + wait_for_steady_state = optional(bool) + + # Service IAM Role + create_iam_role = optional(bool, true) + iam_role_arn = optional(string) + iam_role_name = optional(string) + iam_role_use_name_prefix = optional(bool, true) + iam_role_path = optional(string) + iam_role_description = optional(string) + iam_role_permissions_boundary = optional(string) + iam_role_tags = optional(map(string), {}) + iam_role_statements = optional(list(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string) + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + values = list(string) + variable = string + }))) + }))) + + # Task Definition + create_task_definition = optional(bool, true) + task_definition_arn = optional(string) + container_definitions = optional(any, {}) + cpu = optional(number, 2048) + ephemeral_storage = optional(object({ + size_in_gib = number + })) + family = optional(string) + memory = optional(number, 4096) + requires_compatibilities = optional(list(string), ["FARGATE"]) + runtime_platform = optional( + object({ + cpu_architecture = optional(string) + operating_system_family = optional(string) + }), + # Default + { + operating_system_family = "LINUX" + cpu_architecture = "ARM64" + } + ) + volume = optional(any, {}) + task_tags = optional(map(string), {}) + + # Task Execution IAM Role + create_task_exec_iam_role = optional(bool, true) + task_exec_iam_role_arn = optional(string) + task_exec_iam_role_name = optional(string) + task_exec_iam_role_use_name_prefix = optional(bool, true) + task_exec_iam_role_path = optional(string) + task_exec_iam_role_description = optional(string) + task_exec_iam_role_permissions_boundary = optional(string) + task_exec_iam_role_tags = optional(map(string), {}) + task_exec_iam_role_policies = optional(map(string), {}) + task_exec_iam_role_max_session_duration = optional(number) + + # Task Execution IAM Role Policy + create_task_exec_policy = optional(bool, true) + task_exec_ssm_param_arns = optional(list(string), []) + task_exec_secret_arns = optional(list(string), []) + task_exec_iam_statements = optional(list(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string) + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + values = list(string) + variable = string + }))) + }))) + + # Tasks - IAM role + create_tasks_iam_role = optional(bool, true) + tasks_iam_role_arn = optional(string) + tasks_iam_role_name = optional(string) + tasks_iam_role_use_name_prefix = optional(bool, true) + tasks_iam_role_path = optional(string) + tasks_iam_role_description = optional(string) + tasks_iam_role_permissions_boundary = optional(string) + tasks_iam_role_tags = optional(map(string), {}) + tasks_iam_role_policies = optional(map(string), {}) + tasks_iam_role_statements = optional(list(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string) + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + values = list(string) + variable = string + }))) + }))) + + # Security Group + create_security_group = optional(bool, true) + security_group_name = optional(string) + security_group_use_name_prefix = optional(bool, true) + security_group_description = optional(string) + security_group_ingress_rules = optional(any, {}) + security_group_egress_rules = optional(any, + { + egress = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + } + } + ) + security_group_tags = optional(map(string), {}) + }) + default = {} +} + ################################################################################ # EFS ################################################################################ @@ -176,6 +612,57 @@ variable "enable_efs" { variable "efs" { description = "Map of values passed to EFS module definition. See the [EFS module](https://github.com/terraform-aws-modules/terraform-aws-efs) for full list of arguments supported" - type = any - default = {} + type = object({ + name = optional(string) + + # File System + availability_zone_name = optional(string) + creation_token = optional(string) + performance_mode = optional(string) + encrypted = optional(bool, true) + kms_key_arn = optional(string) + provisioned_throughput_in_mibps = optional(number) + throughput_mode = optional(string) + lifecycle_policy = optional(object({ + transition_to_ia = optional(string) + transition_to_archive = optional(string) + transition_to_primary_storage_class = optional(string) + })) + protection = optional(object({ + replication_overwrite = optional(string) + })) + + # File System Policy + attach_policy = optional(bool, true) + bypass_policy_lockout_safety_check = optional(bool) + source_policy_documents = optional(list(string), []) + override_policy_documents = optional(list(string), []) + policy_statements = optional(any, {}) + deny_nonsecure_transport = optional(bool, true) + deny_nonsecure_transport_via_mount_target = optional(bool, true) + + # Mount targets + mount_targets = optional(map(object({ + ip_address = optional(string) + ip_address_type = optional(string) + ipv6_address = optional(string) + region = optional(string) + security_groups = optional(list(string), []) + subnet_id = string + })), + # Default + {} + ) + + # Security Group + create_security_group = optional(bool, true) + security_group_name = optional(string) + security_group_use_name_prefix = optional(bool, true) + security_group_description = optional(string) + security_group_ingress_rules = optional(any, {}) + + # Access Point(s) + access_points = optional(any, {}) + }) + default = {} } diff --git a/versions.tf b/versions.tf index a8de733f..e3e52642 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10" required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.0" + version = ">= 6.19" } } }