Nov 08 2022
Turn a Terraform Map of Maps into a Single Map…
…Or how to avoid the ‘Call to function “merge” failed: arguments must be maps or objects, got “tuple”.’ error message. Maps of maps example.

Issue Producing a Single Map
Here’s a typical 2 level data structure that fits users in subgroups, themselves belonging to departments:
locals {
   groups = {
      it = {
         admin   = ["it1","it2"]
         editors = ["it3","it4"]
      },
      accounts = {
         editors = ["account1","account2"]
         viewers = ["account3","account4"]
      }
   }
}
There are good chances you will need to flatten that structure to create some Terraform ressources.
Our target is a map of unique subgroups department_subgroup = [ “user1”, “user2” ]. In our example:
subgroups = {
  "accounts_editors" = [
    "account1",
    "account2",
  ]
  "accounts_viewers" = [
    "account3",
    "account4",
  ]
  "it_admin" = [
    "it1",
    "it2",
  ]
  "it_editors" = [
    "it3",
    "it4",
  ]
}
It is pretty simple to get a list of distinct maps flattening the resulting lists of a double loop:
subgroups = flatten([
   for group,subgroups in local.groups : [
      for subgroup, users in subgroups : {
         "${group}_${subgroup}" = users
      }
   ]
])
# output:
subgroups = [
  {
    "accounts_editors" = [
      "account1",
      "account2",
    ]
  },
  {
    "accounts_viewers" = [
      "account3",
      "account4",
    ]
  },
  {
    "it_admin" = [
      "it1",
      "it2",
    ]
  },
  {
    "it_editors" = [
      "it3",
      "it4",
    ]
  },
]All we need is merge these maps into a single map but if we do, we end up with:
‘Call to function “merge” failed: arguments must be maps or objects, got “tuple”.’

2 ways to the rescue: the ugly and the elegant.
The Ugly Way : Another Terraform Loop
The first way you can think of is to process the new map through another for loop. That makes 3 loops and does not make any sense since a simple merge would do the job. Each map has one element only, so we take the first key and first value.
subgroups = { for subgroup in flatten([
   for group,subgroups in local.groups : [
      for subgroup, users in subgroups : {
         "${group}_${subgroup}" = users
      }
   ]
]) : keys(subgroup)[0] => values(subgroup)[0] }
The Elegant Way : Function Expansion
This is much shorter than above solution:
subgroups = merge(flatten([
   for group,subgroups in local.groups : [
      for subgroup, users in subgroups : {
         "${group}_${subgroup}" = users
      }
   ]
])...)The result is exactly the same but what has changed? Just the 3 dots…
Expansion takes each element out of a list and pass them on to the calling function.




