Transforming a list of objects to a map

EC2 has this aws_ec2_tag resource: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag#argument-reference

that I would like to call so:

resource "aws_ec2_tag" "example" {
  for_each    = local.resource_id_to_tag
  resource_id = each.value.resource_id
  key         = each.value.tag_name
  value       = each.value.tag_value
}

The problem is, currently, local.resource_id_to_tag looks like this:

[{"0-Environment":{"resource_id":"i-0309cb15668304037","tag_name":"Environment","tag_value":"Test"},"0-Name":{"resource_id":"i-0309cb15668304037","tag_name":"Name","tag_value":"Example"}},{"1-Environment":{"resource_id":"i-05954524c431a01f8","tag_name":"Environment","tag_value":"Test"},"1-Name":{"resource_id":"i-05954524c431a01f8","tag_name":"Name","tag_value":"Example"}},{"2-Environment":{"resource_id":"i-083085c422fc336c3","tag_name":"Environment","tag_value":"Test"},"2-Name":{"resource_id":"i-083085c422fc336c3","tag_name":"Name","tag_value":"Example"}},{"3-Environment":{"resource_id":"i-0f5c131cae288f9e6","tag_name":"Environment","tag_value":"Test"},"3-Name":{"resource_id":"i-0f5c131cae288f9e6","tag_name":"Name","tag_value":"Example"}},{"4-Environment":{"resource_id":"i-0bca1458fe2d2bb8e","tag_name":"Environment","tag_value":"Test"},"4-Name":{"resource_id":"i-0bca1458fe2d2bb8e","tag_name":"Name","tag_value":"Example"}}]

… and I can’t wrap my head around into how I can transform it to something that will work with aws_ec2_tag rightaway:

{
    "0-Environment":{"resource_id":,"tag_name":"Environment","tag_value":"Test"},"0-Name":{"resource_id":"i-0309cb15668304037","tag_name":"Name","tag_value":"Example"},
    "1-Environment":{"resource_id":,"tag_name":"Environment","tag_value":"Test"},"1-Name":{"resource_id":"i-05954524c431a01f8","tag_name":"Name","tag_value":"Example"},
    "2-Environment":{"resource_id":,"tag_name":"Environment","tag_value":"Test"},"2-Name":{"resource_id":"i-083085c422fc336c3","tag_name":"Name","tag_value":"Example"},
    "3-Environment":{"resource_id":,"tag_name":"Environment","tag_value":"Test"},"3-Name":{"resource_id":"i-0f5c131cae288f9e6","tag_name":"Name","tag_value":"Example"},
    "4-Environment":{"resource_id":,"tag_name":"Environment","tag_value":"Test"},"4-Name":{"resource_id":"i-0bca1458fe2d2bb8e","tag_name":"Name","tag_value":"Example"}
}

I think what you want here is a combination of the merge function and the ... expansion operator:

locals {
  resource_id_map = merge(local.resource_id_to_tag...)
  resource_id_to_tag = [
    {
      "0-Environment": {
        "resource_id": "i-0309cb15668304037",
        "tag_name": "Environment",
        "tag_value": "Test"
      },
      "0-Name": {
        "resource_id": "i-0309cb15668304037",
        "tag_name": "Name",
        "tag_value": "Example"
      }
    },
    {
      "1-Environment": {
        "resource_id": "i-05954524c431a01f8",
        "tag_name": "Environment",
        "tag_value": "Test"
      },
      "1-Name": {
        "resource_id": "i-05954524c431a01f8",
        "tag_name": "Name",
        "tag_value": "Example"
      }
    },
    {
      "2-Environment": {
        "resource_id": "i-083085c422fc336c3",
        "tag_name": "Environment",
        "tag_value": "Test"
      },
      "2-Name": {
        "resource_id": "i-083085c422fc336c3",
        "tag_name": "Name",
        "tag_value": "Example"
      }
    },
    {
      "3-Environment": {
        "resource_id": "i-0f5c131cae288f9e6",
        "tag_name": "Environment",
        "tag_value": "Test"
      },
      "3-Name": {
        "resource_id": "i-0f5c131cae288f9e6",
        "tag_name": "Name",
        "tag_value": "Example"
      }
    },
    {
      "4-Environment": {
        "resource_id": "i-0bca1458fe2d2bb8e",
        "tag_name": "Environment",
        "tag_value": "Test"
      },
      "4-Name": {
        "resource_id": "i-0bca1458fe2d2bb8e",
        "tag_name": "Name",
        "tag_value": "Example"
      }
    }
  ]
}
$ echo local.resource_id_map | terraform console
{
  "0-Environment" = {
    "resource_id" = "i-0309cb15668304037"
    "tag_name" = "Environment"
    "tag_value" = "Test"
  }
  "0-Name" = {
    "resource_id" = "i-0309cb15668304037"
    "tag_name" = "Name"
    "tag_value" = "Example"
  }
  "1-Environment" = {
    "resource_id" = "i-05954524c431a01f8"
    "tag_name" = "Environment"
    "tag_value" = "Test"
  }
  "1-Name" = {
    "resource_id" = "i-05954524c431a01f8"
    "tag_name" = "Name"
    "tag_value" = "Example"
  }
  "2-Environment" = {
    "resource_id" = "i-083085c422fc336c3"
    "tag_name" = "Environment"
    "tag_value" = "Test"
  }
  "2-Name" = {
    "resource_id" = "i-083085c422fc336c3"
    "tag_name" = "Name"
    "tag_value" = "Example"
  }
  "3-Environment" = {
    "resource_id" = "i-0f5c131cae288f9e6"
    "tag_name" = "Environment"
    "tag_value" = "Test"
  }
  "3-Name" = {
    "resource_id" = "i-0f5c131cae288f9e6"
    "tag_name" = "Name"
    "tag_value" = "Example"
  }
  "4-Environment" = {
    "resource_id" = "i-0bca1458fe2d2bb8e"
    "tag_name" = "Environment"
    "tag_value" = "Test"
  }
  "4-Name" = {
    "resource_id" = "i-0bca1458fe2d2bb8e"
    "tag_name" = "Name"
    "tag_value" = "Example"
  }
}
1 Like

Hi @gc-ss,

In order to see what you mean I had to reformat your local value definition to show the structure, so I might as well include that in here in case other readers find it useful too:

[
  {
    "0-Environment": {
      "resource_id": "i-0309cb15668304037",
      "tag_name": "Environment",
      "tag_value": "Test"
    },
    "0-Name": {
      "resource_id": "i-0309cb15668304037",
      "tag_name": "Name",
      "tag_value": "Example"
    }
  },
  {
    "1-Environment": {
      "resource_id": "i-05954524c431a01f8",
      "tag_name":"Environment",
      "tag_value":"Test"
    },
    "1-Name": {
      "resource_id": "i-05954524c431a01f8",
      "tag_name": "Name",
      "tag_value": "Example"
    }
  },
  {
    "2-Environment": {
      "resource_id": "i-083085c422fc336c3",
      "tag_name": "Environment",
      "tag_value":"Test"
    },
    "2-Name": {
      "resource_id": "i-083085c422fc336c3",
      "tag_name": "Name",
      "tag_value":"Example"
    }
  },
  {
    "3-Environment": {
      "resource_id": "i-0f5c131cae288f9e6",
      "tag_name": "Environment",
      "tag_value": "Test"
    },
    "3-Name": {
      "resource_id": "i-0f5c131cae288f9e6",
      "tag_name": "Name",
      "tag_value": "Example"
    }
  },
  {
    "4-Environment": {
      "resource_id": "i-0bca1458fe2d2bb8e",
      "tag_name": "Environment",
      "tag_value": "Test"
    },
    "4-Name": {
      "resource_id":"i-0bca1458fe2d2bb8e",
      "tag_name":"Name",
      "tag_value":"Example"
    }
  }
]

So although this is JSON-style syntax, in Terraform terms it seems like what we have here is a list of maps of objects, and what we need is just a map of objects. It also looks like the keys within the maps are unique across all of the maps, due to the numeric prefix, and so there aren’t any conflicts to worry about when merging all of the keys of the individual maps into a single new map.

Given that, I think this requirement can be met with the merge function. Normally that function expects a variable number of arguments that are each maps, but we can use the special ... syntax in the function call to tell Terraform to expand your list into a sequence of arguments where each element of the list is a separate function argument:

locals {
  all_tags = merge(local.resource_id_to_tag...)
}
1 Like

Oh, heh… looks like @alisdair and I collided. At least we both suggested the same thing, which is encouraging! :smiley:

2 Likes

@apparentlymart , @alisdair I REALLY appreciate the input guys. I am going to ask a lot of questions here because I want to soak this in.

First: I have been struggling with being able to debug my terraform scripts - what would really be awesome is some kind of a repl experience. I think terraform console is what I am looking for? If so, I would like to dig a bit deeper into the terraform console bits:

i. I saved the JSON to a file called local.resource_id_map and then literally typed echo local.resource_id_map | terraform console
ii. This resulted in error:

│ Error: Reference to undeclared local value
│ 
│   on <console-input> line 1:
│   (source code not available)
│ 
│ A local value with the name "resource_id_map" has not been declared.

What did I do wrong?

Second - I am trying out the merge and … but - is there a better way to solve this problem?

All I really need to do is pass in a list of tags per instance to the aws_ec2_tag resource and this way, while it might work, does not feel the simplest.

I can’t think of any simpler approach than the merge call, or any better way to solve the problem.

The easiest way to experiment with terraform console is to use it from in the directory of an existing configuration. That gives you access to the existing values from the config.

I’d recommend running it in interactive mode and typing expressions. You can’t pipe an entire source file to the command.

1 Like