Search code examples
terraformhcl

Merge some keys of a Terraform map


I am trying to "merge" a list of objects (each items of local.what_i_have below) together by keeping some keys as scalar (identical values, here instance_id and tag_name) and merging others (here tag_value) into a set/list.

locals {
  what_i_have_v2 = {
    "cluster=foo|instance=foo#1|tag=tag1" = [
      {
        instance_id = "i-fff11111"
        tag_name = "tag1"
        tag_value = "val1_a"
      },
      {
        instance_id = "i-fff11111"
        tag_name = "tag1"
        tag_value = "val1_b"
      },
    ],
    "cluster=foo|instance=foo#2|tag=tag1" = [
      {
        instance_id = "i-fff22222"
        tag_name = "tag1"
        tag_value = "val1_a"
      },
      {
        instance_id = "i-fff22222"
        tag_name = "tag1"
        tag_value = "val1_b"
      },
    ],
    "cluster=bar|instance=bar#1|tag=tag1" = [
      {
        instance_id = "i-bbb11111"
        tag_name = "tag1"
        tag_value = "val1_a"
      },
    ],
    "cluster=bar|instance=bar#1|tag=tag2" = [
      {
        instance_id = "i-bbb11111"
        tag_name = "tag2"
        tag_value = "val2_a"
      },
      {
        instance_id = "i-bbb11111"
        tag_name = "tag2"
        tag_value = "val2_b"
      },
    ],
  },
}

Any of the following output might work for me:

locals {
  what_i_want_v1 = {
    "cluster=foo|instance=foo#1" = {
      instance_id = "i-fff11111"
      tag_name = "tag1"
      tag_value = ["val1_a", "val1_b"] # Values for identical `what_i_have` key are now "merged" in a set/list
    },
    "cluster=foo|instance=foo#2" = {
      instance_id = "i-fff22222"
      tag_name = "tag1"
      tag_value = ["val1_a", "val1_b"]
    },
    "cluster=bar|instance=bar#1" = {
      instance_id = "i-bbb11111"
      tag_name = "tag1"
      tag_value = ["val1_a"]
    },
  },

  what_i_want_v2 = [ # Not a map anymore but I don't mind
    {
      instance_id = "i-fff11111"
      tag_name = "tag1"
      tag_value = ["val1_a", "val1_b"]
    },
    {
      instance_id = "i-fff22222"
      tag_name = "tag1"
      tag_value = ["val1_a", "val1_b"]
    },
    {
      instance_id = "i-bbb11111"
      tag_name = "tag1"
      tag_value = ["val1_a"]
    },
  ],

  what_i_want_v3 = [
    {
      instance_id = "i-fff11111"
      tag_name = "tag1"
      tag_value = "val1_a+val1_b" # Not an set/list but a string with a "+" separator
    },
    {
      instance_id = "i-fff22222"
      tag_name = "tag1"
      tag_value = "val1_a+val1_b"
    },
    {
      instance_id = "i-bbb11111"
      tag_name = "tag1"
      tag_value = "val1_a"
    },
  ],
}

Is this possible?

I naively tried to merge() to see what it does (local.attempt_1 below):

locals {
 attempt_1 = {for k, v in local.what_i_have: k => merge(v...)}
}

But it (obviously) swallows values:

# attempt_1 =
{
  "cluster=bar|instance=bar#1" = {
    instance_id = "i-bbb11111"
    tag_name    = "tag1"
    tag_value   = "val1_a"
  }
  "cluster=foo|instance=foo#1" = {
    instance_id = "i-fff11111"
    tag_name    = "tag1"
    tag_value   = "val1_b"
  }
  "cluster=foo|instance=foo#2" = {
    instance_id = "i-fff22222"
    tag_name    = "tag1"
    tag_value   = "val1_b"
  }
}

Context: I am building what_i_have as a for_each for a aws_ec2_tag resource, to tag instances from a list of tags to have.

Update 1: what_i_have can contain any tag name, updated the map in question (as what_i_have_v2).

Here is the original flawed what_i_have:

locals {
  what_i_have = {
    "cluster=foo|instance=foo#1" = [
      {
        instance_id = "i-fff11111"
        tag_name = "tag1"
        tag_value = "val1_a"
      },
      {
        instance_id = "i-fff11111"
        tag_name = "tag1"
        tag_value = "val1_b"
      },
    ],
    "cluster=foo|instance=foo#2" = [
      {
        instance_id = "i-fff22222"
        tag_name = "tag1"
        tag_value = "val1_a"
      },
      {
        instance_id = "i-fff22222"
        tag_name = "tag1"
        tag_value = "val1_b"
      },
    ],
    "cluster=bar|instance=bar#1" = [
      {
        instance_id = "i-bbb11111"
        tag_name = "tag1"
        tag_value = "val1_a"
      },
    ],
  },
}

Solution

  • You could flatten the values and then iterate over them:

      flattened = flatten(values(local.what_i_have))
    
      what_i_want_v2 = [
        for instance in distinct([for item in local.flattened : item.instance_id]) : {
          instance_id = instance
          tag_name    = "tag1"
          tag_value   = distinct([for item in local.flattened : item.tag_value if item.instance_id == instance])
        }
      ]
    
    > local.what_i_want_v2
    [
      {
        "instance_id" = "i-bbb11111"
        "tag_name" = "tag1"
        "tag_value" = tolist([
          "val1_a",
        ])
      },
      {
        "instance_id" = "i-fff11111"
        "tag_name" = "tag1"
        "tag_value" = tolist([
          "val1_a",
          "val1_b",
        ])
      },
      {
        "instance_id" = "i-fff22222"
        "tag_name" = "tag1"
        "tag_value" = tolist([
          "val1_a",
          "val1_b",
        ])
      },
    ]