dbt の CI をリポジトリごとに YAML で複製していると、Slim CI のマニフェスト受け渡しや PR コメントの体裁がすぐズレる。差分ビルド用の manifest.json をどこから取るか、失敗したコマンドをどこまで走らせるか、バージョンをどこで固定するか。同じ判断がワークフローの数だけ増える。
dbt-jobs は、その判断を GitHub Actions の composite action に閉じ込めた。呼び出し側はジョブタイプとコマンドを渡す。
- uses: ta93abe/dbt-jobs@v1
with:
type: ci
adapter: snowflake
command: |
dbt build --select state:modified+
deferral: main
post-pr-comment: true
ジョブタイプで失敗の扱いを分ける
入力 type は ci / merge / deploy の三つだけ。ここが本体の分岐になる。
ci(PR) は、コマンドを途中で止めない。複数行の command を順に実行し、失敗があっても残りを走らせてから結果を集約する。PR では「最初のモデルで落ちたのでテスト結果が見えない」より、その PR で壊れた範囲を一度に見たい。任意で run_results.json を PR コメントにまとめる。
merge は fail-fast。マージ後のビルドが通ったら manifest.json と run_results.json を dbt-artifacts として上げる。これが次の PR の Slim CI の材料になる。
deploy は merge と同じ失敗検知に加え、必要なら dbt source freshness を挟む。本番に近いジョブほど早く止める。
PR では情報を集め、本番ではすぐ止める。シェルの set +e / set -e をジョブタイプに対応させた、というより、その方針を action の契約にした。
Slim CI は artifact の受け渡し
deferral にブランチ名を渡すと、そのブランチの成功ランから dbt-artifacts を探す。見つかれば prod-manifest に展開し、コマンドに state: セレクタがあるときだけ --defer --state を足す。
マニフェストが無いときは差分セレクタを外してフルビルドに落とす。Slim CI を「必ず効く」前提にしない。初回や期限切れ artifact でも CI 自体は通せるようにした。
CI スキーマは ci-schema-prefix と PR 番号から作る。Snowflake なら SNOWFLAKE_SCHEMA=dbt_pr_job_<n> のように PR ごとに隔離する。本番スキーマを PR から触らない。
フラグとバージョンは呼び出し側に書かせない
command の各行は dbt から始まる許可リストだけ通す。build / run / test など。任意のシェルは受けない。
そのうえで、まだ付いていないフラグだけ足す。
--profiles-dir/--project-dirは常に--target/--threadsは指定があるとき--defer --stateは deferral とマニフェストとstate:が揃ったとき
dbt-core のバージョンは、入力 → pyproject.toml → requirements.txt → setup.cfg / setup.py / Pipfile の順で拾う。プロジェクトがすでに固定している版に合わせる。action 側で最新を押し付けると、ローカルと CI の差が出る。
アダプタはいま Snowflake。正規化は actions/setup に寄せてあり、パッケージ名の対応を足せば増やせる前提にした。認証は workflow の環境変数と、リポジトリ内の profiles.yml が env_var() を読む形。秘密を action に埋め込まない。
呼び出し側に残すもの
残すのは「どのジョブか」「何を走らせるか」「差分の基準ブランチは何か」だけにした。YAML の複製を減らすためであって、dbt プロジェクトの中身を隠すためではない。
ギャラリーの dbt-jobs にも同じプロジェクトを置いている。こちらは設計のメモ、あちらは作品としての入口。