tech.guitarrapc.cóm

Technical updates

TerraformのString DirectivesとHeredocを使っていい感じの文字列を生成する

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と組み合わせると、複数行の文字列をいい感じに出力できます。ぜひ使ってみてください。


  1. TerraformでKubernetesリソースの管理までやってるならoutput不要ですが、全部のリソースをTerraformで管理することは稀ですよね。