👓

【小ネタ】terraformのfor_eachの中でfor_eachを使いたい

2025/02/07に公開

背景

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に参考のソースを載せておきます。

モジュールに分割したとき
https://github.com/merutin/terraform-googlecloud/blob/3bb2da1b4bffec6862e2d931683beb4eb24ce036/modules/loop/main.tf

Dynamicブロックを利用するとき
https://github.com/merutin/terraform-googlecloud/blob/main/modules/loop/main.tf

参考記事

https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
https://zenn.dev/wim/articles/terraform_nest_loop
https://qiita.com/sekai/items/93aa4978f8cfc87e0913
https://ototo-lab.com/blog/learn_terraform_expressions/

DELTAテックブログ

Discussion