Terraform 因无效的 for_each 参数而失败/给定的“for_each”参数值不合适

Fal*_*zky 12 foreach amazon-web-services terraform

运行时terraform planterraform apply使用提供给for_each错误的列表时,说

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.
Run Code Online (Sandbox Code Playgroud)

重现此错误的最小示例如下:

resource "aws_ssm_parameter" "foo" {
  for_each = ["a", "b"]

  name  = "foo-${each.value}"
  type  = "String"
  value = "bar-${each.value}"
}
Run Code Online (Sandbox Code Playgroud)

Fal*_*zky 29

解释

此错误通常是由将列表传递给 引起的for_each,但for_each仅适用于无序数据类型,即集合和映射。

解决方案

分辨率取决于情况。

字符串列表

如果列表只是一个字符串列表,最简单的解决方法是添加一个toset()-call 将列表转换为可由 for_each 处理的集合,如下所示

resource "aws_ssm_parameter" "foo" {
  for_each = toset(["a", "b"])

  name  = "foo-${each.value}"
  type  = "String"
  value = "bar-${each.value}"
}
Run Code Online (Sandbox Code Playgroud)

可以重新排列为地图的列表

如果输入是一个列表,但很容易重新排列成一个地图,这通常是最好的方法。假设我们有一个这样的列表

locals {
  animals = [
    {
      name = "Bello"
      age = 3
      type = "dog"
    },
    {
      name = "Minga"
      age = 4
      type = "cat"
    },
  ]
}
Run Code Online (Sandbox Code Playgroud)

那么适当的重组可能是这样的

locals {
  animals = {
    Bello : {
      age = 3
      type = "dog"
    },
    Minga : {
      age = 4
      type = "cat"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后允许您定义

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."
}
Run Code Online (Sandbox Code Playgroud)

您不想重新排列的列表

有时自然会有一个列表,例如来自一个不受控制的模块的输出或来自一个用 定义的资源count。在这种情况下,您可以像这样使用计数

resource "aws_ssm_parameter" "foo" {
  count = length(local.my_list)

  name  = my_list[count.index].name
  type  = "String"
  value = my_list[count.index].value
}
Run Code Online (Sandbox Code Playgroud)

它适用于包含名称和值作为键的映射列表。但是,很多时候,将列表转换为地图更合适,而不是像这样

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
}
Run Code Online (Sandbox Code Playgroud)

这里应该选择任何合适的东西来代替x.id. 如果my_list是一个对象列表,通常有一些可以使用的公共字段,如名称或键。这种方法有利于count如上使用的优点是,在从列表中插入或删除元素时,这表现得更好。count不会注意到插入或删除,因此将更新插入发生位置之后的所有资源,而for_each实际上只添加或删除具有新 ID 或已删除 ID 的资源。

  • 它真的与有序数据类型与无序数据类型有关吗?我一直认为列表根本不可能,因为列表中可能存在重复元素(而集合只能具有唯一值),这可能会导致多个资源具有完全相同的名称(这在 terraform 中是不允许的)。 (3认同)
  • 我要纠正的是,该错误不是“由于将列表传递给 for_each 而引起的”,而是因为 `for_each` 仅适用于无序数据类型。列表是有序类型,而集合或映射则不是。 (2认同)
  • 我同意; 这很奇怪。如果该语言能够迭代无序集合,那么到底有什么可能阻止对有序集合的迭代呢?Terraform 不符合最小惊讶法则。 (2认同)