Метааргумент for_each
По умолчанию блок resource настраивает один реальный объект инфраструктуры (аналогично, блок module включает содержимое дочернего модуля в конфигурацию один раз). Однако иногда требуется управлять несколькими похожими объектами (например, фиксированным пулом вычислительных экземпляров), не создавая отдельный блок для каждого из них. OpenTofu предлагает два способа сделать это: count и for_each.
Если блок resource или module содержит аргумент for_each, значением которого является map или set строк, OpenTofu создает по одному экземпляру для каждого элемента этой map или set.
В одном блоке resource или module нельзя использовать одновременно count и for_each.
Базовый синтаксис
for_each — это метааргумент, определенный языком OpenTofu. Его можно использовать с модулями и ресурсами любого типа.
Метааргумент for_each принимает map или set строк и создает экземпляр для каждого элемента этой map или set. С каждым экземпляром связан отдельный объект инфраструктуры; при применении конфигурации каждый из них создается, обновляется или удаляется независимо.
Map:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}Set строк:
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}Дочерний модуль:
# my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}# publish_bucket/bucket-and-cloudfront.tf
variable "name" {} # this is the input parameter of the module
resource "aws_s3_bucket" "example" {
# Because var.name includes each.key in the calling
# module block, its value will be different for
# each instance of this module.
bucket = var.name
# ...
}
resource "aws_iam_user" "deploy_user" {
# ...
}Объект each
В блоках, где задан for_each, в выражениях становится доступен дополнительный объект each, позволяющий изменять конфигурацию каждого экземпляра. У этого объекта есть два атрибута:
-
each.key— ключ map (или элемент set), соответствующий данному экземпляру. -
each.value— значение map, соответствующее данному экземпляру. (Если задан set, это значение совпадает сeach.key.)
Ограничения на значения, используемые в for_each
Ключи map (или все значения в случае set строк) должны быть известными значениями, иначе появится сообщение об ошибке: зависимости for_each невозможно определить до применения конфигурации, поэтому может потребоваться -target/-exclude.
Ключи for_each не могут быть результатом нечистых функций или зависеть от их результата. К таким функциям относятся uuid, bcrypt и timestamp, поскольку их вычисление откладывается на этапе основного вычисления.
Конфиденциальные значения, например конфиденциальные входные переменные, конфиденциальные выходные значения или конфиденциальные атрибуты ресурсов, нельзя использовать в качестве аргументов for_each. Значение, используемое в for_each, служит для идентификации экземпляра ресурса и всегда отображается в пользовательском интерфейсе, поэтому конфиденциальные значения не разрешены. Попытка использовать конфиденциальные значения в качестве аргументов for_each приведет к ошибке.
Если вы преобразуете значение, содержащее конфиденциальные данные, в аргумент для for_each, помните, что большинство функций OpenTofu возвращают конфиденциальный результат, если хотя бы один из переданных им аргументов содержит конфиденциальные данные. Во многих случаях аналогичного результата можно добиться с помощью выражения for вместо функции. Например, если нужно вызвать keys(local.map), где local.map — объект с конфиденциальными значениями (но неконфиденциальными ключами), можно создать значение для передачи в for_each с помощью toset([for k,v in local.map : k]).
Использование выражений в for_each
Метааргумент for_each принимает map или set выражений. Однако, в отличие от большинства аргументов, значение for_each должно быть известно до выполнения OpenTofu каких-либо действий с удаленными ресурсами. Это означает, что for_each не может ссылаться на атрибуты ресурсов, значения которых становятся известны только после применения конфигурации (например, уникальный идентификатор, созданный удаленным API при создании объекта).
Значение for_each должно представлять собой map или set, содержащий по одному элементу на каждый требуемый экземпляр ресурса. Чтобы использовать последовательность в качестве значения for_each, необходимо применить выражение, явно возвращающее значение типа set, например функцию toset. Чтобы избежать неожиданных результатов преобразования, аргумент for_each не преобразует списки и кортежи в множества неявно. Если требуется объявить экземпляры ресурсов на основе вложенной структуры данных или комбинаций элементов из нескольких структур данных, можно использовать выражения и функции OpenTofu для получения подходящего значения. Например:
- Преобразуйте многоуровневую вложенную структуру в плоский список, используя вложенные выражения
forвместе с функциейflatten. - Создайте полный список комбинаций элементов из двух или более коллекций, используя функцию
setproductв выраженииfor.
Цепочка for_each между ресурсами
Поскольку ресурс, использующий for_each, при использовании в выражениях представляется как map объектов, в ситуациях, когда между двумя наборами объектов существует взаимно-однозначное соответствие, можно напрямую использовать один ресурс в качестве for_each другого.
Например, в AWS с объектом aws_vpc обычно связано несколько других объектов, предоставляющих дополнительные службы для этой VPC, например «шлюз Интернета». Если вы объявляете несколько экземпляров VPC с помощью for_each, можно связать этот for_each с другим ресурсом, чтобы объявить шлюз Интернета для каждой VPC:
variable "vpcs" {
type = map(object({
cidr_block = string
}))
}
resource "aws_vpc" "example" {
# One VPC for each element of var.vpcs
for_each = var.vpcs
# each.value here is a value from var.vpcs
cidr_block = each.value.cidr_block
}
resource "aws_internet_gateway" "example" {
# One Internet Gateway per VPC
for_each = aws_vpc.example
# each.value here is a full aws_vpc object
vpc_id = each.value.id
}
output "vpc_ids" {
value = {
for k, v in aws_vpc.example : k => v.id
}
# The VPCs aren't fully functional until their
# internet gateways are running.
depends_on = [aws_internet_gateway.example]
}Этот шаблон с цепочкой явно и лаконично описывает связь между экземплярами шлюзов Интернета и экземплярами VPC. Благодаря этому OpenTofu предполагает, что ключи экземпляров обоих типов всегда будут меняться синхронно. Как правило, такая конфигурация также упрощает понимание и сопровождение кода.
Ссылки на экземпляры
Если задан for_each, OpenTofu различает сам блок и связанные с ним несколько экземпляров ресурса или модуля. Экземпляры идентифицируются ключом map (или элементом set) из значения, указанного для for_each.
-
<TYPE>.<NAME>илиmodule.<NAME>(например,azurerm_resource_group.rg) обозначает блок. -
<TYPE>.<NAME>[<KEY>]илиmodule.<NAME>[<KEY>](например,azurerm_resource_group.rg["a_group"],azurerm_resource_group.rg["another_group"]и т. д.) обозначает отдельные экземпляры.
Это отличается от ресурсов и модулей без count или for_each, на которые можно ссылаться без индекса или ключа.
Аналогично, ресурсы дочерних модулей с несколькими экземплярами в выводе плана и других разделах интерфейса предваряются module.<NAME>[<KEY>]. Для модуля без count или for_each адрес не будет содержать индекс модуля, поскольку для ссылки на него достаточно имени модуля.
Во вложенных блоках provisioner или connection специальный объект self обозначает текущий экземпляр ресурса, а не блок ресурса целиком.
Использование множеств
В языке OpenTofu нет литерального синтаксиса для значений типа set, но с помощью функции toset можно явно преобразовать список строк в set:
locals {
subnet_ids = toset([
"subnet-abcdef",
"subnet-012345",
])
}
resource "aws_instance" "server" {
for_each = local.subnet_ids
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set
tags = {
Name = "Server ${each.key}"
}
}При преобразовании списка в set порядок элементов списка теряется, а повторяющиеся элементы удаляются. toset(["b", "a", "b"]) создаст set, содержащий только "a" и "b", без определенного порядка; второй "b" будет отброшен.
Если вы пишете модуль с входной переменной, которая будет использоваться как set строк для for_each, можно задать для нее тип set(string), чтобы не выполнять явное преобразование типа:
variable "subnet_ids" {
type = set(string)
}
resource "aws_instance" "server" {
for_each = var.subnet_ids
# (and the other arguments as above)
}
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.10/language/meta-arguments/for_each/