################################################################################ # Cluster ################################################################################ locals { execute_command_configuration = { logging = "OVERRIDE" log_configuration = { cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, null) } } } resource "aws_ecs_cluster" "this" { count = var.create ? 1 : 0 name = var.cluster_name dynamic "configuration" { for_each = var.create_cloudwatch_log_group ? [var.cluster_configuration] : [] content { dynamic "execute_command_configuration" { for_each = try([merge(local.execute_command_configuration, configuration.value.execute_command_configuration)], [{}]) content { kms_key_id = try(execute_command_configuration.value.kms_key_id, null) logging = try(execute_command_configuration.value.logging, "DEFAULT") dynamic "log_configuration" { for_each = try([execute_command_configuration.value.log_configuration], []) content { cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) } } } } } } dynamic "configuration" { for_each = !var.create_cloudwatch_log_group && length(var.cluster_configuration) > 0 ? [var.cluster_configuration] : [] content { dynamic "execute_command_configuration" { for_each = try([configuration.value.execute_command_configuration], [{}]) content { kms_key_id = try(execute_command_configuration.value.kms_key_id, null) logging = try(execute_command_configuration.value.logging, "DEFAULT") dynamic "log_configuration" { for_each = try([execute_command_configuration.value.log_configuration], []) content { cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) } } } } } } dynamic "service_connect_defaults" { for_each = length(var.cluster_service_connect_defaults) > 0 ? [var.cluster_service_connect_defaults] : [] content { namespace = service_connect_defaults.value.namespace } } dynamic "setting" { for_each = flatten([var.cluster_settings]) content { name = setting.value.name value = setting.value.value } } tags = var.tags } ################################################################################ # CloudWatch Log Group ################################################################################ resource "aws_cloudwatch_log_group" "this" { count = var.create && var.create_cloudwatch_log_group ? 1 : 0 name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs/${var.cluster_name}"), "") retention_in_days = var.cloudwatch_log_group_retention_in_days kms_key_id = var.cloudwatch_log_group_kms_key_id tags = merge(var.tags, var.cloudwatch_log_group_tags) } ################################################################################ # Cluster Capacity Providers ################################################################################ locals { default_capacity_providers = merge( { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } ) } resource "aws_ecs_cluster_capacity_providers" "this" { count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 cluster_name = aws_ecs_cluster.this[0].name capacity_providers = distinct(concat( [for k, v in var.fargate_capacity_providers : try(v.name, k)], [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] )) # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations dynamic "default_capacity_provider_strategy" { for_each = local.default_capacity_providers iterator = strategy content { capacity_provider = try(strategy.value.name, strategy.key) base = try(strategy.value.default_capacity_provider_strategy.base, null) weight = try(strategy.value.default_capacity_provider_strategy.weight, null) } } depends_on = [ aws_ecs_capacity_provider.this ] } ################################################################################ # Capacity Provider - Autoscaling Group(s) ################################################################################ resource "aws_ecs_capacity_provider" "this" { for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } name = try(each.value.name, each.key) auto_scaling_group_provider { auto_scaling_group_arn = each.value.auto_scaling_group_arn # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work managed_termination_protection = length(try([each.value.managed_scaling], [])) == 0 ? "DISABLED" : try(each.value.managed_termination_protection, null) dynamic "managed_scaling" { for_each = try([each.value.managed_scaling], []) content { instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) status = try(managed_scaling.value.status, null) target_capacity = try(managed_scaling.value.target_capacity, null) } } } tags = merge(var.tags, try(each.value.tags, {})) } ################################################################################ # Task Execution - IAM Role # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html ################################################################################ locals { task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.cluster_name), "") create_task_exec_iam_role = var.create && var.create_task_exec_iam_role create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy } data "aws_iam_policy_document" "task_exec_assume" { count = local.create_task_exec_iam_role ? 1 : 0 statement { sid = "ECSTaskExecutionAssumeRole" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ecs-tasks.amazonaws.com"] } } } resource "aws_iam_role" "task_exec" { count = local.create_task_exec_iam_role ? 1 : 0 name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null path = var.task_exec_iam_role_path description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.cluster_name}") assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json permissions_boundary = var.task_exec_iam_role_permissions_boundary force_detach_policies = true tags = merge(var.tags, var.task_exec_iam_role_tags) } resource "aws_iam_role_policy_attachment" "task_exec_additional" { for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } role = aws_iam_role.task_exec[0].name policy_arn = each.value } data "aws_iam_policy_document" "task_exec" { count = local.create_task_exec_policy ? 1 : 0 # Pulled from AmazonECSTaskExecutionRolePolicy statement { sid = "Logs" actions = [ "logs:CreateLogStream", "logs:PutLogEvents", ] resources = ["*"] } # Pulled from AmazonECSTaskExecutionRolePolicy statement { sid = "ECR" actions = [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", ] resources = ["*"] } dynamic "statement" { for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] content { sid = "GetSSMParams" actions = ["ssm:GetParameters"] resources = var.task_exec_ssm_param_arns } } dynamic "statement" { for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] content { sid = "GetSecrets" actions = ["secretsmanager:GetSecretValue"] resources = var.task_exec_secret_arns } } dynamic "statement" { for_each = var.task_exec_iam_statements content { sid = try(statement.value.sid, null) actions = try(statement.value.actions, null) not_actions = try(statement.value.not_actions, null) effect = try(statement.value.effect, null) resources = try(statement.value.resources, null) not_resources = try(statement.value.not_resources, null) dynamic "principals" { for_each = try(statement.value.principals, []) content { type = principals.value.type identifiers = principals.value.identifiers } } dynamic "not_principals" { for_each = try(statement.value.not_principals, []) content { type = not_principals.value.type identifiers = not_principals.value.identifiers } } dynamic "condition" { for_each = try(statement.value.conditions, []) content { test = condition.value.test values = condition.value.values variable = condition.value.variable } } } } } resource "aws_iam_policy" "task_exec" { count = local.create_task_exec_policy ? 1 : 0 name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") policy = data.aws_iam_policy_document.task_exec[0].json tags = merge(var.tags, var.task_exec_iam_role_tags) } resource "aws_iam_role_policy_attachment" "task_exec" { count = local.create_task_exec_policy ? 1 : 0 role = aws_iam_role.task_exec[0].name policy_arn = aws_iam_policy.task_exec[0].arn }