diff --git a/.circleci/config.yml b/.circleci/config.yml index 73c8a10..4b9b427 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: validate: docker: - - image: trussworks/circleci-docker-primary:a18ba9987556eec2e48354848a3c9fb4d5b69ac8 + - image: trussworks/circleci-docker-primary:tf12-4c1fd54273446484259597ae3da9deb2806498ed steps: - checkout - restore_cache: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8d31e32..5e17f67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,8 +17,7 @@ repos: - id: markdownlint - repo: git://github.com/antonbabenko/pre-commit-terraform - rev: v1.12.0 + rev: v1.18.0 hooks: - id: terraform_docs - id: terraform_fmt - - id: terraform_validate_no_variables diff --git a/README.md b/README.md index 8e26667..d957475 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - Creates an ECS service. Creates the following resources: @@ -25,16 +24,16 @@ module "app_ecs_service" { name = "app" environment = "prod" - ecs_cluster_name = "cluster-name" - ecs_vpc_id = "${module.vpc.vpc_id}" - ecs_subnet_ids = "${module.vpc.private_subnets}" + ecs_cluster = aws_ecs_cluster.mycluster + ecs_vpc_id = module.vpc.vpc_id + ecs_subnet_ids = module.vpc.private_subnets tasks_desired_count = 2 tasks_minimum_healthy_percent = 50 tasks_maximum_percent = 200 associate_alb = true - alb_security_group = "${module.security_group.id}" - lb_target_group = "${module.target_group.id}" + alb_security_group = module.security_group.id + lb_target_group = module.target_group.id } ``` @@ -47,19 +46,20 @@ module "app_ecs_service" { name = "app" environment = "prod" - ecs_cluster_name = "cluster-name" - ecs_vpc_id = "${module.vpc.vpc_id}" - ecs_subnet_ids = "${module.vpc.private_subnets}" + ecs_cluster = aws_ecs_cluster.mycluster + ecs_vpc_id = module.vpc.vpc_id + ecs_subnet_ids = module.vpc.private_subnets tasks_desired_count = 2 tasks_minimum_healthy_percent = 50 tasks_maximum_percent = 200 associate_nlb = true nlb_subnet_cidr_blocks = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"] - lb_target_group = "${module.target_group.id}" + lb_target_group = module.target_group.id } ``` + ## Inputs | Name | Description | Type | Default | Required | @@ -77,10 +77,10 @@ module "app_ecs_service" { | container\_health\_check\_port | An additional port on which the container can receive a health check. Zero means the container port can only receive a health check on the port set by the container_port variable. | string | `"0"` | no | | container\_image | The image of the container. | string | `"golang:1.12.5-alpine"` | no | | container\_port | The port on which the container will receive traffic. | string | `"80"` | no | -| ecr\_repo\_arns | The ARNs of the ECR repos. By default, allows all repositories. | list | `[ "*" ]` | no | -| ecs\_cluster\_name | The name of the ECS cluster. | string | n/a | yes | +| ecr\_repo\_arns | The ARNs of the ECR repos. By default, allows all repositories. | list(string) | `[ "*" ]` | no | +| ecs\_cluster | ECS cluster object for this task. | object | n/a | yes | | ecs\_instance\_role | The name of the ECS instance role. | string | `""` | no | -| ecs\_subnet\_ids | Subnet IDs for the ECS tasks. | list | n/a | yes | +| ecs\_subnet\_ids | Subnet IDs for the ECS tasks. | list(string) | n/a | yes | | ecs\_use\_fargate | Whether to use Fargate for the task definition. | string | `"false"` | no | | ecs\_vpc\_id | VPC ID to be used by ECS. | string | n/a | yes | | environment | Environment tag, e.g prod. | string | n/a | yes | @@ -90,7 +90,7 @@ module "app_ecs_service" { | logs\_cloudwatch\_group | CloudWatch log group to create and use. Default: /ecs/{name}-{environment} | string | `""` | no | | logs\_cloudwatch\_retention | Number of days you want to retain log events in the log group. | string | `"90"` | no | | name | The service name. | string | n/a | yes | -| nlb\_subnet\_cidr\_blocks | List of Network Load Balancer (NLB) CIDR blocks to allow traffic from. | list | `[]` | no | +| nlb\_subnet\_cidr\_blocks | List of Network Load Balancer (NLB) CIDR blocks to allow traffic from. | list(string) | `[]` | no | | target\_container\_name | Name of the container the Load Balancer should target. Default: {name}-{environment} | string | `""` | no | | tasks\_desired\_count | The number of instances of a task definition. | string | `"1"` | no | | tasks\_maximum\_percent | Upper limit on the number of running tasks. | string | `"200"` | no | @@ -113,15 +113,10 @@ module "app_ecs_service" { ## Upgrade Path -### 1.14.0 to 1.15.0 - -In upgrading to this version you need to pass through the ECS Cluster Name and not the ECS Cluster ARN. -The difference would be changing `ecs_cluster_arn` to `ecs_cluster_name` and passing in the name info. -The module will take care of pulling the ARN from the ECS Cluster data resource on your behalf. +### 1.15.0 to 2.0.0 -If you decide you do not want metric alarms you can also set two more settings: - -```hcl - cloudwatch_alarm_cpu_enable = false - cloudwatch_alarm_mem_enable = false -``` +v2.0.0 of this module is built against Terraform v0.12. In addition to +requiring this upgrade, the v1.15.0 version of the module took the name +of the ECS cluster as a parameter; v2.0.0 takes the actual object of the +ECS cluster as a parameter instead. You will need to update previous +instances of this module with the altered parameter. diff --git a/main.tf b/main.tf index d456769..a32b689 100644 --- a/main.tf +++ b/main.tf @@ -25,16 +25,16 @@ * name = "app" * environment = "prod" * - * ecs_cluster_name = "cluster-name" - * ecs_vpc_id = "${module.vpc.vpc_id}" - * ecs_subnet_ids = "${module.vpc.private_subnets}" + * ecs_cluster = aws_ecs_cluster.mycluster + * ecs_vpc_id = module.vpc.vpc_id + * ecs_subnet_ids = module.vpc.private_subnets * tasks_desired_count = 2 * tasks_minimum_healthy_percent = 50 * tasks_maximum_percent = 200 * * associate_alb = true - * alb_security_group = "${module.security_group.id}" - * lb_target_group = "${module.target_group.id}" + * alb_security_group = module.security_group.id + * lb_target_group = module.target_group.id * } * ``` * @@ -47,24 +47,20 @@ * name = "app" * environment = "prod" * - * ecs_cluster_name = "cluster-name" - * ecs_vpc_id = "${module.vpc.vpc_id}" - * ecs_subnet_ids = "${module.vpc.private_subnets}" + * ecs_cluster = aws_ecs_cluster.mycluster + * ecs_vpc_id = module.vpc.vpc_id + * ecs_subnet_ids = module.vpc.private_subnets * tasks_desired_count = 2 * tasks_minimum_healthy_percent = 50 * tasks_maximum_percent = 200 * * associate_nlb = true * nlb_subnet_cidr_blocks = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"] - * lb_target_group = "${module.target_group.id}" + * lb_target_group = module.target_group.id * } * ``` */ -data "aws_ecs_cluster" "main" { - cluster_name = "${var.ecs_cluster_name}" -} - locals { awslogs_group = "${var.logs_cloudwatch_group == "" ? "/ecs/${var.environment}/${var.name}" : var.logs_cloudwatch_group}" target_container_name = "${var.target_container_name == "" ? "${var.name}-${var.environment}" : var.target_container_name}" @@ -109,6 +105,7 @@ locals { } ] EOF + } # @@ -116,12 +113,12 @@ EOF # resource "aws_cloudwatch_log_group" "main" { - name = "${local.awslogs_group}" - retention_in_days = "${var.logs_cloudwatch_retention}" + name = local.awslogs_group + retention_in_days = var.logs_cloudwatch_retention tags = { Name = "${var.name}-${var.environment}" - Environment = "${var.environment}" + Environment = var.environment Automation = "Terraform" } } @@ -131,7 +128,7 @@ resource "aws_cloudwatch_metric_alarm" "alarm_cpu" { alarm_name = "${local.cloudwatch_alarm_name}-cpu" alarm_description = "Monitors ECS CPU Utilization" - alarm_actions = ["${var.cloudwatch_alarm_actions}"] + alarm_actions = var.cloudwatch_alarm_actions comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "2" @@ -139,11 +136,11 @@ resource "aws_cloudwatch_metric_alarm" "alarm_cpu" { namespace = "AWS/ECS" period = "120" statistic = "Average" - threshold = "${var.cloudwatch_alarm_cpu_threshold}" + threshold = var.cloudwatch_alarm_cpu_threshold dimensions = { - "ClusterName" = "${var.ecs_cluster_name}" - "ServiceName" = "${aws_ecs_service.main.name}" + "ClusterName" = var.ecs_cluster.name + "ServiceName" = aws_ecs_service.main[count.index].name } } @@ -152,7 +149,7 @@ resource "aws_cloudwatch_metric_alarm" "alarm_mem" { alarm_name = "${local.cloudwatch_alarm_name}-mem" alarm_description = "Monitors ECS CPU Utilization" - alarm_actions = ["${var.cloudwatch_alarm_actions}"] + alarm_actions = var.cloudwatch_alarm_actions comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "2" @@ -160,11 +157,11 @@ resource "aws_cloudwatch_metric_alarm" "alarm_mem" { namespace = "AWS/ECS" period = "120" statistic = "Average" - threshold = "${var.cloudwatch_alarm_mem_threshold}" + threshold = var.cloudwatch_alarm_mem_threshold dimensions = { - "ClusterName" = "${var.ecs_cluster_name}" - "ServiceName" = "${aws_ecs_service.main.name}" + "ClusterName" = var.ecs_cluster.name + "ServiceName" = aws_ecs_service.main[count.index].name } } @@ -173,7 +170,7 @@ resource "aws_cloudwatch_metric_alarm" "alarm_cpu_no_lb" { alarm_name = "${local.cloudwatch_alarm_name}-cpu" alarm_description = "Monitors ECS CPU Utilization" - alarm_actions = ["${var.cloudwatch_alarm_actions}"] + alarm_actions = var.cloudwatch_alarm_actions comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "2" @@ -181,11 +178,11 @@ resource "aws_cloudwatch_metric_alarm" "alarm_cpu_no_lb" { namespace = "AWS/ECS" period = "120" statistic = "Average" - threshold = "${var.cloudwatch_alarm_cpu_threshold}" + threshold = var.cloudwatch_alarm_cpu_threshold dimensions = { - "ClusterName" = "${var.ecs_cluster_name}" - "ServiceName" = "${aws_ecs_service.main_no_lb.name}" + "ClusterName" = var.ecs_cluster.name + "ServiceName" = aws_ecs_service.main_no_lb[count.index].name } } @@ -194,7 +191,7 @@ resource "aws_cloudwatch_metric_alarm" "alarm_mem_no_lb" { alarm_name = "${local.cloudwatch_alarm_name}-mem" alarm_description = "Monitors ECS CPU Utilization" - alarm_actions = ["${var.cloudwatch_alarm_actions}"] + alarm_actions = var.cloudwatch_alarm_actions comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = "2" @@ -202,11 +199,11 @@ resource "aws_cloudwatch_metric_alarm" "alarm_mem_no_lb" { namespace = "AWS/ECS" period = "120" statistic = "Average" - threshold = "${var.cloudwatch_alarm_mem_threshold}" + threshold = var.cloudwatch_alarm_mem_threshold dimensions = { - "ClusterName" = "${var.ecs_cluster_name}" - "ServiceName" = "${aws_ecs_service.main_no_lb.name}" + "ClusterName" = var.ecs_cluster.name + "ServiceName" = aws_ecs_service.main_no_lb[count.index].name } } @@ -217,18 +214,18 @@ resource "aws_cloudwatch_metric_alarm" "alarm_mem_no_lb" { resource "aws_security_group" "ecs_sg" { name = "ecs-${var.name}-${var.environment}" description = "${var.name}-${var.environment} container security group" - vpc_id = "${var.ecs_vpc_id}" + vpc_id = var.ecs_vpc_id tags = { Name = "ecs-${var.name}-${var.environment}" - Environment = "${var.environment}" + Environment = var.environment Automation = "Terraform" } } resource "aws_security_group_rule" "app_ecs_allow_outbound" { description = "All outbound" - security_group_id = "${aws_security_group.ecs_sg.id}" + security_group_id = aws_security_group.ecs_sg.id type = "egress" from_port = 0 @@ -238,55 +235,55 @@ resource "aws_security_group_rule" "app_ecs_allow_outbound" { } resource "aws_security_group_rule" "app_ecs_allow_https_from_alb" { - count = "${var.associate_alb}" + count = var.associate_alb ? 1 : 0 description = "Allow in ALB" - security_group_id = "${aws_security_group.ecs_sg.id}" + security_group_id = aws_security_group.ecs_sg.id type = "ingress" - from_port = "${var.container_port}" - to_port = "${var.container_port}" + from_port = var.container_port + to_port = var.container_port protocol = "tcp" - source_security_group_id = "${var.alb_security_group}" + source_security_group_id = var.alb_security_group } resource "aws_security_group_rule" "app_ecs_allow_health_check_from_alb" { - count = "${var.associate_alb > 0 && var.container_health_check_port > 0 ? 1 : 0}" + count = var.associate_alb && var.container_health_check_port > 0 ? 1 : 0 description = "Allow in health check from ALB" - security_group_id = "${aws_security_group.ecs_sg.id}" + security_group_id = aws_security_group.ecs_sg.id type = "ingress" - from_port = "${var.container_health_check_port}" - to_port = "${var.container_health_check_port}" + from_port = var.container_health_check_port + to_port = var.container_health_check_port protocol = "tcp" - source_security_group_id = "${var.alb_security_group}" + source_security_group_id = var.alb_security_group } resource "aws_security_group_rule" "app_ecs_allow_tcp_from_nlb" { - count = "${var.associate_nlb}" + count = var.associate_nlb ? 1 : 0 description = "Allow in NLB" - security_group_id = "${aws_security_group.ecs_sg.id}" + security_group_id = aws_security_group.ecs_sg.id type = "ingress" - from_port = "${var.container_port}" - to_port = "${var.container_port}" + from_port = var.container_port + to_port = var.container_port protocol = "tcp" - cidr_blocks = ["${var.nlb_subnet_cidr_blocks}"] + cidr_blocks = var.nlb_subnet_cidr_blocks } resource "aws_security_group_rule" "app_ecs_allow_health_check_from_nlb" { - count = "${var.associate_nlb > 0 && var.container_health_check_port > 0 ? 1 : 0}" + count = var.associate_nlb && var.container_health_check_port > 0 ? 1 : 0 description = "Allow in health check from NLB" - security_group_id = "${aws_security_group.ecs_sg.id}" + security_group_id = aws_security_group.ecs_sg.id type = "ingress" - from_port = "${var.container_health_check_port}" - to_port = "${var.container_health_check_port}" + from_port = var.container_health_check_port + to_port = var.container_health_check_port protocol = "tcp" - cidr_blocks = ["${var.nlb_subnet_cidr_blocks}"] + cidr_blocks = var.nlb_subnet_cidr_blocks } # @@ -294,7 +291,7 @@ resource "aws_security_group_rule" "app_ecs_allow_health_check_from_nlb" { # data "aws_iam_policy_document" "instance_role_policy_doc" { - count = "${var.ecs_instance_role != "" ? 1 : 0}" + count = var.ecs_instance_role != "" ? 1 : 0 statement { actions = [ @@ -303,7 +300,7 @@ data "aws_iam_policy_document" "instance_role_policy_doc" { "ecs:Submit*", ] - resources = ["${data.aws_ecs_cluster.main.arn}"] + resources = [var.ecs_cluster.arn] } statement { @@ -316,7 +313,7 @@ data "aws_iam_policy_document" "instance_role_policy_doc" { condition { test = "StringEquals" variable = "ecs:cluster" - values = ["${data.aws_ecs_cluster.main.arn}"] + values = [var.ecs_cluster.arn] } } @@ -336,7 +333,7 @@ data "aws_iam_policy_document" "instance_role_policy_doc" { "logs:PutLogEvents", ] - resources = ["${aws_cloudwatch_log_group.main.arn}"] + resources = [aws_cloudwatch_log_group.main.arn] } statement { @@ -354,16 +351,16 @@ data "aws_iam_policy_document" "instance_role_policy_doc" { "ecr:BatchGetImage", ] - resources = ["${var.ecr_repo_arns}"] + resources = var.ecr_repo_arns } } resource "aws_iam_role_policy" "instance_role_policy" { - count = "${var.ecs_instance_role != "" ? 1 : 0}" + count = var.ecs_instance_role != "" ? 1 : 0 name = "${var.ecs_instance_role}-policy" - role = "${var.ecs_instance_role}" - policy = "${data.aws_iam_policy_document.instance_role_policy_doc.json}" + role = var.ecs_instance_role + policy = data.aws_iam_policy_document.instance_role_policy_doc[0].json } # @@ -388,7 +385,7 @@ data "aws_iam_policy_document" "task_execution_role_policy_doc" { "logs:PutLogEvents", ] - resources = ["${aws_cloudwatch_log_group.main.arn}"] + resources = [aws_cloudwatch_log_group.main.arn] } statement { @@ -406,70 +403,71 @@ data "aws_iam_policy_document" "task_execution_role_policy_doc" { "ecr:BatchGetImage", ] - resources = ["${var.ecr_repo_arns}"] + resources = var.ecr_repo_arns } } resource "aws_iam_role" "task_role" { name = "ecs-task-role-${var.name}-${var.environment}" - assume_role_policy = "${data.aws_iam_policy_document.ecs_assume_role_policy.json}" + assume_role_policy = data.aws_iam_policy_document.ecs_assume_role_policy.json } resource "aws_iam_role" "task_execution_role" { - count = "${var.ecs_use_fargate ? 1 : 0}" + count = var.ecs_use_fargate ? 1 : 0 name = "ecs-task-execution-role-${var.name}-${var.environment}" - assume_role_policy = "${data.aws_iam_policy_document.ecs_assume_role_policy.json}" + assume_role_policy = data.aws_iam_policy_document.ecs_assume_role_policy.json } resource "aws_iam_role_policy" "task_execution_role_policy" { - count = "${var.ecs_use_fargate ? 1 : 0}" + count = var.ecs_use_fargate ? 1 : 0 - name = "${aws_iam_role.task_execution_role.name}-policy" - role = "${aws_iam_role.task_execution_role.name}" - policy = "${data.aws_iam_policy_document.task_execution_role_policy_doc.json}" + name = "${aws_iam_role.task_execution_role[0].name}-policy" + role = aws_iam_role.task_execution_role[0].name + policy = data.aws_iam_policy_document.task_execution_role_policy_doc.json } # # ECS # -data "aws_region" "current" {} +data "aws_region" "current" { +} # Create a task definition with a golang image so the ecs service can be easily # tested. We expect deployments will manage the future container definitions. resource "aws_ecs_task_definition" "main" { family = "${var.name}-${var.environment}" network_mode = "awsvpc" - task_role_arn = "${aws_iam_role.task_role.arn}" + task_role_arn = aws_iam_role.task_role.arn # Fargate requirements - requires_compatibilities = "${compact(list(var.ecs_use_fargate ? "FARGATE" : ""))}" - cpu = "${var.ecs_use_fargate ? var.fargate_task_cpu : ""}" - memory = "${var.ecs_use_fargate ? var.fargate_task_memory : ""}" - execution_role_arn = "${join("", aws_iam_role.task_execution_role.*.arn)}" + requires_compatibilities = compact([var.ecs_use_fargate ? "FARGATE" : ""]) + cpu = var.ecs_use_fargate ? var.fargate_task_cpu : "" + memory = var.ecs_use_fargate ? var.fargate_task_memory : "" + execution_role_arn = join("", aws_iam_role.task_execution_role.*.arn) - container_definitions = "${var.container_definitions == "" ? local.default_container_definitions : var.container_definitions}" + container_definitions = var.container_definitions == "" ? local.default_container_definitions : var.container_definitions lifecycle { ignore_changes = [ - "requires_compatibilities", - "cpu", - "memory", - "execution_role_arn", - "container_definitions", + requires_compatibilities, + cpu, + memory, + execution_role_arn, + container_definitions, ] } } # Create a data source to pull the latest active revision from data "aws_ecs_task_definition" "main" { - task_definition = "${aws_ecs_task_definition.main.family}" - depends_on = ["aws_ecs_task_definition.main"] # ensures at least one task def exists + task_definition = aws_ecs_task_definition.main.family + depends_on = [aws_ecs_task_definition.main] # ensures at least one task def exists } locals { - ecs_service_launch_type = "${var.ecs_use_fargate ? "FARGATE" : "EC2"}" + ecs_service_launch_type = var.ecs_use_fargate ? "FARGATE" : "EC2" ecs_service_ordered_placement_strategy = { EC2 = [ @@ -482,86 +480,119 @@ locals { field = "instanceId" }, ] - FARGATE = [] } ecs_service_placement_constraints = { - EC2 = [{ - type = "distinctInstance" - }] - + EC2 = [ + { + type = "distinctInstance" + }, + ] FARGATE = [] } } resource "aws_ecs_service" "main" { - count = "${var.associate_alb || var.associate_nlb ? 1 : 0}" + count = var.associate_alb || var.associate_nlb ? 1 : 0 - name = "${var.name}" - cluster = "${data.aws_ecs_cluster.main.arn}" + name = var.name + cluster = var.ecs_cluster.arn - launch_type = "${local.ecs_service_launch_type}" + launch_type = local.ecs_service_launch_type # Use latest active revision task_definition = "${aws_ecs_task_definition.main.family}:${max( - "${aws_ecs_task_definition.main.revision}", - "${data.aws_ecs_task_definition.main.revision}")}" + aws_ecs_task_definition.main.revision, + data.aws_ecs_task_definition.main.revision, + )}" - desired_count = "${var.tasks_desired_count}" - deployment_minimum_healthy_percent = "${var.tasks_minimum_healthy_percent}" - deployment_maximum_percent = "${var.tasks_maximum_percent}" + desired_count = var.tasks_desired_count + deployment_minimum_healthy_percent = var.tasks_minimum_healthy_percent + deployment_maximum_percent = var.tasks_maximum_percent - ordered_placement_strategy = "${local.ecs_service_ordered_placement_strategy[local.ecs_service_launch_type]}" - placement_constraints = "${local.ecs_service_placement_constraints[local.ecs_service_launch_type]}" + dynamic ordered_placement_strategy { + for_each = local.ecs_service_ordered_placement_strategy[local.ecs_service_launch_type] + + content { + type = ordered_placement_strategy.value.type + field = ordered_placement_strategy.value.field + } + } + + dynamic placement_constraints { + for_each = local.ecs_service_placement_constraints[local.ecs_service_launch_type] + + content { + type = placement_constraints.value.type + } + } network_configuration { - subnets = ["${var.ecs_subnet_ids}"] - security_groups = ["${aws_security_group.ecs_sg.id}"] + subnets = var.ecs_subnet_ids + security_groups = [aws_security_group.ecs_sg.id] assign_public_ip = false } load_balancer { - target_group_arn = "${var.lb_target_group}" - container_name = "${local.target_container_name}" - container_port = "${var.container_port}" + target_group_arn = var.lb_target_group + container_name = local.target_container_name + container_port = var.container_port } lifecycle { - ignore_changes = ["task_definition"] + ignore_changes = [task_definition] } } -# XXX: We have to duplicate this resource with a count instead of parameterizing +# NOTE: We have to duplicate this resource with a count instead of parameterizing # the load_balancer argument due to this Terraform bug: # https://github.com/hashicorp/terraform/issues/16856 resource "aws_ecs_service" "main_no_lb" { - count = "${var.associate_alb || var.associate_nlb ? 0 : 1}" + count = var.associate_alb || var.associate_nlb ? 0 : 1 - name = "${var.name}" - cluster = "${data.aws_ecs_cluster.main.arn}" + name = var.name + cluster = var.ecs_cluster.arn - launch_type = "${local.ecs_service_launch_type}" + launch_type = local.ecs_service_launch_type # Use latest active revision task_definition = "${aws_ecs_task_definition.main.family}:${max( - "${aws_ecs_task_definition.main.revision}", - "${data.aws_ecs_task_definition.main.revision}")}" + aws_ecs_task_definition.main.revision, + data.aws_ecs_task_definition.main.revision, + )}" + + desired_count = var.tasks_desired_count + deployment_minimum_healthy_percent = var.tasks_minimum_healthy_percent + deployment_maximum_percent = var.tasks_maximum_percent + + dynamic ordered_placement_strategy { + for_each = local.ecs_service_ordered_placement_strategy[local.ecs_service_launch_type] + # for_each = var.ecs_use_fargate ? [] : ["attribute:ecs.availability-zone", "instanceId"] - desired_count = "${var.tasks_desired_count}" - deployment_minimum_healthy_percent = "${var.tasks_minimum_healthy_percent}" - deployment_maximum_percent = "${var.tasks_maximum_percent}" + content { + type = ordered_placement_strategy.value.type + field = ordered_placement_strategy.value.field + } + } + + dynamic placement_constraints { + for_each = local.ecs_service_placement_constraints[local.ecs_service_launch_type] + # for_each = var.ecs_use_fargate ? [] : ["distinctInstance"] - ordered_placement_strategy = "${local.ecs_service_ordered_placement_strategy[local.ecs_service_launch_type]}" - placement_constraints = "${local.ecs_service_placement_constraints[local.ecs_service_launch_type]}" + content { + type = placement_constraints.value.type + } + } network_configuration { - subnets = ["${var.ecs_subnet_ids}"] - security_groups = ["${aws_security_group.ecs_sg.id}"] + subnets = var.ecs_subnet_ids + security_groups = aws_security_group.ecs_sg.id assign_public_ip = false } lifecycle { - ignore_changes = ["task_definition"] + ignore_changes = [task_definition] } } + diff --git a/outputs.tf b/outputs.tf index f477cf3..c98b233 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,39 +1,40 @@ output "ecs_security_group_id" { description = "Security Group ID assigned to the ECS tasks." - value = "${aws_security_group.ecs_sg.id}" + value = aws_security_group.ecs_sg.id } output "task_execution_role_arn" { description = "The ARN of the task execution role that the Amazon ECS container agent and the Docker daemon can assume." - value = "${join("", aws_iam_role.task_execution_role.*.arn)}" + value = join("", aws_iam_role.task_execution_role.*.arn) } output "task_role_arn" { description = "The ARN of the IAM role assumed by Amazon ECS container tasks." - value = "${aws_iam_role.task_role.arn}" + value = aws_iam_role.task_role.arn } output "task_role_name" { description = "The name of the IAM role assumed by Amazon ECS container tasks." - value = "${aws_iam_role.task_role.name}" + value = aws_iam_role.task_role.name } output "task_definition_arn" { description = "Full ARN of the Task Definition (including both family and revision)." - value = "${aws_ecs_task_definition.main.arn}" + value = aws_ecs_task_definition.main.arn } output "task_definition_family" { description = "The family of the Task Definition." - value = "${aws_ecs_task_definition.main.family}" + value = aws_ecs_task_definition.main.family } output "awslogs_group" { description = "Name of the CloudWatch Logs log group containers should use." - value = "${local.awslogs_group}" + value = local.awslogs_group } output "awslogs_group_arn" { description = "ARN of the CloudWatch Logs log group containers should use." - value = "${aws_cloudwatch_log_group.main.arn}" + value = aws_cloudwatch_log_group.main.arn } + diff --git a/variables.tf b/variables.tf index b9a9097..293144c 100644 --- a/variables.tf +++ b/variables.tf @@ -1,11 +1,11 @@ variable "name" { description = "The service name." - type = "string" + type = string } variable "environment" { description = "Environment tag, e.g prod." - type = "string" + type = string } variable "cloudwatch_alarm_name" { @@ -47,134 +47,138 @@ variable "cloudwatch_alarm_mem_threshold" { variable "logs_cloudwatch_retention" { description = "Number of days you want to retain log events in the log group." default = 90 - type = "string" + type = string } variable "logs_cloudwatch_group" { description = "CloudWatch log group to create and use. Default: /ecs/{name}-{environment}" default = "" - type = "string" + type = string } variable "ecr_repo_arns" { description = "The ARNs of the ECR repos. By default, allows all repositories." - type = "list" + type = list(string) default = ["*"] } variable "ecs_use_fargate" { description = "Whether to use Fargate for the task definition." default = false - type = "string" + type = string } -variable "ecs_cluster_name" { - description = "The name of the ECS cluster." - type = "string" +variable "ecs_cluster" { + description = "ECS cluster object for this task." + type = object({ + arn = string + name = string + }) } variable "ecs_instance_role" { description = "The name of the ECS instance role." default = "" - type = "string" + type = string } variable "ecs_vpc_id" { description = "VPC ID to be used by ECS." - type = "string" + type = string } variable "ecs_subnet_ids" { description = "Subnet IDs for the ECS tasks." - type = "list" + type = list(string) } variable "fargate_task_cpu" { description = "Number of cpu units used in initial task definition. Default is minimum." default = 256 - type = "string" + type = string } variable "fargate_task_memory" { description = "Amount (in MiB) of memory used in initial task definition. Default is minimum." default = 512 - type = "string" + type = string } variable "tasks_desired_count" { description = "The number of instances of a task definition." default = 1 - type = "string" + type = string } variable "tasks_minimum_healthy_percent" { description = "Lower limit on the number of running tasks." default = "100" - type = "string" + type = string } variable "tasks_maximum_percent" { description = "Upper limit on the number of running tasks." default = "200" - type = "string" + type = string } variable "container_image" { description = "The image of the container." default = "golang:1.12.5-alpine" - type = "string" + type = string } variable "container_port" { description = "The port on which the container will receive traffic." default = 80 - type = "string" + type = string } variable "container_health_check_port" { description = "An additional port on which the container can receive a health check. Zero means the container port can only receive a health check on the port set by the container_port variable." default = 0 - type = "string" + type = string } variable "container_definitions" { description = "Container definitions provided as valid JSON document. Default uses golang:1.12.5-alpine running a simple hello world." default = "" - type = "string" + type = string } variable "target_container_name" { description = "Name of the container the Load Balancer should target. Default: {name}-{environment}" default = "" - type = "string" + type = string } variable "associate_alb" { description = "Whether to associate an Application Load Balancer (ALB) with the ECS service." default = false - type = "string" + type = string } variable "associate_nlb" { description = "Whether to associate a Network Load Balancer (NLB) with the ECS service." default = false - type = "string" + type = string } variable "alb_security_group" { description = "Application Load Balancer (ALB) security group ID to allow traffic from." default = "" - type = "string" + type = string } variable "lb_target_group" { description = "Either Application Load Balancer (ALB) or Network Load Balancer (NLB) target group ARN tasks will register with." default = "" - type = "string" + type = string } variable "nlb_subnet_cidr_blocks" { description = "List of Network Load Balancer (NLB) CIDR blocks to allow traffic from." default = [] - type = "list" + type = list(string) } +