Nisman
Nisman

Reputation: 1309

How to merge two level nested maps in terraform?

I know there is an open feature request for deepmerge but I just wanted to see if there is any work around for my use case. lets consider the following local variables:

locals {
  default = {
    class = "class1"
    options = {
       option1 = "1"
       option2 = "2"
    }
  }
  configs = {
    configA = {
        name = "A"
        max = 10
        min = 5
        enabled  = true
        options = {
            option3 = "3"
        }
    }
    configB = {
        name = "B"
        max  = 20
        min     = 10
        enabled  = false
    }
  }
}

so I can merge the configs with default like this:

for key, config in local.configs : key => merge(local.default, config)

and the result will be:

configs = {
    configA = {
        name = "A"
        class = "class1"
        max = 10
        min = 5
        enabled  = true
        options = {
            option3 = "3"
        }
    }
    configB = {
        name = "B"
        class = "class1"
        max  = 20
        min     = 10
        enabled  = false
        options = {
            option1 = "1"
            option2 = "2"
        }
    }
  }

The problem is the nested map (options property) gets completely replaced by configA since merge cannot handle nested merge. Is there any work around for it in terraform 1.1.3 ?

Upvotes: 2

Views: 9837

Answers (2)

jonseymour
jonseymour

Reputation: 1106

There are various ways to do this including using this deepmerge provider:

https://registry.terraform.io/modules/cloudposse/config/yaml/0.5.0/submodules/deepmerge

Here is a way that assumes only that /usr/bin/jq exists. I am not saying it is pretty, but it does work and ensures that you get the same semantics as a jq * operator.

locals {
   left = {...}
   right = {...}
   merged = { 
      for k, v in data.external.merge.result : k => jsondecode(v) 
   }
}

data "external" "merge" {
  program = [
    "/usr/bin/jq",
    "((.left|fromjson) * (.right|fromjson))|with_entries(.value|=tojson)"
  ]
  query = {
    left = jsonencode(local.left)
    right = jsonencode(local.right)
  }
}

Upvotes: 2

tmatilai
tmatilai

Reputation: 4176

If you know the structure of the map, you can merge the included elements separately as you wish.

In this case this should work:

merged = {
  for key, config in local.configs : key =>
  merge(
    local.default,
    config,
    { options = merge(local.default.options, lookup(config, "options", {})) }
  )
}

So first merge the top-level elements, and then handle the options separately.

Upvotes: 5

Related Questions