skip to Main Content

I’ve created subnets in one AWS account and shared them with another AWS account.

I’m using the following Terraform code to get the individual subnet info:

data "aws_subnets" "subnets" {
  filter {
    name   = "vpc-id"
    values = [var.vpc_id]
  }
}

data "aws_subnet" "subnet" {
  for_each = toset(data.aws_subnets.subnets.ids)
  id       = each.value
}

data.aws_subnet.subnet is a collection of all of the subnets with all of their information within. I want to use this to generate an aws_ec2_tag for each tag on each subnet. The purpose of this is to ensure that tags are the same between the source AWS account and the AWS account that I’m sharing the subnets with.

I’ve got this so far, but I’m running up against a wall:

resource "aws_ec2_tag" "subnet_tags" {
  provider          = aws.dst
  for_each = {
    for subnet_id, subnet in data.aws_subnet.subnet : subnet_id => {
      for tag_key, tag_value in subnet.tags : tag_key => tag_value
    }
  }

  resource_id = data.aws_subnet.subnet[each.key].id
  key = each.key
  value = each.value
}

I’m getting the following error:

╷
│ Error: Incorrect attribute value type
│
│  on main.tf line 52, in resource "aws_ec2_tag" "subnet_tags":
│  52:  value = each.value
│   ├────────────────
│   │ each.value is object with 7 attributes
│
│ Inappropriate value for attribute "value": string required.

Please help me recreate my tags for the shared subnets. Thanks!

2

Answers


  1. Chosen as BEST ANSWER

    I ended up chaining locals to get what I needed. I needed a unique key for each subnet/tag map for the for_each loop. It's not an elegant solution, but it works without any major changes to my module.

    NOTE that with the same provider you'd just be duplicating tags for the same resources. I'm using a separate provider for the AWS account that I'm sharing the subnets with, hence provider = aws.dst.

    data "aws_subnets" "subnets" {
      filter {
        name   = "vpc-id"
        values = [var.vpc_id]
      }
    }
    
    data "aws_subnet" "subnet" {
      for_each = toset(data.aws_subnets.subnets.ids)
      id       = each.value
    }
    
    locals {
      subnet_tags = {
            for subnet_id, subnet in data.aws_subnet.subnet : subnet_id => {
                for tag_key, tag_value in subnet.tags : tag_key => tag_value
            }
      }
      flat_tags = flatten([
        for key, value in local.subnet_tags : [
          for tag_key, tag_value in value : {
            unique_key = "${key}-${tag_key}"
            resource_id = key
            key = tag_key
            value = tag_value
          }
        ]
      ])
    }
    
    resource "aws_ec2_tag" "test" {
      provider = aws.dst
      for_each = {
        for tag in local.flat_tags : tag.unique_key => tag
      }
    
      resource_id = each.value.resource_id
      key = each.value.key
      value = each.value.value
    }
    

  2. Your Data

    You need to understand your data before you use it …

    best you can do is use some outputs:

    data "aws_subnets" "subnets" {
      filter {
        name   = "vpc-id"
        values = [var.vpc_id]
      }
    }
    
    data "aws_subnet" "subnet" {
      for_each = toset(data.aws_subnets.subnets.ids)
      id       = each.value
    }
    
    output "loop_tags" {
        value = {
            for subnet_id, subnet in data.aws_subnet.subnet : subnet_id => {
                for tag_key, tag_value in subnet.tags : tag_key => tag_value
            }
        }
    }
    

    In my test if we do a TF plan on that we get:

      + loop_tags   = {
          + subnet-00ef25ac05236191d = {
              + abc = "123"
              + ert = "678"
            }
          + subnet-0103d668dece0ab77 = {
              + def = "345"
            }
          + subnet-045ba40ac4d5a668c = {
              + abc = "123"
            }
        }
    

    that is not something you can use in the aws_ec2_tag


    Combine Tags

    So the first thing we need to do is combine all those tags…
    I’m going to use a couple of terraform functions:

    values takes a map and returns a list containing the values of the elements in that map.

    The values are returned in lexicographical order by their corresponding keys, so the values will be returned in the same order as their keys would be returned from keys.

    merge takes an arbitrary number of maps or objects, and returns a single map or object that contains a merged set of elements from all arguments.

    If more than one given map or object defines the same key or attribute, then the one that is later in the argument sequence takes precedence. If the argument types do not match, the resulting type will be an object matching the type structure of the attributes after the merging rules have been applied.

    locals {
      combined_subnet_tags = merge(values(data.aws_subnet.subnet)[*].tags...)
    }
    

    that will give a unique list of all tags:

        {
          + abc = "123"
          + def = "345"
          + ert = "678"
        }
    

    Loops

    The next problem you have is that there is a list of subnets and also a list of unique tags, but the resource "aws_ec2_tag" can create only one at a time, so we can use a module for that:

    module "aws_ec2_tags" {
      for_each = data.aws_subnet.subnet
      source   = "./aws_ec2_tag"
    
      all_tags      = local.combined_subnet_tags
      resource_id   = each.key
      resource_tags = each.value.tags
    }
    

    that way we are looping over all subnets…

    inside that module we need to do a couple of things:

    • the subnets already have tags we don’t need to add duplicates
    • setsubtract is the function to use, but it expects parameters as specific data type so we need to do some intermediary conversions
    variable "all_tags" {
      type = map(string)
    }
    
    variable "resource_id" {
      type = string
    }
    
    variable "resource_tags" {
      type = map(string)
    }
    
    locals {
      a        = [for k, v in var.all_tags : { "${k}" : v }]
      b        = [for k, v in var.resource_tags : { "${k}" : v }]
      subtract = setsubtract(local.a, local.b)
      tags     = { for k, v in var.resource_tags : k => v }
    }
    
    resource "aws_ec2_tag" "subnet_tags" {
      for_each = local.tags
    
      resource_id = var.resource_id
      key         = each.key
      value       = each.value
    }
    

    Full Code

    https://github.com/heldersepu/hs-scripts/tree/master/TerraForm/aws_subnet_tags

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search