【问题标题】:Terraform 0.12 nested for loopsTerraform 0.12 嵌套 for 循环
【发布时间】:2019-09-26 13:47:48
【问题描述】:

我正在尝试使用 Terraform 0.12 的新功能实现嵌套 for 循环,以便遍历 AWS IAM 用户,每个用户都可以附加一个或多个策略。用来表示这个列表的变量是 map(list(string)) 类型的,看起来像这样:

{
  "user 1" = [ "policy1", "policy2" ],
  "user 2" = [ "policy1" ]
}

通过keys() 获取要创建的用户列表很容易,但由于目前在 Terraform 中没有嵌套循环资源创建的机制,因此策略附件必须作为独立于每个用户的单个循环发生。因此,我正在尝试从地图输入中构建一个 user:policy 关联列表,根据上面的示例,它看起来像这样:

[
  [ "user1", "policy1" ],
  [ "user1", "policy2" ],
  [ "user2", "policy1" ]
]

我正在尝试构造该列表并将其存储在像这样的局部变量中,其中var.iam-user-policy-map 是输入映射:

locals {
  ...
  association-list = [
    for user in keys(var.iam-user-policy-map):
    [
      for policy in var.iam-user-policy-map[user]:
      [user, policy]
    ]
  ]
  ...
}

但是,我在尝试访问该嵌套列表中的值时遇到错误。我正在尝试使用引用 local.association-list[count.index][0] 和使用 local.association-list[count.index][1] 的策略访问关联的用户部分,但是在运行 terraform plan 时会出错:

Error: Incorrect attribute value type

  on main.tf line 27, in resource "aws_iam_user_policy_attachment" "test-attach":
  27:   user = local.association-list[count.index][0]

Inappropriate value for attribute "user": string required.


Error: Incorrect attribute value type

  on main.tf line 27, in resource "aws_iam_user_policy_attachment" "test-attach":
  27:   user = local.association-list[count.index][0]

Inappropriate value for attribute "user": string required.


Error: Invalid index

  on main.tf line 28, in resource "aws_iam_user_policy_attachment" "test-attach":
  28:   policy_arn = "arn:aws-us-gov:iam::aws:policy/${local.association-list[count.index][1]}"
    |----------------
    | count.index is 0
    | local.association-list is tuple with 2 elements

The given key does not identify an element in this collection value.


Error: Invalid template interpolation value

  on main.tf line 28, in resource "aws_iam_user_policy_attachment" "test-attach":
  28:   policy_arn = "arn:aws-us-gov:iam::aws:policy/${local.association-list[count.index][1]}"
    |----------------
    | count.index is 1
    | local.association-list is tuple with 2 elements

Cannot include the given value in a string template: string required.

我做错了什么?

【问题讨论】:

    标签: nested-loops terraform


    【解决方案1】:

    本地值 association-list 中的 for 表达式正在生成字符串列表列表,但您对它的引用将其视为字符串列表列表。

    要获得您想要的扁平化表示,您可以使用 flatten 函数,但因为它会将所有内容分组到一个 single 扁平列表中,我建议将最里面的值改为对象. (这也将使对它的引用更加清晰。)

    locals {
      association-list = flatten([
        for user in keys(var.iam-user-policy-map) : [
          for policy in var.iam-user-policy-map[user] : {
            user   = user
            policy = policy
          }
        ]
      ])
    }
    

    此表达式的结果将具有以下形状:

    [
      { user = "user1", policy = "policy1" },
      { user = "user1", policy = "policy2" },
      { user = "user2", policy = "policy2" },
    ]
    

    您对它的引用可以采用以下形式:

    user = local.association-list[count.index].user
    
    policy_arn = "arn:aws-us-gov:iam::aws:policy/${local.association-list[count.index].policy}"
    

    【讨论】:

    • 我认为这是一个比我最初设想的更好的设计,谢谢。
    • 这里还有另一个问题,即数字引用的元素会受到列表中项目更改的影响。如果您有多个策略附件并且您更改了其中一个,则 terraform 将显示您未更改的其他策略附件的更改,因为它会重新排序列表。在模块中使用 for_each 会导致关键引用元素,例如module.iam-policy-attachments.aws_iam_role_policy_attachment.role_policy["policy_name"] 而不是 module.iam-policy-attachments.aws_iam_role_policy_attachment.role_policy[1],因此单个政策更改不会影响其他人
    • 这里有很好的解释blog.gruntwork.io/…
    • 官方文档中有一个非常有用的示例,它解释了如何使用它,然后从中创建一个可以与for_each 一起使用的地图:terraform.io/docs/language/functions/…
    【解决方案2】:

    如果你需要 'for_each' 的映射,'merge' 很方便。

    variable "iam-user-policy-map" {
      default = {
        "user 1" = ["policy1", "policy2"],
        "user 2" = ["policy1"]
      }
    }
    
    locals {
      association-map = merge([
        for user, policies in var.iam-user-policy-map : {
          for policy in policies :
            "${user}-${policy}" => {
              "user"   = user
              "policy" = policy
            }
        }
      ]...)
    }
    
    output "association-map" {
      value = local.association-map
    }
    

    输出:

    association-map = {
      "user 1-policy1" = {
        "policy" = "policy1"
        "user" = "user 1"
      }
      "user 1-policy2" = {
        "policy" = "policy2"
        "user" = "user 1"
      }
      "user 2-policy1" = {
        "policy" = "policy1"
        "user" = "user 2"
      }
    }
    

    for_each 用法示例:

    resource "null_resource" "echo" {
      for_each = local.association-map
      provisioner "local-exec" {
        command = "echo 'policy - ${each.value.policy}, user - ${each.value.user}'"
      }
    }
    

    https://github.com/hashicorp/terraform/issues/22263#issuecomment-969549347

    【讨论】:

      猜你喜欢
      • 2021-07-08
      • 1970-01-01
      • 2021-12-29
      • 2022-01-11
      • 2020-12-03
      • 1970-01-01
      • 1970-01-01
      • 2021-01-27
      相关资源
      最近更新 更多