先のエントリでも既述したように、defaultsファンクションを用いて、optional(list(string))とかのobjectを定義することは(今のところ)できません。
まあまだまだExperimentalなんで仕方がないことなんですが、設計修正を待て、と言われて待っているわけですが、それでもやっぱり書きたい。
一応merge例がissueには上がっていますが、要するにマージしてしまえ、という話になります。
なにもしなければ、optionalの各要素はnullが放り込まれていますので、これにマージしてしまえばいいだろう、ということですね。
例えばこう。
variable "whitelist" {
type = object({
all = optional(list(string))
ssh = optional(list(string))
mysql = optional(list(string))
website = optional(list(string))
})
}
# Define an IP whitelist for each element(CIDR).
whitelist = {
all = [ "127.0.0.1/32" ]
ssh = [ "127.0.0.1/32" ]
# mysql = []
website = [ "0.0.0.0/0" ]
}
locals {
whitelist_defaults = {
ssh = tolist(["0.0.0.0/0"])
website = tolist(["0.0.0.0/0"])
}
whitelist = merge(local.whitelist_defaults,var.whitelist)
}
output "whitelist" {
value = local.whitelist
}
ひとまず、きれいにマージはされます。個人的には、tolist(null)がデフォルトになるので、tolist([])で固めたい、となるところでしょうか。nullと[]が返ってくるのでは意味が変わりますし。似たようなもんではありますが。
これを直したい、となるとちょっとごちゃつきます。
whitelist = merge(
{ for k,v in merge(local.whitelist_defaults,var.whitelist) : k => tolist([]) if v == null },
{ for k,v in merge(local.whitelist_defaults,var.whitelist) : k => v if v != null }
)
やってることは単純で、null判定される固定値のmapとnullではないmapを結合マップから生成してさらにマージする、ということなんですが、コード的にはうざいですね。でも未定義のtolist(null)の除外を考えるとまあしょうがないのかな、という気もします。さっさとdefaultsをCollectionsにも反映していただきたい。
localsは変数っぽく振舞いますが、書き換えを行ういわゆる変動値ではなく、リテラル値を動的に定義するための仕掛けなので、whitelistを再定義だ!って思って
whitelist = { for k,v in local.whitelist : ~~~ }
などとも書けません。プログラミング言語、ではない、ということです。これらのIaCコードはいわゆるプログラミング言語とは異なる、ある種のメタ言語であり、変数、なんて読んでいますが単一の実行中にはその値を変動させません(というか変化したり、一度定義した値を書き換えられたら困ります)。
なので、基本単一の定義内でこのようにごちゃついた書き方をしてワンライナーに収束させる必要があります。
この辺りはシェル芸になれていればそこまで苦ではないのでしょうが、昨今シェル芸は流行らないようなので、書きなれたインフラエンジニアって減ってきている気がしますね。map()の場合も似たようなものでしょう。
多段に組んだStructureになってくるとさらにネストが増えるのでしょうが、個人的にはそこまでvariablesを深くするのは(見栄えはいいんですが)避けたほうがいいのかな、と思います。
ただ、Route53のレコードやTargetGroupのルーティングルールのように、ちょっと深いStructureを書いたほうがよさそうなケースもあるので、思案のしどころではあるのでしょうが。
ま、DefaultsがCollectionsサポートしてくれたらさっさと書き直すような箇所なので、あくまでExperimentalな現状においての使い方、ということになるのでしょうけれど。