こんにちは。ホワイトプラスでエンジニアをやっている古賀です。
lenet ではCIの一つとしてCircleCI を使っており、静的解析・フォーマットチェック・ユニットテスト(フロントエンド・バックエンド)などを実行しています。
CIにpush するたびに全てのテストを実行すると、変更差分に関係のないテストが実行されてしまい、コードベースが大きくなると非効率な面が際立ちます。
昨年従量課金プランへ移行するのに合わせて、主にモノレポで採用されている差分ビルド(変更差分に関係あるテストのみ回す)を取り入れ、CIの効率化を図っています。
ここでは、CircleCIのセットアップワークフローを使って差分ビルドを行う方法を紹介します。
セットアップワークフローとは
- 簡単に言うと、ユーザーが動的に設定ファイルを生成し、それに基づいて実行内容を決定できるというものです。
- この機能以前も、設定ファイルに書かれた内容を条件分岐によって実行内容を変えることはできましたが、より柔軟に実行内容を設定することができます。
- 使用するにはドキュメントに記載の通り、CircleCI Web上で
Enable dynamic config using setup workflows
を有効にし、.circleci/config.yml
にsetup:true
を追加します。 - これでセットアップワークフローが使えるようになったので、次は設定ファイルの動的生成を実装します。
設定ファイルの動的生成
- lenet のフロントエンドアプリケーションは、ディレクトリを分けて管理されています。
- CIでどのような処理を行うかはアプリケーション毎に異なるため、それぞれにCirlceCI設定ファイルを用意して、モジュール管理者が自由に設定できるようにしたいですね。
- そして、変更があったコードが属するモジュールのCirlceCI設定ファイルに基づいてCIを実行する、というのがやりたいことです。
. ├── app-1 │ └── .circleci │ └── config.yml ├── app-2 │ └── .circleci │ └── config.yml └── app-3 └── .circleci └── config.yml
- これは設定ファイル分割とパスフィルタリングを組み合わせたもので、CircleCI support ページで紹介されている circle-makotom/circle-advanced-setup-workflow を参考にしました。
- やっていることは大きく3つあり、「変更差分の検知」「設定ファイルのマージ」「ワークフローの実行依頼」です。実装概要を以下に説明します。
step1. 変更差分の検知
- プロジェクトルートにある
.circleci/config.yml
のworkflows
を以下のように記述します。 - これは、インラインOrbで定義している
config-splitting/setup-dynamic-config
ジョブを呼び出し、必要なパラメータを渡しています。base-revision
:ファイル差分を検出する比較元ブランチ名shared-config
:共通設定を定義したファイルパスmodules
:変更差分を検出したいモジュールパス
workflows: setup-workflow: jobs: - config-splitting/setup-dynamic-config: base-revision: origin/master shared-config: .circleci/shared-config.yml modules: | app-1 app-2 app-3
- 呼び出された
config-splitting/setup-dynamic-config
では、origin/master
と作業ブランチの差分ファイルをgit diff --name-only
で取得し、modules
に設定されたディレクトリパスに該当するかどうかを調べ、該当するディレクトリパスを一時ファイルに書き出します。 - 例えば、
app-1
とapp-2
に差分ファイルがあった場合、以下のような中身を持つ一時ファイルが生成されます。
app-1 app-2
step2. 設定ファイルのマージ
- 次に、一時ファイルに書き込まれた各ディレクトリパスの末尾に、
/.circleci/config.yml
を追記し、更に先ほどshared-config
パラメータで指定した共通設定ファイルのパスを追記します。
app-1/.circleci/config.yml app-2/.circleci/config.yml .circleci/shared-config.yml
- そして、YAMLファイルのマージツールであるyqを使って、一時ファイルに書かれたファイルをマージし、設定ファイルを生成します。
- マージの例を見てみましょう。
# app-1/.circleci/config.yml jobs: app1-job: docker: - image: cimg/base:2023.03 steps: - common-echo - run: name: app1 echo command: echo 'app1-job' workflows: app1-workflow: jobs: - app1-job
# app-2/.circleci/config.yml jobs: app2-job: docker: - image: cimg/base:2023.03 steps: - common-echo - run: name: app2 echo command: echo 'app2-job' workflows: app2-workflow: jobs: - app2-job
# .circleci/shared-config.yml version: 2.1 commands: common-echo: steps: - run: name: common-echo command: echo 'common-echo'
# マージ後のファイル version: 2.1 commands: common-echo: steps: - run: name: common-echo command: echo 'common-echo' jobs: app1-job: docker: - image: cimg/base:2023.03 steps: - common-echo - run: name: app1 echo command: echo 'app1-job' app2-job: docker: - image: cimg/base:2023.03 steps: - common-echo - run: name: app2 echo command: echo 'app2-job' workflows: app1-workflow: jobs: - app1-job app2-workflow: jobs: - app2-job
- ここまでで、設定ファイルの動的生成が完了しました。
step3. ワークフローの実行依頼
- 最後に continuation という Orb を使って、CircleCI のワークフロー実行用APIに先ほど生成した設定ファイルを渡して叩きます。
- すると、設定ファイルに記述された内容でワークフローが実行されます。
- 以上が実装概要になります。
リソースの重複定義対策
- yqコマンドで設定ファイルをマージする時、リソースの重複定義があった場合に統合されてしまいます。
- 例えば
workflows
定義に、同名のワークフローが複数のファイルに定義されていた場合、そのうちの1つだけが残り他の定義は無視されてしまいます。
# app-1/.circleci/config.yml jobs: app1-job: docker: - image: cimg/base:2023.03 steps: - run: name: app1 echo command: echo 'app1-job' workflows: app1-workflow: jobs: - app1-job
# app-2/.circleci/config.yml jobs: app2-job: docker: - image: cimg/base:2023.03 steps: - run: name: app2 echo command: echo 'app2-job' workflows: app1-workflow: # app-1/.circleci/config.yml の workflow と同じ名前 jobs: - app2-job
# マージ後のファイル jobs: app1-job: docker: - image: cimg/base:2023.03 steps: - run: name: app1 echo command: echo 'app1-job' app2-job: docker: - image: cimg/base:2023.03 steps: - run: name: app2 echo command: echo 'app2-job' workflows: app1-workflow: # app-2/.circleci/config.yml のワークフローだけになってしまった jobs: - app2-job
- これでは
app-1/.circleci/config.yml
で定義したワークフローが実行されなくなってしまいますね。 - モジュール毎の設定ファイルが増えるほど、重複定義は起こりやすくなります。
- これを防ぐために、lenet では重複チェック用のシェルスクリプトを作っています。
duplicateDefinition=$(xargs -a "$1" yq '. | to_entries[] | select(.value | type == "object") | .value | keys[]' | sort | uniq -d) if [ -n "$duplicateDefinition" ]; then echo "Error: duplicate definition found: $duplicateDefinition" exit 1 fi echo "No duplicates found."
- step2の設定ファイルマージを行う前のファイルパスを
$1
に渡してスクリプトを実行し、yqコマンドでYAMLファイルをパースします。 - 重複チェック対象にするのは、
workflows
やjobs
などの定型キーの子要素に当たるユーザ定義のリソース名で、同名のリソースが複数定義されていた場合にエラーメッセージを出力し処理を終了させています。 - これで重複定義の心配なく、設定ファイルを書いていけるようになりました。
最後に
ホワイトプラスでは一緒に働く仲間を募集しています。 カジュアル面談もできますので、お気軽にご応募ください!