Nested dynamic blocks, am I missing something?

Hi,

locals {
  org_policy_constraints = {
    "compute.restrictSharedVpcSubnetworks" = {
      allow = local.xpn_subnets_list == [] ? null : local.xpn_subnets_list
      deny  = local.xpn_subnets_list == [] ? ["all"] : null
    }
  }
}

resource "google_folder_organization_policy" "policy" {
  for_each = local.org_policy_constraints
  constraint = "constraints/${each.key}"
  folder     = var.folder_id

  dynamic "list_policy" {
    for_each = {
      for verb, list in each.value : verb => list
      if verb == "allow" || verb == "deny"
    }

    content {
      dynamic "allow" {
        for_each = {
          for verb, list in each.value : verb => list
          if verb == "allow" && list != null
        }

        content {
          all    = contains(allow.value, "all") ? true : null
          values = ! contains(allow.value, "all") ? allow.value : null
        }
      }

      dynamic "deny" {
        for_each = {
          for verb, list in each.value : verb => list
          if verb == "deny" && list != null
        }

        content {
          all    = contains(deny.value, "all") ? true : null
          values = ! contains(deny.value, "all") ? deny.value : null
        }
      }
    }
  }
}

The objective here is to either create an allow OR deny block underneath the list_policy block.
I cannot figure out why it systematically generates the following error, when the input map local.org_policy_constraints contains one null element + one non null element:

Error: list_policy: attribute supports 1 item maximum, config has 2 declared

What am I missing here? Any constructive comment would be greatly appreciated.

Thanks in advance.

1 Like

Formalizing it for this post made me realize my mistake, right in front of me the whole time: i was missing the list != null check on the parent nested block…

dynamic "list_policy" {
for_each = {
  for verb, list in each.value : verb => list
  if (verb == "allow" || verb == "deny") && list != null
}

Thanks for being such a good listener Forum, much appreciated! :slight_smile:

1 Like

Thanks for sharing this solution, @sleterrier!

There is another potential cause of this situation which doesn’t seem to apply in your case but I want to share it in case someone else finds this thread in future and can therefore try both of these solutions.

During the plan phase Terraform often needs to work with incomplete information because certain values cannot be known until after the planned changes have been applied. In such cases, Terraform will interpret the partial information as best it can but it must sometimes make conservative assumptions in order to work within the language expectations.

One example of this is when the for_each expression in a dynamic block uses an unknown value (that is, a value that won’t be known until apply) as part of its computation. If that were true for your dynamic "allow" and dynamic "deny" blocks in the example you shared, Terraform would conservatively assume that both blocks would be present during planning, which might trigger a validation rule that only one of the two can be present. (I don’t know if such a rule exists; just using this as an example.)

For that reason, it’s best to ensure that your for_each expressions only make use of values decided statically in the configuration. That can include both constant values written directly, like ["foo"], but it can also include any expression derived from constant values elsewhere in the configuration, such as a reference to an input variable whose value is set statically in the calling module.

For dynamic blocks in particular Terraform will try to make the best of a partially-unknown situation, but it will often err on the side of reporting an error if there is the possibility that a problem will occur during plan.

1 Like