When running terraform plan
or terraform apply
with a list provided to for_each
an error occurs saying
Error: Invalid for_each argument
on main.tf line 2, in resource "aws_ssm_parameter" "foo":
2: for_each = ["a", "b"]
The given "for_each" argument value is unsuitable: the "for_each" argument
must be a map, or set of strings, and you have provided a value of type tuple.
A minimum example to reproduce this error is this:
resource "aws_ssm_parameter" "foo" {
for_each = ["a", "b"]
name = "foo-${each.value}"
type = "String"
value = "bar-${each.value}"
}
This error is often caused by passing a list to for_each
, but for_each
only works with unordered data-types, i.e. with sets and maps.
The resolution depends on the situation.
If the list is just a list of strings, the easiest fix is to add a toset()
-call to transform the list to a set that can be handled by for_each, like this
resource "aws_ssm_parameter" "foo" {
for_each = toset(["a", "b"])
name = "foo-${each.value}"
type = "String"
value = "bar-${each.value}"
}
If the input is a list, but easily be rearranged to a map this is usually the best way. Say we have a list like this
locals {
animals = [
{
name = "Bello"
age = 3
type = "dog"
},
{
name = "Minga"
age = 4
type = "cat"
},
]
}
Then an appropriate re-structuring might be this
locals {
animals = {
Bello : {
age = 3
type = "dog"
},
Minga : {
age = 4
type = "cat"
}
}
}
which then allows you to define
resource "aws_ssm_parameter" "foo" {
for_each = local.animals
name = each.key
type = string
value = "This is a ${each.value.type}, ${each.value.age} years old."
}
Sometimes it is natural to have a list, e.g. comming from an output of a module that one does not control or from a resource that is defined with count
. In such a situation, one can either work with count like this
resource "aws_ssm_parameter" "foo" {
count = length(local.my_list)
name = my_list[count.index].name
type = "String"
value = my_list[count.index].value
}
which works for a list of maps containing name and value as keys. Often times, though, it is more appropriate to transform the list to a map instead like this
resource "aws_ssm_parameter" "foo" {
for_each = { for x in local.my_list: x.id => x }
name = each.value.name
type = "String"
value = each.value.value
}
Here one should choose anything appropriate in place of x.id
. If my_list
is a list of objects, there is usually some common field like a name or key, that can be used. The advantage of this approach in favor of using count
as above, is that this behaves better when inserting or removing elements from the list. count
will not notice the insertion or deletion as such and will hence update all resources following the place where the insertion took place, while for_each
really only adds or removes the resource with the new or deleted id.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With