Terraformのfor_eachをちゃんと理解する

背景

どの言語でコードを書くにも必要になる繰り返し処理だが、Terraform ではじめてこれを書く際は syntax 以外にも細かい前提知識を知っておかないと地味に手こずった。個人的な覚え書きがてら実際のユースケースを添えてまとめる。内容は v1.3.x の仕様に沿っている。

前提知識

例えば Terraform でユーザ一覧を扱いたいケースは以下のように書く。

resource "organizatoin_member" "member" { for_each = toset(["hoo@example.org", "bar@example.org", ...]) email = each.value }

for_each に渡す要素の値は必ずユニークになっている必要があるので、Map か Set のみが使える。resource block 内では each.value で要素にアクセスできる。Set の場合は key と value はどちらも同じになる。

ここで書いた resource を plan すると以下のような結果になる。

Terraform will perform the following actions: # organizatoin_member.member["hoo@example.org"] will be created + resource "organizatoin_member" "member" { + email = "hoo@example.org" } # organizatoin_member.member["bar@example.org"] will be created + resource "organizatoin_member" "member" { + email = "bar@example.org" } ...

organizatoin_member.member["hoo@example.org"] は state 上でこのリソースをユーニークに識別するアドレスとなる。for_each が unique key を持つ型のみを受け付ける理由はこれにある。

よくあるユースケース

少し複雑なユースケースを見ながら理解を深める。ここでは二重 for ループを回したいようなパターンを考える。 例えば組織一覧を Map 型変数で定義して、それぞれの要素で更にユーザの一覧を配列で持っているようなケース。

locals { organizations = { dev_group = { name = "Development Group" members = [ "hoo@example.org", ... ] } biz_group = { ... } ... } }

この変数を元に以下のような resource を作成したい。

  • 組織を表す organization resource
    • apply 時に決まる ID と組織名を持っている
  • ユーザを表す organization_user resource
    • apply 時に決まる ID と email address を持っている
  • ユーザと所属組織の関連を表す organization_membership resource
    • apply 時に決まる organization の ID と user の ID を持っている

まず組織 resource はシンプルに以下のような形で作成できる。

resource "company_organization" "organization" { for_each = local.organizations name = each.value.name }

各 organization に所属するユーザは以下のように一括で作成すればいい。

data "organization_user" "user" { for_each = toset(flatten([for org in local.organizations : org.members])) email = each.key }

Organization と user の紐付けはどうするか。以下のような書き方ができる。

resource "organization_membership" "membership" { for_each = { for key, value in local.organizations : company_organization.organization[key].id => value } organization_id = each.key member_ids = [for email in each.value.members : organization_user.user[email]] }

ポイントとしては resource の作成時に他の resource、company_organizationorganization_user が参照できること。このとき locals に定義している Map の key を index に参照先の resource を指定する。ちょっと回りくどい感じもするが、他の言語では二重 for ループで行うのと近いことができる。公式ドキュメントにも似たようなケースが紹介されている。

Chaining for_each Between Resources: The for_each Meta-Argument - Configuration Language | Terraform | HashiCorp Developer

まとめ

Terraform で for_each を扱う際に最低限知っておく情報と実際によくあるユースケースの例を挙げた。初めて触れる Terraform のコードは一瞬困惑するものの慣れれば問題なく書けるけど、syntax だけでなく Terraform が扱ってくれる state などの構造についても理解しておく必要があるなと思った。関数や expression だけでなく Manipulating State - Terraform CLI | Terraform | HashiCorp Developer あたりもきちんと読んでおくと、ちょっと込み入った事をやろうとしても悩まなくて済みそうだ。

Tweet