TT Lab
はじめる
学ぶ 学習パス コース

GitLab CI/CD

before_script を継承したのに一行消えた

TT Labで続きを見る

目標

include・extends・!reference・YAMLアンカー・default・spec:inputsで設定の重複を取り除き、それぞれのマージのルールがジョブに実際に何を残すかを、実行ログとマージ後の設定で確認します。

なぜ重要なのか

パイプラインファイルが大きくなると、共通部分を切り出して再利用するようになります。ところが、再利用の仕組みごとにマージのルールが違います。ハッシュはマージされ、配列は置き換わり、アンカーはファイルの中だけで、defaultは誰も指定しなかったときだけ使われます。このルールを知らないと、「引き継いだと信じていた準備コマンドが1つのジョブでだけ抜ける」という事故が、エラーなしで起きます。入力を受け取るテンプレートは、同じジョブを環境ごとにコピーしなくて済むようにしてくれますが、許容範囲を決めておかないと、綴りを間違えた環境名もそのままジョブになります。

ステップ

  1. /root/glci-reuseをgitリポジトリにしてください。/root/glci-reuse/ci/templates.ymlに隠しジョブ.base(before_scriptはecho base-setupの1行、変数はLOG_LEVEL: info)を置き、.gitlab-ci.ymlはinclude: - local: ci/templates.ymlでそのファイルを取り込んだうえで、stages[build, test]とジョブbuild(stageはbuild、extends: .base、scriptはecho "LOG=$LOG_LEVEL")を置きます。gitlab-ci-local --shell-isolation --no-artifacts-to-sourceで実行し、buildのログにbase-setupとLOG=infoが出力されるか確認してください。
  2. ジョブを2つ追加してください。test(stageはtest)は.baseをextendsしながら、変数をLOG_LEVEL: debug、PYTEST: "1"と書き、scriptにecho "LOG=$LOG_LEVEL PYTEST=$PYTEST"を置きます。lint(stageはtest)は.baseをextendsしながら、自分のbefore_script(echo lint-setup)とscriptecho lintを置きます。実行して、testのログにbase-setupとLOG=debug PYTEST=1があり、lintのログにはlint-setupだけがあってbase-setupはないことを確認してください。
  3. ジョブpackage(stageはbuild)を追加してください。extendsは使わず、before_scriptを!reference [.base, before_script]とecho package-setupの2項目で書き、実行するとbase-setupの次にpackage-setupが出力されるようにします。scriptはecho packageです。
  4. /root/glci-reuse/broken-anchor.ymlでci/templates.ymlをincludeし、このファイルで定義されていないアンカー*base_varsを、variablesに<<: *base_varsとして使うジョブbuildを置いてください。gitlab-ci-local --file broken-anchor.yml --listの出力を/root/glci-reuse/anchor-error.txtに保存します。そして.gitlab-ci.ymlには、同じファイルの中でアンカー&docs_vars(DOCS_OUT: public)を定義し、ジョブdocs(stageはtest)のvariablesに<<: *docs_varsとDOCS_FMT: htmlを置いて、scriptのecho "$DOCS_OUT/$DOCS_FMT"でpublic/htmlが出力されるようにしてください。
  5. .gitlab-ci.ymlのトップレベルにdefault:でbefore_scriptecho default-setupを置いてください。そしてジョブreport(stageはtest、scriptはecho report)は、inherit: default: falseでデフォルトを引き継がないようにします。実行して、docsのログにはdefault-setupが、reportのログには準備の行が1つもなく、build・testのログには依然としてbase-setupが出力されていることを確認してください。
  6. /root/glci-reuse/ci/deploy.ymlを、spec:inputsヘッダーのあるテンプレートにしてください。入力envはstaging・productionのどちらかだけを許可し、replicasは数値で、デフォルトは1です。ヘッダーの後ろ(---)には、ジョブdeploy-$[[ inputs.env ]](stageはdeploy)がecho "deploy <env> replicas=<replicas>"を実行するように書きます。.gitlab-ci.ymlはstagesにdeployを追加し、このファイルを2回includeします。stagingはデフォルトのreplicas、productionはreplicas 3です。
  7. gitlab-ci-local --previewの出力を/root/glci-reuse/expanded.ymlに保存してください。このファイルには、include・extends・!reference・アンカー・default・inputsがすべて解決された結果が入っている必要があります。採点ツールは、リポジトリのコピーで同じコマンドを実行して内容が同じかどうかと、いくつかの値を確認します。

参考

テンプレートファイルをincludeしてextendsで引き継ぐ

/root/glci-reuseをgitリポジトリにしてください。/root/glci-reuse/ci/templates.ymlに隠しジョブ.base(before_scriptはecho base-setupの1行、変数はLOG_LEVEL: info)を置き、.gitlab-ci.ymlはinclude: - local: ci/templates.ymlでそのファイルを取り込んだうえで、stages[build, test]とジョブbuild(stageはbuild、extends: .base、scriptはecho "LOG=$LOG_LEVEL")を置きます。gitlab-ci-local --shell-isolation --no-artifacts-to-sourceで実行し、buildのログにbase-setupとLOG=infoが出力されるか確認してください。

includeは、複数のファイルを1つの設定にまとめてから解釈します。名前がドットで始まるジョブは一覧に載らず、引き継がれる断片としてだけ使われます。gitlab-ci-localはgitが追跡しているファイルだけを見るので、新しいファイルはgit addします。

ハッシュはマージされ、配列は丸ごと置き換わる

ジョブを2つ追加してください。test(stageはtest)は.baseをextendsしながら、変数をLOG_LEVEL: debug、PYTEST: "1"と書き、scriptにecho "LOG=$LOG_LEVEL PYTEST=$PYTEST"を置きます。lint(stageはtest)は.baseをextendsしながら、自分のbefore_script(echo lint-setup)とscriptecho lintを置きます。実行して、testのログにbase-setupとLOG=debug PYTEST=1があり、lintのログにはlint-setupだけがあってbase-setupはないことを確認してください。

extendsは深いマージです。variablesのようなハッシュはキー単位でマージされ、引き継いだキーの上に上書きされますが、before_scriptのような配列はマージされず、ジョブが書いたもので丸ごと置き換わります。

配列をマージしたいときは!reference

ジョブpackage(stageはbuild)を追加してください。extendsは使わず、before_scriptを!reference [.base, before_script]とecho package-setupの2項目で書き、実行するとbase-setupの次にpackage-setupが出力されるようにします。scriptはecho packageです。

!referenceは、ほかのジョブ(隠しジョブを含む)の特定のキーの値を、その場に差し込むタグです。includeしたファイルの断片も指せるので、配列が丸ごと置き換わるextendsの限界を補います。

アンカーはファイルの境界を越えられない

/root/glci-reuse/broken-anchor.ymlでci/templates.ymlをincludeし、このファイルで定義されていないアンカー*base_varsを、variablesに<<: *base_varsとして使うジョブbuildを置いてください。gitlab-ci-local --file broken-anchor.yml --listの出力を/root/glci-reuse/anchor-error.txtに保存します。そして.gitlab-ci.ymlには、同じファイルの中でアンカー&docs_vars(DOCS_OUT: public)を定義し、ジョブdocs(stageはtest)のvariablesに<<: *docs_varsとDOCS_FMT: htmlを置いて、scriptのecho "$DOCS_OUT/$DOCS_FMT"でpublic/htmlが出力されるようにしてください。

アンカーとエイリアスは、YAMLパーサーがファイル1つを読むときに処理します。includeはそのあとにGitLabがマージする段階なので、別のファイルのアンカーはすでに消えたあとです。ファイルをまたぐ再利用は、extendsか!referenceで行います。

defaultは下地の値であり、extendsとinheritに押される

.gitlab-ci.ymlのトップレベルにdefault:でbefore_scriptecho default-setupを置いてください。そしてジョブreport(stageはtest、scriptはecho report)は、inherit: default: falseでデフォルトを引き継がないようにします。実行して、docsのログにはdefault-setupが、reportのログには準備の行が1つもなく、build・testのログには依然としてbase-setupが出力されていることを確認してください。

defaultは、何も指定していないジョブに埋められるグローバルな下地の値なので、extendsでbefore_scriptをすでに受け取ったジョブでは使われません。inheritは、デフォルトを受け取るかどうかをジョブごとに切るスイッチです。

入力を受け取るテンプレートで環境別のジョブを生み出す

/root/glci-reuse/ci/deploy.ymlを、spec:inputsヘッダーのあるテンプレートにしてください。入力envはstaging・productionのどちらかだけを許可し、replicasは数値で、デフォルトは1です。ヘッダーの後ろ(---)には、ジョブdeploy-$[[ inputs.env ]](stageはdeploy)がecho "deploy <env> replicas=<replicas>"を実行するように書きます。.gitlab-ci.ymlはstagesにdeployを追加し、このファイルを2回includeします。stagingはデフォルトのreplicas、productionはreplicas 3です。

specヘッダーは、includeするときに渡せる値の形と許容範囲を決めます。テンプレートの中では$[[ inputs.이름 ]]で値を使います(プレースホルダーは入力の名前です)。許可リストにない値を渡すと、パイプラインが作られる前に拒否されます。

GitLabが見ることになるマージ後の設定を取り出しておく

gitlab-ci-local --previewの出力を/root/glci-reuse/expanded.ymlに保存してください。このファイルには、include・extends・!reference・アンカー・default・inputsがすべて解決された結果が入っている必要があります。採点ツールは、リポジトリのコピーで同じコマンドを実行して内容が同じかどうかと、いくつかの値を確認します。

設定が複数のファイルに散らばるほど、「このジョブが実際に何を実行するのか」をファイルだけを見て答えるのは難しくなります。マージされた結果をレビューに貼っておけば、マージのルールを頭の中で計算しなくて済みます。GitLab画面のパイプラインエディターにも、同じ機能(全体の設定を見る)があります。