GitHub Actionsでのcodegen & PR作成でOpenAPIの変更を楽々レビューする
概要
バックエンドのリポジトリにあるOpenAPI定義が変更されたときに、OpenAPIクライアントコードを生成して、フロントエンドのリポジトリに対してPRを自動作成するGitHub Actionsを作成しました。
モチベーション
バックエンド側でOpenAPIを変更したPRが上がってきたら、生成されるクライアントコードの差分も見てレビューしたかった。
required
の付け忘れやnullable
なenumの見落としだったり、使用しているライブラリによる自動生成コードの挙動だったりを、yamlファイルだけ眺めてレビューするのは自分には難しかったです。
なので毎回手元でBE, FEのリポジトリをチェックアウトして、コマンド叩いて...とやってたんですが、流石に面倒なので自動化しようと思いました。
最初は差分をgithub-commentで表示する方法を考えたのですが、生成されたコードもgit管理しているので結局PRを作成する必要があること、typecheckを始めとするCIが回る恩恵があることを理由に、PRを自動で作成する手法をとりました。
実装
ざっくり流れは以下のフローチャートの通りです。
Repository DispatchでFEのGitHub Actionsをトリガーしています。
FE側のworkflow
name: Create Codegen PR
on:
repository_dispatch:
types:
- codegen
env:
node-version: '18'
pnpm-version: '8'
jobs:
create-pr:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Parse Payload
run: |
echo "PR_NUMBER=${{ github.event.client_payload.pr_number }}" >> $GITHUB_ENV
- name: Checkout BE
uses: actions/checkout@v3
with:
repository: your-org/backend-repo
path: backend
token: ${{ secrets.GH_PAT }}
- name: Checkout BE Branch
run: |
gh pr checkout $PR_NUMBER
working-directory: backend
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
- name: Checkout Web
uses: actions/checkout@v3
with:
path: frontend
- name: Check PR Already Exists
run: |
echo "PR_EXISTS=$(gh pr list | grep chore/be-$PR_NUMBER | wc -l)" >> $GITHUB_ENV
working-directory: frontend
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout Web Branch
run: |
if [ $PR_EXISTS -eq 1 ]; then
gh pr checkout chore/be-$PR_NUMBER
else
git checkout -b chore/be-$PR_NUMBER
fi
working-directory: frontend
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: pnpm/action-setup@v2
with:
version: ${{ env.pnpm-version }}
- uses: actions/setup-node@v3
with:
node-version: ${{ env.node-version }}
cache: 'pnpm'
cache-dependency-path: 'frontend/pnpm-lock.yaml'
- uses: actions/cache@v3
id: pnpm-cache
with:
path: 'frontend/node_modules'
key: ${{ runner.os }}-pnpm-${{ hashFiles(format('{0}/frontend{1}', github.workspace, '/pnpm-lock.yaml')) }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install dependencies
if: steps.pnpm-cache.outputs.cache-hit != 'true'
run: pnpm install
working-directory: frontend
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Codegen from OpenAPI
run: |
pnpm codegen
echo "CODEGEN_DIFF=$(git diff --shortstat)" >> $GITHUB_ENV
working-directory: frontend
- name: Commit Codegen Diff
if: ${{ env.CODEGEN_DIFF }}
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add .
git commit -m "your commit message"
git push origin HEAD
working-directory: frontend
- name: Create PR
if: ${{ env.CODEGEN_DIFF && env.PR_EXISTS == 0 }}
run: |
gh pr create --title "your pr title" \
--body "your pr body" \
--base main \
--head chore/be-$PR_NUMBER
working-directory: frontend
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
雑な補足
- BEのリポジトリがprivateなので、チェックアウト時にPATを利用しています
- FE側のコード生成のconfigが、こんな感じでopenapi.yamlを参照するようになっているので、それに従ってcheckoutしています
.
├── frontend/
│ └── codegen.config.json
└── backend/
└── docs/
└── openapi.yaml
- pnpmを利用しているので、dependenciesのインストールとキャッシュのステップが入ってます
- commitはgithub-actions[bot]ユーザーとして行っています
- FE側のブランチは
chore/be-{BE側のPR番号}
という命名にしています
BE側のworkflow
curlでRepository Dispatchをトリガーするだけになっています。
FE側でPR作成後に、BEのPRにリンクを貼るみたいなのもやりたかったんですが、PRの本文でメンションしておけば十分かなと思って割愛しました。
name: Dispatch Frontend Workflow
on:
pull_request:
types:
- opened
- synchronize
paths:
- docs/openapi.yaml
jobs:
codegen-diff:
runs-on: ubuntu-latest
steps:
- run: |
curl -X POST https://api.github.com/repos/your-org/your-repo/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
--data '{"event_type": "codegen", "client_payload": { "pr_number": ${{ github.event.number }} }}'
workflowの境界
FE側のコード生成をする都合上がっつりFEの環境構築のstepが発生するので、BE側のworkflowはOpenAPIの変更を検知してFE側に通知するだけに留めました。
最低限要件として、PRの作成のstepはFE側のworkflowで行う必要があります。
異なるプライベートリポジトリへPRを出す場合はPersonal Access Tokenを使わなければならず、そうするとPRの作成者がPATの発行者になってしまうためです。
FE側でstepを実行すれば、GITHUB_TOKEN
の権限でPRが作成できるので、botのアカウントがPRを作成してくれます。
場合によってはBE側で完結するようにしたほうがGitHub Actionsの料金とかはお得になったりするかもしれません。
Discussion