Remove code duplication in tags with for_each

hello,
I have a list of tags that i’d like to apply to both EC2 and ebs volumes.
The EC2s defined as a map:

variable "vms" {
  type = map(object({
    ami     = string
    ...
    tag_productfamily = string
  }))
}

Non vm specific tags are in the “locals”:

locals {
  base_tags = {
    Client                = var.Client_abbr
     ....
  }
}

Currently I use the following code for ec2s ( and ) tags:

resource "aws_instance" "this" {
  for_each = var.vms
   ...
  ami           = data.aws_ami.AM2[each.key].id
  ....
tags = merge(
    {
      ...
      ProductFamily = "${each.value.tag_productfamily}"
      ...
    },
    local.base_tags
  )

  volume_tags = merge(
    local.base_tags,
    {
    ...
    ProductFamily = "${each.value.tag_productfamily}"
    ...
    }
  )

The desire is to eliminate the repeating lines for “ProductFamily”. But “for_each” is getting in a way.

Thank you for looking.

P.S. updated the original post by replacing “Name and Team” → “ProductFamily” to simplify the example.
P.P.S the use of “default_tags” is not ideal, since various products use different infrastructure in the same account.

There is no mention of “Name” or “Team” in your code, so there is nothing to eliminate.

@maxb ,
thank you for catching the mistake. I simplified the code, by replacing the “Name” and “Team” by “ProductFamily”, but did not update the description. Now all should be correct.

Thank you.

If it’s just the one tag, ProductFamily, then just live with the duplication. It’ll be shorter than what you might write instead.

If there are many tags, I guess you could compute the full set of tags within the for_each expression, but it probably won’t look very nice… something like

  for_each = { for name, info in var.vms: name => merge(info, {
    tags = merge({
      ProductFamily = info.tag_productfamily
    }, local.base_tags)
  })

  tags = each.value.tags
  volume_tags = each.value.tags

By the way, stop writing

just write

each.value.tag_productfamily

as wrapping simple expressions in "${}" has been deprecated since the language changes of Terraform 0.12.

1 Like

Agreed. The benefits for such complexity aren’t there.

Thank you @maxb!

i noticed that every time the code runs it either removes all tags or adds all of them.
Not sure why.

the simplified code is like this:

locals {
  base_tags = {
    Client                = var.Client_abbr
    ...
  }
}

...

resource "aws_instance" "this" {
  for_each = var.vms
  ...
  tags_all = merge(
    {
      ...
      ProductFamily = ${each.value.tag_productfamily}
      ...
    },
    local.base_tags
  )
}

the only difference i can see from the version discussed above is the use of “tags” vs current “tags_all”

The reason for using tags_all is to add provider level tags should I have them ( currently not defined).

looking for some help on for_each also.

I have a set of subnets that I specify via resource blocks, as everyone can imagine allot of code repetition.

sure it can be done with a for each loop based on az’s but my az’s would be az1, az2, az3 as the v of a k, v pair, where I for example need a, b, or c in my availability_zone tag.

sure someone’s done this… how could I change the 3 blocks into 1 using a for_each config.

G

resource "aws_subnet" "public_1" {
  # The VPC ID.
  vpc_id = aws_vpc.main.id

  # The CIDR block for the subnet.
  cidr_block = "192.168.11.0/24"

  # The AZ for the subnet.
  availability_zone = "af-south-1a"

  # Required for EKS. Instances launched into the subnet should be assigned a public IP address.
  map_public_ip_on_launch = true


 
# A map of tags to assign to the resource.
  tags = merge(
    local.tags,
    {
      Name                                        = "sn_pub-af-south-1a"
      "kubernetes.io/cluster/${var.cluster_name}" = "shared"
      "kubernetes.io/role/elb"                    = 1
    }
  )
}

resource "aws_subnet" "public_2" {
  # The VPC ID
  vpc_id = aws_vpc.main.id

  # The CIDR block for the subnet.
  cidr_block = "192.168.12.0/24"

  # The AZ for the subnet.
  availability_zone = "af-south-1b"

  # Required for EKS. Instances launched into the subnet should be assigned a public IP address.
  map_public_ip_on_launch = true

  # A map of tags to assign to the resource.
  tags = merge(
    local.tags,
    {
      Name                                        = "sn_pub-af-south-1b"
      "kubernetes.io/cluster/${var.cluster_name}" = "shared"
      "kubernetes.io/role/elb"                    = 1
    }
  )
}

resource "aws_subnet" "public_3" {
  # The VPC ID
  vpc_id = aws_vpc.main.id

  # The CIDR block for the subnet.
  cidr_block = "192.168.13.0/24"

  # The AZ for the subnet.
  availability_zone = "af-south-1c"

  # Required for EKS. Instances launched into the subnet should be assigned a public IP address.
  map_public_ip_on_launch = true

  # A map of tags to assign to the resource.
  tags = merge(
    local.tags,
    {
      Name                                        = "sn_pub-af-south-1c",
      "kubernetes.io/cluster/${var.cluster_name}" = "shared"
      "kubernetes.io/role/elb"                    = 1
    }
  )
}