0

I feel like I've tried this a bunch of different ways but I may be a little off in terms of how I am calling these variables. I have the following code:

  config_rule_params = {
      "access_keys_rotated" = {
          "input_parameters" = "{\"maxAccessKeyAge\": \"90\"}",
          "maximum_execution_frequency" = "TwentyFour_Hours",
          "source" = {
              "owner" = "AWS",
              "source_identifier" = "ACCESS_KEYS_ROTATED"
          }
      },
      "acm_certificate_expiration_check" = {
          "input_parameters" = "{\"daysToExpiration\": \"30\"}",
          "maximum_execution_frequency" = "TwentyFour_Hours",
          "source" = {
              "owner" = "AWS",
              "source_identifier" = "ACM_CERTIFICATE_EXPIRATION_CHECK"
          },
          "scope" = {
              "compliance_resource_types" = "AWS::ACM::Certificate"
          }
      }
  }
}

resource "aws_config_config_rule" "parameterised_config_rules" {
    for_each                    = local.config_rule_params
    name                        = each.key
    input_parameters            = each.value.input_parameters
    maximum_execution_frequency = each.value.maximum_execution_frequency
    
    dynamic "source" {
        for_each = local.config_rule_params[*].source[*]
        content {
            owner = each.value.owner
            source_identifier = each.source_identifier
        }
    }

    dynamic "scope" {
        for_each = local.config_rule_params[*].scope[*]
        content {
            compliance_resource_types = each.value.compliance_resource_types
        }
    }
}

Eventually I will have a ton of rules added under config_rule_params and not all of them will have source, scope or even other parameters. How can I properly call these variables when creating my resource? Currently getting the following error:

Error: Unsupported attribute
  on .terraform/modules/baselines_config_rules_module/modules/baseline-config-rules/main.tf line 53, in resource "aws_config_config_rule" "parameterised_config_rules":
  53:         for_each = local.config_rule_params[*].source[*]
This object does not have an attribute named "source".
Error: Unsupported attribute
  on .terraform/modules/baselines_config_rules_module/modules/baseline-config-rules/main.tf line 61, in resource "aws_config_config_rule" "parameterised_config_rules":
  61:         for_each = local.config_rule_params[*].scope[*]
This object does not have an attribute named "scope".
ERROR: Job failed: exit code 1
Marcin
  • 215,873
  • 14
  • 235
  • 294
J. Patwary
  • 427
  • 1
  • 7
  • 22

2 Answers2

2

You're correctly using the [*] operator as a concise way to adapt a value that might either be null or not into a list with either zero or one elements, but there are two things to change here:

  • The iterator symbol for a dynamic block is, by default, the name of the block you are generating. each is the iterator symbol for the top-level resource itself, even inside a dynamic block.
  • As a consequence of the previous item, you can use each.value as part of the for_each expression in your dynamic block, to refer to the current element of local.config_rule_params.

Putting those together, we get something like this:

resource "aws_config_config_rule" "parameterised_config_rules" {
  for_each                    = local.config_rule_params

  name                        = each.key
  input_parameters            = each.value.input_parameters
  maximum_execution_frequency = each.value.maximum_execution_frequency
    
  dynamic "source" {
    for_each = each.value.source[*]
    content {
      owner             = source.value.owner
      source_identifier = source.value.source_identifier
    }
  }

  dynamic "scope" {
    for_each = each.value.scope[*]
    content {
      compliance_resource_types = scope.value.compliance_resource_types
    }
  }
}

Notice that in the dynamic "source" block the current element is source.value, while in the dynamic "scope" block the current element is scope.value. Because of that, it's valid to also use each.value in those dynamic blocks, and so you can refer to both levels of repetition together when building out these nested blocks.

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
  • Thanks, this worked for source (I'm guessing because both items had a source argument). Scope is still caught in error for the first item (access_keys_rotated, which doesn't have a scope argument): Error: Unsupported attribute - for_each = each.value.scope[*]. each.value is object with 3 attributes. This object does not have an attribute named "scope". – J. Patwary Dec 16 '20 at 02:25
  • Got it working. Just needed to add scope + scope values to all items ("scope" = { "compliance_resource_types" = [ ] }, but leaving it null works just fine. Thanks again. – J. Patwary Dec 16 '20 at 02:54
  • Indeed, because object types are defined by which attributes they have in that context an attribute that is explicitly set to `null` is _not_ the same as omitting an attribute altogether: if you omit the attribute, the value has a different object type. – Martin Atkins Dec 16 '20 at 17:03
1

When you use for_each in dynamic blocks, by default the iterator is refereed to using label of the block (source and scope), rather then each:

The iterator argument (optional) sets the name of a temporary variable that represents the current element of the complex value. If omitted, the name of the variable defaults to the label of the dynamic block ("setting" in the example above).

In your example it would be source and scope:

    dynamic "source" {
        for_each = local.config_rule_params[*].source[*]
        content {
            owner = source.value.owner
            source_identifier = source.source_identifier
        }
    }

    dynamic "scope" {
        for_each = local.config_rule_params[*].scope[*]
        content {
            compliance_resource_types = scope.value.compliance_resource_types
        }
    }
Marcin
  • 215,873
  • 14
  • 235
  • 294