集合の組合せに対してTerraformで設定する(setproductとfor_eachを使う)

「このメンバーの一覧に対して、このリストの一覧の権限の付与したい」ってケース、結構あると思います。「このメンバー一覧に対して、この権限だけを付与したい」という場合はTerraformだとfor_eachで割と簡単に書くことができますが(一重ループ)、タイトルのように複数の集合の要素の組合せ(集合の直積)だとfor_eachが簡単にできません(二重ループ)。あれこれ試行錯誤したので、メモしておきます。

Terraformのsetproductについて

集合の直積についてはTerraformだとsetproductで簡単にできます。ありがたい。

こういう感じ。配列の配列を返してくれます。

> setproduct(["staging", "production"], ["a", 2])
[
  [
    "staging",
    "a",
  ],
  [
    "staging",
    "2",
  ],
  [
    "production",
    "a",
  ],
  [
    "production",
    "2",
  ],
]

setproductをfor_eachで回す

Terraformは二重ループ的なことは対応していなくて、ループを回す対象が以下のどちらかである必要があります。

  • map
  • 要素がstringのset

setproductの戻り値は配列の配列になっているため、The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type tuple.と怒られます。for_eachで解決できるように、型を変えていきましょう。今回はmapにします(インデックスで管理するのはダルいので)。

google_project_iam_memberを例に説明するとこんな感じ。

resource "google_project_iam_member" "iam_member" {
  for_each = {
    for p in local.member_role_pairs : "${p.member} ${p.role}" => p # ここがミソ!
  }
  member = each.value.member
  role   = each.value.role
}
locals {
  members = [
    "group:aaa@gmail.com",
    "group:bbb@gmail.com",
  ]
  roles = [
    "roles/bigquery.dataEditor",
    "roles/bigquery.jobUser",
  ]
  member_role_pairs = [
    for pair in setproduct(local.members, local.roles) : { # ここもミソ!
      member = pair[0]
      role   = pair[1]
    }
  ]
}

ざっくり説明するとこんな感じですね。

  • 一旦、配列の配列をmapの配列に変換
  • forの中でconcatしたものをkeyに無理やり変換して、最終的にmapに変換

...と偉そうに書いたものの、去年も同じことでちょっと悩んでてid:heleeenさんに助けてもらっていたんですが、ちゃんとメモしていなくて今年も困ってしまいました。来年は困らないように今年はちゃんとメモしたぞ...!