ステージ名のタイプミス一つでデプロイジョブが静かに消えた
目標
GitLabの設定スキーマで検証するツールをゲートで包んでチームのポリシーを加え、コミット前フック・パイプラインの最初のジョブ・includeを解いたリポジトリ全体の検証に掛け、マージリクエストのレビュー用に、2つのコミットの間のジョブ一覧の変化を取り出します。
なぜ重要なのか
パイプライン設定のエラーは、たいてい静かです。ステージ名の綴りミスやインデント1つのずれが、ジョブを消したりパイプラインの生成を止めたりしますが、その事実はプッシュしたあとで初めて明らかになります。検証を人の目の代わりにツールに任せつつ、ツールが何を捕まえて何を見逃すのかを知っておかないと、その隙間をポリシーで埋められません。同じ検査をコミット前とパイプラインの最初のジョブの2か所に置くのは、ローカルのフックは飛ばせるからで、レビューでは、テキストのdiffよりも「どのジョブが生まれ、消え、自動になるのか」のほうが重要な情報です。
ステップ
/root/glci-gateをgitリポジトリにし(.gitignoreに.gitlab-ci-local/を置く)、/root/glci-gate/ci-validate.sh <설정파일>を作ってください。そのファイル1つを一時的なgitリポジトリの.gitlab-ci.ymlとして入れ、gitlab-ci-local --listで検証して、拒否されればINVALID <이유>(ツールの出力で意味のある最初の行)と1、通ればVALIDと0で終了します(プレースホルダーは、順に設定ファイル、理由です)。.gitlab-ci.ymlには下のfilesにある正常な設定を置いて、コミットしてください。採点ツールは、存在しないステージ・許容外のwhen・存在しないneedsの対象・YAML文法エラーのサンプルで確認します。- ci-validate.shが、ツールの検証を通ったファイルに、チームのポリシーを2つ追加で適用するようにしてください。1つのジョブに
rulesとonly/exceptが一緒にあればPOLICY <잡> rules-with-only-except、artifactsにpathsがあるのにexpire_inがなければPOLICY <잡> artifacts-without-expire_inを、ジョブ名順に1行ずつ出力して2で終了します(プレースホルダーはジョブ名です)。ポリシーをすべて守っていればVALIDと0です。隠しジョブ(ドットで始まる)と予約キー(stages・variables・default・include・workflowなど)は、ジョブではありません。 /root/glci-gate/hooks/pre-commitを作ってコミットし、同じファイルを.git/hooks/pre-commitにコピーして実行権限を与えてください。フックは、ステージングされた.gitlab-ci.ymlがあるときだけ、そのステージングされた内容(git show :.gitlab-ci.yml)をci-validate.shで検証し、通らなければ理由を標準エラー出力に出力して、コミットを止めます。採点ツールは、コピーで間違った設定とポリシー違反の設定をコミットしてみて、設定と無関係なファイルのコミットを止めないかも確認します。/root/glci-gate/broken.ymlに下のfilesにある間違った設定をそのまま置き、ci-validate.shで一度に1つずつ明らかになる問題を直して、/root/glci-gate/fixed.ymlを作ってください。ジョブ名(build・unit・deploy)と各ジョブのscriptは変えません。fixed.ymlはVALIDである必要があり、unitはbuildを待つ必要があり、deployはmainで手動承認(allow_failureはfalse)として作られる必要があります。2つのファイルともコミットします(フックは.gitlab-ci.ymlだけを見ます)。.gitlab-ci.ymlにジョブci-lint(stage.pre、bash ci-validate.sh .gitlab-ci.yml)を追加して、コミットしてください。実行すると、ci-lintがVALIDで通り、残りのジョブが動くはずです。採点ツールは、コピーでポリシー違反(有効期限のないアーティファクト)を入れた設定で実行して、ci-lintが失敗しbuildが始まらないかを確認します。/root/glci-gate/ci-validate-repo.sh <저장소>を作ってください。リポジトリを一時的なコピーにして(フックは除く)コミットしたあと、gitlab-ci-local --previewでincludeが解かれたマージ後の設定を得て、失敗すればINVALID <이유>と1、成功すればマージ後の設定をci-validate.shに渡して、その結果(VALIDは0・POLICYは2)をそのまま出力します(プレースホルダーは、順にリポジトリ、理由です)。このリポジトリで実行するとVALIDになるはずです。採点ツールは、includeしたファイル側にエラーやポリシー違反を入れたコピーで確認します。/root/glci-gate/ci-diff.sh <저장소> <옛커밋> <새커밋> [브랜치]を作ってください。2つのコミットをそれぞれ一時的な作業ツリーに取り出し、ブランチ(デフォルトはmain)のパイプラインとして計算するように--variable CI_COMMIT_BRANCH=<브랜치>を与えたgitlab-ci-local --list-csv-allで、ジョブ名とwhenを得て、ジョブ名順にADDED <잡>、REMOVED <잡>、CHANGED <잡> <옛when>-><새when>を出力します(プレースホルダーは、順にリポジトリ、旧コミット、新コミット、ブランチ、ジョブ名、旧when、新whenです)。元のリポジトリの作業ツリーとブランチには触れず、一時的な作業ツリーは削除します。採点ツールは、コピーでジョブを追加し、削除し、whenを変えたコミットを作って確認します。
参考
- このVMにはGitLabサーバーもランナーもなく、gitlab-ci-local 4.75.1が.gitlab-ci.ymlをGitLabと同じルールで解釈し、シェルでジョブを実行します。
image:を書くとDockerで動かそうとするので使いません。保護変数・マスキング・CI_JOB_TOKEN・ランナーのタグ・マージリクエストパイプラインの作成はサーバーの機能なので、ここでは再現されません。 - 実行はリポジトリのルートで
gitlab-ci-local --shell-isolation --no-artifacts-to-source(ジョブごとに別の作業ディレクトリを使い、アーティファクトをリポジトリに書き戻さない)、ジョブ一覧はgitlab-ci-local --list-csv-all、解釈された設定はgitlab-ci-local --previewです。gitlab-ci-localはgitが追跡しているファイルだけをジョブに渡すので、ファイルを作ったらgit addしてください。採点ツールはリポジトリをコピーし、すべてのファイルをコミットしたあとで、同じツールでもう一度実行します。 - gitlab-ci-local 4.75.1の検証(実測): whenの許容値・存在しないステージ・存在しないneeds・存在しないextends・YAMLの文法・scriptなしは拒否し、rulesとonlyの併用・有効期限のないアーティファクトは通します。GitLabのCI Lint(プロジェクトのパイプラインエディター・Lint API)はサーバーの機能なので、ここからは呼び出せません。
- フックがコミットを止めるかを試すときは、コピーのリポジトリで行ってください。元のリポジトリで止められたコミットは、ステージングだけが残ります。
- Validate GitLab CI/CD configuration(CI Lint)・CI Lint API・CI/CD YAML syntax reference・GitLabの設定検証のソース(processable.rb)・gitlab-ci-local
スキーマと参照をツールに検証させる
/root/glci-gateをgitリポジトリにし(.gitignoreに.gitlab-ci-local/を置く)、/root/glci-gate/ci-validate.sh <설정파일>を作ってください。そのファイル1つを一時的なgitリポジトリの.gitlab-ci.ymlとして入れ、gitlab-ci-local --listで検証して、拒否されればINVALID <이유>(ツールの出力で意味のある最初の行)と1、通ればVALIDと0で終了します(プレースホルダーは、順に設定ファイル、理由です)。.gitlab-ci.ymlには下のfilesにある正常な設定を置いて、コミットしてください。採点ツールは、存在しないステージ・許容外のwhen・存在しないneedsの対象・YAML文法エラーのサンプルで確認します。
gitlab-ci-localは、GitLabの設定スキーマでまず検証し、ステージ・needs・extendsの参照が実際にあるかも見ます。問題があれば終了コードが0ではありません。出力にはリモートリポジトリの案内のようなノイズが混ざるので、取り除いて最初の理由だけを残してください。
ツールが通すものをチームのポリシーで捕まえる
ci-validate.shが、ツールの検証を通ったファイルに、チームのポリシーを2つ追加で適用するようにしてください。1つのジョブにrulesとonly/exceptが一緒にあればPOLICY <잡> rules-with-only-except、artifactsにpathsがあるのにexpire_inがなければPOLICY <잡> artifacts-without-expire_inを、ジョブ名順に1行ずつ出力して2で終了します(プレースホルダーはジョブ名です)。ポリシーをすべて守っていればVALIDと0です。隠しジョブ(ドットで始まる)と予約キー(stages・variables・default・include・workflowなど)は、ジョブではありません。
このツールは、rulesとonlyを混ぜたジョブと、有効期限のないアーティファクトを通します(実測)。GitLabサーバーは前者をkey may not be used with rulesで拒否します(GitLabのソースの設定検証)。ツール1つの判定をゲートのすべてだと信じず、違いを知っていれば、その分をポリシーで埋めます。
間違った設定はコミットの時点で止める
/root/glci-gate/hooks/pre-commitを作ってコミットし、同じファイルを.git/hooks/pre-commitにコピーして実行権限を与えてください。フックは、ステージングされた.gitlab-ci.ymlがあるときだけ、そのステージングされた内容(git show :.gitlab-ci.yml)をci-validate.shで検証し、通らなければ理由を標準エラー出力に出力して、コミットを止めます。採点ツールは、コピーで間違った設定とポリシー違反の設定をコミットしてみて、設定と無関係なファイルのコミットを止めないかも確認します。
フックは、作業ツリーのファイルではなく、コミットされる内容(インデックス)を見る必要があります。直したあとaddしていない状態でコミットすると、作業ツリーは問題なくても、コミットされるのは古い内容です。.git/hooksはリポジトリにアップロードされないので、チームで共有するには、追跡される場所に置いて、インストール方法を案内します。
4か所の間違った設定を直す
/root/glci-gate/broken.ymlに下のfilesにある間違った設定をそのまま置き、ci-validate.shで一度に1つずつ明らかになる問題を直して、/root/glci-gate/fixed.ymlを作ってください。ジョブ名(build・unit・deploy)と各ジョブのscriptは変えません。fixed.ymlはVALIDである必要があり、unitはbuildを待つ必要があり、deployはmainで手動承認(allow_failureはfalse)として作られる必要があります。2つのファイルともコミットします(フックは.gitlab-ci.ymlだけを見ます)。
ツールは最初のエラーで止まるので、直すたびにもう一度動かして、次のエラーを見ます。ステージ名の綴りミス、存在しないジョブを指すneeds、許可されないwhenの値、rulesとonlyの併用、有効期限のないアーティファクトが混ざっています。
パイプラインの最初のジョブが自分の設定を検証する
.gitlab-ci.ymlにジョブci-lint(stage.pre、bash ci-validate.sh .gitlab-ci.yml)を追加して、コミットしてください。実行すると、ci-lintがVALIDで通り、残りのジョブが動くはずです。採点ツールは、コピーでポリシー違反(有効期限のないアーティファクト)を入れた設定で実行して、ci-lintが失敗しbuildが始まらないかを確認します。
フックはローカルで飛ばせる(--no-verify)ので、サーバー側にも同じ検査が必要です。.preステージに置けば、ほかのすべてのジョブより先に動くので、間違った設定でランナーの時間を使う前に止まります。検査スクリプトはリポジトリの中にあるので、ジョブからそのまま呼び出せます。
includeしたファイルまで合わせて検証する
/root/glci-gate/ci-validate-repo.sh <저장소>を作ってください。リポジトリを一時的なコピーにして(フックは除く)コミットしたあと、gitlab-ci-local --previewでincludeが解かれたマージ後の設定を得て、失敗すればINVALID <이유>と1、成功すればマージ後の設定をci-validate.shに渡して、その結果(VALIDは0・POLICYは2)をそのまま出力します(プレースホルダーは、順にリポジトリ、理由です)。このリポジトリで実行するとVALIDになるはずです。採点ツールは、includeしたファイル側にエラーやポリシー違反を入れたコピーで確認します。
ファイル1つだけの検証は、includeしたファイルのエラーを見られません。そのファイルはコピーにないからです。--previewは、include・extends・アンカーをすべて解決した結果を出すので、その結果にポリシーを適用すれば、複数のファイルに散らばった設定も一度に検査されます。
レビュアーにパイプラインがどう変わるかを見せる
/root/glci-gate/ci-diff.sh <저장소> <옛커밋> <새커밋> [브랜치]を作ってください。2つのコミットをそれぞれ一時的な作業ツリーに取り出し、ブランチ(デフォルトはmain)のパイプラインとして計算するように--variable CI_COMMIT_BRANCH=<브랜치>を与えたgitlab-ci-local --list-csv-allで、ジョブ名とwhenを得て、ジョブ名順にADDED <잡>、REMOVED <잡>、CHANGED <잡> <옛when>-><새when>を出力します(プレースホルダーは、順にリポジトリ、旧コミット、新コミット、ブランチ、ジョブ名、旧when、新whenです)。元のリポジトリの作業ツリーとブランチには触れず、一時的な作業ツリーは削除します。採点ツールは、コピーでジョブを追加し、削除し、whenを変えたコミットを作って確認します。
設定ファイルのdiffは、include・extends・rulesが混ざると、実際に何が変わるのかを教えてくれません。パイプラインが作るジョブ一覧どうしを比べれば、「このマージで本番デプロイが自動になる」といった変化が1行で明らかになります。git worktree add --detachでコミットを別のディレクトリに取り出せますが、その状態にはブランチがなく、ブランチ条件のrulesがすべて抜けるので、ブランチを変数で渡します。その警告は--ignore-predefined-varsで消します。