Я пытаюсь реализовать вложенные циклы for, используя новые функции Terraform 0.12, чтобы зацикливаться на пользователях AWS IAM, к каждому из которых может быть прикреплена одна или несколько политик. Переменная, используемая для представления этого списка, имеет тип map(list(string)) и выглядит примерно так:
{
"user 1" = [ "policy1", "policy2" ],
"user 2" = [ "policy1" ]
}
Получить список пользователей для создания достаточно просто с помощью keys(), но поскольку в настоящее время в Terraform нет механизма вложения зацикленных ресурсов, вложения политики должны выполняться как отдельный цикл, независимый от каждого пользователя. Итак, я пытаюсь создать список ассоциаций пользователя: политика из ввода карты, который будет выглядеть примерно так, основываясь на приведенном выше примере:
[
[ "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.
Что я делаю неправильно?





Выражение for в вашем локальном значении association-list создает список из списков списков строк, но ваши ссылки на него обрабатывают его как список списков строк.
Чтобы получить желаемое плоское представление, вы можете использовать функцию flatten, но, поскольку в противном случае она сгруппирует все в плоский список Один, я бы рекомендовал вместо этого сделать самое внутреннее значение объектом. (Это также сделает ссылки на него более понятными.)
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/…
Если вам нужна карта для '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
Я думаю, что это лучший дизайн, чем я изначально предполагал, спасибо.