👓
【小ネタ】terraformのfor_eachの中でfor_eachを使いたい
背景
terraformで構築をしているときに、for_eachの中でfor_eachを利用したいパターンが出てきました。
2つ目のfor_eachはループとして利用するのではなく、実行有無の判断のために利用したのですが、どのように書くのかわからなかったので、調べた内容をメモとして残します。
結論
2つ目のfor_eachの中でeach.valueを利用すると、最初のfor_eachで利用した変数が格納されることがわかりました。そのため、割と普通に書けます。
以下はサンプル用に作成した、Cloud Storageのバケットを複数作成する場合の書き方です。
main.tf
module "buckets" {
source = "./modules/loop"
buckets = {
"bucket1" = {
region = "us-central1"
},
"bucket2" = {
region = "asia-northeast1",
lifecycle_rule = {
age = 60
}
},
}
}
terraform.tf
variable "buckets" {
type = map(object({
region = string
storage_class = optional(string, "STANDARD")
versioning = optional(bool, true)
age = optional(number)
lifecycle_rule = optional(object({
age = optional(number)
with_state = optional(string)
created_before = optional(string)
matches_storage_class = optional(list(string))
}))
}))
description = "Map of bucket configurations"
}
resource "google_storage_bucket" "default" {
for_each = var.buckets
name = "${each.key}-${random_id.bucket_suffix[each.key].hex}"
location = each.value.region
storage_class = each.value.storage_class
uniform_bucket_level_access = true
versioning {
enabled = each.value.versioning
}
dynamic "lifecycle_rule" {
## for_eachの判定条件として、each.valueを利用している
for_each = each.value.lifecycle_rule != null ? [1] : []
content {
action {
type = "Delete"
}
condition {
## each.value.xxx は最初のループの変数がそのまま取得できる
age = try(each.value.age, null)
with_state = try(each.value.lifecycle_rule.with_state, null)
created_before = try(each.value.lifecycle_rule.created_before, null)
matches_storage_class = try(each.value.lifecycle_rule.matches_storage_class, [])
}
}
}
}
resource "random_id" "bucket_suffix" {
for_each = var.buckets
byte_length = 4
}
おまけ
このままでは短いので、ほかの書き方も試してみます。
新しい別の変数を定義してみる
ライフサイクルは複数設定できるので、2つに増やしてみます。
以下のように修正して実行したのですが、意図したとおりには動きません。
cyclesのrule1にあるageは見てくれません。
main.tf
module "buckets" {
source = "./modules/loop"
## 省略
cycles = {
rule1 = {
age = 30
},
rule2 = {
age = 90
}
}
}
terraform.tf
## 一部のみ
dynamic "lifecycle_rule" {
for_each = each.value.lifecycle_rule != null ? var.cycles : {}
content {
action {
type = "Delete"
}
condition {
age = try(each.value.cycles.age, null)
with_state = try(each.value.lifecycle_rule.with_state, null)
created_before = try(each.value.lifecycle_rule.created_before, null)
matches_storage_class = try(each.value.lifecycle_rule.matches_storage_class, [])
}
}
}
Dynamicブロックについては、名称でアクセスできるようになるので、それを利用します。
ここでは、lifecycle_ruleです。
resource "google_storage_bucket" "default" {
for_each = var.buckets
name = "${each.key}-${random_id.bucket_suffix[each.key].hex}"
location = each.value.region
storage_class = each.value.storage_class
uniform_bucket_level_access = true
versioning {
enabled = each.value.versioning
}
dynamic "lifecycle_rule" {
for_each = each.value.lifecycle_rules != null ? each.value.lifecycle_rules : []
content {
action {
type = "Delete"
}
condition {
age = try(lifecycle_rule.value.age, null)
with_state = try(lifecycle_rule.value.with_state, null)
created_before = try(lifecycle_rule.value.created_before, null)
matches_storage_class = try(lifecycle_rule.value.matches_storage_class, [])
}
}
}
}
loopごとにmoduleを分割しても上手くいくようです。
結論
terraformでのループについて、私が実際に困ったことベースでまとめてみました。
普段はあまり書かないので、Dynamicブロックでの挙動などは記事を書くときに知って勉強になりました。
参考ソース
あまりきれいに書けてないのですが、GitHubに参考のソースを載せておきます。
モジュールに分割したとき
Dynamicブロックを利用するとき
参考記事
Discussion