From 6886f87f04725b52db18144b00f7289dd5fb66c1 Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Mon, 2 Mar 2026 16:26:16 -0500 Subject: [PATCH 01/10] test: replace real AWS provider with mock providers in all tests Convert the remaining 6 test files from using `provider "aws"` to `mock_provider "aws"` so tests can run without AWS credentials. - Convert command = apply to command = plan (mock providers simulate resource creation without needing real AWS APIs) - Provide dd_api_key_secret_arn explicitly to avoid computed attribute references that are unknown at plan time - Replace !contains(keys()) null checks with == null assertions (plan-time behavior keeps null keys in the map) - Add dedicated secrets_manager_default_test for secret creation All 32 tests pass across 11 test files. AWSCORE-694 --- tests/character_limit.tftest.hcl | 20 ++++++- tests/default_config.tftest.hcl | 89 ++++++++++++++++++------------ tests/enhanced_features.tftest.hcl | 27 +++++++-- tests/multi_region.tftest.hcl | 22 +++++++- tests/optional_env_vars.tftest.hcl | 23 +++++++- tests/vpc_config.tftest.hcl | 20 ++++++- 6 files changed, 151 insertions(+), 50 deletions(-) diff --git a/tests/character_limit.tftest.hcl b/tests/character_limit.tftest.hcl index ba3b3a4..e416055 100644 --- a/tests/character_limit.tftest.hcl +++ b/tests/character_limit.tftest.hcl @@ -1,6 +1,22 @@ # Test that character limits are respected -provider "aws" { - region = "us-east-1" +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } } variables { diff --git a/tests/default_config.tftest.hcl b/tests/default_config.tftest.hcl index ae81342..dc22858 100644 --- a/tests/default_config.tftest.hcl +++ b/tests/default_config.tftest.hcl @@ -1,11 +1,28 @@ # Test the default configuration of the Datadog Forwarder module -provider "aws" { - region = "us-east-1" +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } } variables { - dd_api_key = "test-api-key-value" - dd_site = "datadoghq.com" + dd_api_key = "test-api-key-value" + dd_site = "datadoghq.com" + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" } run "default_config_test" { @@ -65,9 +82,10 @@ run "default_config_test" { } # === Secrets Management === + # Using provided dd_api_key_secret_arn, so module secret should not be created assert { - condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 1 - error_message = "Secrets Manager secret should be created by default" + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 0 + error_message = "Secrets Manager secret should not be created when dd_api_key_secret_arn is provided" } # === Storage Configuration === @@ -102,11 +120,18 @@ run "default_config_test" { condition = aws_lambda_permission.eventbridge_invoke.principal == "events.amazonaws.com" error_message = "EventBridge permission should be created" } + + # === Output Validation === + assert { + condition = output.datadog_forwarder_function_name == "DatadogForwarder" + error_message = "datadog_forwarder_function_name should match expected value" + } } + +# Test environment variable values run "environment_variables_test" { - command = apply + command = plan - # Test actual environment variable values (only available after apply) assert { condition = aws_lambda_function.forwarder.environment[0].variables.DD_SITE == "datadoghq.com" error_message = "DD_SITE environment variable should be set correctly" @@ -127,54 +152,48 @@ run "environment_variables_test" { error_message = "DD_TRACE_ENABLED should be true by default" } - # Test that optional environment variables are NOT set when null assert { - condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_TAGS") - error_message = "DD_TAGS should not be present when null" + condition = aws_lambda_function.forwarder.environment[0].variables.DD_API_KEY_SECRET_ARN == "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" + error_message = "DD_API_KEY_SECRET_ARN should reference the provided secret ARN" } + # Test that optional environment variables are null when not provided assert { - condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_FETCH_LAMBDA_TAGS") - error_message = "DD_FETCH_LAMBDA_TAGS should not be present when null" - } - assert { - condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_FETCH_S3_TAGS") - error_message = "DD_FETCH_S3_TAGS should not be present when null" + condition = aws_lambda_function.forwarder.environment[0].variables.DD_TAGS == null + error_message = "DD_TAGS should be null when not provided" } assert { - condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_FORWARD_LOG") - error_message = "DD_FORWARD_LOG should not be present when null" + condition = aws_lambda_function.forwarder.environment[0].variables.DD_FETCH_LAMBDA_TAGS == null + error_message = "DD_FETCH_LAMBDA_TAGS should be null when not provided" } assert { - condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_LOG_LEVEL") - error_message = "DD_LOG_LEVEL should not be present when null" + condition = aws_lambda_function.forwarder.environment[0].variables.DD_FETCH_S3_TAGS == null + error_message = "DD_FETCH_S3_TAGS should be null when not provided" } - # Test that key outputs have values after apply assert { - condition = output.datadog_forwarder_arn != null && output.datadog_forwarder_arn != "" - error_message = "datadog_forwarder_arn output should have a value" + condition = aws_lambda_function.forwarder.environment[0].variables.DD_FORWARD_LOG == null + error_message = "DD_FORWARD_LOG should be null when not provided" } assert { - condition = output.datadog_forwarder_function_name == "DatadogForwarder" - error_message = "datadog_forwarder_function_name should match expected value" + condition = aws_lambda_function.forwarder.environment[0].variables.DD_LOG_LEVEL == null + error_message = "DD_LOG_LEVEL should be null when not provided" } +} - assert { - condition = output.datadog_forwarder_role_arn != null && output.datadog_forwarder_role_arn != "" - error_message = "datadog_forwarder_role_arn should have a value" - } +# Test that Secrets Manager secret is created by default when no secret ARN is provided +run "secrets_manager_default_test" { + command = plan - assert { - condition = output.forwarder_log_group_name != null && output.forwarder_log_group_name != "" - error_message = "forwarder_log_group_name should have a value" + variables { + dd_api_key_secret_arn = null } assert { - condition = output.forwarder_log_group_arn != null && output.forwarder_log_group_arn != "" - error_message = "forwarder_log_group_arn should have a value" + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 1 + error_message = "Secrets Manager secret should be created when no dd_api_key_secret_arn is provided" } } diff --git a/tests/enhanced_features.tftest.hcl b/tests/enhanced_features.tftest.hcl index e0eb1c4..47fab43 100644 --- a/tests/enhanced_features.tftest.hcl +++ b/tests/enhanced_features.tftest.hcl @@ -1,11 +1,28 @@ # Test enhanced features configuration -provider "aws" { - region = "us-east-1" +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } } variables { dd_api_key = "test-api-key-value" dd_site = "datadoghq.com" + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" dd_fetch_lambda_tags = true dd_fetch_log_group_tags = true dd_fetch_step_functions_tags = true @@ -39,7 +56,7 @@ run "enhanced_features_test" { } } run "enhanced_features_env_vars_test" { - command = apply + command = plan assert { condition = aws_lambda_function.forwarder.layers[0] == "arn:aws:lambda:us-east-1:464622532012:layer:Datadog-Forwarder:80" @@ -79,9 +96,9 @@ run "enhanced_features_env_vars_test" { error_message = "DD_STORE_FAILED_EVENTS should be true when enabled" } - # Test S3 bucket name is set + # Test S3 bucket name key is present in env vars assert { - condition = length(aws_lambda_function.forwarder.environment[0].variables.DD_S3_BUCKET_NAME) > 0 + condition = contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_S3_BUCKET_NAME") error_message = "DD_S3_BUCKET_NAME should be set when S3 bucket is created" } } diff --git a/tests/multi_region.tftest.hcl b/tests/multi_region.tftest.hcl index 654b186..fb435eb 100644 --- a/tests/multi_region.tftest.hcl +++ b/tests/multi_region.tftest.hcl @@ -1,6 +1,22 @@ # Test multi-region support -provider "aws" { - region = "us-east-1" +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } } variables { @@ -34,7 +50,7 @@ run "multi_region_us_east_2" { # Simulate the creation of resources in us-east-1 and us-east-2 to make sure resources name do not conflict (IAM roles, etc.) run "multi_region_us_east_1_existing_resources" { - command = apply + command = plan } run "multi_region_us_east_2_existing_resources" { diff --git a/tests/optional_env_vars.tftest.hcl b/tests/optional_env_vars.tftest.hcl index 600f726..aea961d 100644 --- a/tests/optional_env_vars.tftest.hcl +++ b/tests/optional_env_vars.tftest.hcl @@ -1,11 +1,28 @@ # Test optional environment variables are set when provided -provider "aws" { - region = "us-east-1" +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } } variables { dd_api_key = "test-api-key-value" dd_site = "datadoghq.com" + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" dd_tags = "env:test,service:forwarder" dd_fetch_lambda_tags = true dd_fetch_log_group_tags = true @@ -19,7 +36,7 @@ variables { } run "optional_env_vars_test" { - command = apply + command = plan # Test that optional environment variables ARE set when provided assert { diff --git a/tests/vpc_config.tftest.hcl b/tests/vpc_config.tftest.hcl index 981f36f..bb54f0a 100644 --- a/tests/vpc_config.tftest.hcl +++ b/tests/vpc_config.tftest.hcl @@ -1,6 +1,22 @@ # Test VPC configuration of the Datadog Forwarder module -provider "aws" { - region = "us-east-1" +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } } variables { From 70f345f779203e6b9538af31c99e771e5e142174 Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Mon, 9 Mar 2026 14:09:42 -0400 Subject: [PATCH 02/10] fix: mark dd_api_key_secret_arn output as sensitive The mock provider treats secretsmanager secret ARNs as sensitive values, causing all tests to fail with "Output refers to sensitive values" error. --- outputs.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/outputs.tf b/outputs.tf index 83a0bc2..be12fed 100644 --- a/outputs.tf +++ b/outputs.tf @@ -21,6 +21,7 @@ output "datadog_forwarder_role_name" { output "dd_api_key_secret_arn" { description = "ARN of SecretsManager Secret with Datadog API Key (only set if created by this module)" value = local.should_create_secret ? aws_secretsmanager_secret.dd_api_key_secret[0].arn : null + sensitive = true } output "forwarder_bucket_name" { From b2de6ea645b693947acdc40ecde7f233cea5f6d1 Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Mon, 9 Mar 2026 14:24:49 -0400 Subject: [PATCH 03/10] test: add dedicated secrets management tests and restore default_config to true defaults Remove dd_api_key_secret_arn from default_config variables so it tests the actual default path (auto-create secret from dd_api_key). Move all secret management permutations to a dedicated secrets_management test file covering auto-create, external ARN, SSM parameter, and explicit create_dd_api_key_secret=false paths. --- tests/default_config.tftest.hcl | 32 +++--- tests/secrets_management.tftest.hcl | 160 ++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 20 deletions(-) create mode 100644 tests/secrets_management.tftest.hcl diff --git a/tests/default_config.tftest.hcl b/tests/default_config.tftest.hcl index dc22858..f7f8c96 100644 --- a/tests/default_config.tftest.hcl +++ b/tests/default_config.tftest.hcl @@ -20,9 +20,8 @@ mock_provider "aws" { } variables { - dd_api_key = "test-api-key-value" - dd_site = "datadoghq.com" - dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" + dd_api_key = "test-api-key-value" + dd_site = "datadoghq.com" } run "default_config_test" { @@ -82,10 +81,10 @@ run "default_config_test" { } # === Secrets Management === - # Using provided dd_api_key_secret_arn, so module secret should not be created + # Default path: dd_api_key provided, no external secret reference → module creates secret assert { - condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 0 - error_message = "Secrets Manager secret should not be created when dd_api_key_secret_arn is provided" + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 1 + error_message = "Secrets Manager secret should be created by default when only dd_api_key is provided" } # === Storage Configuration === @@ -152,11 +151,6 @@ run "environment_variables_test" { error_message = "DD_TRACE_ENABLED should be true by default" } - assert { - condition = aws_lambda_function.forwarder.environment[0].variables.DD_API_KEY_SECRET_ARN == "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" - error_message = "DD_API_KEY_SECRET_ARN should reference the provided secret ARN" - } - # Test that optional environment variables are null when not provided assert { condition = aws_lambda_function.forwarder.environment[0].variables.DD_TAGS == null @@ -182,18 +176,16 @@ run "environment_variables_test" { condition = aws_lambda_function.forwarder.environment[0].variables.DD_LOG_LEVEL == null error_message = "DD_LOG_LEVEL should be null when not provided" } -} -# Test that Secrets Manager secret is created by default when no secret ARN is provided -run "secrets_manager_default_test" { - command = plan - - variables { - dd_api_key_secret_arn = null + # Verify secret ARN path is used (not SSM) + assert { + condition = contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_API_KEY_SECRET_ARN") + error_message = "DD_API_KEY_SECRET_ARN should be present when module auto-creates the secret" } assert { - condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 1 - error_message = "Secrets Manager secret should be created when no dd_api_key_secret_arn is provided" + condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_API_KEY_SSM_NAME") + error_message = "DD_API_KEY_SSM_NAME should not be present when using Secrets Manager" } } + diff --git a/tests/secrets_management.tftest.hcl b/tests/secrets_management.tftest.hcl new file mode 100644 index 0000000..7318682 --- /dev/null +++ b/tests/secrets_management.tftest.hcl @@ -0,0 +1,160 @@ +# Test all API key configuration paths +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + + mock_data "aws_region" { + defaults = { + region = "us-east-1" + } + } + + mock_data "aws_partition" { + defaults = { + partition = "aws" + } + } +} + +# Path 1: dd_api_key only (auto-create secret) +# This is the default path when no external secret reference is provided. +run "auto_create_secret_test" { + command = plan + + variables { + dd_api_key = "test-api-key-value" + dd_api_key_secret_arn = null + } + + # Secret resources should be created + assert { + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 1 + error_message = "Secrets Manager secret should be created when only dd_api_key is provided" + } + + assert { + condition = length(aws_secretsmanager_secret_version.dd_api_key_secret_version) == 1 + error_message = "Secrets Manager secret version should be created when only dd_api_key is provided" + } + + # Lambda should use DD_API_KEY_SECRET_ARN (not SSM) + assert { + condition = contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_API_KEY_SECRET_ARN") + error_message = "DD_API_KEY_SECRET_ARN env var should be present when auto-creating secret" + } + + assert { + condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_API_KEY_SSM_NAME") + error_message = "DD_API_KEY_SSM_NAME should not be present when using Secrets Manager" + } + + # Secret version should also be created alongside the secret + assert { + condition = aws_secretsmanager_secret.dd_api_key_secret[0].name_prefix == "DatadogAPIKey-DatadogForwarder" + error_message = "Secret name prefix should include the function name" + } +} + +# Path 2: dd_api_key_secret_arn provided (external secret) +# The module should NOT create its own secret and should use the provided ARN. +run "external_secret_arn_test" { + command = plan + + variables { + dd_api_key = "test-api-key-value" + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" + } + + # No secret resources should be created + assert { + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 0 + error_message = "Secrets Manager secret should not be created when dd_api_key_secret_arn is provided" + } + + assert { + condition = length(aws_secretsmanager_secret_version.dd_api_key_secret_version) == 0 + error_message = "Secrets Manager secret version should not be created when dd_api_key_secret_arn is provided" + } + + # Lambda should use the provided ARN + assert { + condition = aws_lambda_function.forwarder.environment[0].variables.DD_API_KEY_SECRET_ARN == "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" + error_message = "DD_API_KEY_SECRET_ARN should reference the provided secret ARN" + } + + assert { + condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_API_KEY_SSM_NAME") + error_message = "DD_API_KEY_SSM_NAME should not be present when using Secrets Manager" + } + + # Output should be null (module didn't create the secret) + assert { + condition = output.dd_api_key_secret_arn == null + error_message = "dd_api_key_secret_arn output should be null when module does not create the secret" + } +} + +# Path 3: dd_api_key_ssm_parameter_name provided (SSM parameter) +# The module should use SSM instead of Secrets Manager entirely. +run "ssm_parameter_test" { + command = plan + + variables { + dd_api_key = null + dd_api_key_secret_arn = null + dd_api_key_ssm_parameter_name = "/datadog/api-key" + } + + # No secret resources should be created + assert { + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 0 + error_message = "Secrets Manager secret should not be created when using SSM parameter" + } + + assert { + condition = length(aws_secretsmanager_secret_version.dd_api_key_secret_version) == 0 + error_message = "Secrets Manager secret version should not be created when using SSM parameter" + } + + # Lambda should use DD_API_KEY_SSM_NAME (not secret ARN) + assert { + condition = aws_lambda_function.forwarder.environment[0].variables.DD_API_KEY_SSM_NAME == "/datadog/api-key" + error_message = "DD_API_KEY_SSM_NAME should be set to the provided SSM parameter name" + } + + assert { + condition = !contains(keys(aws_lambda_function.forwarder.environment[0].variables), "DD_API_KEY_SECRET_ARN") + error_message = "DD_API_KEY_SECRET_ARN should not be present when using SSM parameter" + } + + # Output should be null (no secret created) + assert { + condition = output.dd_api_key_secret_arn == null + error_message = "dd_api_key_secret_arn output should be null when using SSM parameter" + } +} + +# Path 4: create_dd_api_key_secret = false with external secret ARN +# Explicit opt-out of secret creation (e.g., when secret is created in the same Terraform plan). +run "explicit_no_create_secret_test" { + command = plan + + variables { + dd_api_key = null + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:ExternalSecret-abc123" + create_dd_api_key_secret = false + } + + assert { + condition = length(aws_secretsmanager_secret.dd_api_key_secret) == 0 + error_message = "Secrets Manager secret should not be created when create_dd_api_key_secret is false" + } + + assert { + condition = aws_lambda_function.forwarder.environment[0].variables.DD_API_KEY_SECRET_ARN == "arn:aws:secretsmanager:us-east-1:123456789012:secret:ExternalSecret-abc123" + error_message = "DD_API_KEY_SECRET_ARN should reference the externally provided secret ARN" + } +} From 5578ee17a46dd0b70e522167accbe260f512733e Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Mon, 9 Mar 2026 14:55:01 -0400 Subject: [PATCH 04/10] ci: add GitHub Actions workflows for terraform fmt and test Add two CI workflows that run on pull requests: - terraform-fmt: checks formatting with terraform fmt -recursive - terraform-test: runs terraform test using mock providers (no AWS credentials needed) --- .github/workflows/terraform-fmt.yaml | 25 ++++++++++++++++++++++++ .github/workflows/terraform-test.yaml | 28 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 .github/workflows/terraform-fmt.yaml create mode 100644 .github/workflows/terraform-test.yaml diff --git a/.github/workflows/terraform-fmt.yaml b/.github/workflows/terraform-fmt.yaml new file mode 100644 index 0000000..149ee7a --- /dev/null +++ b/.github/workflows/terraform-fmt.yaml @@ -0,0 +1,25 @@ +name: Terraform Format Check + +permissions: + contents: read + +on: + pull_request: + workflow_dispatch: + +jobs: + terraform-fmt: + name: Check Terraform Formatting + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 + with: + terraform_version: "1.12.0" + + - name: Run terraform fmt check + run: terraform fmt -recursive -check -diff . diff --git a/.github/workflows/terraform-test.yaml b/.github/workflows/terraform-test.yaml new file mode 100644 index 0000000..2331d96 --- /dev/null +++ b/.github/workflows/terraform-test.yaml @@ -0,0 +1,28 @@ +name: Terraform Tests + +permissions: + contents: read + +on: + pull_request: + workflow_dispatch: + +jobs: + terraform-test: + name: Run Terraform Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 + with: + terraform_version: "1.12.0" + + - name: Terraform Init + run: terraform init + + - name: Terraform Test + run: terraform test From 7e1c76e6f423506cb6c292810165b7d5e6e55dbe Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Mon, 9 Mar 2026 15:11:19 -0400 Subject: [PATCH 05/10] ci: trigger workflow runs From 8cf211f3af0099ad06300a1cba2d1a031dfdb43a Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Mon, 9 Mar 2026 15:16:01 -0400 Subject: [PATCH 06/10] style: fix terraform formatting in s3_log_bucket_arns test --- tests/s3_log_bucket_arns.tftest.hcl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/s3_log_bucket_arns.tftest.hcl b/tests/s3_log_bucket_arns.tftest.hcl index 95b1edf..cd296b3 100644 --- a/tests/s3_log_bucket_arns.tftest.hcl +++ b/tests/s3_log_bucket_arns.tftest.hcl @@ -53,11 +53,11 @@ run "custom_s3_log_access_restricts_buckets" { } variables { - function_name = "TestForwarder" - iam_role_path = "/" - partition = "aws" - region = "us-east-1" - account_id = "123456789012" + function_name = "TestForwarder" + iam_role_path = "/" + partition = "aws" + region = "us-east-1" + account_id = "123456789012" dd_s3_log_bucket_arns = [ "arn:aws:s3:::my-log-bucket/*", "arn:aws:s3:::my-other-bucket/logs/*", From 17f3a4c53f67f4652447dc2c8a68f8c4a03c2517 Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Wed, 11 Mar 2026 10:40:06 -0400 Subject: [PATCH 07/10] fix: remove unnecessary dd_api_key from external secret ARN test dd_api_key defaults to null and should not be set alongside dd_api_key_secret_arn per the variable docs. Removing it makes the test accurately represent the "bring your own secret" path. --- tests/secrets_management.tftest.hcl | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/secrets_management.tftest.hcl b/tests/secrets_management.tftest.hcl index 7318682..3f557f7 100644 --- a/tests/secrets_management.tftest.hcl +++ b/tests/secrets_management.tftest.hcl @@ -64,7 +64,6 @@ run "external_secret_arn_test" { command = plan variables { - dd_api_key = "test-api-key-value" dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" } From d5ff5cb29e2f1410d8a7708c4daed3cd15640517 Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Wed, 11 Mar 2026 10:44:16 -0400 Subject: [PATCH 08/10] refactor: clean up test variables and assertions - Remove dd_api_key where it conflicts with dd_api_key_secret_arn or dd_api_key_ssm_parameter_name (variable docs say choose ONE approach) - Remove explicit null assignments for variables that already default to null (dd_api_key, dd_api_key_secret_arn) - Remove out-of-scope S3 bucket assertion from optional_env_vars test (already covered by enhanced_features test) Affected files: secrets_management, enhanced_features, optional_env_vars, existing_resources, existing_iam_role_without_bucket --- tests/enhanced_features.tftest.hcl | 1 - tests/existing_iam_role_without_bucket.tftest.hcl | 8 -------- tests/existing_resources.tftest.hcl | 1 - tests/optional_env_vars.tftest.hcl | 6 ------ tests/secrets_management.tftest.hcl | 6 +----- 5 files changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/enhanced_features.tftest.hcl b/tests/enhanced_features.tftest.hcl index 47fab43..df29309 100644 --- a/tests/enhanced_features.tftest.hcl +++ b/tests/enhanced_features.tftest.hcl @@ -20,7 +20,6 @@ mock_provider "aws" { } variables { - dd_api_key = "test-api-key-value" dd_site = "datadoghq.com" dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" dd_fetch_lambda_tags = true diff --git a/tests/existing_iam_role_without_bucket.tftest.hcl b/tests/existing_iam_role_without_bucket.tftest.hcl index 249b6ed..8beda8b 100644 --- a/tests/existing_iam_role_without_bucket.tftest.hcl +++ b/tests/existing_iam_role_without_bucket.tftest.hcl @@ -27,8 +27,6 @@ run "existing_role_with_ssm_no_bucket" { command = plan variables { - dd_api_key = "test-api-key-value" - dd_site = "datadoghq.com" existing_iam_role_arn = "arn:aws:iam::123456789012:role/existing-datadog-role" dd_api_key_ssm_parameter_name = "/datadog/api-key" } @@ -51,8 +49,6 @@ run "existing_role_with_secret_arn_no_bucket" { command = plan variables { - dd_api_key = "test-api-key-value" - dd_site = "datadoghq.com" existing_iam_role_arn = "arn:aws:iam::123456789012:role/existing-datadog-role" dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:datadog-api-key-AbCdEf" } @@ -75,8 +71,6 @@ run "existing_role_with_tag_fetching" { command = plan variables { - dd_api_key = "test-api-key-value" - dd_site = "datadoghq.com" existing_iam_role_arn = "arn:aws:iam::123456789012:role/existing-datadog-role" dd_api_key_ssm_parameter_name = "/datadog/api-key" dd_fetch_lambda_tags = true @@ -100,8 +94,6 @@ run "existing_role_with_failed_events" { command = plan variables { - dd_api_key = "test-api-key-value" - dd_site = "datadoghq.com" existing_iam_role_arn = "arn:aws:iam::123456789012:role/existing-datadog-role" dd_api_key_ssm_parameter_name = "/datadog/api-key" dd_store_failed_events = true diff --git a/tests/existing_resources.tftest.hcl b/tests/existing_resources.tftest.hcl index c55bca0..e1c0a78 100644 --- a/tests/existing_resources.tftest.hcl +++ b/tests/existing_resources.tftest.hcl @@ -20,7 +20,6 @@ mock_provider "aws" { } variables { - dd_api_key = "test-api-key-value" dd_site = "datadoghq.com" existing_iam_role_arn = "arn:aws:iam::123456789012:role/existing-datadog-role" dd_forwarder_existing_bucket_name = "existing-datadog-bucket" diff --git a/tests/optional_env_vars.tftest.hcl b/tests/optional_env_vars.tftest.hcl index aea961d..31889ad 100644 --- a/tests/optional_env_vars.tftest.hcl +++ b/tests/optional_env_vars.tftest.hcl @@ -20,7 +20,6 @@ mock_provider "aws" { } variables { - dd_api_key = "test-api-key-value" dd_site = "datadoghq.com" dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:DatadogAPIKey-mock" dd_tags = "env:test,service:forwarder" @@ -89,9 +88,4 @@ run "optional_env_vars_test" { error_message = "DD_ADDITIONAL_TARGET_LAMBDAS should be set when provided" } - # Test that S3 bucket is created when tag fetching is enabled - assert { - condition = length(aws_s3_bucket.forwarder_bucket) == 1 - error_message = "S3 bucket should be created when tag fetching is enabled" - } } diff --git a/tests/secrets_management.tftest.hcl b/tests/secrets_management.tftest.hcl index 3f557f7..e02303c 100644 --- a/tests/secrets_management.tftest.hcl +++ b/tests/secrets_management.tftest.hcl @@ -25,8 +25,7 @@ run "auto_create_secret_test" { command = plan variables { - dd_api_key = "test-api-key-value" - dd_api_key_secret_arn = null + dd_api_key = "test-api-key-value" } # Secret resources should be created @@ -102,8 +101,6 @@ run "ssm_parameter_test" { command = plan variables { - dd_api_key = null - dd_api_key_secret_arn = null dd_api_key_ssm_parameter_name = "/datadog/api-key" } @@ -142,7 +139,6 @@ run "explicit_no_create_secret_test" { command = plan variables { - dd_api_key = null dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:ExternalSecret-abc123" create_dd_api_key_secret = false } From 41be55f028d24d79597bfc36f518008c02c7ecad Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Wed, 11 Mar 2026 11:30:49 -0400 Subject: [PATCH 09/10] feat: enforce mutual exclusivity of API key configuration variables Add validation rules that reject conflicting API key configurations instead of silently ignoring one. The three approaches (dd_api_key, dd_api_key_secret_arn, dd_api_key_ssm_parameter_name) are now mutually exclusive at the variable level. Add tests for all three pairwise conflict scenarios. --- tests/create_api_key_secret_flag.tftest.hcl | 46 +++++++++++++++++++++ variables.tf | 10 +++++ 2 files changed, 56 insertions(+) diff --git a/tests/create_api_key_secret_flag.tftest.hcl b/tests/create_api_key_secret_flag.tftest.hcl index c1f30d5..70c58ed 100644 --- a/tests/create_api_key_secret_flag.tftest.hcl +++ b/tests/create_api_key_secret_flag.tftest.hcl @@ -211,3 +211,49 @@ run "invalid_ssm_parameter_name_fails_validation" { var.dd_api_key_ssm_parameter_name ] } + +# ───────────────────────────────────────────────────────────────────────────── +# Scenario 6: mutual exclusivity — conflicting API key configurations rejected +# ───────────────────────────────────────────────────────────────────────────── + +# dd_api_key + dd_api_key_secret_arn should fail +run "api_key_and_secret_arn_conflict_fails_validation" { + command = plan + + variables { + dd_api_key = "test-api-key-value" + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-key-AbCdEf" + } + + expect_failures = [ + var.dd_api_key + ] +} + +# dd_api_key + dd_api_key_ssm_parameter_name should fail +run "api_key_and_ssm_parameter_conflict_fails_validation" { + command = plan + + variables { + dd_api_key = "test-api-key-value" + dd_api_key_ssm_parameter_name = "/datadog/api-key" + } + + expect_failures = [ + var.dd_api_key + ] +} + +# dd_api_key_secret_arn + dd_api_key_ssm_parameter_name should fail +run "secret_arn_and_ssm_parameter_conflict_fails_validation" { + command = plan + + variables { + dd_api_key_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-key-AbCdEf" + dd_api_key_ssm_parameter_name = "/datadog/api-key" + } + + expect_failures = [ + var.dd_api_key_secret_arn + ] +} diff --git a/variables.tf b/variables.tf index b38673c..de2b633 100644 --- a/variables.tf +++ b/variables.tf @@ -10,6 +10,11 @@ variable "dd_api_key" { Choose ONE approach for API key management. EOT sensitive = true + + validation { + condition = var.dd_api_key == null || (var.dd_api_key_secret_arn == null && var.dd_api_key_ssm_parameter_name == null) + error_message = "dd_api_key is mutually exclusive with dd_api_key_secret_arn and dd_api_key_ssm_parameter_name. Choose one approach for API key management." + } } variable "dd_allowed_kms_keys" { @@ -53,6 +58,11 @@ variable "dd_api_key_secret_arn" { condition = var.dd_api_key_secret_arn == null || can(regex("^arn:.*:secretsmanager:.*", var.dd_api_key_secret_arn)) error_message = "dd_api_key_secret_arn must be a valid Secrets Manager ARN." } + + validation { + condition = var.dd_api_key_secret_arn == null || var.dd_api_key_ssm_parameter_name == null + error_message = "dd_api_key_secret_arn and dd_api_key_ssm_parameter_name are mutually exclusive. Choose one approach for API key management." + } } variable "dd_api_key_ssm_parameter_name" { From 8dc4758f53d2279ef4058d1a310264bf58e000db Mon Sep 17 00:00:00 2001 From: Kyle Harris Date: Wed, 11 Mar 2026 11:35:39 -0400 Subject: [PATCH 10/10] refactor: use deprecation warnings instead of hard errors for API key conflicts Replace variable validation blocks with check blocks that produce warnings instead of errors. This avoids breaking existing customers who may have multiple API key variables set. The warnings explain which value is being ignored and that this will become a hard error in a future release. --- data.tf | 23 +++++++++++++++++++++ tests/create_api_key_secret_flag.tftest.hcl | 22 +++++++++++--------- variables.tf | 9 -------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/data.tf b/data.tf index c1ea205..dcd1bed 100644 --- a/data.tf +++ b/data.tf @@ -62,3 +62,26 @@ locals { } : {} ) } + +# Deprecation warnings for conflicting API key configurations. +# These will become hard validation errors in a future major release. +check "dd_api_key_not_used_with_secret_arn" { + assert { + condition = var.dd_api_key == null || var.dd_api_key_secret_arn == null + error_message = "DEPRECATED: dd_api_key and dd_api_key_secret_arn are both set. Only one API key approach should be used. Currently dd_api_key is being ignored in favor of dd_api_key_secret_arn. Remove dd_api_key to silence this warning. This will become an error in a future release." + } +} + +check "dd_api_key_not_used_with_ssm_parameter" { + assert { + condition = var.dd_api_key == null || var.dd_api_key_ssm_parameter_name == null + error_message = "DEPRECATED: dd_api_key and dd_api_key_ssm_parameter_name are both set. Only one API key approach should be used. Currently dd_api_key is being ignored in favor of dd_api_key_ssm_parameter_name. Remove dd_api_key to silence this warning. This will become an error in a future release." + } +} + +check "dd_secret_arn_not_used_with_ssm_parameter" { + assert { + condition = var.dd_api_key_secret_arn == null || var.dd_api_key_ssm_parameter_name == null + error_message = "DEPRECATED: dd_api_key_secret_arn and dd_api_key_ssm_parameter_name are both set. Only one API key approach should be used. Currently dd_api_key_secret_arn is being ignored in favor of dd_api_key_ssm_parameter_name. Remove dd_api_key_secret_arn to silence this warning. This will become an error in a future release." + } +} diff --git a/tests/create_api_key_secret_flag.tftest.hcl b/tests/create_api_key_secret_flag.tftest.hcl index 70c58ed..2953a22 100644 --- a/tests/create_api_key_secret_flag.tftest.hcl +++ b/tests/create_api_key_secret_flag.tftest.hcl @@ -213,11 +213,13 @@ run "invalid_ssm_parameter_name_fails_validation" { } # ───────────────────────────────────────────────────────────────────────────── -# Scenario 6: mutual exclusivity — conflicting API key configurations rejected +# Scenario 6: mutual exclusivity — conflicting API key configurations warn +# These are deprecation warnings (check blocks) that will become hard errors +# in a future major release. # ───────────────────────────────────────────────────────────────────────────── -# dd_api_key + dd_api_key_secret_arn should fail -run "api_key_and_secret_arn_conflict_fails_validation" { +# dd_api_key + dd_api_key_secret_arn should warn +run "api_key_and_secret_arn_conflict_warns" { command = plan variables { @@ -226,12 +228,12 @@ run "api_key_and_secret_arn_conflict_fails_validation" { } expect_failures = [ - var.dd_api_key + check.dd_api_key_not_used_with_secret_arn ] } -# dd_api_key + dd_api_key_ssm_parameter_name should fail -run "api_key_and_ssm_parameter_conflict_fails_validation" { +# dd_api_key + dd_api_key_ssm_parameter_name should warn +run "api_key_and_ssm_parameter_conflict_warns" { command = plan variables { @@ -240,12 +242,12 @@ run "api_key_and_ssm_parameter_conflict_fails_validation" { } expect_failures = [ - var.dd_api_key + check.dd_api_key_not_used_with_ssm_parameter ] } -# dd_api_key_secret_arn + dd_api_key_ssm_parameter_name should fail -run "secret_arn_and_ssm_parameter_conflict_fails_validation" { +# dd_api_key_secret_arn + dd_api_key_ssm_parameter_name should warn +run "secret_arn_and_ssm_parameter_conflict_warns" { command = plan variables { @@ -254,6 +256,6 @@ run "secret_arn_and_ssm_parameter_conflict_fails_validation" { } expect_failures = [ - var.dd_api_key_secret_arn + check.dd_secret_arn_not_used_with_ssm_parameter ] } diff --git a/variables.tf b/variables.tf index de2b633..0008c1e 100644 --- a/variables.tf +++ b/variables.tf @@ -10,11 +10,6 @@ variable "dd_api_key" { Choose ONE approach for API key management. EOT sensitive = true - - validation { - condition = var.dd_api_key == null || (var.dd_api_key_secret_arn == null && var.dd_api_key_ssm_parameter_name == null) - error_message = "dd_api_key is mutually exclusive with dd_api_key_secret_arn and dd_api_key_ssm_parameter_name. Choose one approach for API key management." - } } variable "dd_allowed_kms_keys" { @@ -59,10 +54,6 @@ variable "dd_api_key_secret_arn" { error_message = "dd_api_key_secret_arn must be a valid Secrets Manager ARN." } - validation { - condition = var.dd_api_key_secret_arn == null || var.dd_api_key_ssm_parameter_name == null - error_message = "dd_api_key_secret_arn and dd_api_key_ssm_parameter_name are mutually exclusive. Choose one approach for API key management." - } } variable "dd_api_key_ssm_parameter_name" {