Метааргумент for_each
По умолчанию блок ресурса настраивает один реальный объект инфраструктуры (аналогично, блок модуля однократно включает содержимое дочернего модуля в конфигурацию). Однако иногда возникает необходимость управлять несколькими похожими объектами (например, фиксированным пулом вычислительных экземпляров), не создавая для каждого отдельный блок. OpenTofu предоставляет два способа сделать это: count и for_each.
Если блок ресурса или модуля содержит аргумент for_each, значением которого является карта или набор строк, OpenTofu создаёт по одному экземпляру для каждого элемента этой карты или набора.
В одном блоке ресурса или модуля нельзя использовать for_each вместе с count или enabled.
Основной синтаксис
for_each — это метааргумент, определённый языком OpenTofu. Его можно использовать с модулями и ресурсами любого типа.
Метааргумент for_each принимает карту или набор строк и создаёт экземпляр для каждого элемента этой карты или набора. С каждым экземпляром связан отдельный объект инфраструктуры; при применении конфигурации каждый экземпляр создаётся, обновляется или удаляется независимо.
Карта:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}Набор строк:
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— ключ карты (или элемент набора), соответствующий этому экземпляру. -
each.value— значение карты, соответствующее этому экземпляру. (Если задан набор, это то же самое, что иeach.key.)
Ограничения на значения, используемые в for_each
Ключи карты (или все значения в случае набора строк) должны быть известными значениями. В противном случае появится сообщение об ошибке, указывающее, что у 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 принимает карты или наборы, заданные с помощью выражений. Однако, в отличие от большинства аргументов, значение for_each должно быть известно до того, как OpenTofu выполнит какие-либо действия с удалёнными ресурсами. Это означает, что for_each не может ссылаться на атрибуты ресурсов, значения которых становятся известны только после применения конфигурации (например, на уникальный идентификатор, сгенерированный удалённым API при создании объекта).
Значение for_each должно быть картой или набором, содержащим по одному элементу для каждого требуемого экземпляра ресурса. Чтобы использовать последовательность в качестве значения for_each, необходимо применить выражение, явно возвращающее набор, например функцию toset. Чтобы избежать неожиданностей при преобразовании, аргумент for_each не преобразует списки или кортежи в наборы автоматически. Если требуется объявить экземпляры ресурсов на основе вложенной структуры данных или комбинаций элементов из нескольких структур данных, можно использовать выражения и функции OpenTofu, чтобы получить подходящее значение. Например:
- Преобразовать многоуровневую вложенную структуру в плоский список, используя вложенные выражения
forс функциейflatten. - Создать полный список комбинаций элементов из двух или более коллекций, используя функцию
setproductвнутри выраженияfor.
Связывание for_each между ресурсами
Поскольку ресурс, использующий for_each, в выражениях отображается как карта объектов, в ситуациях, когда между двумя наборами объектов существует взаимно-однозначное соответствие, можно напрямую использовать один ресурс в качестве 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 различает сам блок и несколько связанных с ним экземпляров ресурсов или модулей. Экземпляры идентифицируются ключом карты (или элементом набора) из значения, переданного в 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 нет литерального синтаксиса для значений-наборов, однако можно использовать функцию toset, чтобы явно преобразовать список строк в набор:
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}"
}
}При преобразовании списка в набор порядок элементов списка теряется, а повторяющиеся элементы удаляются. toset(["b", "a", "b"]) создаст набор, содержащий только "a" и "b", в произвольном порядке; второй "b" будет отброшен.
Если вы пишете модуль с входной переменной, которая будет использоваться в 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.11/language/meta-arguments/for_each/