Skip to content

Module fails with “Invalid count argument” when dd_api_key_secret_arn or dd_api_key_ssm_parameter_name is derived from a resource created in the same plan #8

Description

@Jno21

Description

When dd_api_key_secret_arn or dd_api_key_ssm_parameter_name is passed from a resource that is created in the same apply, the module’s count expression becomes unknown at plan time and Terraform errors out.

This happens because the module currently uses:

count = var.dd_api_key_secret_arn == null && var.dd_api_key_ssm_parameter_name == null ? 1 : 0

If either variable is set from another resource in the same plan, its value is unknown during planning, so the count cannot be determined.

Minimal reproducible example

resource "aws_secretsmanager_secret" "dd_api_key" {
  name_prefix = "DatadogAPIKey"
  description = "Datadog API Key"
}

resource "aws_secretsmanager_secret_version" "dd_api_key_version" {
  secret_id     = aws_secretsmanager_secret.dd_api_key.id
  secret_string = "myapikey"
}

module "datadog_forwarder_secretmanager" {
  source = "../../"

  # This value is unknown at plan time since the secret is created in the same apply
  dd_api_key_secret_arn = aws_secretsmanager_secret.dd_api_key.arn
  dd_site               = "datadoghq.com"
}

Error

Error: Invalid count argument
  on main.tf line 28, in resource "aws_secretsmanager_secret" "dd_api_key_secret":
  28:   count = var.dd_api_key_secret_arn == null && var.dd_api_key_ssm_parameter_name == null ? 1 : 0

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this,
use the -target argument to first apply only the resources that the count depends on.

Expected behavior

The module should allow either:

  • importing an existing secret/parameter, or
  • creating the secret/parameter,
    even when the imported value is produced by resources created within the same plan.

Actual behavior

Plan fails because count depends on unknown values.

Notes

Using -target or splitting applies (create secret first, then the module) works but is not ideal. Using future “deferrable” expressions could potentially help, but that is not viable for most users today.


Proposed solutions

Option A (breaking): Remove dd_api_key variable and let callers manage the secret/parameter

Have the module only consume dd_api_key_secret_arn or dd_api_key_ssm_parameter_name.
Callers create and manage the secret/parameter themselves (with full control over KMS, rotation, etc.).
Provide examples for both Secrets Manager and SSM.

Example usage:

resource "aws_secretsmanager_secret" "dd_api_key_secret" {
  name_prefix = "DatadogAPIKey-${var.function_name}"
  description = "Datadog API Key"
  tags        = var.tags
}

resource "aws_secretsmanager_secret_version" "dd_api_key_secret_version" {
  secret_id     = aws_secretsmanager_secret.dd_api_key_secret.id
  secret_string = var.dd_api_key
}

module "datadog_forwarder_secretmanager" {
  source                 = "../../"
  dd_api_key_secret_arn  = aws_secretsmanager_secret.dd_api_key_secret.arn
  dd_site                = "datadoghq.com"
}

Changes

  • Simplest mental model; gives users full control.
  • Breaking change for users relying on the module to create the secret.

Option B (breaking): Add explicit boolean “mode” switches

Add mutually exclusive booleans to make the creation path a known plan-time choice (not derived from unknown strings):

  • use_imported_dd_api_key_secret (default: false)
  • use_dd_api_key_ssm_parameter_name (default: false)

Use validation to enforce mutual exclusivity and ensure required inputs are set when a mode is chosen.

Changes

  • Keeps current behavior.
  • Slight behavior change for existing users.

Option C (breaking): Single boolean switch

Add one boolean variable:

variable "dd_api_key_imported" {
  description = "If true, the module uses an imported secret/parameter instead of creating one."
  type        = bool
  default     = false
}

When dd_api_key_imported = true, users must provide one of:

  • dd_api_key_secret_arn, or
  • dd_api_key_ssm_parameter_name.

If dd_api_key_imported = false, the module creates the secret/parameter internally.
Use validation to enforce mutual exclusivity and ensure required inputs are set when this is chosen.

Changes

  • Easy migration path from current behavior.
  • Slight behavior adjustment; users must now set dd_api_key_imported explicitly if they import.

My preference

Option A remains the cleanest long-term (callers manage the secret/parameter), as it provides maximum flexibility and decouples responsibilities cleanly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions