🈲

【GitHub Actions】ベースブランチへのマージを自動でブロックする方法

2023/09/22に公開

はじめに

GitHub Actionsを用い、ベースブランチへのマージを自動でブロックする方法をまとめました。

弊社プロダクト[1]ではリリースが行われる日を「休日の前の日を除いた平日」と定めています。

git-pr-release[2]を用い、1日あたり数十個のPull Requestを、まとまった単位でリリースしています。まとまった単位でリリースするために、ベースブランチへのマージを特定の時刻に締め切り、QAチームに動作確認を依頼しています。

この「特定の時刻でマージを締め切る」作業は、ベースブランチのブランチ保護ルールの変更によって行なっています。以前は人の手によって作業がなされていたため、特定の時刻に締め切られないことで動作確認対象のPull Requestが余計に増えてしまい、リリースが遅延していまう問題がありました。

この問題を対処すべく、ベースブランチへのマージを自動でブロックをする仕組みを作り、業務改善を図りました。

本記事の内容は、以下の通りです。

  • GitHub Actionsの設定ファイル
  • 休日の前日かどうかの判定
  • ブランチ保護ルールの変更

GitHub Actionsの設定ファイル

はじめに全体の設定ファイルを添付します。

.github/workflows/block-merge.yml
# INFO: マージブロックするのはリリースが行われる日。リリースが行われるのは、休日の前の日を除いた平日。
# 例1) 金曜日はリリースは行われない。休日である土曜日の前の日であるため。
# 例2) 月曜が祝日の場合はリリースは行われない。平日ではないため。
# 例3) 金曜日が祝日の場合はリリースは行われない。金曜日が休日であるため。
name: Block Merge
on:
  schedule:
    - cron: "30 0 * * 1-4" # 月曜から木曜の午前09:30 (JST) に実行する。上記にあるように、金土日は必ずリリースがないため。

jobs:
  block-merge:
    name: Block Merge
    runs-on: ubuntu-latest
    steps:
      - name: Get today and tomorrow in Asia/Tokyo
        env:
          TZ: "Asia/Tokyo"
        id: date
        run: |
          echo "today=$(date +'%Y/%-m/%-d')" >> "$GITHUB_OUTPUT"
          echo "tomorrow=$(date +'%Y/%-m/%-d' --date '1 day')" >> "$GITHUB_OUTPUT"

      - name: Show today and tomorrow
        run: |
          echo "today is ${{ steps.date.outputs.today }}"
          echo "tomorrow is ${{ steps.date.outputs.tomorrow }}"

      - name: Check if merge should be blocked
        id: check_merge_block
        run: |
          HOLIDAYS=$(curl -sL https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv | iconv -f cp932)

          # MEMO: 2023/11/2 (非祝日) などが 2023/11/23 (祝日) と文字列的に部分一致してしまわないように、"2023/11/2," のように末尾にカンマをつけて一致を見ている。
          if grep -q -e "${{ steps.date.outputs.today }}," -e "${{ steps.date.outputs.tomorrow }}," <(echo "$HOLIDAYS"); then
            echo "merge_blocked=false" >> "$GITHUB_OUTPUT"
          else
            echo "merge_blocked=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Show merge_blocked
        run: |
          echo "merge_blocked is ${{ steps.check_merge_block.outputs.merge_blocked }}"

      - name: Generate token
        id: github_app
        uses: actions/create-github-app-token@v1
        if: ${{ steps.check_merge_block.outputs.merge_blocked == 'true' }}
        with:
          app_id: ${{ secrets.MERGE_BLOCKER_APP_ID }}
          private_key: ${{ secrets.MERGE_BLOCKER_PRIVATE_KEY }}

      - name: Block Merge if today and tomorrow is not Japan Holidays
        env:
          GITHUB_TOKEN: ${{ steps.github_app.outputs.token }}
        if: ${{ steps.check_merge_block.outputs.merge_blocked == 'true' }}
        run: |
          # NOTE: required_approving_review_countを6にすることでマージブロックを行う
          curl -LfSs \
            -X PATCH \
            -H "Accept: application/vnd.github+json" \
            -H "Authorization: Bearer $GITHUB_TOKEN" \
            -H "X-GitHub-Api-Version: 2022-11-28" \
            https://api.github.com/repos/OWNER/REPO/branches/BRANCH/protection/required_pull_request_reviews \
            -d '{"required_approving_review_count":6}'

休日の前日かどうかの判定

内閣府[3]に問い合わせることで判定を行なっています。

Google Calendar APIでも良かったかもしれません。おそらく結果は同じかと思います。

普通に (普通の?) シェル芸です。判定結果をジョブの出力[4]に定義し、step間で使いまわしています。

- name: Get today and tomorrow in Asia/Tokyo
  env:
    TZ: "Asia/Tokyo"
  id: date
  run: |
    echo "today=$(date +'%Y/%-m/%-d')" >> "$GITHUB_OUTPUT"
    echo "tomorrow=$(date +'%Y/%-m/%-d' --date '1 day')" >> "$GITHUB_OUTPUT"

- name: Show today and tomorrow
  run: |
    echo "today is ${{ steps.date.outputs.today }}"
    echo "tomorrow is ${{ steps.date.outputs.tomorrow }}"

- name: Check if merge should be blocked
  id: check_merge_block
  run: |
    HOLIDAYS=$(curl -sL https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv | iconv -f cp932)

    # MEMO: 2023/11/2 (非祝日) などが 2023/11/23 (祝日) と文字列的に部分一致してしまわないように、"2023/11/2," のように末尾にカンマをつけて一致を見ている。
    if grep -q -e "${{ steps.date.outputs.today }}," -e "${{ steps.date.outputs.tomorrow }}," <(echo "$HOLIDAYS"); then
      echo "merge_blocked=false" >> "$GITHUB_OUTPUT"
    else
      echo "merge_blocked=true" >> "$GITHUB_OUTPUT"
    fi

- name: Show merge_blocked
  run: |
    echo "merge_blocked is ${{ steps.check_merge_block.outputs.merge_blocked }}"

ブランチ保護ルールの変更

GitHub API[5]を用いてブランチ保護ルールを更新しています。

https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2022-11-28#update-pull-request-review-protection

レビュー必須人数を6 (最大値) に変更することでマージブロックを実現しています。弊社のエンジニアは十数名程度なので、6人からのapproveが来ることはないだろう、という判断です。なお、マージブロックが有効な間は下記スクショのようになります。

ちなみに、ブランチをロックする[6]という機能もブランチ保護ルールにあるのですが、lock branchの有効/無効のみを更新するAPIが現時点ではないため、レビュー人数を変更する方法を採用しています。

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#lock-branch

ブランチ保護ルールへのアクセス権限 (AdministrationのRead and write) を持ったGitHub Appを作成し、都度tokenを生成しています。

- name: Generate token
  id: github_app
  uses: actions/create-github-app-token@v1
  if: ${{ steps.check_merge_block.outputs.merge_blocked == 'true' }}
  with:
    app_id: ${{ secrets.MERGE_BLOCKER_APP_ID }}
    private_key: ${{ secrets.MERGE_BLOCKER_PRIVATE_KEY }}

- name: Block Merge if today and tomorrow is not Japan Holidays
  env:
    GITHUB_TOKEN: ${{ steps.github_app.outputs.token }}
  if: ${{ steps.check_merge_block.outputs.merge_blocked == 'true' }}
  run: |
    # NOTE: required_approving_review_countを6にすることでマージブロックを行う
    curl -LfSs \
      -X PATCH \
      -H "Accept: application/vnd.github+json" \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      -H "X-GitHub-Api-Version: 2022-11-28" \
      https://api.github.com/repos/OWNER/REPO/branches/BRANCH/protection/required_pull_request_reviews \
      -d '{"required_approving_review_count":6}'

おわりに

デフォルトブランチへのマージが自動でブロックされることで、リリース作業が効率化されました。

本当はブロックの解除も自動化したいところですが、運用上自動化できないところがあり、検討中です。

日本の祝日判定は汎用的に使えるものかと思うので、参考になれば幸いです。

参考

https://trocco.io/lp/index.html

https://github.com/x-motemen/git-pr-release

https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html

https://docs.github.com/ja/actions/using-jobs/defining-outputs-for-jobs

https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2022-11-28#update-pull-request-review-protection

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#lock-branch

脚注
  1. trocco®︎ ↩︎

  2. git-pr-release ↩︎

  3. 「国民の祝日」について - 内閣府 ↩︎

  4. ジョブの出力の定義 - GitHub Docs ↩︎

  5. Update pull request review protection - GitHub Docs ↩︎

  6. ブランチをロックする - GitHub Docs ↩︎

Discussion