ERROR referencing attribute for a ec_deployment_traffic_filter resource created with for_each

Hello community.

Goal
I want to output all the ids from a resource created with for_each.

Problem
When I output the ec_deployment_traffic_filter with [ * ], it shows the resources created. No problem. But when I output [ * ].id or [ * ].name, I receive the error This object does not have an attribute named "name"

Help
So I have to main questions:

  • What I am doing wroing?
  • What is the best way to iterate and show all the ids?

The Code
This works ok, it the output shows me the 3 traffic filters created.

variable "elastic_traffic_filters" {

  type = set(string)
  description = "Names and VPCs for configuring the Elastic Cloud traffic filters"
  default = [ "vpce-123", "vpce-456", "vpc-789" ]

}

resource "ec_deployment_traffic_filter" "this" {
  for_each = var.elastic_traffic_filters
  name   = "Allow traffic from ${replace(each.key, " ", "_")}"
  region = var.aws_region
  type   = "vpce"

  rule {
    source = "${each.value}"
  }

  depends_on = [aws_vpc_endpoint.vpce]
}

output "traffic_filters" {
  value = ec_deployment_traffic_filter.this    <<<< ----------
}

---- OK: RESULT AS EXPECTED ------
traffic_filters             = {
      + vpce-123 = {
          + description        = null
          + id                 = (known after apply)
          + include_by_default = false
          + name               = "Allow traffic from vpce-123"
          + region             = "us-east-1"
          + rule               = [
              + {
                  + azure_endpoint_guid = ""
                  + azure_endpoint_name = ""
                  + description         = ""
                  + id                  = (known after apply)
                  + source              = "vpce-123"
                },
            ]
          + timeouts           = null
          + type               = "vpce"
        }
      + vpce-456 = {
          + description        = null
          + id                 = (known after apply)
          + include_by_default = false
          + name               = "Allow traffic from vpce-456"
          + region             = "us-east-1"
          + rule               = [
              + {
                  + azure_endpoint_guid = ""
                  + azure_endpoint_name = ""
                  + description         = ""
                  + id                  = (known after apply)
                  + source              = "vpce-456"
                },
            ]
          + timeouts           = null
          + type               = "vpce"
        }
      + vpce-789 = {
          + description        = null
          + id                 = (known after apply)
          + include_by_default = false
          + name               = "Allow traffic from vpce-789"
          + region             = "us-east-1"
          + rule               = [
              + {
                  + azure_endpoint_guid = ""
                  + azure_endpoint_name = ""
                  + description         = ""
                  + id                  = (known after apply)
                  + source              = "vpce-789"
                },
            ]
          + timeouts           = null
          + type               = "vpce"
        }
    }

However, if I modify the output to print me the attributes, I have error.

variable "elastic_traffic_filters" {

  type = set(string)
  description = "Names and VPCs for configuring the Elastic Cloud traffic filters"
  default = [ "vpce-123", "vpce-456", "vpc-789" ]

}

resource "ec_deployment_traffic_filter" "this" {
  for_each = var.elastic_traffic_filters
  name   = "Allow traffic from ${replace(each.key, " ", "_")}"
  region = var.aws_region
  type   = "vpce"

  rule {
    source = "${each.value}"
  }

  depends_on = [aws_vpc_endpoint.vpce]
}

output "traffic_filters" {
  value = ec_deployment_traffic_filter.this[*].id    <<<< ----------
}

---- ERROR ------
Error: Unsupported attribute
│ 
│   on outputs.tf line 19, in output "traffic_filters":
│   19:   value = module.ec_cluster.traffic_filters[*].id
│ 
│ This object does not have an attribute named "id".

Finally, If I use this other output, it works fine too.

variable "elastic_traffic_filters" {

  type = set(string)
  description = "Names and VPCs for configuring the Elastic Cloud traffic filters"
  default = [ "vpce-123", "vpce-456", "vpc-789" ]

}

resource "ec_deployment_traffic_filter" "this" {
  for_each = var.elastic_traffic_filters
  name   = "Allow traffic from ${replace(each.key, " ", "_")}"
  region = var.aws_region
  type   = "vpce"

  rule {
    source = "${each.value}"
  }

  depends_on = [aws_vpc_endpoint.vpce]
}

output "traffic_filters" {
  value = ec_deployment_traffic_filter.this["vpc-123"].id    <<<< ----------
}

---- OK: RESULT AS EXPECTED ------
Changes to Outputs:
  + traffic_filters             = (known after apply)

Thanks for the help!

Hi @Sarony11,

I think the problem here is that you are trying to use the splat operator [*] with a map. The splat operator is for list (and list-like) values, as described in Splat Expressions with Maps:

The splat expression patterns shown above apply only to lists, sets, and tuples. To get a similar result with a map or object value you must use for expressions.

Resources that use the for_each argument will appear in expressions as a map of objects, so you can’t use splat expressions with those resources. For more information, see Referring to Resource Instances.

This documentation section isn’t telling the full story about why you got exactly that error about an attribute named “name”. The rest of the story is in the subsequent section Single Values as Lists, which (despite the slightly-too-specific heading) describes the behavior for any type that isn’t a list, set, or tuple type.

Specifically, ec_deployment_traffic_filter.this[*] (without the .id suffix) wraps your map of instances in a single-element list, something like this:

[
  {
    "vpce-123": {
      id = "..."
      # (and all of the other attributes)
    },
    "vpce-456": {
      id = "..."
      # (and all of the other attributes)
    },
    "vpce-789": {
      id = "..."
      # (and all of the other attributes)
    },
  }
]

This list doesn’t have an attribute named .id because lists don’t have any attributes at all! But that’s a distraction here because of course you didn’t actually intend to construct a list, so I can understand why you didn’t find this error message helpful.

As the “Splat Expressions with Maps” section noted, to project a map value like what for_each produces you need to use for expression instead. There are two different options for this depending on what type you want to return to your module’s caller:

output "traffic_filters" {
  value = tomap({
    for k, v in ec_deployment_traffic_filter.this : k => v.id
  })
}

This option returns a map from instance keys to traffic filter IDs. This approach is a good choice if there’s any chance that the caller of your module will need to find the ID of a specific traffic filter based on the names given in var.elastic_traffic_filters, since they’d be able to access e.g. module.example.traffic_filters["vpce-123"] to get the ID of that specific traffic filter.

If you want to prevent the caller from associating individual IDs with individual VPCs – for example, if it’s only appropriate to use the IDs all together at once – then the other variant is to return just a set of the IDs and discard the instance keys:

output "traffic_filters" {
  value = toset([
    for v in ec_deployment_traffic_filter.this : v.id
  ])
}

This option returns a set of ids without any information about which ID belongs to which specific object. The caller can only use these all together as a fungible set.

Notice the subtle differences between these two examples:

  • The first one uses a for expression with braces { for ... }, which means it constructs an object value. The wrapping tomap then converts that to a map, because that’s the more appropriate data type here where the set of keys isn’t fixed.
  • The second one uses a for expression with brackets [ for ... ], which means it constructs a tuple value. The wrapping toset then converts it to a set, which is the more appropriate data type here because the order of these entries is meaningless (it’s just an alphabetical sort by key) and the set data type makes it clear that the caller cannot rely on the order.

Beautiful! Thanks a lot for the help!.

After trying many things I had a mess in my brain. Now I understand although is true that the error message did not help me to find the proper path.

Anyway, thanks not only for your solution but for your amazing explanation to understand whats going on.

Problem most than solved :). Regards!