From b12b626821c33eb889a2a70408f623b02d8d880f Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 9 Nov 2025 08:07:40 -0600 Subject: [PATCH 1/6] feat!: Upgrade Terraform and AWS min required providers to `1.10` and `6.19` respectively --- README.md | 15 +- examples/github-complete/README.md | 6 +- examples/github-complete/versions.tf | 6 +- examples/github-separate/README.md | 6 +- examples/github-separate/versions.tf | 6 +- main.tf | 541 +++++++++++++-------------- variables.tf | 22 +- versions.tf | 4 +- 8 files changed, 304 insertions(+), 302 deletions(-) diff --git a/README.md b/README.md index ae785f82..3e5811a8 100644 --- a/README.md +++ b/README.md @@ -210,8 +210,8 @@ module "atlantis" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | ~> 5.0 | +| [terraform](#requirement\_terraform) | >= 1.10.0 | +| [aws](#requirement\_aws) | >= 6.19 | ## Providers @@ -221,11 +221,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 @@ -255,6 +255,7 @@ No resources. | [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 | | [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 | diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index 0713a7e0..9cb57fb1 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.10.0 | +| [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 diff --git a/examples/github-complete/versions.tf b/examples/github-complete/versions.tf index 0b115f06..3712b302 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.10.0" 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..6cab6376 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.10.0 | +| [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 diff --git a/examples/github-separate/versions.tf b/examples/github-separate/versions.tf index 0b115f06..3712b302 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.10.0" 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..cb7911b3 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,37 @@ 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 + customer_owned_ipv4_pool = var.alb.customer_owned_ipv4_pool + desync_mitigation_mode = var.alb.desync_mitigation_mode + dns_record_client_routing_policy = var.alb.dns_record_client_routing_policy + 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_tls_version_and_cipher_suite_headers = var.alb.enable_tls_version_and_cipher_suite_headers + enable_waf_fail_open = var.alb.enable_waf_fail_open + enable_xff_client_port = var.alb.enable_xff_client_port + 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 = try(coalesce(var.alb.subnets, var.alb_subnets), []) + xff_header_processing_mode = var.alb.xff_header_processing_mode + timeouts = var.alb.timeouts # 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,14 +75,14 @@ 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_listener, ) }, - lookup(var.alb, "listeners", {}) + var.alb.listeners ) # Target group(s) @@ -92,7 +91,7 @@ module "alb" { atlantis = { name = var.name protocol = "HTTP" - port = local.atlantis_port + port = var.atlantis.port create_attachment = false target_type = "ip" deregistration_delay = 10 @@ -111,54 +110,52 @@ 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 + # { + # 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 = var.alb.security_group_egress_rules + # { + # all = { + # ip_protocol = "-1" + # cidr_ipv4 = "0.0.0.0/0" + # } + # } + 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 +165,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,188 +188,185 @@ 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.mount_points } 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.settings + # { + # name = "containerInsights" + # value = "enabled" + # } # 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_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, {}) + ignore_task_definition_changes = var.service.ignore_task_definition_changes + alarms = var.service.alarms + 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_controller = var.service.deployment_controller + deployment_maximum_percent = var.service.deployment_maximum_percent + deployment_minimum_healthy_percent = var.service.deployment_minimum_healthy_percent + desired_count = var.service.desired_count + enable_ecs_managed_tags = var.service.enable_ecs_managed_tags + enable_execute_command = var.service.enable_execute_command + 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 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 = try(coalesce(var.service.subnet_ids, var.service_subnets)) + ordered_placement_strategy = var.service.ordered_placement_strategy + placement_constraints = var.service.placement_constraints + platform_version = var.service.platform_version + propagate_tags = var.service.propagate_tags + scheduling_strategy = var.service.scheduling_strategy + service_connect_configuration = var.service.service_connect_configuration + service_registries = var.service.service_registries + 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 + dependencies = var.atlantis.dependencies # depends_on is a reserved word + disable_networking = var.atlantis.disable_networking + dns_search_domains = var.atlantis.dns_search_domains + dns_servers = var.atlantis.dns_servers + docker_labels = var.atlantis.docker_labels + docker_security_options = var.atlantis.docker_security_options + enable_execute_command = try(coalesce(var.atlantis.enable_execute_command, var.service.enable_execute_command), false) + 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) + environment_files = var.atlantis.environment_files + essential = var.atlantis.essential + extra_hosts = var.atlantis.extra_hosts + firelens_configuration = var.atlantis.firelens_configuration + health_check = var.atlantis.health_check + hostname = var.atlantis.hostname + image = var.atlantis.image + interactive = var.atlantis.interactive + links = var.atlantis.links + linux_parameters = var.atlantis.linux_parameters + log_configuration = var.atlantis.log_configuration + memory = var.atlantis.memory + memory_reservation = var.atlantis.memory_reservation mount_points = local.mount_points name = "atlantis" port_mappings = [{ name = "atlantis" - containerPort = local.atlantis_port - hostPort = local.atlantis_port + containerPort = var.atlantis.port + hostPort = 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 + pseudo_terminal = var.atlantis.pseudo_terminal + readonly_root_filesystem = var.atlantis.readonly_root_filesystem + repository_credentials = var.atlantis.repository_credentials + resource_requirements = var.atlantis.resource_requirements + secrets = var.atlantis.secrets + start_timeout = var.atlantis.start_timeout + stop_timeout = var.atlantis.stop_timeout + system_controls = var.atlantis.system_controls + ulimits = var.atlantis.ulimits + user = try(coalesce(var.atlantis.user, "${var.atlantis_uid}:${var.atlantis_gid}")) + volumes_from = var.atlantis.volumes_from + working_directory = var.atlantis.working_directory # 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) + 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_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) + cpu = var.service.cpu + ephemeral_storage = var.service.ephemeral_storage + family = var.service.family + ipc_mode = var.service.ipc_mode + memory = var.service.memory + network_mode = var.service.network_mode + pid_mode = var.service.pid_mode + task_definition_placement_constraints = var.service.task_definition_placement_constraints + proxy_configuration = var.service.proxy_configuration + requires_compatibilities = var.service.requires_compatibilities + runtime_platform = var.service.runtime_platform + # { + # operating_system_family = "LINUX" + # cpu_architecture = "X86_64" + # } + skip_destroy = var.service.skip_destroy volume = { for k, v in merge( { efs = { @@ -379,86 +374,84 @@ module "ecs_service" { 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 = module.efs.access_points["atlantis"].id iam = "ENABLED" } } } }, - lookup(var.service, "volume", {}) + var.service.volume ) : k => v if var.enable_efs } - task_tags = try(var.service.task_tags, {}) + 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", {}) + 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 # 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) + external_id = var.service.external_id + scale = var.service.scale + force_delete = var.service.force_delete + wait_until_stable = var.service.wait_until_stable + wait_until_stable_timeout = var.service.wait_until_stable_timeout # 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, {}) + enable_autoscaling = var.service.enable_autoscaling + autoscaling_min_capacity = var.service.autoscaling_min_capacity + autoscaling_max_capacity = var.service.autoscaling_max_capacity + autoscaling_policies = var.service.autoscaling_policies + autoscaling_scheduled_actions = var.service.autoscaling_scheduled_actions # 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,26 +462,28 @@ 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) + + name = try(coalesce(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, {}) + 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 # 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, []) + 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 = concat( [{ actions = [ @@ -502,28 +497,28 @@ module "efs" { } ] }], - lookup(var.efs, "policy_statements", []) + var.efs.policy_statements ) - deny_nonsecure_transport = try(var.efs.deny_nonsecure_transport, true) + deny_nonsecure_transport = var.efs.deny_nonsecure_transport # 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( + 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_rules ) # Access point(s) @@ -544,16 +539,16 @@ module "efs" { } } }, - 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) + create_backup_policy = var.efs.create_backup_policy + enable_backup_policy = var.efs.enable_backup_policy # Replication configuration - create_replication_configuration = try(var.efs.create_replication_configuration, false) - replication_configuration_destination = try(var.efs.replication_configuration_destination, {}) + create_replication_configuration = var.efs.create_replication_configuration + replication_configuration_destination = var.efs.replication_configuration_destination tags = var.tags } diff --git a/variables.tf b/variables.tf index f18e1867..1122698f 100644 --- a/variables.tf +++ b/variables.tf @@ -4,12 +4,28 @@ variable "create" { default = true } +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 "tags" { description = "A map of tags to add to all resources" type = map(string) default = {} } +variable "vpc_id" { + description = "ID of the VPC where the resources will be provisioned" + type = string + default = "" +} + +################################################################################ +# Atlantis +################################################################################ + variable "name" { description = "Common name to use on all resources created unless a more specific name is provided" type = string @@ -34,12 +50,6 @@ variable "atlantis_uid" { default = 100 } -variable "vpc_id" { - description = "ID of the VPC where the resources will be provisioned" - type = string - default = "" -} - ################################################################################ # Load Balancer ################################################################################ diff --git a/versions.tf b/versions.tf index a8de733f..516b5aa4 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10.0" required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.0" + version = ">= 6.19" } } } From 95bf4607efd0f45cd291478dd9a80b9e44749eda Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 9 Nov 2025 16:04:59 -0600 Subject: [PATCH 2/6] feat: Update variable definitions to match module version upgrades --- README.md | 16 +- examples/github-complete/main.tf | 21 +- examples/github-separate/main.tf | 4 +- main.tf | 297 ++++++--------- variables.tf | 609 +++++++++++++++++++++++++++---- 5 files changed, 681 insertions(+), 266 deletions(-) diff --git a/README.md b/README.md index 3e5811a8..d8a6796a 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 @@ -235,31 +236,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), [])
subnets = optional(list(string), [])

# Listener(s)
default_port = optional(number, 80)
default_protocol = optional(string, "HTTP")
https_listener_ssl_policy = optional(string, "ELBSecurityPolicy-TLS13-1-3-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_retention_in_days = optional(number, 90)
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)
})))
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/examples/github-complete/main.tf b/examples/github-complete/main.tf index cb387c72..59fa3f27 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -65,6 +65,8 @@ module "atlantis" { } 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,13 +76,15 @@ module "atlantis" { # ALB alb = { + subnets = 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 + # 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}" @@ -125,7 +129,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 +141,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/main.tf b/examples/github-separate/main.tf index 4d25ea28..3ec64c38 100644 --- a/examples/github-separate/main.tf +++ b/examples/github-separate/main.tf @@ -78,8 +78,8 @@ module "atlantis" { } } - service_subnets = module.vpc.private_subnets - vpc_id = module.vpc.vpc_id + # service_subnets = module.vpc.private_subnets + vpc_id = module.vpc.vpc_id tags = local.tags } diff --git a/main.tf b/main.tf index cb7911b3..52b9b61f 100644 --- a/main.tf +++ b/main.tf @@ -33,27 +33,22 @@ module "alb" { create = var.create && var.create_alb # Load balancer - access_logs = var.alb.access_logs - customer_owned_ipv4_pool = var.alb.customer_owned_ipv4_pool - desync_mitigation_mode = var.alb.desync_mitigation_mode - dns_record_client_routing_policy = var.alb.dns_record_client_routing_policy - 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_tls_version_and_cipher_suite_headers = var.alb.enable_tls_version_and_cipher_suite_headers - enable_waf_fail_open = var.alb.enable_waf_fail_open - enable_xff_client_port = var.alb.enable_xff_client_port - 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 = try(coalesce(var.alb.subnets, var.alb_subnets), []) - xff_header_processing_mode = var.alb.xff_header_processing_mode - timeouts = 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.subnets # Listener(s) default_port = var.alb.default_port @@ -78,7 +73,7 @@ module "alb" { 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, + var.alb.https_default_action, var.alb.https_listener, ) }, @@ -89,13 +84,12 @@ module "alb" { target_groups = merge( { atlantis = { - name = var.name - protocol = "HTTP" - port = var.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 @@ -120,28 +114,8 @@ module "alb" { security_group_description = var.alb.security_group_description vpc_id = var.vpc_id security_group_ingress_rules = 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 = var.alb.security_group_egress_rules - # { - # all = { - # ip_protocol = "-1" - # cidr_ipv4 = "0.0.0.0/0" - # } - # } - security_group_tags = var.alb.security_group_tags + security_group_egress_rules = var.alb.security_group_egress_rules + security_group_tags = var.alb.security_group_tags # Route53 record(s) route53_records = merge( @@ -188,7 +162,7 @@ locals { containerPath = local.mount_path sourceVolume = "efs" readOnly = false - }] : var.atlantis.mount_points + }] : var.atlantis.mountPoints } module "ecs_cluster" { @@ -201,16 +175,13 @@ module "ecs_cluster" { # Cluster name = try(coalesce(var.cluster.name, var.name)) configuration = var.cluster.configuration - setting = var.cluster.settings - # { - # name = "containerInsights" - # value = "enabled" - # } + setting = var.cluster.setting # Cloudwatch log group 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 @@ -227,43 +198,35 @@ module "ecs_service" { create = var.create # Service - ignore_task_definition_changes = var.service.ignore_task_definition_changes - alarms = var.service.alarms capacity_provider_strategy = var.service.capacity_provider_strategy cluster_arn = var.create_cluster && var.create ? module.ecs_cluster.arn : var.cluster_arn - deployment_controller = var.service.deployment_controller - deployment_maximum_percent = var.service.deployment_maximum_percent - deployment_minimum_healthy_percent = var.service.deployment_minimum_healthy_percent - desired_count = var.service.desired_count + 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 = var.service.enable_execute_command + 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 = var.atlantis.port } }, var.service.load_balancer ) - 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 = try(coalesce(var.service.subnet_ids, var.service_subnets)) - ordered_placement_strategy = var.service.ordered_placement_strategy - placement_constraints = var.service.placement_constraints - platform_version = var.service.platform_version - propagate_tags = var.service.propagate_tags - scheduling_strategy = var.service.scheduling_strategy - service_connect_configuration = var.service.service_connect_configuration - service_registries = var.service.service_registries - timeouts = var.service.timeouts - triggers = var.service.triggers - wait_for_steady_state = var.service.wait_for_steady_state + 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 = var.service.create_iam_role @@ -282,16 +245,15 @@ module "ecs_service" { container_definitions = merge( { atlantis = { - command = var.atlantis.command - cpu = var.atlantis.cpu - dependencies = var.atlantis.dependencies # depends_on is a reserved word - disable_networking = var.atlantis.disable_networking - dns_search_domains = var.atlantis.dns_search_domains - dns_servers = var.atlantis.dns_servers - docker_labels = var.atlantis.docker_labels - docker_security_options = var.atlantis.docker_security_options - enable_execute_command = try(coalesce(var.atlantis.enable_execute_command, var.service.enable_execute_command), false) - entrypoint = 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( [ { @@ -305,68 +267,57 @@ module "ecs_service" { ], var.atlantis.environment ) - environment_files = var.atlantis.environment_files - essential = var.atlantis.essential - extra_hosts = var.atlantis.extra_hosts - firelens_configuration = var.atlantis.firelens_configuration - health_check = var.atlantis.health_check - hostname = var.atlantis.hostname - image = var.atlantis.image - interactive = var.atlantis.interactive - links = var.atlantis.links - linux_parameters = var.atlantis.linux_parameters - log_configuration = var.atlantis.log_configuration - memory = var.atlantis.memory - memory_reservation = var.atlantis.memory_reservation - 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 = var.atlantis.port - hostPort = var.atlantis.port protocol = "tcp" }] - privileged = var.atlantis.privileged - pseudo_terminal = var.atlantis.pseudo_terminal - readonly_root_filesystem = var.atlantis.readonly_root_filesystem - repository_credentials = var.atlantis.repository_credentials - resource_requirements = var.atlantis.resource_requirements - secrets = var.atlantis.secrets - start_timeout = var.atlantis.start_timeout - stop_timeout = var.atlantis.stop_timeout - system_controls = var.atlantis.system_controls - ulimits = var.atlantis.ulimits - user = try(coalesce(var.atlantis.user, "${var.atlantis_uid}:${var.atlantis_gid}")) - volumes_from = var.atlantis.volumes_from - working_directory = var.atlantis.working_directory + 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 + 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 }, }, var.service.container_definitions ) - cpu = var.service.cpu - ephemeral_storage = var.service.ephemeral_storage - family = var.service.family - ipc_mode = var.service.ipc_mode - memory = var.service.memory - network_mode = var.service.network_mode - pid_mode = var.service.pid_mode - task_definition_placement_constraints = var.service.task_definition_placement_constraints - proxy_configuration = var.service.proxy_configuration - requires_compatibilities = var.service.requires_compatibilities - runtime_platform = var.service.runtime_platform - # { - # operating_system_family = "LINUX" - # cpu_architecture = "X86_64" - # } - skip_destroy = var.service.skip_destroy + 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 = { for k, v in merge( { efs = { @@ -414,19 +365,9 @@ module "ecs_service" { tasks_iam_role_policies = var.service.tasks_iam_role_policies tasks_iam_role_statements = var.service.tasks_iam_role_statements - # Task set - external_id = var.service.external_id - scale = var.service.scale - force_delete = var.service.force_delete - wait_until_stable = var.service.wait_until_stable - wait_until_stable_timeout = var.service.wait_until_stable_timeout - # Autoscaling - enable_autoscaling = var.service.enable_autoscaling - autoscaling_min_capacity = var.service.autoscaling_min_capacity - autoscaling_max_capacity = var.service.autoscaling_max_capacity - autoscaling_policies = var.service.autoscaling_policies - autoscaling_scheduled_actions = var.service.autoscaling_scheduled_actions + # Atlantis only supports a single instance + enable_autoscaling = false # Security Group create_security_group = var.service.create_security_group @@ -469,7 +410,7 @@ module "efs" { name = try(coalesce(var.efs.name, var.name)) - # File system + # 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 @@ -478,33 +419,37 @@ module "efs" { 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 + # 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 = concat( - [{ - actions = [ - "elasticfilesystem:ClientMount", - "elasticfilesystem:ClientWrite", - ] - principals = [ - { - type = "AWS" - identifiers = [module.ecs_service.tasks_iam_role_arn] - } - ] - }], + 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 = var.efs.deny_nonsecure_transport + 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 = var.efs.mount_targets - # Security group + # 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 @@ -518,22 +463,22 @@ module "efs" { referenced_security_group_id = module.ecs_service.security_group_id } }, - 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" } } @@ -542,13 +487,9 @@ module "efs" { var.efs.access_points ) - # Backup policy - create_backup_policy = var.efs.create_backup_policy - enable_backup_policy = var.efs.enable_backup_policy - - # Replication configuration - create_replication_configuration = var.efs.create_replication_configuration - replication_configuration_destination = var.efs.replication_configuration_destination + # Backup Policy + create_backup_policy = false + enable_backup_policy = false tags = var.tags } diff --git a/variables.tf b/variables.tf index 1122698f..144e465f 100644 --- a/variables.tf +++ b/variables.tf @@ -34,20 +34,129 @@ variable "name" { 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 "atlantis_uid" { - description = "UID of the atlantis user" - type = number - default = 100 + 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 } ################################################################################ @@ -74,24 +183,115 @@ 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 = {} -} - -variable "alb_https_default_action" { - description = "Default action for the ALB https listener" - type = any - default = { - forward = { - target_group_key = "atlantis" - } - } -} - -variable "alb_subnets" { - description = "List of subnets to place ALB in. Required if `create_alb` is `true`" - type = list(string) - 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), []) + subnets = 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), {}) + }) + default = {} + nullable = false } variable "create_route53_records" { @@ -100,40 +300,6 @@ variable "create_route53_records" { default = true } -################################################################################ -# 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 = 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 = [] -} - ################################################################################ # ACM ################################################################################ @@ -174,6 +340,262 @@ 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) + }))) + 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 ################################################################################ @@ -186,6 +608,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 = {} } From 6f79d44336bee8b95d6b50da4325d02d1b4cf95c Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 9 Nov 2025 17:27:08 -0600 Subject: [PATCH 3/6] chore: Validate examples --- README.md | 26 +++++++++------ examples/github-complete/README.md | 6 ++-- examples/github-complete/main.tf | 10 +++--- examples/github-complete/versions.tf | 2 +- examples/github-separate/README.md | 10 +++--- examples/github-separate/main.tf | 32 ++++++++++--------- examples/github-separate/versions.tf | 2 +- main.tf | 10 +++--- modules/github-repository-webhook/README.md | 2 +- modules/github-repository-webhook/versions.tf | 2 +- modules/gitlab-repository-webhook/README.md | 2 +- modules/gitlab-repository-webhook/versions.tf | 2 +- versions.tf | 2 +- 13 files changed, 57 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index d8a6796a..31f5e5e2 100644 --- a/README.md +++ b/README.md @@ -21,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 = { @@ -49,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", @@ -58,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 = { + subnets = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + } + + # ACM certificate_domain_name = "example.com" route53_zone_id = "Z2ES7B9AZ6SHAE" @@ -81,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 @@ -118,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", @@ -127,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" @@ -211,7 +217,7 @@ module "atlantis" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.10.0 | +| [terraform](#requirement\_terraform) | >= 1.10 | | [aws](#requirement\_aws) | >= 6.19 | ## Providers @@ -236,10 +242,10 @@ 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 |
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), [])
subnets = optional(list(string), [])

# Listener(s)
default_port = optional(number, 80)
default_protocol = optional(string, "HTTP")
https_listener_ssl_policy = optional(string, "ELBSecurityPolicy-TLS13-1-3-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](#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), [])
subnets = 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\_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 |
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_retention_in_days = optional(number, 90)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_kms_key_id = optional(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 |
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 |
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 | diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index 9cb57fb1..ffd297d9 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -19,7 +19,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.10.0 | +| [terraform](#requirement\_terraform) | >= 1.10 | | [aws](#requirement\_aws) | >= 6.19 | | [github](#requirement\_github) | >= 5.0 | | [random](#requirement\_random) | >= 3.0 | @@ -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 59fa3f27..1b5f15c7 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,6 +65,7 @@ module "atlantis" { ] } + # ECS Service service = { subnet_ids = module.vpc.private_subnets @@ -82,10 +84,6 @@ module "atlantis" { 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 diff --git a/examples/github-complete/versions.tf b/examples/github-complete/versions.tf index 3712b302..bf6cad10 100644 --- a/examples/github-complete/versions.tf +++ b/examples/github-complete/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.10.0" + required_version = ">= 1.10" required_providers { aws = { diff --git a/examples/github-separate/README.md b/examples/github-separate/README.md index 6cab6376..21270b65 100644 --- a/examples/github-separate/README.md +++ b/examples/github-separate/README.md @@ -19,7 +19,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.10.0 | +| [terraform](#requirement\_terraform) | >= 1.10 | | [aws](#requirement\_aws) | >= 6.19 | | [github](#requirement\_github) | >= 5.0 | | [random](#requirement\_random) | >= 3.0 | @@ -35,13 +35,13 @@ Note that this example may create resources which cost money. Run `terraform des | 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 3ec64c38..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 3712b302..bf6cad10 100644 --- a/examples/github-separate/versions.tf +++ b/examples/github-separate/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.10.0" + required_version = ">= 1.10" required_providers { aws = { diff --git a/main.tf b/main.tf index 52b9b61f..3d8ac40d 100644 --- a/main.tf +++ b/main.tf @@ -318,21 +318,21 @@ module "ecs_service" { requires_compatibilities = var.service.requires_compatibilities runtime_platform = var.service.runtime_platform track_latest = true - volume = { for k, v in merge( - { + volume = merge( + { for k, v in { efs = { efs_volume_configuration = { file_system_id = module.efs.id transit_encryption = "ENABLED" authorization_config = { - access_point_id = module.efs.access_points["atlantis"].id + access_point_id = try(module.efs.access_points["atlantis"].id, "") iam = "ENABLED" } } } - }, + } : k => v if var.enable_efs }, var.service.volume - ) : k => v if var.enable_efs } + ) task_tags = var.service.task_tags # Task execution IAM role 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/versions.tf b/versions.tf index 516b5aa4..e3e52642 100644 --- a/versions.tf +++ b/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.10.0" + required_version = ">= 1.10" required_providers { aws = { From 08e71ac0baea2ccce887f3cce19bdc501a700071 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 9 Nov 2025 18:36:08 -0600 Subject: [PATCH 4/6] docs: Add upgrade guide --- README.md | 4 +- docs/UPGRADE-5.0.md | 140 +++++++++++++++++++++++++++++++ examples/github-complete/main.tf | 2 +- main.tf | 2 +- variables.tf | 14 ++-- 5 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 docs/UPGRADE-5.0.md diff --git a/README.md b/README.md index 31f5e5e2..b313486a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ module "atlantis" { # ALB alb = { - subnets = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] } # ACM @@ -242,7 +242,7 @@ 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 |
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), [])
subnets = 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](#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\_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 |
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 | 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/main.tf b/examples/github-complete/main.tf index 1b5f15c7..8381399f 100644 --- a/examples/github-complete/main.tf +++ b/examples/github-complete/main.tf @@ -78,7 +78,7 @@ module "atlantis" { # ALB alb = { - subnets = module.vpc.public_subnets + subnet_ids = module.vpc.public_subnets # For example only enable_deletion_protection = false diff --git a/main.tf b/main.tf index 3d8ac40d..d86920c6 100644 --- a/main.tf +++ b/main.tf @@ -48,7 +48,7 @@ module "alb" { 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.subnets + subnets = var.alb.subnet_ids # Listener(s) default_port = var.alb.default_port diff --git a/variables.tf b/variables.tf index 144e465f..dd9d9f4c 100644 --- a/variables.tf +++ b/variables.tf @@ -4,6 +4,12 @@ variable "create" { default = true } +variable "name" { + description = "Common name to use on all resources created unless a more specific name is provided" + type = string + default = "atlantis" +} + variable "region" { description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" type = string @@ -26,12 +32,6 @@ variable "vpc_id" { # Atlantis ################################################################################ -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 = object({ @@ -207,7 +207,7 @@ variable "alb" { name = optional(string) preserve_host_header = optional(bool) security_groups = optional(list(string), []) - subnets = optional(list(string), []) + subnet_ids = optional(list(string), []) # Listener(s) default_port = optional(number, 80) From f7e64ff6d31ec460c00071c0e76818ffc193a9cb Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 9 Nov 2025 18:42:11 -0600 Subject: [PATCH 5/6] feat: Add support for `deployment_circuit_breaker` --- README.md | 2 +- main.tf | 1 + variables.tf | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b313486a..f90d1434 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ No resources. | [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 |
object({
capacity_provider_strategy = optional(map(object({
base = optional(number)
capacity_provider = string
weight = optional(number)
})))
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 | +| [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/main.tf b/main.tf index d86920c6..0eb5bf03 100644 --- a/main.tf +++ b/main.tf @@ -200,6 +200,7 @@ module "ecs_service" { # Service capacity_provider_strategy = var.service.capacity_provider_strategy cluster_arn = var.create_cluster && var.create ? module.ecs_cluster.arn : var.cluster_arn + deployment_circuit_breaker = var.service.deployment_circuit_breaker deployment_maximum_percent = 100 deployment_minimum_healthy_percent = 0 desired_count = 1 diff --git a/variables.tf b/variables.tf index dd9d9f4c..3768eb5b 100644 --- a/variables.tf +++ b/variables.tf @@ -432,6 +432,10 @@ variable "service" { 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) From 4a8a709b79091e8071ff1a14e3bdd4f7e7265bd1 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 9 Nov 2025 18:46:30 -0600 Subject: [PATCH 6/6] fix: Update example MSV Terraform version due to SecretsManager module --- examples/github-complete/README.md | 2 +- examples/github-complete/versions.tf | 2 +- examples/github-separate/README.md | 2 +- examples/github-separate/versions.tf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/github-complete/README.md b/examples/github-complete/README.md index ffd297d9..f1fb3158 100644 --- a/examples/github-complete/README.md +++ b/examples/github-complete/README.md @@ -19,7 +19,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | +| [terraform](#requirement\_terraform) | >= 1.11 | | [aws](#requirement\_aws) | >= 6.19 | | [github](#requirement\_github) | >= 5.0 | | [random](#requirement\_random) | >= 3.0 | diff --git a/examples/github-complete/versions.tf b/examples/github-complete/versions.tf index bf6cad10..8427c067 100644 --- a/examples/github-complete/versions.tf +++ b/examples/github-complete/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { aws = { diff --git a/examples/github-separate/README.md b/examples/github-separate/README.md index 21270b65..04812ad3 100644 --- a/examples/github-separate/README.md +++ b/examples/github-separate/README.md @@ -19,7 +19,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.10 | +| [terraform](#requirement\_terraform) | >= 1.11 | | [aws](#requirement\_aws) | >= 6.19 | | [github](#requirement\_github) | >= 5.0 | | [random](#requirement\_random) | >= 3.0 | diff --git a/examples/github-separate/versions.tf b/examples/github-separate/versions.tf index bf6cad10..8427c067 100644 --- a/examples/github-separate/versions.tf +++ b/examples/github-separate/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.10" + required_version = ">= 1.11" required_providers { aws = {