- 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)
}