Github Actions

Github Actionsでビルド結果に差分が無いかを確認する

ハローワールド。

Renovate の PR を 89% 自動マージして楽している話というポストでは、Renovate[1]を自動マージする話が書かれています。

その中で、ビルド結果の差分を測定し差分がなければRenovateによる本番コードへの影響はなしと判断して自動でマージしている、という部分がありました。
これは、ライブラリを含めた全てのコードをWebPackなどでバンドルするフロントエンドでは特に有効なやり方です。

上記記事では、CircleCIを用いて行っていましたが、今回はGithub Actionsで同様のことを行ってみました。

環境

今回は、このブログに対して行ってみました。
結論から言うと、NextのSSGでは使えませんでした。ビルドするたびにファイル名が異なるので。

また、共通化のために、下記のようなComposite Actionを用意しています。下記は単純にyarn(node)をセットアップして、yarn installを行っています。そのときにキャッシュを利用して高速化しています。

.github/actions/yarn-prepare/action.yml
name: 'yarn prepare' description: '' runs: using: "composite" steps: - name: Use Node.js uses: actions/setup-node@v3 with: node-version: 16 - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" shell: bash - uses: actions/cache@v3 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - run: yarn install shell: bash

差分がないかを確認するアクション

差分がないかを確認するために利用するのは、Github ActionsのArtifactの機能です。これはCircleCIにもありますね。

ざっくり、下記のような戦略を取ります。

  1. 現在のブランチ(HEAD)でビルドして、ビルドした結果をアップロードします
  2. 現在のブランチの派生元ブランチ(Renovateの場合はmain|masterしかありませんが)でビルドして、ビルドした結果をアップロードします
  3. ビルドした2つの結果のハッシュを比較します

Github ActionsのWorkflowではこんな感じです。current-buildが1.にあたり、source-buildが2.にあたります。どちらも成功したら3.にあたるdiffを実行します。

ビルド結果のDIffを確認するGithub ActionsのWorkflow

それぞれの動作は下記になっています。

# This file was auto-generated by the Firebase CLI # https://github.com/firebase/firebase-tools name: build-diff 'on': - push jobs: current-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ./.github/actions/yarn-prepare - name: yarn build current env: CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }} CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }} run: | NODE_ENV=production yarn build - name: Upload output file uses: actions/upload-artifact@v3 with: name: ${{ github.sha }}-current path: out source-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: '0' - name: switch to source branch run: git checkout $(git show-branch -a | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}') - uses: ./.github/actions/yarn-prepare - name: yarn build source env: CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }} CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }} run: | NODE_ENV=production yarn build - name: Upload output file uses: actions/upload-artifact@v3 with: name: ${{ github.sha }}-source path: out diff: runs-on: ubuntu-latest needs: - current-build - source-build steps: - uses: actions/checkout@v3 with: fetch-depth: '0' - name: download current export file uses: actions/download-artifact@v3 with: name: ${{ github.sha }}-current path: out-current - name: download source export file uses: actions/download-artifact@v3 with: name: ${{ github.sha }}-source path: out-source - name: compress current file run: tar -cf current.tar out-current/ - name: compress source file run: tar -cf source.tar out-source/ - name: compare build run: | if [ $(sha256sum current.tar | awk '{print $1}') = $(sha256sum source.tar | awk '{print $1}') ]; then exit 0; else exit 1; fi

それぞれのジョブをそれぞれ見てみます。

現在のブランチのビルド

current-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ./.github/actions/yarn-prepare - name: yarn build current env: CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }} CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }} run: | NODE_ENV=production yarn build - name: Upload output file uses: actions/upload-artifact@v3 with: name: ${{ github.sha }}-current path: out

ビルドの部分がすごく長いですが、単純な処理となっています。

このブログはHeadless CMSとしてContentfullを採用しており、シークレットをGithub Secretsに登録している関係で、上記のようになっています。
シークレットはComposite Actionで使えないため、ビルドは上記のような感じとなっています。

- name: yarn build current env: CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }} CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }} run: | NODE_ENV=production yarn build

下記がアップロードを行っている処理です。Github Actionsでは、nameのところにコマンドなどを利用して名付けることができなさそうだったので、Githubにもともとある、対象のコミットハッシュのハッシュに-currentをつけた名前でアップロードしています。
アップロード対象は、ビルド結果であるoutフォルダです。

- name: Upload output file uses: actions/upload-artifact@v3 with: name: ${{ github.sha }}-current path: out

派生元のブランチのビルド

source-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: '0' - name: switch to source branch run: git checkout $(git show-branch -a | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}') - uses: ./.github/actions/yarn-prepare - name: yarn build source env: CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }} CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }} CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }} NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }} run: | NODE_ENV=production yarn build - name: Upload output file uses: actions/upload-artifact@v3 with: name: ${{ github.sha }}-source path: out

大体現在のブランチのビルドと同じですが、違う箇所が少しあります。

今回は対象のコミット以外のコミットも取得する必要があるため、チェックアウト時に下記のように全ての情報をfetchする必要があります。

- uses: actions/checkout@v3 with: fetch-depth: '0'

続いて、派生元のブランチ(というかコミット)にswitch(というかcheckout)する必要があります。
派生元のブランチを特定する方法は Gitで今のブランチの派生元ブランチを特定する - Qiita を参考にしました。
今回は、派生元のリモートブランチを特定する必要があるので、少しだけコマンドが変わって git show-branch -aを利用しています。

- name: switch to source branch run: git checkout $(git show-branch -a | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}')

最後にアップロードのnameが変わっています。上記の理由と同様にコミットハッシュに -sourceをつけています。

- name: Upload output file uses: actions/upload-artifact@v3 with: name: ${{ github.sha }}-source path: out

ビルド結果に差分があるかを確認する

上記でビルドを行ったら、最後はそれらの差分を取るだけです。

diff: runs-on: ubuntu-latest needs: - current-build - source-build steps: - uses: actions/checkout@v3 with: fetch-depth: '0' - name: download current export file uses: actions/download-artifact@v3 with: name: ${{ github.sha }}-current path: out-current - name: download source export file uses: actions/download-artifact@v3 with: name: ${{ github.sha }}-source path: out-source - name: compress current file run: tar -cf current.tar out-current/ - name: compress source file run: tar -cf source.tar out-source/ - name: compare build run: | if [ $(sha256sum current.tar | awk '{print $1}') = $(sha256sum source.tar | awk '{print $1}') ]; then exit 0; else exit 1; fi

current-buildsource-buildの実行を待つために下記のように設定します。

needs: - current-build - source-build

続いて、アップロードされたartifactをダウンロードしてきます。nameを指定してダウンロードします。pathを指定すればダウンロードする先も変更できます。

- name: download current export file uses: actions/download-artifact@v3 with: name: ${{ github.sha }}-current path: out-current - name: download source export file uses: actions/download-artifact@v3 with: name: ${{ github.sha }}-source path: out-source

ダウンロードしたのはフォルダなので、簡単にチェックサムを測れるように、tarアーカイブをしておきます。

- name: compress current file run: tar -cf current.tar out-current/ - name: compress source file run: tar -cf source.tar out-source/

最後に、差分を測定し、差分があればエラーとします。
気を利かせて、エラーの場合はどのファイルに差分があるか、みたいなのを表示できればいいですが、今回はとりあえずこれだけです。
sha256sumはubuntuにデフォルトで入っています。結果はhashとファイル名が連結されているので、ハッシュだけを取るためにawkを利用しています。

- name: compare build run: | if [ $(sha256sum current.tar | awk '{print $1}') = $(sha256sum source.tar | awk '{print $1}') ]; then exit 0; else exit 1; fi

最後に

Next.jsでビルド結果を安定させる方法を求む。


  1. Renovateは依存関係にあるライブラリのアップデートを行ってくれるアプリケーションです ↩︎