• Выражение for_each для циклического перебора ресурсов и их вложенных блоков.
• Выражение for для циклического перебора списков и ассоциативных массивов.
• Строковая директива for для циклического перебора списков и ассоциативных массивов внутри строк.
Рассмотрим их одну за другой.
Циклы с параметром count
В главе 2 с помощью консоли AWS вы создали учетную запись AWS и пользователя Access Management (IAM). Теперь с помощью этого пользователя вы можете создавать и администрировать всех будущих пользователей IAM прямо в коде Terraform. Рассмотрим следующий код, который должен находиться в файле live/global/iam/main.tf:
provider "aws" {
region = "us-east-2"
}
resource "aws_iam_user" "example" {
name = "neo"
}
Здесь используется ресурс aws_iam_user для создания одного нового пользователя IAM. Но если необходимо создать трех пользователей? В языке программирования общего назначения вы бы применили цикл for:
# Это просто псевдокод. Он не будет работать в Terraform.
for (i = 0; i < 3; i++) {
resource "aws_iam_user" "example" {
name = "neo"
}
}
В языке Terraform нет встроенной поддержки циклов for и другой традиционной процедурной логики, поэтому такой синтаксис работать не будет. Однако у каждого ресурса Terraform есть метапараметр под названием count. Это самая старая, простая ограниченная разновидность итератора в Terraform: она просто определяет, сколько копий ресурса нужно создать. Вот как с помощью этого параметра создать трех пользователей IAM:
resource "aws_iam_user" "example" {
count = 3
name = "neo"
}
У этого кода есть одна проблема: у всех трех пользователей IAM будет одно и то же имя. Это приведет к ошибке, так как имена пользователей должны быть уникальными. Если бы у вас был доступ к стандартному циклу for, вы могли бы использовать индекс i, чтобы изменить каждое имя:
# Это просто псевдокод. Он не будет работать в Terraform.
for (i = 0; i < 3; i++) {
resource "aws_iam_user" "example" {
name = "neo.${i}"
}
}
Чтобы добиться того же в Terraform и получить индекс каждой итерации в цикле, можно воспользоваться ссылкой count.index:
resource "aws_iam_user" "example" {
count = 3
name = "neo.${count.index}"
}
Если выполнить команду plan для представленного выше кода, можно увидеть, что Terraform собирается создать трех пользователей IAM с разными именами ("neo.0", "neo.1", "neo.2"):
Terraform will perform the following actions:
# aws_iam_user.example[0] will be created
+ resource "aws_iam_user" "example" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "neo.0"
+ path = "/"
+ unique_id = (known after apply)
}
# aws_iam_user.example[1] will be created
+ resource "aws_iam_user" "example" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "neo.1"
+ path = "/"
+ unique_id = (known after apply)
}
# aws_iam_user.example[2] will be created
+ resource "aws_iam_user" "example" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "neo.2"
+ path = "/"
+ unique_id = (known after apply)
}
Plan: 3 to add, 0 to change, 0 to destroy.
Конечно, такое имя, как "neo.0", будет не очень полезным. Но если совместить count.index с некоторыми встроенными в Terraform функциями, каждую итерацию этого цикла можно изменить еще сильнее.
Например, все нужные вам имена пользователей IAM можно перечислить во входной переменной внутри live/global/iam/variables.tf:
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
В языке программирования общего назначения с циклами и массивами вы бы назначили каждому пользователю IAM отдельное имя путем поиска значений в массиве var.user_names по индексу i:
# Это просто псевдокод. Он не будет работать в Terraform.
for (i = 0; i < 3; i++) {
resource "aws_iam_user" "example" {
name = vars.user_names[i]
}
}
В Terraform то же самое можно сделать с помощью count в сочетании:
• с синтаксисом доступа к массиву по индексу, который похож на синтаксис большинства других языков:
ARRAY[
Например, вот как взять из массива var.user_names элемент с индексом 1:
var.user_names[1]
• с функцией length. У Terraform есть встроенная функция под названием length, которая имеет следующий синтаксис:
length(
Как вы уже догадались, функция length возвращает количество элементов в заданном массиве. Она также работает со строками и ассоциативными массивами.
Если все это объединить, получится следующее:
resource "aws_iam_user" "example" {
count = length(var.user_names)
name = var.user_names[count.index]
}
Теперь, если выполнить команду plan, можно увидеть, что Terraform собирается создать трех пользователей IAM с уникальными именами:
Terraform will perform the following actions:
# aws_iam_user.example[0] will be created
+ resource "aws_iam_user" "example" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "neo"
+ path = "/"
+ unique_id = (known after apply)
}
# aws_iam_user.example[1] will be created
+ resource "aws_iam_user" "example" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "trinity"
+ path = "/"
+ unique_id = (known after apply)
}
# aws_iam_user.example[2] will be created
+ resource "aws_iam_user" "example" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "morpheus"
+ path = "/"
+ unique_id = (known after apply)
}
Plan: 3 to add, 0 to change, 0 to destroy.
Обратите внимание: если в ресурсе используется параметр count, он превращается в массив ресурсов. Поскольку aws_iam_user.example теперь является массивом пользователей IAM, вместо стандартного синтаксиса для чтения атрибутов (
Например, если вы хотите предоставить в качестве выходной переменной ARN одного из пользователей IAM, нужно сделать следующее:
output "neo_arn" {
value = aws_iam_user.example[0].arn
description = "The ARN for user Neo"
}
Если вам нужны ARN всех пользователей IAM, вы должны указать символ * вместо индекса:
output "all_arns" {
value = aws_iam_user.example[*].arn
description = "The ARNs for all users"
}
Если выполнить команду apply, вывод neo_arn будет содержать только ARN пользователя Neo, тогда как all_arns выведет список всех ARN:
$ terraform apply
(...)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
neo_arn = arn:aws:iam::123456789012:user/neo
all_arns = [
"arn:aws:iam::123456789012:user/neo",
"arn:aws:iam::123456789012:user/trinity",
"arn:aws:iam::123456789012:user/morpheus",
]
К сожалению, у параметра count есть два ограничения, которые делают его куда менее полезным. Во-первых, с помощью count можно пройтись по всему ресурсу, но при этом нельзя перебирать его вложенные блоки. Вложенный блок — это аргумент, который устанавливается внутри ресурса в следующем формате:
resource "xxx" "yyy" {
[CONFIG...]
}
}
NAME — это имя вложенного блока (например, tag), а CONFIG состоит из одного или нескольких аргументов, предназначенных специально для него (вроде key и value). Посмотрите, как устанавливаются теги в ресурсе aws_autoscaling_group:
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnet_ids.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
}
Каждый тег требует создания нового вложенного блока со значениями для key, value и propagate_at_launch. В предыдущем листинге вручную указан единственный тег, но можно разрешить пользователям передавать собственные. У вас может появиться соблазн воспользоваться параметром count для циклического перебора этих тегов и генерации динамических вложенных блоков tag, но, к сожалению, применение count внутри вложенного блока не поддерживается.
Второе ограничение параметра count даст о себе знать, когда вы попытаетесь его изменить. Рассмотрим созданный ранее список пользователей IAM:
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["neo", "trinity", "morpheus"]