Terraform でif文の代わりになる表現を使う
はじめに
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 にサンプル上げたのでお試しください。
Discussion