Search code examples
listdictionarymergeterraform

Terraform how to merge a list of maps in a single map without overriding values for duplicate keys


I have the following input as list of maps where there are duplicate keys. I want to merge these maps into a single map where for each duplicate keys, the values are appended instead of replaced by the latest values.

Input:

input_maps = [
    {
        “e-p”: {
             "a-emea": {
                "n@abc.com": {
                    "role": "READER"
                 }
              }
             "c-emea": {
                 "n@abc.com": {
                    "role": "READER"
                  }
              }
        }
        “g-p”: {
            "a-global": {
                "n@abc.com": {
                    "role": "READER"
                 }
             }
         }
    },
   {},
   {
        “g-p”: {
            “a-global": {
                "r@abc.com": {
                    "role": "READER"
                 }
             }
            “d-global": {
                "r@abc.com": {
                    "role": "READER"
                }
             }
         }
        “u-p”: {
            “a-us": {
                "r@abc.com": {
                    "role": "READER"
                }
            }
         }
  
    },
]

output:

output_maps = {
    “e-p”: {
        "a-emea": {
            "n@abc.com": {
                "role": "READER"
             }
         }
         "c-emea": {
             "n@abc.com": {
                 "role": "READER"
              }
          }
        }
    “g-p”: {
        "a-global": {
            "n@abc.com": {
                "role": "READER"
             }
            "r@abc.com": {
                "role": "READER"
            }
         }
        “d-global": {
            "r@abc.com": {
                "role": "READER"
            }
        }
     }
    “u-p”: {
        “a-us": {
            "r@abc.com": {
                "role": "READER"
            }
        }
    }
  }

I have tried following code:

output_maps = merge([
    for input_map in local.input_maps:{
        for key, value in input_map => {
            for sub_key, sub_value in value : sub_key => sub_value
            }
        }
    ]...)  

This code replaces the values instead of merging the values for a key (for-example key "g-p"). I have tried various ways however, could not make the deep merge work.


Solution

  • Working example:

    locals {
      input_maps = [
        {
          "e-p" : {
            "a-emea" : {
              "n@abc.com" : {
                "role" : "READER"
              }
            }
            "c-emea" : {
              "n@abc.com" : {
                "role" : "READER"
              }
            }
          }
          "g-p" : {
            "a-global" : {
              "n@abc.com" : {
                "role" : "READER"
              }
            }
          }
        },
        {},
        {
          "g-p" : {
            "a-global" : {
              "r@abc.com" : {
                "role" : "READER"
              }
            }
            "d-global" : {
              "r@abc.com" : {
                "role" : "READER"
              }
            }
          }
          "u-p" : {
            "a-us" : {
              "r@abc.com" : {
                "role" : "READER"
              }
            }
          }
        }
      ]
    
      # Flatten the input maps into a list of maps
      # Each map contains the map_key, zone_key, and roles
      flatten_maps = flatten([
        for _, input_map in local.input_maps : [
          for map_key, zones in input_map : [
            for zone_key, roles in zones : {
              map_key  = map_key,
              zone_key = zone_key,
              value    = roles
            }
          ]
        ]
      ])
    
      # Merge the maps into a single map
      # Key consists of the map_key and zone_key
      # Value consists of roles grouped by zone_key and map_key
      merged_maps = {
        for map in local.flatten_maps :
        "${map.map_key}|${map.zone_key}" => map.value...
      }
    
      # Output the merged maps
      output_maps = merge([
        for _, input_map in local.input_maps : {
          for map_key, zones in input_map : map_key => {
            for zone_key, roles in zones : zone_key =>
            local.merged_maps["${map_key}|${zone_key}"]
          }
        }
      ]...)
    }
    
    output "flatten_maps" {
      value = local.flatten_maps
    }
    
    output "merged_maps" {
      value = local.merged_maps
    }
    
    output "output_maps" {
      value = local.output_maps
    }
    

    Running terraform plan:

    Changes to Outputs:
      + flatten_maps = [
          + {
              + map_key  = "e-p"
              + value    = {
                  + "n@abc.com" = {
                      + role = "READER"
                    }
                }
              + zone_key = "a-emea"
            },
          + {
              + map_key  = "e-p"
              + value    = {
                  + "n@abc.com" = {
                      + role = "READER"
                    }
                }
              + zone_key = "c-emea"
            },
          + {
              + map_key  = "g-p"
              + value    = {
                  + "n@abc.com" = {
                      + role = "READER"
                    }
                }
              + zone_key = "a-global"
            },
          + {
              + map_key  = "g-p"
              + value    = {
                  + "r@abc.com" = {
                      + role = "READER"
                    }
                }
              + zone_key = "a-global"
            },
          + {
              + map_key  = "g-p"
              + value    = {
                  + "r@abc.com" = {
                      + role = "READER"
                    }
                }
              + zone_key = "d-global"
            },
          + {
              + map_key  = "u-p"
              + value    = {
                  + "r@abc.com" = {
                      + role = "READER"
                    }
                }
              + zone_key = "a-us"
            },
        ]
      + merged_maps  = {
          + "e-p|a-emea"   = [
              + {
                  + "n@abc.com" = {
                      + role = "READER"
                    }
                },
            ]
          + "e-p|c-emea"   = [
              + {
                  + "n@abc.com" = {
                      + role = "READER"
                    }
                },
            ]
          + "g-p|a-global" = [
              + {
                  + "n@abc.com" = {
                      + role = "READER"
                    }
                },
              + {
                  + "r@abc.com" = {
                      + role = "READER"
                    }
                },
            ]
          + "g-p|d-global" = [
              + {
                  + "r@abc.com" = {
                      + role = "READER"
                    }
                },
            ]
          + "u-p|a-us"     = [
              + {
                  + "r@abc.com" = {
                      + role = "READER"
                    }
                },
            ]
        }
      + output_maps  = {
          + e-p = {
              + a-emea = [
                  + {
                      + "n@abc.com" = {
                          + role = "READER"
                        }
                    },
                ]
              + c-emea = [
                  + {
                      + "n@abc.com" = {
                          + role = "READER"
                        }
                    },
                ]
            }
          + g-p = {
              + a-global = [
                  + {
                      + "n@abc.com" = {
                          + role = "READER"
                        }
                    },
                  + {
                      + "r@abc.com" = {
                          + role = "READER"
                        }
                    },
                ]
              + d-global = [
                  + {
                      + "r@abc.com" = {
                          + role = "READER"
                        }
                    },
                ]
            }
          + u-p = {
              + a-us = [
                  + {
                      + "r@abc.com" = {
                          + role = "READER"
                        }
                    },
                ]
            }
        }