🌟

Terraform でif文の代わりになる表現を使う

2022/12/13に公開

はじめに

Terraform を HCL で書くとき、いわゆるif文を for_each のなかで書きたくなるときがあるかと思います。
同種類、複数リソースの記述で共通する場合は多いが、各リソースごとに設定が微妙に異なる場合などです。
同じプロパティに当てる値が異なるのであれば変数を用意すれば足りますが、プロパティやブロック、リソースの有無を切りかえるとなるとif文が欲しくなります。
しかし残念ながら今のところ HCL にはモジュールやリソース内ブロックで使えるif文はないようです(ただしfor文内のifや三項演算子は存在する)。
そこで以下のような場面ごとに、if文の代わりになる表現を紹介したいと思います。

  • リソースの出し分け。
  • ブロックの出し分け。
  • プロパティの出し分け。

検証環境

以下イメージを使用したコンテナ環境
image: hashicorp/terraform:1.3.6

サンプルコード

以下をサンプルとして、ここに書き足していこうと思います。

locals {
  params = {
    one = {
      name = "one"
    },
    two = {
      name = "two"
    },
    three = {
      name = "three"
    },
  }
}

resource "google_storage_bucket" "collection" {
  for_each = locals.params

  name                     = each.value.name
  location                 = "asia-northeast1"
  storage_class            = "STANDARD"
  public_access_prevention = "enforced"
}

リソースの出し分け

for_each と for を併用します。

locals {
  params = {
    one = {
      name         = "${var.gcp_project_id}-one"
      is_necessary = true
    },
    two = {
      name         = "${var.gcp_project_id}-two"
      is_necessary = true
    },
    three = {
      name         = "${var.gcp_project_id}-three"
    },
  }
}

resource "google_storage_bucket" "collection" {
  for_each = { for param, attributes in local.params : param => attributes if can(attributes.is_necessary) }

  name                     = each.value.name
  location                 = "asia-northeast1"
  storage_class            = "STANDARD"
  public_access_prevention = "enforced"
}

ローカル変数を for で回す部分で for_each に渡す map を選別しています。
for 内の can は引数に渡された実行内容がエラーでないかを bool で返します。

https://developer.hashicorp.com/terraform/language/functions/can

local.params.three に is_necessary プロパティがないので local.three.is_necessary はエラーを返します。それを can で bool に変換しています。

ブロックの出し分け

dynamic を使います。

locals {
  params = {
    one = {
      name         = "${var.gcp_project_id}-one"
      is_necessary = true
    },
    two = {
      name         = "${var.gcp_project_id}-two"
      is_necessary = true
      lifecycle_rule = {
        action = {
          type          = "SetStorageClass"
          storage_class = "ARCHIVE"
        }
      }
    },
    three = {
      name = "${var.gcp_project_id}-three"
    },
  }
}

resource "google_storage_bucket" "collection" {
  for_each = { for param, attributes in local.params : param => attributes if can(attributes.is_necessary) }

  name                     = each.value.name
  location                 = "asia-northeast1"
  storage_class            = "STANDARD"
  public_access_prevention = "enforced"

  dynamic "lifecycle_rule" {
    for_each = can(each.value.lifecycle_rule) ? [1] : []

    content {
      action {
        type          = each.value.lifecycle_rule.action.type
        storage_class = each.value.lifecycle_rule.action.storage_class
      }
      condition {}
    }
  }
}

ローカル変数のパラメーター two に lifecycle_rule プロパティを追加しています。
dynamic ブロックはその名のとおりブロックを動的に定義します。

https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks

ブロック内は for_each と content を使って表現します。
for_each に関しては、リソースの出し分けで使用した can を使用してパラメーターの有無を bool で返し、それを受ける三項演算子で実行の有無を分岐しています。

プロパティの出し分け

オプショナルなプロパティの出し分けには null を使います。

locals {
  params = {
    one = {
      name         = "${var.gcp_project_id}-one"
      is_necessary = true
      labels = {
        team = "alpha"
      }
    },
    two = {
      name         = "${var.gcp_project_id}-two"
      is_necessary = true
      labels       = null
      lifecycle_rule = {
        action = {
          type          = "SetStorageClass"
          storage_class = "ARCHIVE"
        }
      }
    },
    three = {
      name = "${var.gcp_project_id}-three"
    },
  }
}

resource "google_storage_bucket" "collection" {
  for_each = { for param, attributes in local.params : param => attributes if can(attributes.is_necessary) }

  name                     = each.value.name
  location                 = "asia-northeast1"
  storage_class            = "STANDARD"
  public_access_prevention = "enforced"
  labels                   = each.value.labels

  dynamic "lifecycle_rule" {
    for_each = can(each.value.lifecycle_rule) ? [1] : []

    content {
      action {
        type          = each.value.lifecycle_rule.action.type
        storage_class = each.value.lifecycle_rule.action.storage_class
      }
      condition {}
    }
  }
}

ローカル変数のパラメーター one に labels プロパティを追加しています。
local.params.two の labels = null を削除して、リソース定義のほうで 値と null を出し分けても同様の結果になります。

おわりに

HCL でのリソース、ブロック、プロパティの出し分け方法を書きました。
Github にサンプル上げたのでお試しください。
https://github.com/es-nd/terraform_if_alternative

Discussion