Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terraform failing with Invalid for_each argument / The given "for_each" argument value is unsuitable

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}"
}
like image 826
Falk Tandetzky Avatar asked Jun 08 '20 14:06

Falk Tandetzky


1 Answers

Explanation

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.

Solution

The resolution depends on the situation.

List of strings

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}"
}

List that can be rearranged to a map

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."
}

List that you do not want to rearrange

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.

like image 120
Falk Tandetzky Avatar answered Oct 16 '22 23:10

Falk Tandetzky