nightly タグを付けたらリリースが出てしまった
目標
ブランチ・タグ・ファイルの存在・リモートとの変更によって、ジョブが一覧に入ったり抜けたりすることを、状況を変えながら確認し、ルールの順序とルールが決める変数で、デプロイのポリシーを設定に刻み込みます。
なぜ重要なのか
rulesは、ジョブが始まるときに問う条件ではなく、パイプラインが作られるときに一覧を決めるルールです。最初のマッチだけが適用されるので、項目の順序がそのままポリシーであり、順序を間違えると、エラーなしでデプロイが消えたり、見当違いのタグでリリースが出たりします。changesは何と比べるかによって結果が変わり、条件と値(デプロイ先)を離して置くと、2つが食い違う日が来ます。こうした違いは、設定を読むだけではよく見えないので、状況を変えて実行してみる必要があります。
ステップ
/root/glci-rulesをgitリポジトリにして、.gitignoreに.gitlab-ci-local/を置いてください。.gitlab-ci.ymlにstages[build, deploy]、条件のないジョブunit(build、echo "unit log=$LOG_LEVEL")、そして$CI_COMMIT_BRANCH == "main"のときだけ作られるdeploy-staging(deploy、echo staging)を置き、mainブランチにコミットしてください。採点ツールは、コピーでfeature/loginブランチに移って、deploy-stagingが一覧から消えるかを確認します。- ジョブ
release-notes(deploy、echo notes)を、$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/のときだけ作られるように追加して、コミットしてください。gitlab-ci-localはgitタグを読まないので、--variable CI_COMMIT_TAG=v1.2.0でタグパイプラインを真似ます。v1.2.0なら一覧にあり、nightlyやタグなしならないはずです。 - ジョブ
publish(deploy、echo publish)のrulesを、3つの項目にしてください。タグがあればwhen: on_success、mainブランチならwhen: manualとallow_failure: false、それ以外はwhen: neverです。コミットします。一覧で、mainならmanual(allowFailureはfalse)、タグ変数があればon_success、featureブランチならなし、になるはずです。 - ジョブ
docker-build(build、echo docker)をrules: - exists: [Dockerfile]で追加して、コミットしてください。このリポジトリには、まだDockerfileを作りません。採点ツールは、コピーにDockerfileを入れたときだけジョブができるかを確認します。 /root/glci-rules-origin.gitにbareリポジトリを作ってoriginとして登録し、mainをpushしたあとでgit remote set-head origin mainを実行してください。そのあと、ジョブdocs-build(build、echo docs)をrules: - changes: ["docs/**/*"]で追加してコミットし、もう一度pushします。採点ツールは、コピーでdocsの下を直したブランチと、ほかのファイルだけを直したブランチを作り、前者の場合にだけdocs-buildができるかを確認します。- ジョブ
deploy(deploy、echo "target=$DEPLOY_TARGET")を追加してください。rulesは、タグならvariables: {DEPLOY_TARGET: production}、mainならvariables: {DEPLOY_TARGET: staging}です。コミットしてpushします。mainで実行するとtarget=staging、タグ変数を与えるとtarget=productionが出力されるはずです。 .gitlab-ci.ymlの一番上にworkflow: rules:を置いてください。mainならvariables: {LOG_LEVEL: warn}、それ以外は(when: always)variables: {LOG_LEVEL: debug}です。コミットしてpushします。mainではunitのログがunit log=warn、featureブランチではunit log=debugになるはずです。
参考
- この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の違い(実測): gitタグをCI_COMMIT_TAGとして読まないため、
--variable CI_COMMIT_TAG=...で真似ます。rules:changesはoriginのデフォルトブランチと比較し、compare_toは無視します。workflow:rulesでパイプラインを作らない動作と、manualジョブが後ろのステージを止める動作は再現しません。 - ブランチを切り替えて試すときは、コピーか新しいブランチで行い、採点の前にmainに戻ってコミットしておいてください。
- Specify when jobs run with rules・workflow・Predefined CI/CD variables・CI/CD YAML syntax reference
mainでだけステージングデプロイを作る
/root/glci-rulesをgitリポジトリにして、.gitignoreに.gitlab-ci-local/を置いてください。.gitlab-ci.ymlにstages[build, deploy]、条件のないジョブunit(build、echo "unit log=$LOG_LEVEL")、そして$CI_COMMIT_BRANCH == "main"のときだけ作られるdeploy-staging(deploy、echo staging)を置き、mainブランチにコミットしてください。採点ツールは、コピーでfeature/loginブランチに移って、deploy-stagingが一覧から消えるかを確認します。
rulesはパイプラインを作るときに評価されます。条件が1つも合わなければ、そのジョブは「never」として一覧から抜けます。ブランチを切り替えて、gitlab-ci-local --list-csv-allでwhenの列を比べてみてください。
タグ名の形まで見てリリースノートを作る
ジョブrelease-notes(deploy、echo notes)を、$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/のときだけ作られるように追加して、コミットしてください。gitlab-ci-localはgitタグを読まないので、--variable CI_COMMIT_TAG=v1.2.0でタグパイプラインを真似ます。v1.2.0なら一覧にあり、nightlyやタグなしならないはずです。
=~は正規表現の比較です。タグ変数はタグパイプラインでだけ埋まるので、その存在だけを見ると、nightlyのような一時的なタグにもリリースが出てしまいます。正規表現はスラッシュで囲みます。
最初のマッチだけが適用されるので、順序がそのままポリシーになる
ジョブpublish(deploy、echo publish)のrulesを、3つの項目にしてください。タグがあればwhen: on_success、mainブランチならwhen: manualとallow_failure: false、それ以外はwhen: neverです。コミットします。一覧で、mainならmanual(allowFailureはfalse)、タグ変数があればon_success、featureブランチならなし、になるはずです。
上から読んでいき、最初に合った項目1つだけを使います。条件のないwhen: neverを途中に置くと、その下の項目は二度と読まれず、何のエラーも出ません。manualジョブを本当の承認ゲートとして使うには、allow_failure: falseを一緒に置きます。
Dockerfileがあるリポジトリでだけイメージを作る
ジョブdocker-build(build、echo docker)をrules: - exists: [Dockerfile]で追加して、コミットしてください。このリポジトリには、まだDockerfileを作りません。採点ツールは、コピーにDockerfileを入れたときだけジョブができるかを確認します。
existsは、リポジトリにそのパスのファイルがあるかを見ます。複数のリポジトリが同じテンプレートをincludeするとき、リポジトリごとに設定を直さずに、該当するジョブだけを有効にするために使います。
ドキュメントが変わったブランチでだけドキュメントをビルドする
/root/glci-rules-origin.gitにbareリポジトリを作ってoriginとして登録し、mainをpushしたあとでgit remote set-head origin mainを実行してください。そのあと、ジョブdocs-build(build、echo docs)をrules: - changes: ["docs/**/*"]で追加してコミットし、もう一度pushします。採点ツールは、コピーでdocsの下を直したブランチと、ほかのファイルだけを直したブランチを作り、前者の場合にだけdocs-buildができるかを確認します。
changesは、「何と比べた変更か」が核心です。gitlab-ci-localはリモートのデフォルトブランチ(origin/main)と比較するので、リモートが必要です。GitLabのブランチパイプラインは直前のpushと、マージリクエストパイプラインはターゲットブランチと比較します。
どのルールに引っかかったかがデプロイ先を決める
ジョブdeploy(deploy、echo "target=$DEPLOY_TARGET")を追加してください。rulesは、タグならvariables: {DEPLOY_TARGET: production}、mainならvariables: {DEPLOY_TARGET: staging}です。コミットしてpushします。mainで実行するとtarget=staging、タグ変数を与えるとtarget=productionが出力されるはずです。
rulesの項目のvariablesは、その項目に引っかかったときだけジョブに入ります。条件と値を1か所に置けば、「タグなのにstagingに出てしまった」のような不一致が、構造的になくなります。
パイプライン全体の値はworkflowで決める
.gitlab-ci.ymlの一番上にworkflow: rules:を置いてください。mainならvariables: {LOG_LEVEL: warn}、それ以外は(when: always)variables: {LOG_LEVEL: debug}です。コミットしてpushします。mainではunitのログがunit log=warn、featureブランチではunit log=debugになるはずです。
workflow:rulesは、パイプラインを作るかどうかと、パイプライン全体に入る変数を決めます。ジョブごとに同じ条件を繰り返さなくて済みます。マージリクエストとブランチのパイプラインが重なるのを防ぐ場所もここですが、その動作はGitLabサーバーでしか確認できません。