Jsonnetでatlantis.yamlを書く
Atlantis v0.35での破壊的な変更
カンムではAtlantisというアプリケーションを使ってPull Requestでterraformを実行するようにしています。
Atlantisではatlantis.yamlというファイルでリポジトリの設定を管理していますが、最近のアップデートでyamlライブラリの変更により、既存のYAMLアンカーを使ったテンプレートでエラーが出るようになりました。
YAML anchor configurations in atlantis.yaml files that worked in Atlantis 0.34.0 now fail with duplicate key errors in Atlantis 0.35.0. This is a breaking change that affects users who utilize YAML anchors and aliases to reduce duplication in their Atlantis configurations.
The root cause is the migration from gopkg.in/yaml.v3 to github.com/goccy/go-yaml in version 0.35.0, which introduced stricter YAML parsing that now detects duplicate keys that were previously allowed.
Atlantis v0.35 has breaking changes around YAML anchor · Issue #5665 · runatlantis/atlantis
テンプレートを使ったatlantis.yamlの書き方は以下のようになります。
projects:
- &template
name: template
dir: template
autoplan:
when_modified:
- "**/*.tf"
- ".terraform.lock.hcl"
- <<: *template
name: ue1-prod-titan
dir: ./terraform/titan
workspace: ue1-prod
- <<: *template
name: ue1-stage-titan
dir: ./terraform/titan
workspace: ue1-stage
- &templateの設定が<<: *templateの箇所に差し込むことができるため、when_modifiedなどの重複する設定を一箇所にまとめでDRYに書くことができます。
しかし、Atlantis v0.35から使われるようになった github.com/goccy/go-yaml ではキーの重複を許していない実装のため、上記の書き方ではエラーが発生します。
parsing atlantis.yaml: [10:5] duplicate key "name"
8 | - ".terraform.lock.hcl"
9 | - <<: *template
> 10 | name: ue1-prod-titan
^
11 | dir: ./terraform/titan
12 | workspace: ue1-prod
書き方を変えればエラーを回避できるようですが、同様の問題に困っている方が多いようで、変更のRevertやyaml/go-yamlの利用についてのコメントが寄せられています。
YAMLテンプレートの問題
カンムでもAtlantisをv0.35にあげたのですが、幸いこの変更の影響を受けることはありませんでした。これはatlantis.yamlでのテンプレート利用にすこし問題があったため、Jsonnetからatlantis.yamlに変換するようにしていたためです。
問題の一つはアンカーでの配列のマージです。
when_modifiedは「どのファイルが変更されたらterraform planを実行するのか」という設定ですが、トリガにしたいファイルはプロジェクトによって異なり、*.tfだけで十分のこともあれば、*.sqlを含める必要があることもあります。
テンプレートでは「すべてのプロジェクトに共通するトリガファイル」を書き、個別のプロジェクトで必要なファイルを追加するようにしたいのですが、以下のような書き方はできません。
projects:
- &template
name: template
dir: template
autoplan:
when_modified: &when_modified
- "**/*.tf"
- ".terraform.lock.hcl"
- <<: *template
name: ue1-prod-titan
dir: ./terraform/titan
workspace: ue1-prod
autoplan:
when_modified: *when_modified
- "**/*.sql" # このような書き方はできない
YAMLでの配列の結合についていろいろと調べたのですが、実装特有のやり方を除いて汎用的な方法を見つけることはできませんでした。
もう一つの問題は些細なことですが、- &templateのアンカーの部分はテンプレートとしてだけ機能するわけではなく実態があることです。
テンプレートを使ったatlantis.yamlをJSONに展開してみると "name": "template"というプロジェクトが存在することが分かります。
{
"projects": [
{
"autoplan": {
"when_modified": [
"**/*.tf",
".terraform.lock.hcl"
]
},
"dir": "template",
"name": "template"
},
{
// 略
templateというディレクトリが存在しないため単に無視されているだけですが、あまりきれいなやり方とは思えず、また、トラブルの元になりそうな懸念もありました。
Jsonnetで書く
AtlantisにはPre Workflow Hooksというフックがあり、ワークフロー実行前に処理を差し込むことができます。ドキュメントにはこのフックを使ってatlantis.yamlを生成する例が書かれています。
カンムでは以下の設定とスクリプトを使ってatlantis.jsonnetからatlantis.yamlを生成しています。
pre_workflow_hooks:
- run: repo-config-generator.sh
description: Generating atlantis.yaml
#!/bin/bash
set -ex
# atlantis.jsonnetがなかったらatlantis.yamlを使う
if [ -e atlantis.jsonnet ]; then
exec 9>atlantis.jsonnet.lock
flock -w 60 9 # 念のため排他制御
jsonnet atlantis.jsonnet > atlantis.json
# yqでYAMLに変換
# https://github.com/mikefarah/yq
yq -MPo yaml atlantis.json > atlantis.yaml.new
mv -f atlantis.yaml.new atlantis.yaml
rm -f atlantis.json
fi
atlantis.jsonnetは以下のようになります。
local project = {
branch: '/main/',
autoplan: {
when_modified: [
'.terraform.lock.hcl',
'**/*.tf',
],
},
};
{
version: 3,
projects: [
project {
name: 'hello',
dir: './project/hello',
},
],
}
local projectはただの変数なのでatlantis.yamlに展開されることはありません。
もし、個別のプロジェクトでwhen_modifiedを書き換えたい場合、Jsonnetを機能で配列をマージすることができます。
{
version: 3,
projects: [
project {
name: 'hello',
dir: './project/hello',
autoplan+: {
when_modified+: [ // project の when_modified に追加される
'**/*.sql',
],
},
},
],
}
Discussion