🔀

TerraformでOSに合せて、ロジックを切り替える

2022/11/05に公開

概要

  • Terraformの実行環境のOSに応じて、Windowsと*nix系(MacとかLinux)で作るresourceを切り分けたり、呼ぶスクリプトを変える

Gist

https://gist.github.com/oniku-2929/85ded3dd50aa19562ce30cdf3060c90b

内容

すごい簡単です。
以下でlocal変数is_unixが*nix系の場合はtrue,そうじゃない場合はfalseになります。(Windowsを想定してます)

if_by_os.tf
locals {
  is_unix = substr(abspath(path.cwd), 0, 1) == "/"
}

動作パスを絶対パスで取得して、パスの先頭文字で判断している形です。

https://developer.hashicorp.com/terraform/language/expressions/references#filesystem-and-workspace-info

https://developer.hashicorp.com/terraform/language/functions/abspath

https://developer.hashicorp.com/terraform/language/functions/substr

あとはこれを各箇所で使います。

例:作成するresourceを切り替える

Terraform自身はif文のような機能はもっていないですが, countを利用する事で疑似的に処理の分岐が可能です。

https://developer.hashicorp.com/terraform/language/meta-arguments/count

https://qiita.com/mia_0032/items/978449a06699ed1abe15

if_by_os.tf
resource "null_resource" "do_something_on_win" {
  count = local.is_unix ? 0 : 1
  provisioner "local-exec" {
    command     = "ipconfig | Write-Output"
    interpreter = ["PowerShell", "-Executionpolicy", "bypass", "-Command"]
  }
}
resource "null_resource" "do_something_on_unix" {
  count = local.is_unix ? 1 : 0
  provisioner "local-exec" {
    command = "ifconfig | echo"
  }
}

ですので、上記の例でいえば

  • Windowsなら「do_something_on_win」リソースのみが作られる → 対象リソースの「provisioner local_exec」が実行されるのでPowerShellのコマンドが実行される
  • *nix系なら「do_something_on_unix」リソースのみが作られる → 対象リソースの「provisioner local_exec」が実行されるShell側のコマンドが実行される

という形になります。

例:共有のresourceだが、渡す内容を切り替える

もう1つ例をあげますと
作成するresource自体は共通だけど、渡す内容をOSによって切り替えるという事もやろうと思えばできます。
下記は「external」という外部コマンドを実行し、結果をdata resourceとして格納できるProviderの力を借りています。
https://registry.terraform.io/providers/hashicorp/external/latest/docs

if_by_os.tf
data "external" "os_info_win" {
  count   = local.is_unix ? 0 : 1
  program = ["Powershell.exe", "-Executionpolicy", "bypass", "-File", "./win.ps1"]
}
data "external" "os_info_unix" {
  count   = local.is_unix ? 1 : 0
  program = ["./unix.sh"]
}
locals {
  os_type = local.is_unix ? data.external.os_info_unix[0].result.os_type : data.external.os_info_win[0].result
}
resource "null_resource" "echo_os_info" {
  provisioner "local-exec" {
    command = "echo os_type=${local.os_type}"
  }
}

Winなら「win.ps1」、*nix系なら「unix.sh」が実行され
どちらもJSON形式の

{ "os_type" : "(OSに応じた文字列)"}

という出力を標準出力にだし、それをexternalが拾ってくれるので
その出力をData Sourceとして後から利用している例です。

  • Windowsなら"echo os_type=windows"
  • *nix系なら"echo os_type=unix"

と出力されるような形です。

たとえば、「作成するAWS EC2にOSに応じたタグを入れこんだりする」みたいな使い方ができるかなと思います。

注意点

下記にも記載ありますが、external自体は適切なProviderがTerraformで利用できない(存在しないとか、他の理由)場合の為のワークアラウンドとして用意されているようです。
これは、Terraform以外のソフトウェアやライブラリの依存関係を生み出す事につながるため推奨されていません。

https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source

This mechanism is provided as an "escape hatch" for exceptional situations where a first-class Terraform provider is not more appropriate. Its capabilities are limited in comparison to a true data source, and implementing a data source via an external program is likely to hurt the portability of your Terraform configuration by creating dependencies on external programs and libraries that may not be available (or may need to be used differently) on different operating systems.
この仕組みは、ファーストクラスのTerraformプロバイダがより適切でない例外的な状況に対する「逃げ道」として提供されています。真のデータソースと比較すると機能は限定的で、外部プログラム経由でデータソースを実装すると、異なるOSで利用できない(または異なる使い方が必要な)外部プログラムやライブラリに依存することになり、Terraform構成の移植性が損なわれる可能性があります。

それをいうと、そもそもProvisionerも同じように外部の依存関係を生み出してしまう事になるので、ベストな選択として推奨はされていません。

https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax

Terraform includes the concept of provisioners as a measure of pragmatism, knowing that there are always certain behaviors that cannot be directly represented in Terraform's declarative model.
However, they also add a considerable amount of complexity and uncertainty to Terraform usage. Firstly, Terraform cannot model the actions of provisioners as part of a plan because they can in principle take any action. Secondly, successful use of provisioners requires coordinating many more details than Terraform usage usually requires: direct network access to your servers, issuing Terraform credentials to log in, making sure that all of the necessary external software is installed, etc.
Terraformは、Terraformの宣言型モデルでは直接表現できない動作が常に存在することを知っているため、プラグマティズムの尺度としてプロビジョナの概念を含んでいます。
しかし、これらはTerraformの使い方にかなりの複雑さと不確実性を加えることにもなります。まず、Terraformはプロビジョナーの行動をプランの一部としてモデル化することができません。第二に、プロビジョナーをうまく使うには、Terraformの使い方よりも多くの詳細を調整する必要があります。サーバーへの直接のネットワークアクセス、ログインのためのTerraform認証情報の発行、必要な外部ソフトウェアがすべてインストールされているか確認、などです。

とはいえ、そういう手段があると知っておいて使わないのと、知らないのでは意味が異なるので
理解して使う分にはいいかなと考えている派です。

所感

以前までは

variable "is_unix" {
  type    = bool
  default = true
}

として、実行時にis_unix=falseといった形で渡してました。
そっちのほうが適切なケースもあるかと思いますが、自分的にはこちらの形式が楽だなと感じてます。

😀「これでMacだろうと、Windowsだろうと対応できるZO!」
😶「Windows側のスクリプトをコミットし忘れちゃったンゴ!!」
😇

Discussion