【Terraform】dynamicブロックは2つのargumentの排他制御にも使用できる

cloud ソフトウェア開発
広告

はじめに

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は他にもあります)。

Terraform Registry

source_image_idsource_image_referenceのいずれにも下記のNOTEが付記されています。

One of either source_image_id or source_image_reference must be set.

もう少し平たく言えばsource_image_idsource_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_referencesource_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_referencで指定されたマケプレのイメージが使用され、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 Blocks - Configuration Language | Terraform by HashiCorp
Dynamic blocks automatically construct multi-level, nested block structures. Learn to configure dynamic blocks and understand their behavior.

私の認識としてはdynamicブロックは、resourceブロック内で任意の回数分繰り返し定義可能なargumentの繰り返し回数を制御するものでした。

今回はこういったケースでもdynamicブロックが使えるという紹介でした。

追記(2022/7/24)

ほぼ同じ趣旨をシンプルに書かれている方がいました。こうやって書けば良かったんですね。

Terraformリソースのフィールドの有無を条件分岐する

コメント

タイトルとURLをコピーしました