Using flatten on a nested map of differing value types

Hi, I’m using terraform v0.12.26 and have a structure very similar to the example on the flatten page, except I have an extra key in the map.

I’m trying to follow the example as closely as possible. I’ve gotten it working using a structure of the same shape, but I’ve had to modify my structure to the following:

<instance_uid> = {
    instance_id = <instance_id>
    vols_list   = [volume_label,volume_label,...]
}

an example being:

instances_vols = {
    "hello_world1" = {
        instance_id = "i-1234"
        vols_list = ["data01","data02","inputs01"]
    }
    "hello_world2" = {
        instance_id = "i-4321"
        vols_list = ["data01","inputs01","outputs01","outputs02"]
    }
}

My plan is to have a resource where I can use a for_each like the following:

for_each = {
    for instance in local.instances_volumes : "${instance.instance_uid}.${instance.volume_label}" => instance
}

and I can then use the values as “each.value.instance_uid” in my resource.

The flattened local looks as follows:

locals {
  instances_volumes = flatten([
    for instance_uid, instance_vol_list in var.instances_vols : [
      for instance_keys, instance_values in instance_vol_list : [
        for vol in instance_keys.vols_list : {
          instance_uid = instance_uid
          instance_id  = instance_keys.instance_id
          volume_label = vol
        }
      ]
    ]
  ])
}

but this doesn’t work, since “for vol in instance_keys.vols_list” doesn’t have any attributes. I’m trying to use the keys my name but this doesn’t seem to work.

The map key “hello_world1” and it’s inner key “instance_uid” are a 1:1 mapping - “hello_world1” would always have an instance_uid of “i-1234” (I mean, it would change in practice, but I could refer to “hello_world1” or “i-1234” and it would be the same thing).

How do I structure my flattened local? Or, can I restructure my input to be more suitable? instances_vols is generated elsewhere as an output to feed into this input, using the following:

output "fileserver_instance_vols" {
    value = {
        for fileserver_module in local.fileserver_modules :
        fileserver_module.uid => {
            instance_id = fileserver_module.instance_id
            vols_list   = fileserver_module.vols_list
        }
    }
}

Is there a better way to structure my output such that it’s much more simple to use as the input later?

Thanks.

(also, just curious, how do you make the markdown appear on this page where it highlights the terraform keywords?)

Hi @rba1-source!

I think the following would get the result you were looking for:

locals {
  instances_volumes = flatten([
    for inst_key, inst in var.instances_vols : [
      for vol_key in inst.vols_list : {
        instance_uid = inst_key
        instance_id  = inst.instance_id
        volume_label = vol_key
      }
    ]
  ])
}

In this case the instance id is coming from inst, which is the object like this:

{
  instance_id = "i-1234"
  vols_list = ["data01","data02","inputs01"]
}

Hi @apparentlymart. Thanks, this did the trick. I guess I was just getting confused at which level I was specifying values and keys. Thanks for the help!

A second option is to simplify and use a second map, since there’s a 1:1 mapping between uid and instance_id. As the key would be the same in each case (uid), I could have one map which was { uid = vols_list } and a second which is { uid = instance_id } , and then just use one as an index into the other, i.e.

for_each = var.instances_vols
instance_id = uid_instances[each.key].instance_id

It would mean an extra map, but it would keep things simpler and means I can use the new map to pass into other inputs that don’t need the volume list.

It’s all still a useful exercise in learning how to use for and for_each though, so whichever solution I choose, it was worth asking.

Thanks again!