semantic-release と GitHub Protected Branch の共存を実現する
これはなに
semantic-release は GitHub Protected Branch との相性が悪いと随所で言われていますが、その理由と解決策についてまとめたものです。Protected Branch に対して semantic-release が差分を直接コミットするための手法を紹介します。
前提知識
@semantic-release/git
@semantic-release/git
は、semantic-release によるリリースフローで発生した差分を Git リポジトリにコミットするプラグインです。例えば Node パッケージのリリースプロセスでは package.json
の version
フィールドや CHANGELOG.md
を生成ないし更新しますが、これらの差分を Git リポジトリーにコミットするために @semantic-release/git
が利用されます。
GitHub Protected Branch
GitHub にはブランチに対して保護を設定する機能があります。これにより、ブランチへの直接プッシュやマージが制限されます。例えば main
ブランチに対して保護を設定することで main
ブランチへの直接プッシュを禁止し、代わりに Pull Request を通じてマージするように促せるため、開発プロセスの整備に役立ちます。
問題点
@semantic-release/git
はリリースするプロセスが実施されるブランチに対して差分をコミットしますが、そのブランチが保護設定されている場合、コミットが拒否され失敗してしまいます。当然ですね。そのための保護設定なのだから。Protected Branch の設定で "Allow force pushes" を有効にしても同様に失敗します。
失敗する原因
@semantic-release/git
が Protected Branch へのコミットに失敗するのは、semantic-release が参照する GITHUB_TOKEN
に十分な権限がないためです。
GitHub Actions (GHA) でリポジトリーに対し何かしら操作する場合は、 secrets.GITHUB_TOKEN
という GitHub が自動生成するトークンを利用するのが一般的です。このトークンは GHA ワークフローを実行する度に自動生成され、有効期間が短いことから非常に手軽かつセキュアに利用できるのが強みですが、その分権限範囲が非常に狭く、Protected Branch へのコミット権限を持っていません。これが原因で @semantic-release/git
が失敗するというわけです。
一般的に知られている回避策
Personal Access Token を利用する
Personal Access Token (PAT) は、個々の GitHub ユーザーアカウントが発行するトークンであり、secrets.GITHUB_TOKEN
よりも多くの権限を持たせられます。よって、これを semantic-release が参照する GITHUB_TOKEN
として利用することで Protected Branch へのコミットが可能となります。
手軽に利用できるため、semantic-release 公式ドキュメントや多くのブログ記事でこの方法が紹介されています。しかし PAT は特定個人の GitHub アカウントに直接紐づいたものであり、いうなればそのアカウントになりすましてリポジトリーを操作するのと同じです。つまり、アカウントのパスワードのようなものであることから、セキュリティリスクが高いことが指摘されています。
Pull Request を通じてコミットする
一般的に Protected Branch は Pull Request を通じて差分をマージすることを前提としているため、リリースプロセスで生じる差分を Pull Request に変換すればこの問題を回避できます。 changesets など他のリリースツールはこの手法を採用していますが、semantic-release の強みである「徹底した自動化」が大幅に削がれてしまいます。
解決策: GitHub App を利用する
リリースワークフローのための GitHub App を作成し、この App が発行するトークンを semantic-release に参照させて Protected Branch への直接コミットを可能にします。この方法であれば PAT のように特定個人のアカウントに依存しないトークンを semantic-release に参照させられます。
GitHub App とは
GitHub App とは、GitHub に対し Bot のような振る舞いをする処理を実現するものであり、リポジトリーや Organization にインストールして使用します。Slack + GitHub や Codecov などが有名な GitHub App ですが、今回のようにリリースワークフローで参照するトークンを発行するだけのシンプルな App も簡単に作成できます。
PAT は有効期限を無制限に設定できないため定期的に再発行する手間が発生しますが、GitHub App によるトークンは secrets.GITHUB_TOKEN
と同様に GHA ワークフロー実行の度に発行されるため有効期限が短く、セキュリティリスクが低いのが利点です。
また、GitHub Organization に紐づいた App も作成できるため、これならば Organization 全体で利用可能となると同時に属人性を完全に排除できます。
手順
1. GitHub App を作成する
Register new GitHub App ページにて GitHub App を新規作成します。以下の設定を行います。
- GitHub App name: Marketplace 全体でユニークな名前にすること
-
Homepage URL:
http://localhost
など適当な URL で OK -
Webhook:
- Active: 使用しないのでチェックを外す
-
Permissions:
-
Repository permissions:
-
Administration:
Read and Write
-
Contents:
Read and Write
-
Issues:
Read and Write
-
Metadata:
Read-only
-
Pull Requests:
Read and Write
-
Administration:
-
Repository permissions:
-
Where can this GitHub App be installed?:
Only on this account
を選択
2. GitHub App をインストールする
作成した GitHub App を対象リポジトリにインストールします。https://github.com/settings/apps を開き、作成した GitHub App の Edit ボタンを押します。
次に左メニューにある Install App
を選択し、表示されている GitHub アカウントを選択してその配下にあるリポジトリーを選択します。
All
を選択すればその GitHub アカウントが持つ全てのリポジトリーへ一括インストールされますが、個別に選択するのが望ましいでしょう。
3. App ID と秘密鍵を secrets としてリポジトリーに登録する
GHA ワークフローから GitHub App にアクセスするためには、App ID と秘密鍵が必要です。これらをリポジトリーの secrets として登録します。
https://github.com/settings/apps/[app-name] にアクセスし、App ID
をメモしておきます。
次に Private keys
セクションにある Generate a private key
ボタンを押し、秘密鍵( pem ファイル)をダウンロードします。
App ID と秘密鍵を入手したら、これらをリポジトリーの secrets として登録します。リポジトリーの Settings
> Secrets and Variables
> Actions
と進み、以下の secrets を登録します。名称は任意ですが、以下の例では BOT_APP_ID
と BOT_PRIVATE_KEY
にしています。
-
BOT_APP_ID
: App ID -
BOT_PRIVATE_KEY
: 秘密鍵の内容
これで GHA ワークフロー から GitHub App を認可できるようになりました。
4. Branch Protection の設定を変更する
Require a pull request before merging
オプションにチェックが付いていることを確認します。
5. GitHub Actions ワークフローを設定する
secrets.GITHUB_TOKEN
を用いたワークフローを GitHub App が発行するトークンを用いるように変更します。GitHub App の参照には create-github-app-token という GitHub 公式の GHA を使います。
name: Release
on:
push:
branches:
- main
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
+ - name: Generate Token
+ id: app-token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ secrets.BOT_APP_ID }}
+ private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
+ persist-credentials: false
- name: Setup node.js
uses: actions/setup-node@v4
- name: Install dependencies
run: npm install
- name: Release
run: npm run release
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
actions/checkout
を呼びだす際に persist-credentials
オプションを必ず false
にします。この設定を行わないと secrets.GITHUB_TOKEN
が参照され続けてしまい、GitHub App が発行するトークンが利用されず CD が失敗します。
- 参考文献:
手順は以上です。これで Protected Branch に対して semantic-release が差分を直接コミットできるようになります。
締め
GitHub App を活用することで、リポジトリーのセキュリティーを維持しつつ semantic-release の強みである「徹底した自動化」の両立が可能となります。リリースワークフローの厳格性を保ちつつソフトウェア開発に取り組むことで、品質の向上や開発効率の向上に貢献できることでしょう。
参考文献
Discussion