Terraformの実行結果をoutputで出力するとき、mapやlistから単一文字列(複数行)をHeredocとして出力したいことがあります。 今回はDirectivesとHeredocを使っていい感じの文字列を生成する方法を紹介します。
いい感じの文字列とは
たとえばEKSのIAM Role for ServiceAccount(IRSA)では、サービスアカウントのannotationsにIAM Role ARN設定します。
annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-foo-role
Terraformでも、配列から次のようなarnが並んだいい感じの文字列を出力したいですね。1
# 各arnは arn:aws:iam::012345678901:role/irsa-foo-role みたいな文字列 locals { iam_role_arns = [ aws_iam_role.irsa_foo_role.arn, aws_iam_role.irsa_bar_role.arn, aws_iam_role.irsa_baz_role.arn, aws_iam_role.irsa_qux_role.arn, aws_iam_role.irsa_quux_role.arn, aws_iam_role.irsa_corge_role.arn, ] } output "annotations" { values = ... # なにかしらの処理 } # こういう出力をしたい annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-foo-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-bar-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-baz-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-qux-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-quux-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-corge-role
いい感じじゃない文字列出力とは
ロール毎にannotationsとoutputを出力すると、outputの数が増えていい感じじゃないです。簡単なんですけど、可読性を担保できる限りoutputの数は少ないほうがいいです。
# ×: こうではない output "annoations" { for_each = toset(local.iam_role_arns) value = <<EOT annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-foo-role EOT } # 複数のoutputが出力される annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-foo-role annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-bar-role annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-baz-role annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-qux-role annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-quux-role annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-corge-role
また、配列を出力するとTerraform Cloudでoutputを解読するのが難しすぎるので、いい感じじゃないです。
# ×: こうでもない output "annoations" { value = local.iam_role_arns } # コンソールのoutputはそこまででもないが、Terraform Cloudは配列のoutputはつながってしまうので読み解くのが難しすぎる eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-foo-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-bar-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-baz-role
Terraformでlist/mapと複数行文字列を組み合わせる
どうやったらいい感じの出力を作れるか、構文を探ってみましょう。カギになるのは、「複数行の文字列」と「文字列中の変数展開」です。
複数行の文字列とHeredoc
Terraformで複数行の文字列を出力する時はHerecocを使います。ヒアドキュメントはどの言語にもあるので説明は不要ですね。
TerraformのHeredocは<<任意の文字列から始まり任意の文字列の間に設定した文字列が複数行の文字列として出力されます。
output "heredoc" { value = <<EOT hello world EOT } # 出力 hello world
Heredocあるあるなのがインデントです。例えば上の例の、helloやworldの前にスペースをつけると、出力時もスペースがついてしまいます。任意の文字列を行頭にしないといけないのはBashでもあるあるで不格好、読みにくさがあります。
output "heredoc" { value = <<EOT hello world EOT } # 出力 hello world
これを回避するにはIndented Heredocsを使います。<<-で開始するとHeredocの終端となる任意の文字列の開始位置がヒア文字列の開始インデントと解釈されます。出力を見ると終端文字列のインデントに合わせて出力されています。
output "heredoc" { value = <<-EOT hello world EOT } # 出力 hello world
これで複数行の文字列出力にはIndented Heredocsを使うとよさそうなのがわかりました。
文字列の中で変数を展開する - String Interpolation
Terraformで文字列中に変数を仕込むときは、string interpolationがよく使われます。よく見るこれです。
locals { name = "world" } output "interpolation" { value = "hello ${local.name}" } # 出力 hello, world
しかしこれではmapを受けてHeredocの中でいい感じに出力するのはちょっといけてないです。メンテがつらい未来。
locals { iam_role_arns = [ aws_iam_role.irsa_foo_role.arn, aws_iam_role.irsa_bar_role.arn, aws_iam_role.irsa_baz_role.arn, aws_iam_role.irsa_qux_role.arn, aws_iam_role.irsa_quux_role.arn, aws_iam_role.irsa_corge_role.arn, ] } output "interpolation" { value = <<-EOT annotations: eks.amazonaws.com/role-arn: ${local.iam_role_arns[0]} eks.amazonaws.com/role-arn: ${local.iam_role_arns[1]} eks.amazonaws.com/role-arn: ${local.iam_role_arns[2]} eks.amazonaws.com/role-arn: ${local.iam_role_arns[3]} eks.amazonaws.com/role-arn: ${local.iam_role_arns[4]} eks.amazonaws.com/role-arn: ${local.iam_role_arns[5]} EOT }
文字列の中で変数を展開する - STring Directives
Terraformで文字列に変数を埋め込む方法は他にもあります。String Directivesを使うと文字列中に変数や式を埋め込むことができます。Goのテンプレートの雰囲気ですね。 以下の例は、String Interpolationではやりにくい処理が、String Directivesを使うと簡単に書けることがわかります。
locals { name = "world" } output "directives" { value = ""Hello, %{ if var.name != "" }${var.name}%{ else }unnamed%{ endif }!"" } # 出力 Hello, world!
もちろんforもかけます。%{for 式}から%{ endfor }で囲むと間の出力が繰り返されます。
<<EOT %{ for ip in aws_instance.example[*].private_ip } server ${ip} %{ endfor } EOT
もう答えは見えましたね。
複数行文字列とfor処理をいい感じに書く
さて、String DirectivesとHeredocを使っていい感じの文字列を出力する方法がわかりました。実際にoutput.tfを書いてみると、狙ったいい感じの出力ですね!
locals { iam_role_arns = [ aws_iam_role.irsa_foo_role.arn, aws_iam_role.irsa_bar_role.arn, aws_iam_role.irsa_baz_role.arn, aws_iam_role.irsa_qux_role.arn, aws_iam_role.irsa_quux_role.arn, aws_iam_role.irsa_corge_role.arn, ] } output "annoations" { value = <<-EOT annotations: %{for arn in local.iam_role_arns}eks.amazonaws.com/role-arn: ${arn} %{endfor} EOT } # 出力 annotations: eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-foo-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-bar-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-baz-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-qux-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-quux-role eks.amazonaws.com/role-arn: arn:aws:iam::012345678901:role/irsa-corge-role
まとめ
String Interpolationがぱっと思いつきやすいのですが、String Directivesは式のお供に協力、かつ読み心地もいい感じです。Heredocと組み合わせると、複数行の文字列をいい感じに出力できます。ぜひ使ってみてください。
- TerraformでKubernetesリソースの管理までやってるならoutput不要ですが、全部のリソースをTerraformで管理することは稀ですよね。↩