[GitHub]保護ブランチへのマージ向けPR作成をworkflow_dispatchで手軽くやってみた

Workflow内で保護ブランチへのpushが失敗しており、色々と考えた末に保護を外さない前提での対策を考えた末にPRの自動作成へと行き着きました。シンプルですが使いやすいと思われます。
2020.10.27

はじめに

何も弄ってないのにいきなり動作が変わっていると流石に慌てるものだと思ったこの頃です。

Actionsを利用してのCDを実施していたところ、普段特に問題がなかった箇所でエラーが発生していました。

remote: error: GH006: Protected branch update failed for refs/heads/master.
remote: error: Waiting on code owner review from haoyayoi and/or xxxxxx.
To https://github.com/xxxxxxxx/yyyyyyy
 ! [remote rejected] master -> master (protected branch hook declined)

何かやらかしがコミットに入っていたのかと思いましたが、動作を追ってみたところWorkflow内で行われているnpm versionによるCommitとPushが原因でした。Tokenは勿論設定済み。「あれ、動いてたはずなんだけど」という思いの元に検索してみたものの、そういう仕様ですとしか言えない記述ばかりが見つかる状態でした。

とりあえずはProtectedを逐一外すことで対処しましたが、事故の発生は不可避です。GitHubのフォーラム等を辿った上で行き着いたActions設定を交えて、ログとして書いておきます。

ブランチの保護には例外が通じない

GitHubでは想定しない変更を抑えるためにBranchを保護することができます。勝手に書き換えられることを防ぐ意味でも重要です。ただ、Actionsを使う上ではやや面倒な状態が成立することもあります。

主に、Workflow上で保護ブランチに直接のCommitやPushを発生させたい場合。そんなことあるっけ、と思われそうなのも確かですが、更新に伴うバージョン番号のインクリメントがありがちなケースです。「作業ブランチをMergeさせるタイミングでやればいいじゃないか」という声も聞こえてきそうです。

まぁ実際そうなのですが、細かい修正をマイナー、本番反映させるタイミングでメジャーバージョンを更新させるようなケースになると話が変わってきます。単純なバージョンのインクリメントにもPull-Request、設定によってはReviewが必要になるわけです。

なお、ブランチの保護を反映のタイミングだけ解除するという手段も見かけました。

I was able to create a workflow that temporarily disables the branch protection and then enables it again. This works fine if you don’t have multiple pull requests merged to master at the same time. And of course this means there is no branch protection for a few seconds.

設定変更についてはいつ行われたのか直ぐには判別し難いため、何か発生した場合に調査が非常に難しくなり、正直おすすめはできません。

以下のスレッドにはGitHub CommunityのStaffからのコメントがあり、手順として妥当なものと思えました。

If we enabled GitHub Actions to push to a protected branch then any collaborator in your repo could push any code to any branch they wanted simply by creating a branch and coding the workflow to push to to some other branch. Using the REST api to merge the PR is the right flow and overtime hopefully there will be actions that make that easier to implement.

ということで、作業用ブランチ作りつつPRも発行し、mergeした後はお好みに処理するためのWorkflowを作ってみました。

Merge用PR発行を機械的に行うWorkflow

今回のポイントは以下の通り。

  • workflow_dispatchを介してPRを作成する
  • うっかりworkflow_dispatchを多重実行した場合でも有効なPRは一つだけ

作業ブランチを固定にすることで多重実行してしまってもエラーで中断させます。

PR作成用

on:
  workflow_dispatch:
    branches:
      - master
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.sha }}
      - name: Set up Node
        uses: actions/setup-node@v1
        with:
          node-version: '10.x'
      - name: Git config
        run: |
          git config --global user.email "xxxxxxx@yyyyyy"
          git config --global user.name "GitHub Actions"
      - name: Create Release
        id: create_branch
        run: |
          git checkout -b release
          git push --set-upstream origin release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Update major version
        id: update_major_version
        run: |
          mkdir .git
          echo "version_no=$(npm version minor --no-git-tag-version)" >> $GITHUB_ENV
          npm version minor
          git push
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        working-directory: cdk
      - name: Create release PR
        id: create_release_pr
        uses: peter-evans/create-pull-request@v3
        with:
          branch: release
          base: master
          title: "Deploy ${{env.version_no}}"
          draft: false

npm versionに対して--no-git-tag-versionとオプションを入れていますが、これはCommitを発生させずにバージョン番号を取るためです。このタイミングではCommitが発生しなかったために次の行にてCommit目的で再実行していますが、万が一を考えて前の行ではCommitを明示的に回避させています。

Merge後のお好み用

delete-merged-branchをブランチ削除用に使っています。PR作成に用いたcreate-pull-requestにもdelete-branchなるオプションがあるのですが、同じフロー内でPRのcloseも扱わない場合には動作は期待できないようです。

on:
  pull_request:
    branches:
      - master
    types: [closed]
jobs:
  test:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'release'
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.sha }}
      - name: delete branch
        uses: SvanBoxel/delete-merged-branch@main
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Set up Node
        uses: actions/setup-node@v1
        with:
          node-version: '10.x'
      - name: Hello
        run: echo "Hello"

実際に書いてみると本当に単純なワークフローなんですが、これの確認が結構手間取ります。

実際に動かしてみる

ActionsからPR作成用のWorkflowを実行します。

実行後、Workflowが完了するまで暫く待ちます。

マージします。

Deploy用のWorkflowが動作しました。

Workflow完了後、PRをみてみると作業ブランチの削除も確認できました。

多重実行時

作業ブランチが固定になっているおかげで、ブランチ作成時に重複で失敗して止まっていました。狙い通りです。

あとがき

npm versionで失敗するようになった状況の原因が未だにわかっていないのですが、そもそも出来ていたのがおかしいと考えることにしました。

PR作成用のworkflow_dispatch起点であるWorkflowと、merge後によろしくやってくれるWorkflowの組み合わせは流石に都合よく落ちておらず、やむを得ず作ってみました。意外とベースはシンプルにできた感じです。ベースの動作は確認済みのため、作るのが面倒な場合には今回のサンプルをおすすめします。