➿
terraform の細かすぎて伝わらない小ネタ for_each と型
tl;dr
- terraform でよくお世話になる meta-argument の for_each の変数の渡し方
-
for_each = var.xxx != null ? var.xxx : {}みたいに書くのが嫌だなって -
for_each = tomap(coalesce(var.type_map, {}))というイディオムでどうか
for_each = var.xxx != null ? var.xxx : {} の嫌なところ
- null エラーを回避するために三項演算子を使用している
- 変数を 2 回書いている
- 変数名が長いと読みにくい
- for_each には map か set が渡せるが、このコードでは判断しにくい
- (そもそも判断が必要かどうかは置いておく)
for_each への変数の渡し方
- 今回は直接 variable を渡すようなケースだけ
# null が入った時にエラーで死ぬので却下
for_each = var.type_map
for_each = var.type_set
# 三項演算子は冗長で、型がわかりにくい(から今回の記事を書いた)
for_each = var.type_map != null ? var.type_map : {}
for_each = var.type_set != null ? var.type_set : []
# coalesce() でいけるが空の型 `{} / []` は必要なのが気になる
for_each = coalesce(var.type_map, {})
for_each = coalesce(var.type_set, [])
# 明確な表現
for_each = tomap(coalesce(var.type_map, {}))
for_each = toset(coalesce(var.type_set, []))
# tomap() は null を受け付けないが merge() は空の map として扱える
for_each = tomap(merge(var.type_map))
# 残念ながら set 型では同様に null をスルーできる単独の function がないみたい?
テスト用のコード
- このまま適当な空ディレクトリに突っ込んでテスト可
variable "type_map" {
type = map(object({
name = string
age = number
}))
default = {
user1 = {
name = "Alice"
age = 30
}
user2 = {
name = "Bob"
age = 25
}
}
description = "map 型のテスト変数"
}
# resource "terraform_data" "type_map_no_good" {
# for_each = var.type_map
# input = join("_", [each.key, each.value.name, each.value.age])
# }
resource "terraform_data" "type_map_good" {
# 三項演算子は冗長で、型がわかりにくい
for_each = var.type_map != null ? var.type_map : {}
input = join("_", [each.key, each.value.name, each.value.age])
}
resource "terraform_data" "type_map_better" {
# coalesce() でいけるが空の型 `{}` は必要
for_each = coalesce(var.type_map, {})
input = join("_", [each.key, each.value.name, each.value.age])
}
resource "terraform_data" "type_map_best" {
# set 型と差異の少ない明確な表現
for_each = tomap(coalesce(var.type_map, {}))
input = join("_", [each.key, each.value.name, each.value.age])
}
resource "terraform_data" "type_map_other" {
# tomap() は null を受け付けないが merge() は空の map として扱える
for_each = tomap(merge(var.type_map))
input = join("_", [each.key, each.value.name, each.value.age])
}
variable "type_set" {
type = set(string)
default = [
"Alice",
"Bob",
"Charlie"
]
description = "set 型のテスト変数"
}
# resource "terraform_data" "type_set_no_good" {
# for_each = var.type_set
# input = each.key
# # ちなみに set 型の each.value は each.key と同じ値になる(どっちも使える)
# }
resource "terraform_data" "type_set_good" {
# var をニ回書くの嫌じゃない?
for_each = var.type_set != null ? var.type_set : []
input = each.value
}
resource "terraform_data" "type_set_better" {
# coalesce() でいけるが空の型 `[]` は必要
for_each = coalesce(var.type_set, [])
input = each.key
}
resource "terraform_data" "type_set_best" {
# map 型と差異の少ない明確な表現
for_each = toset(coalesce(var.type_set, []))
input = each.key
}
- tfvars や -var で値を変更してテストしてみて
考察
- map の場合は merge だけで抜ける
- set には同様に null を考慮しないでいい関数がない
- イディオムとしては coalesce() を使用しつつ型変換関数を明示するのがわかりやすいかなと
- 少なくても変数を 2 回書くよりはいいはず
Discussion