SmartHR Tech Blog

SmartHR 開発者ブログ

SmartHR UI のリリース作業を GitHub Actions で自動化した話

こんにちは、フロントエンドエンジニアのモアイと申します。

SmartHR では、SmartHR UI というプロダクト間共通の React コンポーネントライブラリを運用していますが、この記事では SmartHR UI のリリース作業を GitHub Actions で自動化した話をご紹介します。

ちなみに SmartHR UI そのものについては過去の記事で詳しく紹介されていますので、ご興味があればそちらも併せて御覧ください。

tech.smarthr.jp

tech.smarthr.jp

三行まとめ

  • リリース作業が複雑化していたので GitHub Actions を使って自動化した
  • リリース中に作業者の確認を挟むプロセスを Issue とラベル付けによって実現した
  • 自動化最高!

これまでのリリース作業

SmartHR UI では Pull Request ベースで開発しており、様々な歴史的経緯からこのようなブランチ構成で運用しています。

ブランチ構成

また、リリース時に必要なバージョン番号の採番や変更履歴の生成などをまとめて行ってくれる standard-version というツールを使用しており、リリースを行う際には次のような作業が必要になります。

  1. リリースを行う状態のブランチを作成
  2. standard-version を dry run 実行して内容確認
  3. standard-version を実行
  4. npm publish
  5. 作成されたタグを GitHub に push
  6. リリースで発生した変更を master に取り込む Pull Request を作成
  7. GitHub 上でリリースノートを作成

これまではこれら作業をひとつずつ手動で行っていましたが、手順が複雑で作業コストが高い上にミスや作業漏れが発生する懸念があったため、作業を自動化することでこれら問題点を解消したい!となったわけです。

自動化の方法

自動化は GitHub Actions を用いて行い、ざっくり要件は以下のような感じです。

  • 任意のタイミングでリリースを開始できる
  • ボタンポチッで実行できる
  • リリース内容を事前確認できる
  • 作業は可能な限り自動化する

この内、「リリース内容を事前確認できる」ことを実現するために少し工夫が必要になります。やりたいこととしては、実際にリリース処理を行う前にリリースされる内容を確認して問題ないかどうかを判断できるようにしたいのですが、そのためにはリリース処理中に作業者の判断を待つプロセスを設ける必要があります。

今回はそのプロセスを、GitHub 上の Issue と、Issue に対するラベル付け によって表現することにしました(参考にした記事)。つまり、事前確認に必要な情報を本文に載せた Issue を作成し、作業者がその Issue に特定のラベルを付けることで「内容を確認し、問題ないと承認した」ことを表す、ということです。

実際の作業の流れとしては

  1. 作業者がリリース処理を開始すると、事前確認用の Issue が作成される
  2. 作業者はその Issue を確認し、内容に問題なければその Issue に特定のラベルを付ける
  3. ラベルが付けられたことを検知して、実際のリリース処理が走る

というように、処理は確認用 Issue を作成するまでの前半部分と実際のリリース処理を行う後半部分のふたつに分かれる形になり、その間に作業者による確認オペレーションを挟むことで、リリース内容の事前確認プロセスを実現します。

リリース開始から Issue 作成までのワークフロー

ここから、実際の GitHub Actions のワークフローを組んでいきます。

まずは前半部分の、リリース開始から確認用 Issue の作成までを行うワークフローを作成します。

リリース開始のトリガ

最初に、リリースを開始するためのトリガをワークフローに設定します。

on:
  workflow_dispatch:
    inputs:
      mode:
        description: 'Input release mode, "normal" or "prerelease".'
        required: true
        default: ''

jobs:
  start_release:
    if: github.event.inputs.mode == 'normal' || github.event.inputs.mode == 'prerelease'
    runs-on: ubuntu-latest
    env:
      RESULT_PATH: /tmp/result.txt
      IS_PRERELEASE: ${{ github.event.inputs.mode == 'prerelease' }}

リリースは任意のタイミングで開始できるようにしたいため、手動でワークフローを開始できる workflow_dispatch トリガを使います。また、開始時に通常リリースかプレリリースかを選べるようにするために inputs を設定してテキストボックスを設置しています。このテキストボックスは normalprerelease を入力することでリリースモードを選べるようにしていますが、それ以外の入力ではジョブが走らないように設定しているので、誤実行の防止も兼ねています。

これで、このワークフローは GitHub 上の UI から開始できるようになります。

ワークフローを開始するドロップダウン

ソースの準備

次にリリースを行うためのソースの準備を行う処理をワークフローの steps に記述していきます。

steps:
  - uses: actions/checkout@v2
    with:
      fetch-depth: 0
  - uses: actions/setup-node@v2
    with:
      node-version: 14
  - name: prepare release
    run: |
      BASE_TAG=v$(npx -c 'echo "$npm_package_version"')
      git checkout $BASE_TAG
      git merge --no-edit ${{ github.ref }}
  - run: yarn install --frozen-lockfile
  - name: push branch
    run: |
      git checkout -b release-candidate
      git push origin release-candidate
  • ソースをチェックアウト
  • 必要なブランチ操作をなんやかんや行う
  • yarn install
  • この状態を release-candidate ブランチとして GitHub に push

確認用 Issue の作成

リリース内容の事前確認を行うための Issue を作成する処理を記述していきます。

- name: release dry run
  run: yarn standard-version --dry-run > ${{ env.RESULT_PATH }}} 
- name: wrap dry run log
  run: |
    echo "Dry Run Log:
    \`\`\`
    $(cat ${{ env.RESULT_PATH }})
    \`\`\`" > ${{ env.RESULT_PATH }}
- name: create issue
  uses: peter-evans/create-issue-from-file@v2
  with:
    title: Release candidate
    content-filepath: ${{ env.RESULT_PATH }}
    labels: release candidate

standard-version を dry run 実行すると実際の処理を行わずにリリースの内容を確認することが出来るので、その内容をどこか適当な場所に出力しておきます。Issue 上で見やすくなるように出力した内容を少し修飾してから、create-issue-from-file アクションを使って出力ファイルから Issue を作成します。

これで、dry run の内容を本文に載せた Issue が作成されます。

確認用Issueの本文

実際にリリース処理を行うワークフロー

前半のワークフローでリリース内容確認用の Issue が作成できたので、今度は後半部分の実際にリリース処理を行うワークフローを作成します。

ラベル付けによる「承認」とトリガ

作業者は作成されたリリース確認用 Issue を確認して、内容に問題なければラベルを付けることによって「承認」します。今回は approve release というラベルを付けると実際のリリースが開始されるようにしました。

Issueに対するラベル付け

このラベル付けによってワークフローが開始されるようにトリガを設定します。

on:
  issues:
    types:
      - labeled

jobs:
  publish_release:
    if:
      contains(github.event.issue.labels.*.name, 'release candidate')
      && github.event.label.name == 'approve release'

このトリガ設定によって、このワークフローは「Issue に対してラベル付けを行ったとき」に開始されるようになります。ただ、それだけだと全ての Issue に対する全てのラベル付けに反応してしまうので、ジョブ側の実行条件で「リリース確認用 Issue に対して approve release ラベルを付けたとき」に絞っています。

これで、リリース内容の承認時に開始されるワークフローが作成できたので、あとはこのワークフローに実際のリリース処理を記述していけばOKです。

実際のリリース処理

ソースの準備

steps:
  - uses: actions/checkout@v2
    with:
      ref: release-candidate
      fetch-depth: 0
  - uses: actions/setup-node@v2
    with:
      node-version: 14
      registry-url: 'https://registry.npmjs.org'
  - run: yarn install --frozen-lockfile
  • 前半のワークフローで push しておいた release-candidate ブランチをチェックアウト
  • npm への公開のために setup-noderegistry-url を設定
  • yarn install

standard-version を実行

- run: yarn standard-version
  • 変更履歴などを更新したコミットと、タグが作成されます

npm レジストリへ公開

- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

GitHub へタグを push

- run: echo NEW_TAG=$(git describe) >> $GITHUB_ENV
- run: git push origin $NEW_TAG
  • 後々参照するため、タグ名を記録しています

GitHub 上にリリースノートを作成

- run: npx ts-node ./scripts/getLatestChangelog.ts > ${{ env.CHANGELOG_PATH }}
- name: create release on GitHub
  uses: actions/create-release@v1
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    tag_name: ${{ env.NEW_TAG }}
    release_name: ${{ env.NEW_TAG }}
    body_path: ${{ env.CHANGELOG_PATH }}
    commitish: master
    prerelease: ${{ env.IS_PRERELEASE }}

リリースノートにはこのリリースに含まれる変更履歴を載せたいのですが、 standard-version によって作成・更新される変更履歴は、ひとつのファイルの中に今までの全リリースの変更履歴を含んでいるため、自作したスクリプトによって全履歴の中から今回のリリース分だけを抽出し、ファイルに出力しています。 リリースノートの作成は create-release アクションで行い、本文に先程抽出した変更履歴ファイルを指定しています。

リリースの後処理

- name: close issue
  uses: peter-evans/close-issue@v1
- name: delete branch
  run: git push origin :release-candidate
  • 確認用 Issue をクローズ
  • release-candidate ブランチを削除

master への Pull Request を作成

- name: cherry-pick release commit
  run: |
    git checkout master
    git cherry-pick $NEW_TAG
- name: craete pull request
  uses: peter-evans/create-pull-request@v3
  with:
    title: 'chore(release): ${{ env.NEW_TAG }}'
    branch: 'merge-release-${{ env.NEW_TAG }}'

standard-version を実行すると変更履歴や package.json 内のバージョン番号を更新したコミットが作成されるので、その変更を master に取り込むための Pull Request を作成する必要があります。この Pull Request には standard-version によって行われた変更だけを含めたいので git cherry-pick などでブランチをゴニョゴニョしてから、create-pull-request アクションを使って Pull Request を作成します。

なお場合によっては git cherry-pick する際にコンフリクトが発生して Pull Request 作成に失敗してしまうことがありますが、かなりフォローが難しいので、そのときは諦めて Pull Request の作成だけ手動で行ってリカバリすることにしています。

これでリリース処理は完了となり、一連の作業を自動化することができました。

記事中では説明のため一部記述を簡略化しているところがありますが、実際に運用しているワークフローはこちらから確認できます。

github.com

自動化してどうだったか

  • 良かった点
    • リリースが簡単になった
    • リリース作業のコストが下がったため、リリースサイクルを早められた
    • 適切な権限さえあれば誰でもリリースできるようになった
    • 一度 Issue を挟むので、リリース作業が可視化された
  • 懸念
    • ワークフローがエラーなどで失敗した場合、ワークフローの内容を把握していないとリカバリが難しい

現状では概ね好評といった感じです。 GitHub Actions に不慣れだったためかなり手探りで進めた部分もあり、まだまだ改善点はありますが、以前の手動で行っていた状態よりは確実に便利になったのではないかと思います。自動化最高!

最後に

SmartHR ではフロントエンドエンジニアを募集中です!

hello-world.smarthr.co.jp