Terraform 0.11→0.12で追加された新機能
こんにちは佐伯です。
先日Terraform 0.12がリリースされ、Terraform 0.12へのアップグレードについて以下エントリを投稿しましたが、今回はTerraform 0.11からTerraform 0.12で追加された新機能について確認してみました。
Terraform 0.12の新機能
First-class expressions
Terraform 0.12以前ではリソースの設定に変数や他のリソースの値を指定する場合、ami = "${var.ami_id}"
のように"${}"
で囲う必要がありました。Terraform 0.12ではami = var.ami_id
のように定義できます。
Terraform 0.11
resource "aws_instance" "web" { instance_type = "t3.small" ami = "${var.ami_id}" }
Terraform 0.12
resource "aws_instance" "web" { instance_type = "t3.small" ami = var.ami_id }
For expressions
for
が追加され、listやmapの要素をループして参照ができるようになりました。Builf-in Functionと組み合わせて変換したり、if
を含めてフィルタリングもできます。var.list
を定義して、terraform console
で確認してみました。
variable list { type = list(string) default = [ "a", "b", "c", ] }
for
を囲む括弧の種類によって生成させるコレクションのタイプが決まります。以下の例では[]
で囲っているのでlistが生成されます。
> [for s in var.list : upper(s)] [ "A", "B", "C", ]
同じvar.list
からmapを生成する場合は{}
で囲い、=>
記号で区切ります。
> {for s in var.list : s => upper(s)} { "a" = "A" "b" = "B" "c" = "C" }
その他にfor
にif
を含めてフィルタリングも可能です。
> [for s in var.list : upper(s) if s !="a"] [ "B", "C", ]
ユースケースとしては以下の様にoutput
で作成したリソースのインスタンスIDとプライベートIPのmapを出力したりする場合などでしょうか。
output "instance_private_ip_addresses" { value = { for instance in aws_instance.example: instance.id => instance.private_ip } }
詳しくはドキュメントを確認ください。
ただし、やりすぎると可読性が低下するので個人的にはあんまり使わない方がいい気がしてます。
Dynamic configuration blocks
リソースによっては設定をブロックで定義するものがあります。例えばaws_security_groupのingress, egreessなどはブロックで定義します。
Terraform 0.11
resource "aws_security_group" "web_service" { name = "web-service" description = "Allow HTTP/HTTPS inbound traffic" vpc_id = "${aws_vpc.main.id}" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
Terraform 0.12
Terraform 0.12ではfor_each
を使ってブロック内の引数を動的に生成できます。上記の例をDynamic configuration blocksで書くと以下のようになります。
variable web_service_ports { type = list(number) description = "list of web service ports" default = [80, 443] } resource "aws_security_group" "web_service" { name = "web-service" description = "Allow HTTP/HTTPS inbound traffic" vpc_id = aws_vpc.main.id dynamic "ingress" { for_each = var.web_service_ports content { from_port = ingress.value to_port = ingress.value protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
mapを動的ブロックで使用もでき、その場合は<動的ブロック名>.key
、<動的ブロック名>.value
で定義します。ただし、動的ブロックも使いすぎると可読性が低下するので使い所は注意が必要だと思います。
Generalised "splat" operator
Terraform 0.11以前ではcount
を使ってリソースを複数作成した際などに${aws_instance.foo.*.id}
で参照ができました。Terraform 0.12からはfor
でループするか、aws_instance.foo.[*].id
で参照します。
例えば以下は同じ式となります。
# For expression [for o in var.list : o.id] # Splat expression var.list[*].id
複合型のリストから属性やインデックスの参照も可能です。以下は同じ式となります。
# For expression [for o in var.list : o.interfaces[0].name] # Splat expression var.list[*].interfaces[0].name
Nullable argument values
Terraform 0.12では引数にnullを割り当てて未設定にできます。プロバイダーのデフォルト動作を保持しながら、モジュールの呼び出し側で値の上書きが可能なモジュールを作成できます。
以下はモジュールのコード例です。モジュール呼び出し側がoverride_private_ip
を指定しなければ、プライベートIPは動的に割り当てられ、指定すれば明示的にプライベートIPを設定できます。
variable "override_private_ip" { type = string default = null } resource "aws_instance" "example" { # ... (other aws_instance arguments) ... private_ip = var.override_private_ip }
Rich types in module inputs variables and output values
Terraform 0.11以前でも単純なlistやmapには対応していましたが、listとmapがネストされた変数は使える場合もあるけど制限が多かったです。Local Valuesが追加されてからは困ることはなかったのですが、モジュールの場合はvariable
を使わざるを得えないので引数が多くなりがちでした。
Terraform 0.12では様々な型制約を変数に指定できます。以下はコレクション型の例です。
variable list-string { type = list(string) default = ["a", "b", "c"] } variable list-number { type = list(number) default = [1, 2, 3] } variable map-string { type = map(string) default = { key1 = "a", key2 = "b", key3 = "b"} } variable map-number { type = map(number) default = { key1 = 1, key2 = 2, key3 = 3} }
以下は構造型の例です。
variable object { type = object({ number = number string = string }) default = { number = 1 string = "aaa" } } variable tuple { type = tuple([ string, number, string, number ]) default = ["a", 1, "b", 2] }
コレクション型と構造型を合わせた指定も可能です。
variable "ipset" { type = list(object({ value = string type = string })) default = [ { value = "1.1.1.1/32", type="IPV4" }, { value = "2.2.2.2/32", type="IPV4" }, ] }
Resource and module object values
属性なしで作成したリソースを参照してリソースやモジュール全体の情報を参照できるようになりました。以下の様にモジュールでVPCとサブネットを作成し、モジュール呼び出し側へ作成したリソースの全ての情報を返すことができます。
output "vpc" { value = aws_vpc.my_vpc } output "subnet" { value = aws_subnet.my_subnet }
output "vpc" { value = module.network.vpc } output "subnet" { value = module.network.subnet }
Extended template syntax
テンプレートシンタックスが追加されました。${...}
で単純に文字列に変換したり、ディレクティブ(%{...}
)では%{ if <BOOL>} / %{ else } / %{ endif }
でテンプレートを2つのパターンに変換したり、%{ for <NAME> in <COLLECTION> }/ %{ endfor }
で要素をループしてテンプレートを変換できます。
# Template syntax "Hello, ${var.name}!" # Template syntax with if expression "Hello, %{ if var.name != "" }${var.name}%{ else }unnamed%{ endif }!" # Template syntax with for expression <<EOT %{ for ip in aws_instance.example.*.private_ip } server ${ip} %{ endfor } EOT
jsondecode
and csvdecode
interpolation functions
jsondecode
、csvdecode
関数が追加されました。ユースケースがあまり思いつかないのですが、ファイルから値取りたい場合とかですかね...?
# jsondecode > jsondecode("{\"hello\": \"world\"}") { "hello" = "world" } > jsondecode("true") true # csvdecode > csvdecode("a,b,c\n1,2,3\n4,5,6") [ { "a" = "1" "b" = "2" "c" = "3" }, { "a" = "4" "b" = "5" "c" = "6" } ]
Revamped error messages
エラーメッセージが改善されました。例えばタイプがlistのvariableにリスト以外の値を定義した場合、Terraform 0.11では以下の出力でした。
Terraform 0.11
$ terraform validate Error: Error parsing /path/to/variable.tf: At 2:13: Unknown token: 2:13 IDENT list
Terraform 0.12
Terraform 0.12ではどの部分の何がエラーなのかまで出力してくれるようになりました。
$ terraform validate Error: Invalid default value for variable on variable.tf line 3, in variable "string": 3: default = 1 This default value is not compatible with the variable's type constraint: list of any single type required.
Structual plan output
IAM PolicyなどJSONで定義するリソースにおいて、terraform plan
の出力結果が見やすくなりました。テキストでは少しわかりにくいので画像で貼ります。
参考リンク
- terraform/CHANGELOG.md at v0.12.0 · hashicorp/terraform
- HashiCorp Terraform 0.12 Preview
- terraform-guides/infrastructure-as-code/terraform-0.12-examples at master · hashicorp/terraform-guides
最後に
個人的にはfor
やfor_each
、テンプレートシンタックスは使いすぎると自分で書いたのに読み直すと「なにやってるんだこれ...」ってなりかねないのであんまり使わない方針ではありますが、抽象化を目的としたモジュールで使うのはありかなーって思ってます。CHANGELOGの新機能部分のみ確認しましたが、その他にも色々細かい改善がされているようです。まだ全部移行できてないので頑張って0.12にアップグレードしていくぞ!