✨
【Terrraformアイデア】ローカルパスモジュールを使っている場合にpaths-filter向けの設定ファイルを自動生成するスクリプト
Disclaimer
以下は備忘録的なもので、まだ長期的な運用実績もありません(combat proofがない)。
参照される場合はあくまでコード片として捉えてください。
前提
GitHub Actionsを使ってTerraformのCICDを構築しており、同時に dorny/paths-filter
アクションを使っている。
つまり、モノレポ構成で変更されたファイルごとにTerraform CICDを実行するrootモジュールが変わる。
加えて、local path moduleを使っている。
解決したい課題
あるlocal pathモジュールの中を変えた場合、それを参照しているrootモジュールでのみCICDを実行したい。これを実現するためfiltersオプションを使っているが、local pathが増えてくるとこの依存性の宣言の管理・更新が大変になってくる。
なので、この依存性の宣言を自動化したい。
ソリューション
主にhcleditを使って、自動的にローカルパスモジュールを列挙する。
#!/usr/bin/env bash
# 各terraform rootモジュールが依存する[ローカルパスモジュール](https://developer.hashicorp.com/terraform/language/modules/sources#local-paths)を
# すべて出力するスクリプト。
# すべてなので、チャイルドモジュール(= ローカルパスモジュール)が更にローカルパスモジュールのチャイルドモジュールを持っていた場合、
# それも出力される。
#
# 注意点:
# このスクリプトはモジュールの相互参照を考慮していません。
# もしそういった構造を対象にした場合、無限ループに入ります。
# ただ、対処自体はそれほど難しくはないので(処理済みモジュールのマーカーかリストを使えば良い)
# 必要になったら処理を追加しましょう
set -euo pipefail
# set -x # デバッグ用
FILE_NAME=depending-local-modules.txt
if [ "$#" -ne 1 ]; then
echo '引数としてGitリポジトリのrootディレクトリを指定してください' 1>&2
exit 1
fi
git_root_path=$1
############################################
# ルートモジュールのパスを列挙する
############################################
root_module_paths=$(
find $git_root_path -name .terraform.lock.hcl ! -path '*/.terraform/*' | # lockファイルのあるディレクトリを探し
xargs dirname | # 結果からファイル名を取り去って
sort # 最後ソートして仕上げ
)
############################################
# 以前の記録をすべて削除する
############################################
for root_module_path in ${root_module_paths[@]}; do
rm -rf $root_module_path/$FILE_NAME
done
############################################
# 再帰的に見ていって、2階層以上も含めたすべての依存local path moduleを列挙する
############################################
# 対象のrootモジュールに含まれるlocal pathモジュールのsourceパスを再帰的にすべて列挙する関数
list_paths_of_child_local_path_module_recursively () {
root_module_path=$1 # 対象のrootモジュールのパスを引数として渡す
TEMPFILE=$(mktemp) # 一時ファイル(のパス)を生成
child_paths=$(list_paths_of_child_local_path_module_shallow $root_module_path)
for child_path in ${child_paths[@]}; do
echo "$child_path" >> $TEMPFILE
done
for child_path in ${child_paths[@]}; do
grandchild_or_more_paths=$(list_paths_of_child_local_path_module_recursively $(realpath $root_module_path/$child_path))
# ↑ realpathを使っているのは、 ./abc/def/../ghi みたいなのを ./abc/ghi にするため
for grandchild_or_more_path in ${grandchild_or_more_paths[@]}; do
realpath --relative-to=$root_module_path "$root_module_path/$child_path/$grandchild_or_more_path" >> $TEMPFILE
done
done
cat $TEMPFILE | sort | uniq # 重複排除しつつ出力
}
# 対象のrootモジュールに直接含まれるlocal pathモジュールのsourceパスを列挙する関数
list_paths_of_child_local_path_module_shallow () {
root_module_path=$1 # 対象のrootモジュールのパスを引数として渡す
TEMPFILE=$(mktemp) # 一時ファイル(のパス)を生成
: > $TEMPFILE # 一時ファイルの中身を初期化
child_modules=$(
cat $root_module_path/*.tf | # 直下のすべてのtfファイルから
hcledit block list | # ブロック記述を列挙して
{ grep -i '^module' || true; } # その中でmoduleブロックに限定する
# ↑ true周りのごちゃごちゃはgrep結果が0件でもexit statusを非0にしないための回避策
# 詳細は https://stackoverflow.com/a/6550543/4006322
)
for child_module in ${child_modules[@]}; do
cat $root_module_path/*.tf | # 直下のすべてのtfファイルから
hcledit attribute get $child_module.source | # child moduleブロックのsource属性を取り出し
sed -e 's/"//g' | # 先頭と末尾の"を削除
{ grep '^\.' || true; } >> $TEMPFILE
# ↑ local path moduleは必ず '.' から始まるので ( https://developer.hashicorp.com/terraform/language/modules/sources#local-paths )
# それで絞り込んだのち、ファイルに書き出す
done
cat $TEMPFILE | sort | uniq # 重複排除しつつ出力
}
for root_module_path in ${root_module_paths[@]}; do
list_paths_of_child_local_path_module_recursively $root_module_path > $root_module_path/$FILE_NAME
# ↑ 列挙されたchild moduleのパスをファイルへ書き出す
done
############################################
# ルートモジュールごとの依存ローカルパスモジュールから
# dorny/paths-filter 用の設定ファイルを出力する
############################################
for root_module_path in ${root_module_paths[@]}; do
echo "$(realpath --relative-to="$git_root_path" "$root_module_path"):"
cat $root_module_path/$FILE_NAME | {
while IFS= read -r path_of_local_path_module; do
echo " - $(realpath --relative-to="$git_root_path" "$root_module_path/$path_of_local_path_module")/**/*"
done
}
done
メモ
これはGitHub Actions化すると便利。あとでその辺も整理したら改めて記事にする。
ただ、自動コミットされるのは個人的に良いプラクティスだと思っていないため、実行して差分があったら失敗するlinter方式で、実行自体は手元でやるようにするのがいいと2025/04/23時点では思っている。
Discussion