Is for_each the only way to retrieve values from a Terraform set?
Terraform recently introduced the set
datatype, described on this page as:
set(...)
: a collection of unique values that do not have any secondary identifiers or ordering.
It is difficult to find documentation on how to retrieve values from Terraform sets. With a map, you can index on the key:
password = var.passwords["kevin"]
With a list, you can index on the element number:
a_record = var.records[1]
However, I cannot use either of those methods to retrieve values from a set, even a set with only a single item.
In other places, the documentation mentions for_each
as a method to get values out of a set.
variable "subnet_ids" {
type = list(string)
}
resource "aws_instance" "server" {
for_each = toset(var.subnet_ids)
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set
tags = {
Name = "Server ${each.key}"
}
}
Is the for_each
meta variable the only way to access the values in a set?
You can also slice a set by first casting it to a list and then accessing it as a list.
As an example:
variable "set" {
type = set(string)
default = [
"foo",
"bar",
]
}
output "set" {
value = var.set
}
output "set_first_element" {
value = var.set[0]
}
This will error because sets can't be directly accessed by an index:
Error: Invalid index
on main.tf line 14, in output "set_first_element":
14: value = var.set[0]
This value does not have any indices.
If you instead cast it with the tolist
function then you can access it as expected:
output "set_to_list_first_element" {
value = tolist(var.set)[0]
}
Outputs:
set = [
"bar",
"foo",
]
set_to_list_first_element = bar
Note that this returns bar
instead of foo
. Strictly speaking sets are unordered in Terraform so you can't rely on the order at all and are only consistent during a single run of Terraform but in practice they are stable and in the the case of set(string)
they are sorted in lexicographical order:
When a set is converted to a list or tuple, the elements will be in an arbitrary order. If the set's elements were strings, they will be in lexicographical order; sets of other element types do not guarantee any particular order of elements.
The main place this comes up is if you are dealing with a resource or data source that returns a set type in Terraform 0.12 but need to use a singular value. A basic example might be something like this:
data "aws_subnet_ids" "private" {
vpc_id = var.vpc_id
tags = {
Tier = "Private"
}
}
resource "aws_instance" "app" {
ami = var.ami
instance_type = "t2.micro"
subnet_id = tolist(data.aws_subnet_ids.example.ids)[0]
}
This would create an EC2 instance in a subnet tagged with Tier = Private
but set no other constraint on where it should be.
While you referenced being able to access values with for_each
you can also loop through a set with a for
expression:
variable "set_of_objects" {
type = set(object({
port = number
service = string
}))
default = [
{
port = 22
service = "ssh"
},
{
port = 80
service = "http"
},
]
}
output "set_of_objects" {
value = var.set_of_objects
}
output "list_comprehension_over_set" {
value = [ for obj in var.set_of_objects : upper(obj.service) ]
}
This then outputs the following:
list_comprehension_over_set = [
"SSH",
"HTTP",
]
set_of_objects = [
{
"port" = 22
"service" = "ssh"
},
{
"port" = 80
"service" = "http"
},
]