Spec-Zone.ru › OpenTofu 1.9

Метааргумент for_each

По умолчанию блок resource настраивает один реальный объект инфраструктуры (аналогично, блок module включает содержимое дочернего модуля в конфигурацию один раз). Однако иногда требуется управлять несколькими похожими объектами (например, фиксированным пулом вычислительных экземпляров), не создавая для каждого отдельный блок. OpenTofu позволяет сделать это двумя способами: count и for_each.

Если блок resource или module содержит аргумент for_each, значением которого является карта или набор строк, OpenTofu создаёт по одному экземпляру для каждого элемента этой карты или набора.

Примечание

В одном блоке resource или module нельзя одновременно использовать count и for_each.

Базовый синтаксис​

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 относится к текущему экземпляру ресурса, а не ко всему блоку resource.

Использование наборов​

В языке 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.9/language/meta-arguments/for_each/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API