【GitHub Actions】ベースブランチへのマージを自動でブロックする方法
はじめに
GitHub Actionsを用い、ベースブランチへのマージを自動でブロックする方法をまとめました。
弊社プロダクト[1]ではリリースが行われる日を「休日の前の日を除いた平日」と定めています。
git-pr-release
[2]を用い、1日あたり数十個のPull Requestを、まとまった単位でリリースしています。まとまった単位でリリースするために、ベースブランチへのマージを特定の時刻に締め切り、QAチームに動作確認を依頼しています。
この「特定の時刻でマージを締め切る」作業は、ベースブランチのブランチ保護ルールの変更によって行なっています。以前は人の手によって作業がなされていたため、特定の時刻に締め切られないことで動作確認対象のPull Requestが余計に増えてしまい、リリースが遅延していまう問題がありました。
この問題を対処すべく、ベースブランチへのマージを自動でブロックをする仕組みを作り、業務改善を図りました。
本記事の内容は、以下の通りです。
- GitHub Actionsの設定ファイル
- 休日の前日かどうかの判定
- ブランチ保護ルールの変更
GitHub Actionsの設定ファイル
はじめに全体の設定ファイルを添付します。
# 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]を用いてブランチ保護ルールを更新しています。
レビュー必須人数を6 (最大値) に変更することでマージブロックを実現しています。弊社のエンジニアは十数名程度なので、6人からのapproveが来ることはないだろう、という判断です。なお、マージブロックが有効な間は下記スクショのようになります。
ちなみに、ブランチをロックする[6]という機能もブランチ保護ルールにあるのですが、lock branchの有効/無効のみを更新するAPIが現時点ではないため、レビュー人数を変更する方法を採用しています。
ブランチ保護ルールへのアクセス権限 (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}'
おわりに
デフォルトブランチへのマージが自動でブロックされることで、リリース作業が効率化されました。
本当はブロックの解除も自動化したいところですが、運用上自動化できないところがあり、検討中です。
日本の祝日判定は汎用的に使えるものかと思うので、参考になれば幸いです。
参考
Discussion