Nested for loops inside an output?

I’m trying to merge two maps together into one Output, where one map is nested inside a sub-map.

In my main.tf I have:

locals {
    fileserver_modules = {
        fs_abc = module.fs_abc
        fs_def = module.fs_def
        fs_ghi = module.fs_ghi
    }

    storage_modules = {
        st_abc = module.st_abc
        st_def = module.st_def
    }
}

output "storage_map_uid_instanceid" {
# <uid> = <instance_id>
value = {
    for storage_module in local.fileserver_modules :
    storage_module.uid => storage_module.instance_id
    }
}

module.fs_abc has outputs which are string variables, including “uid” and “instance_id”, so creating the “storage_map_uid_instanceid” output is simple and it works, to create me a map which looks like:

{
    "fs_abc" = "i-123446578"
    "fs_def" = "i-901234566"
    ...
}

My “storage_modules” use a module that calls 2 instances of the fileserver_module. It has outputs which are maps, in the same form (i.e. the top-level local.storage_modules[“st_abc”].storage_map_uid_instanceid contains a map looking exactly like the example immediately above, but with different values for uid and instance_id).

I’m trying to create a single output which contains all the uid/instance_id key/value pairs at the top level. This would change my original storage_map_uid_instanceid output at the top of this post to look something like the following:

output "storage_map_uid_instanceid" {
# <uid> = <instance_id>
value = merge(
    {
        for storage_module in local.fileserver_modules :
        storage_module.uid => storage_module.instance_id
    },
    {
        for storage_module, v in local.storage_modules :
            for keys, values in v.storage_map_uid_instanceid :
            keys => values
    }
    )
}

But this doesn’t work.

Can I use a nested for loop in an Ouput value like this? I essentially want to say, “merge the fileserver_modules child map values (uids => instance_ids) with the storage_modules.storage_map_uid_instanceid child map values (uids => instance_ids)”. I’m not sure I have my syntax correct in my output.

I’m using terraform v0.12.25.

Any help appreciated.

I’ve gotten further with this but still not there yet. I can provide a new sample output that illustrates what I’m trying to do.

locals {
    fileserver_modules = {
        fs_abc = module.fs_abc
        fs_def = module.fs_def
    }
    storage_modules = {
        st_abc = module.st_abc.storage_map_uid_instanceid
    }
}

output "storage_map_uid_instanceid" {
# <instance_uid> = <instance_id>
value = merge(
    {
        for storage_module in local.fileserver_modules :
        storage_module.uid => storage_module.instance_id
    },
    {
        for k, v in local.storage_modules :
        k => v
    }
)
}

and this gives me the output as:

storage_map_uid_instanceid = {
"st_abc" = {
    "st_abc" = "i-8710987"
    "st_abc_secondary" = "i-8752098374"
}
"fs_abc" = "i-879789789"
"fs_def" = "i-645364364"
}

So my question is reduced to:
How do I output the storage_map_uid_instanceid map so that it looks like:

storage_map_uid_instanceid = {
    "st_abc" = "i-8710987"
    "st_abc_secondary" = "i-8752098374"
    "fs_abc" = "i-879789789"
    "fs_def" = "i-645364364"
}

Ideally I would also like to structure my locals so that I have something like the following, because I have other outputs on the “module.st_abc” that I need to output at the higher level in a similar way:

    storage_modules = {
        st_abc = module.st_abc
        st_def = module.st_def
    }

Thanks.

I think I understand what you’re trying to do here! There are a couple of language features that should help.

First of all, it looks like you only care about the values of local.storage_modules for building this output, and the keys are irrelevant. The values function helps with this, as it takes a map and returns a list of its values, ignoring the keys.

Secondly, you want to call merge with each of those values. A for expression won’t work here, as it must result in a tuple or an object (depending on the [] or {} brackets used). Instead, you can use the ... function argument expansion operator to convert the list from values into a series of arguments.

Here’s a working example that I think does what you want (if I’ve understood the structure of your module outputs properly):

locals {
  fileserver_modules = {
    fs_abc = {
      uid         = "fs_abc"
      instance_id = "i-879789789"
    }
    fs_def = {
      uid         = "fs_def"
      instance_id = "i-645364364"
    }
  }
  storage_modules = {
    st_abc = {
      st_abc           = "i-8710987"
      st_abc_secondary = "i-8752098374"
    }
  }
}

output "storage_map_uid_instanceid" {
  # <instance_uid> = <instance_id>
  value = merge(
    {
      for storage_module in local.fileserver_modules:
        storage_module.uid => storage_module.instance_id
    },
    merge(values(local.storage_modules)...)
  )
}
$ terraform apply -auto-approve

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

storage_map_uid_instanceid = {
  "fs_abc" = "i-879789789"
  "fs_def" = "i-645364364"
  "st_abc" = "i-8710987"
  "st_abc_secondary" = "i-8752098374"
}

Hope this helps!

As an aside, a request for future: if possible, please try to provide a complete but isolated configuration. It makes it much easier to experiment if I can just copy & paste a single Terraform file, instead of trying to figure out module outputs or other dependencies. Thanks! :grinning:

1 Like

Hi @alisdair - thanks for this, yes I think that would do it! It is just the values I care about, so the Outputs you got is correct.

Apologies if the question was verbose, sometimes it’s difficult to condense it all down. I will definitely try to keep it more simple in the future.

Unfortunately I found an issue. If you use a “merge” like this, you run into this “feature” of terraform:

So whilst your solution logically is correct, and is the exact code I was trying to write myself, terraform thinks it can’t determine all the resources for the for_each which these outputs are being constructed for as inputs - even if all the keys inside the merges are static and known at plan time.

I might still be able to use your “values” code for my submodule outputs, and that will keep it tidier. I did manage to solve this myself yesterday, by duplicating all the outputs of my submodule - it simplified it but is far less elegant - so I will try using “values” instead with my original code and see if that gives me the result I need.