これは、なにをしたくて書いたもの?
Terraformでリソースを作るにあたり、条件によって作る・作らないをコントロールしたいなと。
解法としては繰り返しを使うみたいなのですが、自分でちゃんと追ってみましょうか、と。
環境とお題
今回の環境は、こちら。
$ terraform version Terraform v0.12.25 + provider.mysql v1.9.0
ProviderはMySQLを使い、データベースの作成をコントロールしてみましょう。MySQL自体は、8.0.20を使います。
雛形
まずは、定型的な雛形を用意。
main.tf
terraform { required_version = ">= 0.12.25" } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" version = "1.9.0" }
こちらをベースにカスタマイズしていきましょう。
データベースを複数作る/作らないをコントロールする
Terraformでリソースを複数作成するには、countまたはfor_eachを使用します。
- count: Multiple Resource Instances By Count
- for_each: Multiple Resource Instances Defined By a Map, or Set of Strings
どちらも、指定した数だけリソースを作成することができます。
この時、countを0にしたり、for_eachに空のコレクションを与えるようにすると、そのリソースを作成しないようにすることが
できます。
countで実現する
こんな感じで指定すると、この例だとデータベースが5つ作成されます。
locals { create_count = 5 } resource "mysql_database" "this" { count = local.create_count name = format("my_database%d", count.index + 1) } output "database_names" { value = mysql_database.this[*].name }
Outputとしては、作成したデータベース名を全部返すようにしましょう。
確認。
$ terraform apply -auto-approve mysql_database.this[3]: Creating... mysql_database.this[0]: Creating... mysql_database.this[2]: Creating... mysql_database.this[4]: Creating... mysql_database.this[1]: Creating... mysql_database.this[0]: Creation complete after 0s [id=my_database1] mysql_database.this[2]: Creation complete after 0s [id=my_database3] mysql_database.this[3]: Creation complete after 0s [id=my_database4] mysql_database.this[1]: Creation complete after 0s [id=my_database2] mysql_database.this[4]: Creation complete after 0s [id=my_database5] Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: database_names = [ "my_database1", "my_database2", "my_database3", "my_database4", "my_database5", ]
この時、countに指定する値を0にすると
locals { create_count = 0 }
データベースは作成されなくなります。
$ terraform apply -auto-approve Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: database_names = []
Outputは、空のlistになりましたね。
for_eachで実現する
続いて、for_eachで実現してみましょう。for_eachを使う場合は、list(をsetに変換する)かmapを使いますが、今回はlistで。
locals { database_names = ["database1", "database2"] } resource "mysql_database" "this" { for_each = toset(local.database_names) name = each.value }
この例では、データベースが2つできます。
$ terraform apply -auto-approve mysql_database.this["database1"]: Creating... mysql_database.this["database2"]: Creating... mysql_database.this["database1"]: Creation complete after 0s [id=database1] mysql_database.this["database2"]: Creation complete after 0s [id=database2] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
空のlistを渡した場合は
locals { database_names = [] }
データベースは作成されません。for_eachでも、countと同じことができますね。
$ terraform apply -auto-approve Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
ところで、for_eachの方にはOutputの定義がありません。
output "database_names" { value = mysql_database.this[*].name }
これは、先ほどの定義のままだと、こんな感じでエラーになるからです…。
Error: Unsupported attribute on main.tf line 33, in output "database_names": 33: value = mysql_database.this[*].name This object does not have an attribute named "name".
Splat Expressionsは、listだけみたいですねぇ…。
for_each and splat · Issue #22476 · hashicorp/terraform · GitHub
もしも指定するのなら、固定の要素にするか
output "database_names" { value = mysql_database.this["database1"].name }
listに変換することになります。
output "database_names" { value = values(mysql_database.this)[*].name }
単一のリソースを作る/作らないをコントロールする
ここまでは「複数個のリソースができる」ことを前提に書いてきましたが、「作るとしてもひとつ、もしくは作らない」という
パターンを考えてみたいと思います。
countで実現する
こういうのは、countでやった方がわかりやすいでしょうか?あと、作る・作らないのコントロールは、boolですると良さそうですよね。
locals { create = true } resource "mysql_database" "this" { count = local.create ? 1 : 0 name = "my_database" }
こんな感じで、作成するなら1、しないなら0にしておけばOKです。
count = local.create ? 1 : 0
ただ、Outputにはちょっと困ります。
こんな感じの指定だとlistになるので、呼び出し元がlistとして扱うことになってしまいます。
output "database_names" { value = mysql_database.this[*].name }
単一のリソースを作る前提の場合、これは嬉しくないですね。
かといって、こんな書き方をすると
output "database_name" { value = mysql_database.this[0].name }
リソースを作成する時はいいのですが
$ terraform apply -auto-approve mysql_database.this[0]: Creating... mysql_database.this[0]: Creation complete after 0s [id=my_database] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = my_database
作成しない場合は
locals { create = false }
エラーになってしまいます。
$ terraform apply -auto-approve Error: Invalid index on main.tf line 42, in output "database_name": 42: value = mysql_database.this[0].name |---------------- | mysql_database.this is empty tuple The given key does not identify an element in this collection value.
さあ、どうしたものでしょう。
いろいろ見ていると、concatで使ってなんとかするみたいですね。
concat - Functions - Configuration Language - Terraform by HashiCorp
output "database_name" { value = concat(mysql_database.this.*.name, [null])[0] } ### もしくは output "database_name" { value = concat(mysql_database.this.*.name, [""])[0] }
コミュニティモジュールとか見ていると、後者([""]指定)の方を使うみたいですね。
これで、最初のlistが空の時は、後続のダミーのlistの要素が使われるという感じに。
細工が必要ですねぇ。
for_eachで実現する
for_eachで実現する場合は、こんな感じでしょうか。
locals { create = true } resource "mysql_database" "this" { for_each = local.create ? toset(["my_database"]) : toset([]) name = each.value } output "database_name" { value = concat(values(mysql_database.this)[*].name, [null])[0] # もしくは #value = concat(values(mysql_database.this)[*].name, [""])[0] }
単一のリソースを作るかどうか制御するのが悩みどころだったのですが、こんな感じにすればできるみたいなので、
覚えておきましょう。