- OpenTofu言語
- メタ引数
- for_each メタ引数
for_each
メタ引数
デフォルトでは、リソースブロックは1つの実際のインフラストラクチャオブジェクトを設定します(同様に、モジュールブロックは子のモジュールの内容を1回構成に含めます)。ただし、個別のブロックを記述することなく、いくつかの類似したオブジェクト(コンピューティングインスタンスの固定プールなど)を管理したい場合があります。OpenTofuには、これを行うための2つの方法があります。count
とfor_each
です。
リソースブロックまたはモジュールブロックに、値がマップまたは文字列のセットであるfor_each
引数が含まれている場合、OpenTofuはそのマップまたはセットのメンバーごとに1つのインスタンスを作成します。
特定のリソースまたはモジュールブロックでは、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
オブジェクトを使用できるため、各インスタンスの構成を変更できます。このオブジェクトには2つの属性があります。
each.key
—このインスタンスに対応するマップキー(またはセットメンバー)。each.value
—このインスタンスに対応するマップ値。(セットが指定されている場合は、each.key
と同じです。)
for_each
で使用される値の制限
マップのキー(または文字列セットの場合はすべての値)は既知の値である必要があり、そうでない場合は、for_each
に適用前に決定できない依存関係があるというエラーメッセージが表示され、-target
が必要になる場合があります。
for_each
キーは、uuid
、bcrypt
、またはtimestamp
を含む不純関数の結果(または結果に依存する)であってはなりません。これらの関数の評価は、メイン評価ステップ中に延期されるためです。
機密性の高い入力変数、機密性の高い出力、または機密性の高いリソース属性などの機密性の高い値は、for_each
の引数として使用できません。for_each
で使用される値は、リソースインスタンスを識別するために使用され、UI出力で常に開示されるため、機密性の高い値は許可されません。機密性の高い値をfor_each
の引数として使用しようとすると、エラーが発生します。
機密データを含む値をfor_each
で使用する引数に変換する場合、OpenTofuのほとんどの関数は、機密性の高いコンテンツを含む引数が指定された場合、機密性の高い結果を返すことに注意してください。多くの場合、この目的で使用される関数と同様の結果は、for
式を使用することで実現できます。たとえば、local.map
が機密値(ただし機密性の低いキー)を持つオブジェクトである場合に、keys(local.map)
を呼び出す場合は、toset([for k,v in local.map : k])
を使用して、for_each
に渡す値を作成できます。
for_each
での式の使用
for_each
メタ引数は、マップまたはセットの式を受け入れます。ただし、ほとんどの引数とは異なり、for_each
の値は、OpenTofuがリモートリソースアクションを実行する前に既知である必要があります。これは、for_each
が構成が適用された後まで不明な(オブジェクトが作成されたときにリモートAPIによって生成される一意のIDなど)リソース属性を参照できないことを意味します。
for_each
の値は、目的のリソースインスタンスごとに1つの要素を持つマップまたはセットである必要があります。シーケンスをfor_each
の値として使用するには、toset関数のように、明示的にセット値を返す式を使用する必要があります。変換中に予期しない事態が発生するのを防ぐため、for_each
引数はリストまたはタプルをセットに暗黙的に変換しません。ネストされたデータ構造や、複数のデータ構造の要素の組み合わせに基づいてリソースインスタンスを宣言する必要がある場合は、OpenTofuの式と関数を使用して適切な値を導出できます。例:
- ネストされた
for
式をflatten
関数とともに使用することで、多層ネスト構造をフラットなリストに変換します。 for
式内でsetproduct
関数を使用することで、2つ以上のコレクションの要素の組み合わせの完全なリストを作成します。
リソース間でのfor_each
のチェーン
for_each
を使用するリソースは、他の場所の式で使用される場合、オブジェクトのマップとして表示されるため、2つのオブジェクトのセット間に1対1の関係がある状況では、あるリソースを別のリソースの for_each
として直接使用できます。
たとえば、AWS では、aws_vpc
オブジェクトは、通常、「インターネットゲートウェイ」など、その VPC に追加サービスを提供する他の多くのオブジェクトと関連付けられています。for_each
を使用して複数の VPC インスタンスを宣言する場合、その 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
がないリソースやモジュールとは異なります。
同様に、複数のインスタンスを持つ子モジュールからのリソースは、プラン出力や UI の他の場所で表示される際、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"
のみを含むセットを生成します。2番目の "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)
}