TL;DR
GitLab EEや premiumユーザでなくても、こんなかんじのymlで出来ます。
.only_mr_create_branch:
before_script:
- git config --local user.name "ci_user"
- git config --local user.email "ci_user@hogehoge.xyz"
- git fetch
- git checkout -b testing "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
- git merge origin/"${CI_COMMIT_REF_NAME}"
.exec_only_mr:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
when: always
- when: never
unit_test_merged_branch:
extends: [ .exec_only_mr, .only_mr_create_branch]
stage: test
script:
# マージ後ブランチで依存関係を引き込まないとエラーになってしまう。
- npm install
- npm run test
はじめに
GitLab CIでは、rules
キーワードを用いることでマージリクエスト(MR)作成時・更新時に、MR元ブランチでのCI パイプラインの実行ができるようになっています。
しかし、このCIパイプラインの実行は、そのままではMR元ブランチを元にした実行となるため「MRがターゲットブランチにマージされた後」の状態でテスト結果を確認することが出来ません。
また、あまりケースは多くないかもしれませんが、masterマージ前はテストが通っていたが、マージ後にテストが通らなくなってしまう場合が考えられます。
MR後、masterでテストがFailすると、それなりの時間・コストが committer、レビュワーにかかってしまい、負荷になっている人も多いのではないでしょうか。
- committerに確認依頼、修正依頼 -> 再レビュー、再マージ、テスト確認...のループ
- そもそもレビュワーが手元にブランチを引き込んでローカルでmasterにマージしてみてテスト実行してよ
- ここまでやるのが理想かもしれませんが、レビュワーの時間・コストを考えると全件で実施するのは大変ですね...
このような問題の解決策として、GitLabではマージトレイン等の機能が提供されており、これを用いることでマージ後ブランチでのテスト結果をマージ前に把握することが可能となっています。
ただし、このマージ後ブランチでのテストは、GitLab Premium以上のライセンスでのみ設定でき、通常のCommunity Edition や Freeライセンスのユーザは使用することが出来ません。
今回は、このマージ後ブランチでのテストを頑張って自分で設定してみます。
設定方法
実現したいこと
まず、実現したいことの整理です
- MRの作成・更新時にテストが自動で行われてほしい
- テストの実施は、マージ後ブランチを仮想的に作成して実施されてほしい
- MRでのテストは、master(デフォルトブランチ)へ向けてのMRの場合にみ実施されてほしい
.gitlab-ci.ymlの設定
早速設定していきます。
1. MR時のみ実行されるように設定
公式の例 では、only/except
を使った例が書かれていますが、今回はrules
を使って設定していきます
.exec_only_mr:
rules:
# PipeLineの動作がMR契機の場合 かつ、 MRのターゲットがデフォルトブランチになっている場合のみ
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
when: always
# それ以外では実施してほしくない。
- when: never
また、公式のサンプル を参考にすると以下のようになります。
.exec_only_mr:
rules:
- if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
when: always
- when: never
あとは、MRのときにのみ実施されてほしいJobで、.exec_only_mr
をextends
指定してやれば実現が可能です。以下のようになります。
unit_test_exec_only_mr:
extends: [.exec_only_mr]
stage: test
script:
# すきなテストを実施。
- npm run test
これで、MR時のみに実行されてほしいJobが定義できました
2. MR時に、マージ後のブランチを作成してテストする
つぎに、MR後ブランチを作成してテストされるようにしていきます。
GitLab CI のpredefined variables でMR元、MRターゲットのブランチ名は取得できるので以下のようなスクリプトを実行することで実現ができそうです。
# remoteブランチを取得
$ git fetch
# MRターゲットブランチを testing という名前でチェックアウト
$ git checkout -b testing "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
# testingブランチに、MR元のコミットをマージしてくる
$ git merge origin/"${CI_COMMIT_REF_NAME}"
あとは、このスクリプトをテスト実施前に実行することで、マージ後ブランチでのテストが実施されそうです。ただし、ここで注意すべきなのが下記です
- リポジトリのCI/CD設定で
Git strategy
にgit clone
が指定されているか - jobを実施するDocker Imageでgitが使用できるか (Docker Executorの場合)
- jobを実施するExecutorで gitのユーザ名、メールアドレスが登録されているか
git clone
ではなく git fetch
を使用していると、毎回のJobが完全にクリーンな状態で行われるわけでなく、過去のjobで使用したブランチが残っている状態になってしまうため、スクリプトでのbranch作成時に、A branch named 'testing' already exists.
と怒られてしまいます。(2敗)
git clone
を使用するようにしていると、毎回クリーンな状態になるのでこちらが推奨されます。(ただ、リポジトリ自体が重たい、等の場合は毎回 git cloneするのも難しい場合があるかもしれません。この場合は要工夫です。)
alpineなどの軽量イメージを使用している場合、gitコマンドが使えないことが多々あります。
この場合は、gitがインストールされているイメージを使用してください。 Circle CIが公式で公開しているイメージ類は基本的にgitなどがインストールされてるのでこちらを使うのも手です。
-
cimg/node - Docker Image | Docker Hub
- ※追記 : circle-ciのimage類はcimgでリプレースが進行中なのでこっちをつかう。
- 最初からDocker Imageで使えるコマンド類はこちらも参考に。
- CircleCI-Public/cimg-base: The CircleCI Base (Ubuntu) Docker Convenience Image.
gitのユーザ名、メールアドレスの設定がされていない場合には、ブランチのマージ時に Please tell me who you are.
と注意が出ます。
今回のテスト実行ブランチは、remote側にCI PipeLineからPushしたりするわけではないので、適当なユーザ名、メールアドレスを設定すれば問題有りません。
Docker Executor以外で実行する場合、git configを globalで設定していると他のJobに影響があるかもしれませんので注意してください。
$ git config --local user.name "ci_user"
$ git config --local user.email "ci_user@hogehoge.xyz"
これらの注意点を踏まえると、スクリプトは下記のようになります。before scriptとして準備を定義しているので、このjobをextendsするか ymlアンカーで入れてやればJob実施前にブランチのセットアップが実行されます。
npm installのような依存関係準備を他のhidden job内のbefore scriptで実施している場合には競合して上書きされる可能性があるので注意です。
.only_mr_create_branch:
before_script:
- git config --local user.name "ci_user"
- git config --local user.email "ci_user@hogehoge.xyz"
# 確認のためにbranchのリストを出してみる
- git branch --list --all
- git fetch
- git checkout -b testing "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
- git merge origin/"${CI_COMMIT_REF_NAME}"
3. 1,2,の設定をあわせる
1, 2, の内容を踏まえてると以下のような設定になります。
.only_mr_create_branch:
before_script:
- git config --local user.name "ci_user"
- git config --local user.email "ci_user@hogehoge.xyz"
- git fetch
- git checkout -b testing "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
- git merge origin/"${CI_COMMIT_REF_NAME}"
.exec_only_mr:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
when: always
- when: never
unit_test_merged_branch:
extends: [ .exec_only_mr, .only_mr_create_branch]
stage: test
script:
# マージ後ブランチで依存関係を引き込まないとエラーになってしまう。
- npm install
- npm run test
まとめ
上記では、最低限の設定だけを行いました。他に、 「wip
やDraft
のMRのはテスト実施しない」、「masterマージ後でのみ実施するjobも定義する」、「テスト後、ビルドを実施してどこかに仮デプロイ」など必要な設定を追加することが必要になりそうです。
ここまで設定していくと長々としたymlになってしまうのである程度テンプレート化したほうが、使い回しやすいかもしれません。
premiumユーザに変更して公式提供の機能を使うのがやはり便利そうでですね
ご指摘、コメントなどあれば宜しくおねがいします。
参考
こちらの記事のほうがymlアンカーを使ってscriptをうまく書かれています。スマートだ、、、
GitLabの公式ドキュメントです。