For_each flattened list

Hello,

I have the following local structure.

locals {
  warehouse_dbs_metadata = {
    db1 = {
      edition           = "Standard",
      performance_level = "S0",
      firewall_rules = [
        { name = "test1", start_ip = "10.0.17.62", end_ip = "10.0.17.62" },
        { name = "test2", start_ip = "10.0.17.63", end_ip = "10.0.17.63" }
    ] },
    db2 = {
      edition           = "Standard",
      performance_level = "S0",
      firewall_rules = [
        { name = "test3", start_ip = "10.0.17.62", end_ip = "10.0.17.62" },
        { name = "test4", start_ip = "10.0.17.63", end_ip = "10.0.17.63" }
    ] }
  }

  nestedforeach_p = flatten([
    for w in keys(local.warehouse_dbs_metadata) : [
      for v in values(local.warehouse_dbs_metadata) : [
        for f in v.firewall_rules : [
           {
            key      = w,
            name     = f.name,
            start_ip = f.start_ip,
            end_ip   = f.end_ip
          }
        ]
      ]
    ]
  ])

Output of nestedforeach_p looks like:

test = [
  {
    "end_ip" = "10.0.17.62"
    "key" = "db1"
    "name" = "test1"
    "start_ip" = "10.0.17.62"
  },
  {
    "end_ip" = "10.0.17.63"
    "key" = "db1"
    "name" = "test2"
    "start_ip" = "10.0.17.63"
  },
  {
    "end_ip" = "10.0.17.62"
    "key" = "db1"
    "name" = "test3"
    "start_ip" = "10.0.17.62"
  },
  {
    "end_ip" = "10.0.17.63"
    "key" = "db1"
    "name" = "test4"
    "start_ip" = "10.0.17.63"
  },
  {
    "end_ip" = "10.0.17.62"
    "key" = "db2"
    "name" = "test1"
    "start_ip" = "10.0.17.62"
  },
  {
    "end_ip" = "10.0.17.63"
    "key" = "db2"
    "name" = "test2"
    "start_ip" = "10.0.17.63"
  },
  {
    "end_ip" = "10.0.17.62"
    "key" = "db2"
    "name" = "test3"
    "start_ip" = "10.0.17.62"
  },
  {
    "end_ip" = "10.0.17.63"
    "key" = "db2"
    "name" = "test4"
    "start_ip" = "10.0.17.63"
  },
]

My goal is to use this with for_each withing a resource. However everything i have tried results in a terraform crash.

resource "null_resource" "resource" {
  for_each = local.nestedforeach_p

  triggers = {
    key = each.key,
    name = each.name,
   start_ip = each.start_ip,
   end_ip = each.end_ip
  }
}

I am sure its something dumb, any help would be appreciated.

Here is the error, happy to provide the crash.log

pply cancelled.
wasea-mlsrebr:nested brohrer$ terraform apply
panic: not a string

goroutine 46 [running]:
github.com/zclconf/go-cty/cty.Value.AsString(...)
        /opt/teamcity-agent/work/9e329aa031982669/pkg/mod/github.com/zclconf/go-cty@v1.0.1-0.20190708163926-19588f92a98f/cty/value_ops.go:1026
github.com/zclconf/go-cty/cty.Value.AsValueMap(0x2e278a0, 0xc0005144a0, 0x25cba80, 0xc0005144c0, 0x8)
        /opt/teamcity-agent/work/9e329aa031982669/pkg/mod/github.com/zclconf/go-cty@v1.0.1-0.20190708163926-19588f92a98f/cty/value_ops.go:1095 +0x38a
github.com/hashicorp/terraform/terraform.evaluateResourceForEachExpressionKnown(0x2e26560, 0xc0004c5c20, 0x2e594c0, 0xc0000d2d00, 0xc0000e2dc0, 0xc0000e2e70, 0xc0000e2f20, 0x100b609, 0xc00065b6b8)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval_for_each.go:84 +0x5bd
github.com/hashicorp/terraform/terraform.(*EvalValidateResource).validateForEach(0xc000260870, 0x2e594c0, 0xc0000d2d00, 0x2e26560, 0xc0004c5c20, 0x0, 0xc0000bdef0, 0xc0000b6900)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval_validate.go:572 +0x59
github.com/hashicorp/terraform/terraform.(*EvalValidateResource).Eval(0xc000260870, 0x2e594c0, 0xc0000d2d00, 0x2, 0x2, 0x0, 0x0)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval_validate.go:394 +0x1b57
github.com/hashicorp/terraform/terraform.EvalRaw(0x2de5e80, 0xc000260870, 0x2e594c0, 0xc0000d2d00, 0x0, 0x0, 0x0, 0x0)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval.go:57 +0x131
github.com/hashicorp/terraform/terraform.(*EvalSequence).Eval(0xc000435b00, 0x2e594c0, 0xc0000d2d00, 0x2, 0x2, 0x1ad38c5, 0x2de5e80)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval_sequence.go:20 +0xfd
github.com/hashicorp/terraform/terraform.EvalRaw(0x2de5de0, 0xc000435b00, 0x2e594c0, 0xc0000d2d00, 0x26826a0, 0x3e03465, 0x25ef1a0, 0xc0005be560)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval.go:57 +0x131
github.com/hashicorp/terraform/terraform.Eval(0x2de5de0, 0xc000435b00, 0x2e594c0, 0xc0000d2d00, 0xc000435b00, 0x2de5de0, 0xc000435b00, 0x0)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/eval.go:35 +0x4d
github.com/hashicorp/terraform/terraform.(*Graph).walk.func1(0x28e1c20, 0xc000432080, 0x0, 0x0, 0x0)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/terraform/graph.go:90 +0xf40
github.com/hashicorp/terraform/dag.(*Walker).walkVertex(0xc000442700, 0x28e1c20, 0xc000432080, 0xc000548300)
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/dag/walk.go:392 +0x353
created by github.com/hashicorp/terraform/dag.(*Walker).Update
        /opt/teamcity-agent/work/9e329aa031982669/src/github.com/hashicorp/terraform/dag/walk.go:314 +0xa9b

Logged my issue here > https://github.com/hashicorp/terraform/issues/22560

@apparentlymart - Seems the above panic is fixed but my underlying issue around looping a tuple is not. What is the best way to accomplish my loop with the nested map?

Hi @rohrerb!

One thing I notice from your example is that your for_each value here is a list, whereas for_each requires either a map or a set of strings so that it can use the strings as instance identifiers.

It looks like name is a suitable unique identifier in your case, so you could transform it into a map with that as the key like this:

resource "null_resource" "resource" {
  for_each = { for o in local.nestedforeach_p : o.name => o }

  triggers = {
    key = each.key,
    name = each.value.name,
    start_ip = each.value.start_ip,
    end_ip = each.value.end_ip
  }
}

This will generate resource instances with addresses like null_resource.resource["test1"], null_resource.resource["test2"], etc, because those are the keys of the map provided to for_each.

Note that I also rewrote some of the references in the triggers map to use each.value rather than just each to access attributes of the value objects. each.key and each.value are respectively the key and value from the current map element.