これは、なにをしたくて書いたもの?
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を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だけみたいですねぇ…。
Splat Expressions
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]
}
単一のリソースを作るかどうか制御するのが悩みどころだったのですが、こんな感じにすればできるみたいなので、
覚えておきましょう。