diff --git a/examples/ecs_fargate/main.tf b/examples/ecs_fargate/main.tf index ccea245..45a66d1 100644 --- a/examples/ecs_fargate/main.tf +++ b/examples/ecs_fargate/main.tf @@ -13,11 +13,14 @@ module "datadog_ecs_fargate_task" { # Configure Datadog dd_api_key = var.dd_api_key dd_site = var.dd_site - dd_service = var.dd_service dd_tags = "team:cont-p, owner:container-monitoring" dd_essential = true dd_is_datadog_dependency_enabled = true + dd_service = var.dd_service + dd_env = var.dd_env + dd_version = var.dd_version + dd_environment = [ { name = "DD_CUSTOM_FEATURE", diff --git a/examples/ecs_fargate/variables.tf b/examples/ecs_fargate/variables.tf index fa04ce4..a2ab372 100644 --- a/examples/ecs_fargate/variables.tf +++ b/examples/ecs_fargate/variables.tf @@ -16,7 +16,19 @@ variable "dd_api_key_secret_arn" { } variable "dd_service" { - description = "Service name for resource filtering in Datadog" + description = "The service name for resource filtering and UST tagging in Datadog" + type = string + default = null +} + +variable "dd_env" { + description = "The environment for resource filtering and UST tagging in Datadog" + type = string + default = null +} + +variable "dd_version" { + description = "The version for resource filtering and UST tagging in Datadog" type = string default = null } diff --git a/modules/ecs_fargate/README.md b/modules/ecs_fargate/README.md index da14d45..f361015 100644 --- a/modules/ecs_fargate/README.md +++ b/modules/ecs_fargate/README.md @@ -245,6 +245,7 @@ No modules. | [dd\_cluster\_name](#input\_dd\_cluster\_name) | Datadog cluster name | `string` | `null` | no | | [dd\_cpu](#input\_dd\_cpu) | Datadog Agent container CPU units | `number` | `null` | no | | [dd\_cws](#input\_dd\_cws) | Configuration for Datadog Cloud Workload Security (CWS) |
object({
enabled = optional(bool, false)
cpu = optional(number)
memory_limit_mib = optional(number)
})
|
{
"enabled": false
}
| no | +| [dd\_docker\_labels](#input\_dd\_docker\_labels) | Datadog Agent container docker labels | `map(string)` | `{}` | no | | [dd\_dogstatsd](#input\_dd\_dogstatsd) | Configuration for Datadog DogStatsD |
object({
enabled = optional(bool, true)
origin_detection_enabled = optional(bool, true)
dogstatsd_cardinality = optional(string, "orchestrator")
socket_enabled = optional(bool, true)
})
|
{
"dogstatsd_cardinality": "orchestrator",
"enabled": true,
"origin_detection_enabled": true,
"socket_enabled": true
}
| no | | [dd\_env](#input\_dd\_env) | The task environment name. Used for tagging (UST) | `string` | `null` | no | | [dd\_environment](#input\_dd\_environment) | Datadog Agent container environment variables. Highest precedence and overwrites other environment variables defined by the module. For example, `dd_environment = [ { name = 'DD_VAR', value = 'DD_VAL' } ]` | `list(map(string))` |
[
{}
]
| no | diff --git a/modules/ecs_fargate/datadog.tf b/modules/ecs_fargate/datadog.tf index a35818e..d22fda2 100644 --- a/modules/ecs_fargate/datadog.tf +++ b/modules/ecs_fargate/datadog.tf @@ -119,6 +119,18 @@ locals { ] : [], ) + ust_docker_labels = merge( + var.dd_env != null ? { + "com.datadoghq.tags.env" = var.dd_env + } : {}, + var.dd_service != null ? { + "com.datadoghq.tags.service" = var.dd_service + } : {}, + var.dd_version != null ? { + "com.datadoghq.tags.version" = var.dd_version + } : {}, + ) + application_env_vars = concat( var.dd_apm.profiling != null ? [ { @@ -169,6 +181,11 @@ locals { local.ust_env_vars, local.application_env_vars, ), + # Merge UST docker labels with any existing docker labels. + dockerLabels = merge( + lookup(container, "dockerLabels", {}), + local.ust_docker_labels, + ), # Append new volume mounts to any existing mountPoints. mountPoints = concat( lookup(container, "mountPoints", []), @@ -288,7 +305,6 @@ locals { local.dynamic_env, local.origin_detection_vars, local.cws_vars, - local.ust_env_vars, local.dd_environment, ) @@ -296,12 +312,13 @@ locals { dd_agent_container = [ merge( { - name = "datadog-agent" - image = "${var.dd_registry}:${var.dd_image_version}" - essential = var.dd_essential - environment = local.dd_agent_env - cpu = var.dd_cpu - memory = var.dd_memory_limit_mib + name = "datadog-agent" + image = "${var.dd_registry}:${var.dd_image_version}" + essential = var.dd_essential + environment = local.dd_agent_env + dockerLabels = var.dd_docker_labels + cpu = var.dd_cpu + memory = var.dd_memory_limit_mib secrets = var.dd_api_key_secret != null ? [ { name = "DD_API_KEY" @@ -340,11 +357,6 @@ locals { dd_log_environment = var.dd_log_collection.fluentbit_config.environment != null ? var.dd_log_collection.fluentbit_config.environment : [] - dd_log_agent_env = concat( - local.ust_env_vars, - local.dd_log_environment - ) - # Datadog log router container definition dd_log_container = local.is_fluentbit_supported ? [ merge( @@ -366,7 +378,8 @@ locals { memory_limit_mib = var.dd_log_collection.fluentbit_config.memory_limit_mib user = "0" mountPoints = var.dd_log_collection.fluentbit_config.mountPoints - environment = local.dd_log_agent_env + environment = local.dd_log_environment + dockerLabels = var.dd_docker_labels portMappings = [] systemControls = [] volumesFrom = [] @@ -396,7 +409,7 @@ locals { entryPoint = [] command = ["/cws-instrumentation", "setup", "--cws-volume-mount", "/cws-instrumentation-volume"] mountPoints = local.cws_mount - environment = local.ust_env_vars + dockerLabels = var.dd_docker_labels portMappings = [] systemControls = [] volumesFrom = [] diff --git a/modules/ecs_fargate/variables.tf b/modules/ecs_fargate/variables.tf index 0de5c4c..d5cc82e 100644 --- a/modules/ecs_fargate/variables.tf +++ b/modules/ecs_fargate/variables.tf @@ -96,6 +96,12 @@ variable "dd_environment" { nullable = false } +variable "dd_docker_labels" { + description = "Datadog Agent container docker labels" + type = map(string) + default = {} +} + variable "dd_tags" { description = "Datadog Agent global tags (eg. `key1:value1, key2:value2`)" type = string diff --git a/smoke_tests/ecs_fargate/outputs.tf b/smoke_tests/ecs_fargate/outputs.tf index 05c5064..502544f 100644 --- a/smoke_tests/ecs_fargate/outputs.tf +++ b/smoke_tests/ecs_fargate/outputs.tf @@ -37,3 +37,7 @@ output "role-parsing-with-path" { output "role-parsing-without-path" { value = module.dd_task_role_parsing_without_path } + +output "ust-docker-labels" { + value = module.dd_task_ust_docker_labels +} diff --git a/smoke_tests/ecs_fargate/ust-docker-labels.tf b/smoke_tests/ecs_fargate/ust-docker-labels.tf new file mode 100644 index 0000000..d614c58 --- /dev/null +++ b/smoke_tests/ecs_fargate/ust-docker-labels.tf @@ -0,0 +1,49 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2025-present Datadog, Inc. + +################################################################################ +# Task Definition: UST Docker Labels Test +################################################################################ + +module "dd_task_ust_docker_labels" { + source = "../../modules/ecs_fargate" + + # Configure Datadog with UST tags + dd_api_key = var.dd_api_key + dd_site = var.dd_site + dd_service = "ust-test-service" + dd_env = "ust-test-env" + dd_version = "1.2.3" + dd_tags = "team:test" + dd_essential = true + + dd_is_datadog_dependency_enabled = true + + dd_log_collection = { + enabled = true, + } + + dd_cws = { + enabled = true, + } + + dd_docker_labels = { + "com.datadoghq.tags.service" : "docker-agent-service", + "com.datadoghq.tags.env" : "agent-dev", + "com.datadoghq.tags.version" : "v1.2.3" + } + + # Configure Task Definition with multiple containers + family = "${var.test_prefix}-ust-docker-labels" + container_definitions = jsonencode([ + { + name = "dummy-app", + image = "nginx:latest", + essential = true, + }, + ]) + + requires_compatibilities = ["FARGATE"] +} diff --git a/tests/all_dd_disabled_test.go b/tests/all_dd_disabled_test.go index 9d54e8c..d44efa3 100644 --- a/tests/all_dd_disabled_test.go +++ b/tests/all_dd_disabled_test.go @@ -42,7 +42,6 @@ func (s *ECSFargateSuite) TestAllDDDisabled() { expectedAgentEnvVars := map[string]string{ "DD_API_KEY": "test-api-key", "DD_SITE": "datadoghq.com", - "DD_SERVICE": "test-service", "DD_TAGS": "team:cont-p, owner:container-monitoring", "DD_DOGSTATSD_TAG_CARDINALITY": "orchestrator", "DD_ECS_TASK_COLLECTION_ENABLED": "true", diff --git a/tests/all_dd_inputs_test.go b/tests/all_dd_inputs_test.go index 698314a..a03114b 100644 --- a/tests/all_dd_inputs_test.go +++ b/tests/all_dd_inputs_test.go @@ -51,7 +51,6 @@ func (s *ECSFargateSuite) TestAllDDInputs() { "DD_API_KEY": "test-api-key", "DD_SITE": "datadoghq.com", "ECS_FARGATE": "true", - "DD_SERVICE": "test-service", "DD_RUNTIME_SECURITY_CONFIG_EBPFLESS_ENABLED": "true", "DD_INSTALL_INFO_TOOL": "terraform", // "DD_INSTALL_INFO_INSTALLER_VERSION": "0.0.0", @@ -61,7 +60,6 @@ func (s *ECSFargateSuite) TestAllDDInputs() { expectedLogOptions := map[string]string{ "apikey": "test-api-key", "provider": "ecs", - "dd_service": "dd-test", "Host": "http-intake.logs.datadoghq.com", "TLS": "on", "dd_source": "dd-test", diff --git a/tests/apm_dsd_tcp_udp_test.go b/tests/apm_dsd_tcp_udp_test.go index cc1703e..1a361bb 100644 --- a/tests/apm_dsd_tcp_udp_test.go +++ b/tests/apm_dsd_tcp_udp_test.go @@ -42,7 +42,6 @@ func (s *ECSFargateSuite) TestApmDsdTcpUdp() { expectedAgentEnvVars := map[string]string{ "DD_API_KEY": "test-api-key", "DD_SITE": "datadoghq.com", - "DD_SERVICE": "test-service", "DD_TAGS": "team:cont-p, owner:container-monitoring", "DD_DOGSTATSD_TAG_CARDINALITY": "orchestrator", "DD_ECS_TASK_COLLECTION_ENABLED": "true", diff --git a/tests/logging_only_test.go b/tests/logging_only_test.go index 009ef15..92eba35 100644 --- a/tests/logging_only_test.go +++ b/tests/logging_only_test.go @@ -42,7 +42,6 @@ func (s *ECSFargateSuite) TestLoggingOnly() { expectedAgentEnvVars := map[string]string{ "DD_API_KEY": "test-api-key", "DD_SITE": "datadoghq.com", - "DD_SERVICE": "test-service", "DD_DOGSTATSD_TAG_CARDINALITY": "orchestrator", "DD_ECS_TASK_COLLECTION_ENABLED": "true", "ECS_FARGATE": "true", @@ -87,7 +86,6 @@ func (s *ECSFargateSuite) TestLoggingOnly() { // Verify log router environment variables expectedLogRouterEnvVars := map[string]string{ - "DD_SERVICE": "test-service", "DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL": "true", } AssertEnvVars(s.T(), logRouterContainer, expectedLogRouterEnvVars) diff --git a/tests/ust_docker_labels_test.go b/tests/ust_docker_labels_test.go new file mode 100644 index 0000000..6bde250 --- /dev/null +++ b/tests/ust_docker_labels_test.go @@ -0,0 +1,54 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025-present Datadog, Inc. + +package test + +import ( + "encoding/json" + "log" + + "github.com/aws/aws-sdk-go-v2/service/ecs/types" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +// TestUSTDockerLabels tests that UST docker labels are propagated to all container definitions +// when dd_service, dd_env, and dd_version are set +func (s *ECSFargateSuite) TestUSTDockerLabels() { + log.Println("TestUSTDockerLabels: Running test...") + + // Retrieve the task output for the "ust-docker-labels" module + var containers []types.ContainerDefinition + task := terraform.OutputMap(s.T(), s.terraformOptions, "ust-docker-labels") + s.Equal(s.testPrefix+"-ust-docker-labels", task["family"], "Unexpected task family name") + + err := json.Unmarshal([]byte(task["container_definitions"]), &containers) + s.NoError(err, "Failed to parse container definitions") + s.Equal(4, len(containers), "Expected 4 containers in the task definition (1 app container + 3 agent sidecar)") + + // Expected UST docker labels that should be present on all application containers + expectedUSTLabels := map[string]string{ + "com.datadoghq.tags.service": "ust-test-service", + "com.datadoghq.tags.env": "ust-test-env", + "com.datadoghq.tags.version": "1.2.3", + } + + dummyApp, found := GetContainer(containers, "dummy-app") + s.True(found, "Container dummy-app not found in definitions") + AssertDockerLabels(s.T(), dummyApp, expectedUSTLabels) + + // Expect UST docker labels to be present on all Datadog containers with + // overwritten labels when UST docker labels are specified. + datadogContainers := []string{"datadog-agent", "datadog-log-router", "cws-instrumentation-init"} + expectedAgentUSTLabels := map[string]string{ + "com.datadoghq.tags.service": "docker-agent-service", + "com.datadoghq.tags.env": "agent-dev", + "com.datadoghq.tags.version": "v1.2.3", + } + for _, containerName := range datadogContainers { + container, found := GetContainer(containers, containerName) + s.True(found, "Container %s not found in definitions", containerName) + AssertDockerLabels(s.T(), container, expectedAgentUSTLabels) + } +} diff --git a/tests/utils.go b/tests/utils.go index 225bda5..d372f3b 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -118,3 +118,14 @@ func AssertContainerDependency(t *testing.T, container types.ContainerDefinition assert.True(t, found, "Expected dependency (container:%s, condition:%s) not found in %s container", *expectedDependency.ContainerName, expectedDependency.Condition, *container.Name) } + +// AssertDockerLabels checks if the expected docker labels are all present in the container +func AssertDockerLabels(t *testing.T, container types.ContainerDefinition, expectedLabels map[string]string) { + assert.NotNil(t, container.Name, "Container name cannot be nil") + + for key, expectedValue := range expectedLabels { + value, found := container.DockerLabels[key] + assert.True(t, found, "Docker label %s not found in %s container", key, *container.Name) + assert.Equal(t, expectedValue, value, "Docker label %s value does not match expected in %s container", key, *container.Name) + } +}