Terraformのplan結果をmarkdownとして整形するツール、terraform-j2mdの紹介

こんにちは、@r_takaishi です。最近のおすすめYouTubeチャンネルは Namibia: Live stream in the Namib Desert です。今回は、Terraformのplan結果をmarkdownで整形するツールである reproio/terraform-j2md について紹介します。

どのようなツールなのか

まずはterrafororm-j2mdがどのようなツールなのかお見せします。まず、以下のようなTerraformのコードを用意します。

terraform {
  required_providers {
    env = {
      source = "tchupp/env"
      version = "0.0.2"
    }
  }
}

provider "env" {
  # Configuration options
}

resource "env_variable" "test" {
  name = "foo"
}

terraform planを実行します。 -out オプションを使うことでplanの結果をファイルに出力することができます。これがポイントです。

% terraform plan -out plan.tfplan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # env_variable.test will be created
  + resource "env_variable" "test" {
      + id    = (known after apply)
      + name  = "foo"
      + value = (sensitive value)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Saved the plan to: plan.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "plan.tfplan"

このplan.tfplanファイルを terraform show コマンドに渡すことで、plan結果をjson形式に変換することができます。便利ですね。

% terraform show -json plan.tfplan 
{"format_version":"1.0","terraform_version":"1.1.8","planned_values":{"root_module":{"resources":[{"address":"env_variable.test","mode":"managed","type":"env_variable","name":"test","provider_name":"registry.terraform.io/tchupp/env","schema_version":0,"values":{"name":"foo"},"sensitive_values":{}}]}},"resource_changes":[{"address":"env_variable.test","mode":"managed","type":"env_variable","name":"test","provider_name":"registry.terraform.io/tchupp/env","change":{"actions":["create"],"before":null,"after":{"name":"foo"},"after_unknown":{"id":true,"value":true},"before_sensitive":false,"after_sensitive":{"value":true}}}],"configuration":{"provider_config":{"env":{"name":"env","version_constraint":"0.0.2"}},"root_module":{"resources":[{"address":"env_variable.test","mode":"managed","type":"env_variable","name":"test","provider_config_key":"env","expressions":{"name":{"constant_value":"foo"}},"schema_version":0}]}}}

そして、jsonに変換したplan結果をterraform-j2mdに渡すと、変更内容がmarkdown形式で出力されます。

%  terraform show -json plan.tfplan | ./terraform-j2md
### 1 to add, 0 to change, 0 to destroy, 0 to replace.
- add
    - env_variable.test
<details><summary>Change details</summary>

```diff
# env_variable.test will be created
@@ -1 +1,3 @@
-null
+{
+  "name": "foo"
+}
```

</details>

プレビューするとこのような見た目となり、追加・変更・削除などの概要とそれぞれの詳細を見ることができます。

背景

Reproではインフラの管理にTerraformを使っています。CIによってコミット毎にterraform planを実行するのですが、plan結果をRubyスクリプトで簡単に成形し、PullRequestにコメントするような仕組みをこれまでは使っていました。

しかし、Terraformの特定のバージョン以降、最後に実行された terraform applyの外側で行われた変更(以降、Resource Driftsと呼称します)がplan結果に含まれるようになりました。

plan時にResource Driftsが分かること自体は何も問題はありません。しかし、plan結果をPullRequestにコメントしているのは、そのPullRequestによってどのような変更が加えられるのかを端的に確認するためです。 Resource Driftsはその目的には不要な情報です。また、Resource Driftsの量が多いとPullRequestによる変更が埋もれてしまい、レビューを行いにくいという問題もありました。

また、plan結果を色づけする程度でほぼそのままコメントしているため、ぱっと見てどのような変更が加わるのか認識しにくいという課題もありました。

これらの課題を解決するため、筆者が所属する開発基盤改善チームでは reproio/terraform-j2md というツールを作成しました。

なぜ作ったのか

まず、既存のRubyスクリプトを改修し、Resource Driftsをコメントから取り除くことを考えました。しかし、既存のスクリプトは構造化されていないplan結果を加工しています。ここにResource Driftsを取り除くコードや差分のサマライズを行うコードを加えるとなるど、将来plan結果のフォーマットが変わった際にうまくパースできない可能性が考えられました。

次に、既存ツールで似たようなことができないかを検討しました。suzuki-shunsuke/tfcmt というツールの存在は知っていたので、これを用いて実現できないかと検証を行いましたしかし、以下のような点が要件に合いませんでした。

  • Reproでは環境毎にtfstateを分けており、CIではそれらについてterraform planを並列で実行している。これらのplan結果をtfcmtでPullRequestにコメントすると、コメントが複数追加され、やや見づらい
  • 既存の仕組みでは、過去のplan結果はコメントを消し、最新のplan結果のみ残るようになっている。しかし、tfcmtでは過去のコメント削除は行われない
  • テンプレートでコメントの形式をカスタマイズできるが、plan結果を整形した結果を標準出力に出すことができないため、手元で試しづらい
  • 構造化されていないplan結果をパースしており、フォーマット変更時にうまく動かない可能性がある

なお、tfcmtがコメントするplan結果の形式はかなり見やすいと感じたため、今回作ったツールでは非常に参考にさせていただきました。

他に方法はないか調べる中で、 いつのまにかTerraformにplan結果をjsonに変換する機能が追加されていることに気がつきました。これが使えそうだったので、独自ツールの開発に着手しました。

reproio/terraform-j2md はGo製のツールです。既存のRubyスクリプトを改修してplan結果のjsonを加工することもできたのですが、 hashicorp/terraform-json というplan結果のjsonを扱うための型定義がライブラリとして公開されていた点、開発を担当したメンバーのGoへの習熟という点でGoを選択しました。

terraform-j2mdの持つ機能

terraform-j2mdはplan結果のjsonを標準入力から渡すとmarkdown形式に変換して標準出力に出す、という単純な機能しか持ちません。plan結果のファイルを直接読むことはできないし、PullRequestにコメントする機能もありません。ReproではCIの中で平行して複数のtfstateに対してplanを実行しますが、そのplan結果をmarkdownに変換した後、一つのテキストにマージしてPullRequestにコメントしています。マージ処理はBashスクリプトを使っているし、PullRequestへのコメントもそれまで使っていたスクリプトを少し改修して使っています。

このように単純な機能しか持たないterraform-j2mdですが、内部的には hashicorp/terraform-json という terraform show -json の結果をパースするための型情報を扱うライブラリを使っています。これを使うことでplan結果を独自にパースする必要がなくなるメリットがありますが、terraform-jsonが型としてサポートしていない情報は扱えないというデメリットもあります。例えば、2022−06−30時点ではTerraform v1.1で追加されたmovedブロック関連の情報を扱うことができません。つまり、PullRequestにもmovedブロックに関する情報をコメントとして表示することがまだできないということです。

まとめ

terraform planの結果をPullRequestにコメントするため、plan結果のjsonmarkdownに変換するツール reproio/terraform-j2md について紹介しました。現時点では対応できない機能があったり、うまくmarkdownに変換できていない差分もあることが分かっています。今後も、エンジニアがより楽にTerraformを扱えるように改善を続けていきます。