diff --git a/terraform/modules/service/data.tf b/terraform/modules/service/data.tf index fdb1ad5b..776d0985 100644 --- a/terraform/modules/service/data.tf +++ b/terraform/modules/service/data.tf @@ -30,3 +30,7 @@ data "aws_ssm_parameter" "mtls_image_tag" { count = var.enable_mtls_sidecar ? 1 : 0 name = "/cdap/${local.cdap_ssm_env}/nonsensitive/mtls-sidecar/image-tag" } + +data "aws_service_discovery_http_namespace" "service_discovery_namespace" { + name = "${var.platform.env}.${var.platform.app}.cmscloud.local" +} diff --git a/terraform/modules/service/iam.tf b/terraform/modules/service/iam.tf index 017be947..556a427c 100644 --- a/terraform/modules/service/iam.tf +++ b/terraform/modules/service/iam.tf @@ -26,7 +26,6 @@ resource "aws_iam_role_policy" "execution" { policy = data.aws_iam_policy_document.execution[0].json } - data "aws_iam_policy_document" "execution" { count = var.execution_role_arn != null ? 0 : 1 statement { @@ -77,7 +76,7 @@ data "aws_iam_policy_document" "execution" { } dynamic "statement" { - for_each = var.enable_ecs_service_connect ? [1] : [] + for_each = (length(var.service_connect) > 0) ? [1] : [] content { sid = "AllowPassServiceConnectRole" actions = ["iam:PassRole"] @@ -91,7 +90,7 @@ data "aws_iam_policy_document" "execution" { #--------------------------- resource "aws_iam_role" "service_connect" { - count = var.enable_ecs_service_connect ? 1 : 0 + count = (length(var.service_connect) > 0) ? 1 : 0 name = "${local.service_name_full}-service-connect" assume_role_policy = jsonencode({ @@ -112,14 +111,14 @@ resource "aws_iam_role" "service_connect" { } resource "aws_iam_policy" "service_connect" { - count = var.enable_ecs_service_connect ? 1 : 0 + count = (length(var.service_connect) > 0) ? 1 : 0 name = "${local.service_name_full}-service-connect" description = "Base permissions for ECS Service Connect TLS lifecycle" policy = data.aws_iam_policy_document.service_connect.json } resource "aws_iam_role_policy_attachment" "service_connect" { - count = var.enable_ecs_service_connect ? 1 : 0 + count = (length(var.service_connect) > 0) ? 1 : 0 role = aws_iam_role.service_connect[0].name policy_arn = aws_iam_policy.service_connect[0].arn } @@ -137,7 +136,7 @@ data "aws_iam_policy_document" "service_connect" { } dynamic "statement" { - for_each = var.enable_ecs_service_connect ? [1] : [] + for_each = (length(var.service_connect) > 0) ? [1] : [] content { sid = "AllowCertManagement" actions = [ @@ -163,6 +162,7 @@ data "aws_iam_policy_document" "service_connect" { "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:UpdateSecret", + "secretsmanager:UpdateSecretVersionStage", "secretsmanager:DeleteSecret", "secretsmanager:PutSecretValue", "secretsmanager:TagResource", @@ -284,7 +284,6 @@ resource "aws_iam_policy" "ecs_exec" { policy = data.aws_iam_policy_document.ecs_exec.json } - data "aws_iam_policy_document" "ecs_exec" { statement { sid = "AllowECSExec" @@ -297,3 +296,9 @@ data "aws_iam_policy_document" "ecs_exec" { resources = ["*"] } } + +resource "aws_iam_role_policy_attachment" "ecs_exec" { + count = var.enable_execute_command ? 1 : 0 + role = aws_iam_role.task.name + policy_arn = aws_iam_policy.ecs_exec[0].arn +} diff --git a/terraform/modules/service/main.tf b/terraform/modules/service/main.tf index 869127ae..c35aca03 100644 --- a/terraform/modules/service/main.tf +++ b/terraform/modules/service/main.tf @@ -72,7 +72,7 @@ locals { sc_port_name = try( coalesce( - var.service_connect_port_name, + var.service_connect[0].port_name, local.enable_mtls_sidecar ? "proxy" : try( [for pm in coalesce(var.port_mappings, []) : pm.name if pm.name != null][0], null @@ -248,23 +248,27 @@ locals { } ] - environment = [ - { name = "ECS_FARGATE", value = "true" }, - { name = "DD_APM_ENABLED", value = "true" }, - { name = "DD_APM_NON_LOCAL_TRAFFIC", value = "true" }, - { name = "DD_APM_RECEIVER_PORT", value = "8126" }, - { name = "DD_APM_TELEMETRY_ENABLED", value = "false" }, - { name = "DD_DATA_STREAMS_ENABLED", value = "false" }, - { name = "DD_DOGSTATSD_NON_LOCAL_TRAFFIC", value = "true" }, - { name = "DD_DOGSTATSD_PORT", value = "8125" }, # Default - { name = "DD_ECS_TASK_COLLECTION_ENABLED", value = "true" }, - { name = "DD_ENV", value = var.platform.env }, - { name = "DD_LOGS_ENABLED", value = "false" }, # DD logging is currently not approved - { name = "DD_PROCESS_AGENT_ENABLED", value = "true" }, - { name = "DD_SERVICE", value = local.service_name }, - { name = "DD_SITE", value = "ddog-gov.com" }, - { name = "DD_TAGS", value = "application:${var.platform.app}, service:${local.service_name}" }, - ] + environment = concat( + [ + { name = "ECS_FARGATE", value = "true" }, + { name = "DD_APM_ENABLED", value = "true" }, + { name = "DD_APM_NON_LOCAL_TRAFFIC", value = "true" }, + { name = "DD_APM_RECEIVER_PORT", value = "8126" }, + { name = "DD_APM_TELEMETRY_ENABLED", value = "false" }, + { name = "DD_DATA_STREAMS_ENABLED", value = "false" }, + { name = "DD_DOGSTATSD_NON_LOCAL_TRAFFIC", value = "true" }, + { name = "DD_DOGSTATSD_PORT", value = "8125" }, # Default + { name = "DD_ECS_TASK_COLLECTION_ENABLED", value = "true" }, + { name = "DD_ENV", value = var.platform.env }, + { name = "DD_LOGS_ENABLED", value = "false" }, # DD logging is currently not approved + { name = "DD_PROCESS_AGENT_ENABLED", value = "true" }, + { name = "DD_SERVICE", value = local.service_name }, + { name = "DD_SITE", value = "ddog-gov.com" }, + { name = "DD_TAGS", value = "application:${var.platform.app}, service:${local.service_name}" }, + ], + var.additional_dd_environment + ) + secrets = [{ name = "DD_API_KEY", valueFrom = data.aws_ssm_parameter.datadog_api_key.name }] } } @@ -433,26 +437,38 @@ resource "aws_ecs_service" "this" { } } - dynamic "service_connect_configuration" { - for_each = var.enable_ecs_service_connect ? [1] : [] - content { - enabled = true - namespace = var.service_connect_namespace_arn + service_connect_configuration { + enabled = (length(var.service_connect) > 0) ? true : false + namespace = data.aws_service_discovery_http_namespace.service_discovery_namespace.arn - service { - discovery_name = local.service_name - port_name = local.sc_port_name + log_configuration { + log_driver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.app.name + "awslogs-stream-prefix" = "service-connect" + "awslogs-region" = "us-east-1" + } + } + + access_log_configuration { + format = "TEXT" + include_query_parameters = "ENABLED" + } + + dynamic "service" { + for_each = var.service_connect + + content { + discovery_name = service.value.discovery_name + port_name = service.value.port_name client_alias { - port = coalesce( - var.service_connect_client_port, - local.sc_port_name != null ? try(local.port_map[local.sc_port_name], null) : null - ) - dns_name = local.service_name + dns_name = service.value.dns_name + port = service.value.port } tls { - kms_key = var.platform.kms_alias_primary.target_key_arn + kms_key = var.platform.kms_alias_primary["target_key_arn"] role_arn = aws_iam_role.service_connect[0].arn issuer_cert_authority { @@ -462,6 +478,7 @@ resource "aws_ecs_service" "this" { } } } + deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent deployment_maximum_percent = var.deployment_maximum_percent health_check_grace_period_seconds = var.health_check_grace_period_seconds diff --git a/terraform/modules/service/outputs.tf b/terraform/modules/service/outputs.tf index 254a48b0..02d71cf4 100644 --- a/terraform/modules/service/outputs.tf +++ b/terraform/modules/service/outputs.tf @@ -35,7 +35,7 @@ output "listener_rule_arn" { output "service_connect_role_arn" { description = "ARN of the Service Connect IAM role (if Service Connect is enabled)." - value = var.enable_ecs_service_connect ? aws_iam_role.service_connect[0].arn : null + value = (length(var.service_connect) > 0) ? aws_iam_role.service_connect[0].arn : null } output "task_security_group_id" { @@ -47,28 +47,3 @@ output "task_role_arn" { description = "ARN of the ECS task role (module-managed or externally provided)." value = aws_iam_role.task.arn } - -output "service_connect_port" { - description = "Port clients should use when calling this service via Service Connect." - value = var.enable_ecs_service_connect ? coalesce( - var.service_connect_client_port, - local.sc_port_name != null ? try(local.port_map[local.sc_port_name], null) : null - ) : null -} - -output "service_connect_name" { - description = "Short DNS name for this service within the Service Connect namespace. Other services call this service at http://:/." - value = var.enable_ecs_service_connect ? local.service_name : null -} - -output "service_connect_endpoint" { - description = "Full Service Connect endpoint for this service (e.g. http://api:8080). Null if Service Connect is not enabled." - value = var.enable_ecs_service_connect ? format( - "http://%s:%d", - local.service_name, - coalesce( - var.service_connect_client_port, - local.sc_port_name != null ? try(local.port_map[local.sc_port_name], null) : null - ) - ) : null -} diff --git a/terraform/modules/service/variables.tf b/terraform/modules/service/variables.tf index 25361b81..737103b4 100644 --- a/terraform/modules/service/variables.tf +++ b/terraform/modules/service/variables.tf @@ -119,41 +119,16 @@ variable "alb_security_group_id" { # ------------------------------------------------------- # ECS Service Connect (optional) # ------------------------------------------------------- -variable "enable_ecs_service_connect" { - description = "Enables ECS Service Connect so other services in the namespace can reach this one." - type = bool - default = false -} - -variable "service_connect_namespace_arn" { - type = string - default = null - description = <<-EOT - ARN of the Cloud Map HTTP namespace to use for ECS Service Connect. - When null, Service Connect will not be configured for this service. - EOT -} - -variable "service_connect_port" { - type = number - default = null - description = "Optional. Defaults to the first containerPort in port_mappings. Override this for port remapping (e.g. expose on :80 while container listens on :8080)." -} -variable "service_connect_port_name" { - type = string - default = null - description = "Optional. Defaults to the first named port in port_mappings. Name of the port mapping to use for Service Connect." -} - -variable "service_connect_client_port" { - type = number - default = null - description = <<-EOT - Override the port clients use to call this service via Service Connect. - Defaults to the containerPort of the named port mapping. - Use this for port remapping (e.g. container listens on 8080, clients call on 80 for easy calls by name without port). - EOT +variable "service_connect" { + default = [] + description = "List of service connect discovery names and ports." + type = list(object({ + discovery_name = string + dns_name = string + port = number + port_name = string + })) } variable "deployment_circuit_breaker" { @@ -264,7 +239,6 @@ variable "load_balancers" { default = null } - #-------------------- # ALB Connection #-------------------- @@ -514,3 +488,12 @@ variable "dd_version" { type = string default = "1.0.0" } + +variable "additional_dd_environment" { + default = [] + description = "A list of additional environment variables to append to the default Datadog environment." + type = list(object({ + name = string, + value = string + })) +}