Метааргумент 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.12/language/meta-arguments/for_each/