🐙

GitHub Actions で Release PR を自動作成・更新する

に公開

背景

PKSHA Technology でソフトウェアエンジニアをしている林です。
私が所属している PKSHA Speech Insight 開発チームでは、ブランチベースでリリースを行なっています。

develop・staging・master ブランチの内容が、それぞれ develop・staging・production 環境に対応しています。

開発は通常以下の流れで進めています。

  1. develop ブランチから feature ブランチを派生させて機能開発
  2. 完成したら feature → develop へ PR を作成してマージ
  3. staging リリース時に develop → staging へ PR を作成してマージ
  4. 本番リリース時に staging → master へ PR を作成してマージ

これまでは、 develop → staging と staging → master の PR を手動で作成していました。しかし、手間がかかるのと、その Release PR にどの変更 (Feature PR) が含まれているのかわかりにくい問題がありました。

そこで、GitHub Actions で Release PR を自動作成する仕組みを導入しました。
また、その Release に含まれる Feature PR の一覧を Release PR の body へ記載するようにしました。

実装

前提条件: GitHub Token の設定

この Actions を動作させるには、Personal Access Token が必要です。
個人のユーザーで発行すると、チームから離れたときに動かなくなるので、システムユーザーで発行するようにしています。

  • Repository permissions

    • Contents: Read and write(リポジトリの内容を読み書きするため)
    • Pull requests: Read and write(PR の作成・更新・一覧取得のため)
    • Metadata: Read only(リポジトリ情報の取得のため、自動的に付与される)
  • Organization permissions

    • Members: Read only(PR 作成時にチームを Reviewer に設定するため)

作成したトークンを、リポジトリの Settings から、GH_TOKEN という名前で登録します。

Actions の実装

以下が PR を作成/更新する Actions の YAML です。

name: Create PRs

on:
  pull_request:
    types: [closed]

jobs:
  deploy_to_staging_pr:
    runs-on: blacksmith-2vcpu-ubuntu-2404
    if: github.event.pull_request.merged == true
    env:
      GH_TOKEN: ${{ secrets.GH_TOKEN }}
    steps:
      - name: Check Existing PRs
        id: check_prs
        run: |
          existing_prs=$(gh pr list --repo my-org/my-repo --state open --base staging --head develop)
          if [[ -n $existing_prs ]]; then
            echo "PR from develop to staging already exists. Exiting..."
            echo "pr_exists=true" >> $GITHUB_ENV
          else
            echo "No existing PR from develop to staging found. Proceeding..."
            echo "pr_exists=false" >> $GITHUB_ENV
          fi
      - name: Create PR
        if: env.pr_exists == 'false'
        run: |
          if \
            gh pr create \
              --repo my-org/my-repo \
              --base staging \
              --head develop \
              --label "automated" \
              --reviewer "my-org/repo-owner" \
              --title "deploy to staging" \
              --body "develop -> staging"
          then
            echo "pr_exists=true" >> $GITHUB_ENV
          else
            echo "Failed to create PR."
          fi
      - name: Update PR body
        if: env.pr_exists == 'true'
        run: |
          pr_number=$(
            gh pr list --json number --repo my-org/my-repo --state open --base staging --head develop --jq ".[0].number"
          )
          body=$(
            gh api graphql -f query='
              query($owner: String!, $repo: String!, $prNumber: Int!, $endCursor: String) {
                repository(owner: $owner, name: $repo) {
                  pullRequest(number: $prNumber) {
                    commits(first: 100, after: $endCursor) {
                      pageInfo{ hasNextPage, endCursor }
                      nodes {
                        commit {
                          messageHeadline
                        }
                      }
                    }
                  }
                }
              }' \
              -F owner='my-org' -F repo='my-repo' -F prNumber=$pr_number \
              --paginate \
              --jq '.data.repository.pullRequest.commits.nodes.[].commit.messageHeadline | select(. | contains("Merge pull request #"))' \
            | sed -e "s/^/- /g"
          )
          pr_id=$(
            gh api graphql -f query='
              query($owner: String!, $repo: String!, $prNumber: Int!) {
                repository(owner: $owner, name: $repo) {
                  pullRequest(number: $prNumber) {
                    id
                  }
                }
              }' \
              -F owner='my-org' -F repo='my-repo' -F prNumber="$pr_number" \
              --jq '.data.repository.pullRequest.id'
          )
          gh api graphql \
            -f query='
              mutation($prId: ID!, $body: String!) {
                updatePullRequest(input: {
                  pullRequestId: $prId,
                  body: $body
                }) {
                  pullRequest {
                    body
                  }
                }
              }' \
            -F prId=$pr_id -F body="$body"

  deploy_to_master_pr:
    ...

Create PR の step では、Release PR が存在しないときに PR を作成しています。

Update PR body の step では、PR に含まれるすべてのマージコミットを取得し、それをリスト形式で PR の本文に設定しています。

PR の作成 (gh pr create) や PR の一覧取得 (gh pr list) は gh CLI を利用しています。

PR に含まれる merge commit の一覧取得や、PR の更新は GraphQL API を利用しています。

PR の本文に、含まれるすべてのマージコミット(=マージされた PR)が一覧表示されます。

- Merge pull request #1234 from feature/add-user-api
- Merge pull request #1235 from fix/login-bug
- Merge pull request #1236 from feature/update-payment-flow

GitHub 上では、PR # のところに PR のタイトルが表示されるようになっているので、このデプロイには何が含まれるのかが一目でわかるようになっています。

PR Body

まとめ

GitHub Actions と GraphQL API を組み合わせることで、環境間のデプロイ PR を自動作成し、デプロイ内容を可視化する仕組みを構築しました。

この仕組みにより、手動作業の削減やデプロイ内容の明確化が実現でき、開発チームの生産性向上に貢献しています。

PKSHAテックブログ

Discussion