Check! Terraform でモジュールを動的に選択する
Prologue
Terraform でいろんな環境のサンプルを書いていて、必要に応じて出し分けたいというニーズがあり、試行錯誤の末に落ち着いたやり方を書き留めます。
Terraform でモジュールを動的に選択する
今回の構成はこのようになっています。
- 配列の input variables を用意し、使用するモジュールを指定させる
- 配列に一致する文字列があれば、そのモジュールを含める
- 実行されたモジュールの実行結果を表示する
サンプルコード全体はこちらをご参照ください。
ポイントになるコードを抜粋します。 source
のモジュールは module_name
を返すだけのダミーです。
まず、モジュールを選択させる引数を指定させます。ここでは、文字列の配列を受け付ける引数 modules
を用意します。文字列は出し分けに利用する "キーワード" として後述で扱います。モジュール名と同じか、モジュールを指定しやすい文字列にするとよいでしょう。
modules = [
"one",
"two",
]
variable "modules" {
type = list(string)
}
つぎに、各モジュールに対して、引数に"キーワード"に一致する文字列が指定されていたら、 count
か for_each
を使って出し分ける記述をします。
count
を使う例では、var.modules
にキーワードの文字列が含まれている場合は、count
に 1回 を指定して、モジュールを実行させます。なければ 0回 を指定することで、実行させません。
module "one" {
count = 0 <= try(index(var.modules, "one"), -1) ? 1 : 0
source = "./modules/base"
name = "one"
}
for_each
を使う例では、var.modules
にキーワードの文字列が含まれている場合は、1つの要素(ここでは "selected"
)を持つ配列を set
に変換して実行させます。なければ、空の配列を指定することで実行させません。
module "two" {
for_each = toset(0 <= try(index(var.modules, "two"), -1) ? ["selected"] : [])
source = "./modules/base"
name = "two"
}
実行結果の確認は利用するモジュールに寄りますが、ここでは、outputs
でモジュールからの出力を出力して確認しましょう。
count
を利用した module.one
は、[*]
シンボルを利用して、各モジュール(といっても 1個 または 0個)の出力を参照することができます。
for_each
を利用した module.two
は、該当するキーワードが指定されたとき(for_each
に 1つ以上の要素が含まれる配列の set
を指定したとき)は問題なく参照できますが、キーワードが指定されていないとき(for_each
に空の配列の set
を指定したとき)は参照エラーになってしまうので、 try()
を利用して回避しています。
output "one_module_name" {
value = module.one[*].module_name
}
output "two_module_name" {
value = try(module.two["selected"].module_name, "Not found")
}
それでは、コードの準備ができたら、確認してみましょう。Terraform のコマンドを実行します。
terraform init
terraform plan
すると、このような出力になると示されます。期待通り!✨
Changes to Outputs:
+ one_module_name = [
+ "one",
]
+ two_module_name = "two"
さらに、モジュールを指定しない確認もしてみましょう。引数の modules
を空の配列にすると、それぞれ count
や for_each
で選ばれなくなるので、それぞれのモジュールは実行されません。
modules = [
# "one",
# "two",
]
terraform plan
の実行結果はこのようになります。
Changes to Outputs:
+ one_module_name = []
+ two_module_name = "Not found"
やったー、意図通り!すっきり!🙌✨
なお、エラーが出てしまう方は下記もご参考ください。
つまづきポイント
for_each
を利用したときの参照は少し独特です。個々のインスタンスを参照するには、アトリビュートではなく、配列のような書き方 <TYPE>.<NAME>["<KEY>"]
や module.<NAME>["<KEY>"]
として参照します。
toset()
を使って指定した場合
文字列のリストを module "example" {
for_each = toset(["a_member"])
...
}
output "output_example" {
value = module.example["a_member"].output_a_string
}
map を使って指定した場合
module "example" {
for_each = { a_key = "a_value" }
...
}
output "output_example" {
value = module.example["a_key"].output_a_string
}
エラーの例
アトリビュートの指定を誤ると、たとえば terraform plan
を実行した際には下記のようなエラーが表示されます。
output "output_example" {
value = module.example.output_a_string # <- Wrong reference
}
Error: Unsupported attribute
on outputs.tf line 6, in output "output_example":
6: value = module.example.output_a_string
|----------------
| module.example is object with 1 attribute "a_key"
This object does not have an attribute named "output_a_string".
Epilogue
このやり方でモジュールを選択させることにしたのは、このリポジトリのコードです。Azure PaaS をセキュアに接続するいくつかのサンプルを、モジュールに分けて紹介しています。ご興味ある方は是非ご参照ください😊
Discussion