はじめに
Terraformで相互に排他となる2つのargument(Terraformのドキュメントで登場する英語で言えばOne of either arg1 or arg2 must be set)を同時にコード上に埋め込んでおきたいという要求を満たす必要性が出てきましたが、dynamicブロックを使って解決できました。
要求と解決方法について実例とコードを挙げて説明していきます。
要求
排他となる2つのargumentの実例
「排他となる2つのargument」の実例はこちら。
azurermのプロバイダーでLinuxの仮想マシンを作成する linux_virtual_machine
に該当argumentがありました(もちろん該当するargumentは他にもあります)。
source_image_id
、source_image_reference
のいずれにも下記のNOTEが付記されています。
One of either source_image_id or source_image_reference must be set.
もう少し平たく言えばsource_image_id
と source_image_reference
は、いずれか一方しかコード上で定義できないということになります。
具体的に以下コードで示します。
resource "azurerm_linux_virtual_machine" "mylinux" {
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts-gen2"
version = "latest"
}
}
resource "azurerm_linux_virtual_machine" "mylinux" {
source_image_id = local.source_image_id
}
source_image_reference
とsource_image_id
以外のargumentは省略してありますが、いずれか一方しかresourceブロックにしか登場しないというイメージは伝わるかと思います。
いずれか一方しかコード上で定義できないと困るのか
そもそもいずれか一方しかコード上で定義できなくとも、よくある宣言的に環境を定義しておいてリソースを作成するTerraformのユースケースでは困りません。
「排他となる2つのargument」の章で挙げた例の具体例の通り、当然のことながらどちらか一方を定義しておけば良いだけです。
それではどういったケースで困るかというと、変数の入力の仕方よって作成するリソースのパターンを変えるようなケースが当てはまります。
つまりパターンをいくつか決めた上でいずれかのパターンに対応できるようにTerraformをテンプレート化するケースです。これはmodule化をするケースでも当てはまることがあるでしょう。
排他となる2つのargumentを同時に定義しておきたい
先ほど「排他となる2つのargument」の章で挙げた挙げたコードを1つのファイルにし、そのファイルに2つのargumentを定義しておき、変数の入力によって作成するリソースのパターンを変えるというのが実現したいことです。
Terraformにはnull
というvalueがあるので、nullを利用して下記のように書くことができれば、後は三項演算子も併用してなんとかなると思ったのですが、下記の書き方自体がエラーになってしまいました。
resource "azurerm_linux_virtual_machine" "mylinux" {
source_image_reference {
publisher = null
offer = null
sku = null
version = null
}
source_image_id = local.source_image_id
}
解決方法
結論としては以下のようにdynamicブロックを使えば実現できます(本旨に関連する部分のみの抜粋です)。
terraform {
required_version = "~> 1.1.7"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 2.98.0"
}
}
}
provider "azurerm" {
features {}
}
locals {
source_image_reference = var.source_image_reference
}
variable "source_image_reference" {
type = map(any)
default = {
refcontent = {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts-gen2"
version = "latest"
}
}
}
variable "source_image_id" {
type = string
default = null
}
resource "azurerm_linux_virtual_machine" "mylinux" {
dynamic "source_image_reference" {
for_each = local.source_image_id == null ? local.source_image_reference : {}
content {
publisher = source_image_reference.value.publisher
offer = source_image_reference.value.offer
sku = source_image_reference.value.sku
version = source_image_reference.value.version
}
}
source_image_id = local.source_image_id
}
こう書いておけば、variableのsource_image_id
をnullのままにした場合はsource_image_reference
で指定されたマケプレのイメージが使用され、nullを具体的なイメージのIDで上書きした場合は指定したイメージIDからVMが作成されます。
for_each = local.source_image_id == null ? local.source_image_reference : {}
この部分でlocal.source_image_id
がnullだった場合はlocal.source_image_reference
分の繰り返し処理を行い、そうでなければfor_eachに空の配列を渡して繰り返し処理が行わないように制御しています。
まとめ(dynamicブロックの使いどころ)
私としてはこのdynamicブロックを使うという手法が意外だったので補足しておきます。
そもそもdynamicブロックとは何かという話とdynamicブロックの使いどころは公式ドキュメント見ていただくのが早いです。
私の認識としてはdynamicブロックは、resourceブロック内で任意の回数分繰り返し定義可能なargumentの繰り返し回数を制御するものでした。
今回はこういったケースでもdynamicブロックが使えるという紹介でした。
追記(2022/7/24)
ほぼ同じ趣旨をシンプルに書かれている方がいました。こうやって書けば良かったんですね。
コメント