【问题标题】:Terraform failing with Invalid for_each argument / The given "for_each" argument value is unsuitableTerraform 因 for_each 参数无效/给定的“for_each”参数值不合适而失败
【发布时间】:2020-09-27 12:47:54
【问题描述】:

当运行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.

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

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

  name  = "foo-${each.value}"
  type  = "String"
  value = "bar-${each.value}"
}

【问题讨论】:

    标签: amazon-web-services foreach terraform


    【解决方案1】:

    说明

    此错误通常是由将列表传递给 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}"
    }
    

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

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

    locals {
      animals = [
        {
          name = "Bello"
          age = 3
          type = "dog"
        },
        {
          name = "Minga"
          age = 4
          type = "cat"
        },
      ]
    }
    

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

    locals {
      animals = {
        Bello : {
          age = 3
          type = "dog"
        },
        Minga : {
          age = 4
          type = "cat"
        }
      }
    }
    

    然后允许您定义

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

    不想重新排列的列表

    有时有一个列表是很自然的,例如来自不受控制的模块的输出或来自使用count 定义的资源。在这种情况下,可以像这样使用 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
    }
    

    适用于包含名称和值作为键的映射列表。但是,通常情况下,将列表转换为地图更合适,而不是像这样

    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
    }
    

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

    【讨论】:

    • 我会更正错误不是“由将列表传递给 for_each 引起的”,而是因为 for_each 仅适用于无序数据类型。列表是有序类型,集合或映射不是。
    • 这很奇怪,您可以在动态块内列出地图列表,但如果您在外部使用 for_each 则会出现错误:(
    • 我同意;这很奇怪。如果该语言能够迭代无序集合,那么世界上有什么可能阻止对有序集合的迭代呢? Terraform 不符合最小惊讶法则。
    猜你喜欢
    • 2021-05-09
    • 2022-08-19
    • 2021-03-31
    • 2020-06-04
    • 2021-09-22
    • 2014-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多