GitHub Actions で Vercel bot みたいなプレビューデプロイをつくる
Vercel というウェブサイトをデプロイできるサービスがあって、それを GitHub と連携すると、
GitHub で push したりプルリクエストを開いたりしたときに、自動でいろいろやってくれるんですよ。
デフォルトブランチに push したら自動で再デプロイしてくれたり
デフォルト以外のブランチに push したら自動で preview version をデプロイしてくれて、
PR を開くと自動でコメントしてくれたり
再度 push すると再度プレビューをデプロイしてくれたりします。
特に PR 開くと自動でプレビューをつくってくれるのは、さくっと動作確認できてレビューにもすごく便利です。
Vercel を使っているときはこれを GitHub のリポジトリと連携するだけで、特に設定しなくてもこういう便利なことをやってくれるんですが、
使っていないときにもこれができたらなあと思ったわけです。
ということで、GitHub Actions でこのプレビューデプロイと似たようなことをやってみましょう!
そもそも vercel[bot] は何をやっているのか
どの GitHub のイベントがどういうふうにプレビューデプロイや PR コメントをトリガーしているのか観察してみると、
- push したときにデプロイがはじまる
- PR を開いたときにコメントする
- デプロイが終わっていなければ Preview: In Progress とコメントする
- デプロイが終わり次第コメントを Preview URL をのせたものに更新する
- デプロイがすでに終わっていれば Preview URL をすぐにコメントする
こんな感じかと思います。
PR を開いたときにはじめてデプロイをするという方法もあり、ワークフローがひとつで済むので簡単ですが、
push してから PR を開くまでの間にデプロイをすすめておいて、PR を開いたらすぐにコメントしてくれるほうが時短になるので、これを実現したいです。
処理の内容を図にすると次のようになります。
デプロイが終わるのと PR を開くのと、どちらが先に終わるかによって通るフローが変わってきます。
デプロイが先に終わっていれば PR が開かれたらすぐに preview URL をコメントするし、
PR が先に開いていればデプロイが完了するまではデプロイ中であるとコメントし、デプロイが完了すれば preview URL をのせたコメントに更新します。
ワークフローを書く
見たところワークフローが GitHub に対して必要になる処理は、
- PR にコメントを作成・更新する
- デプロイが完了しているか確認する
- 対応する PR が開いているか確認する
いずれも GitHub API を使うことで実現できます。
PR コメントに関しては peter-evans/find-comment
と peter-evans/create-or-update-comment
を使うのが簡単かと思います。
「デプロイが完了しているか確認する」に関しては、
GitHub の environment を作成して、デプロイ前に「デプロイ中」のステータスを付与し、デプロイ後に「完了」のステータスを付与することで、
GitHub API によってデプロイ状態を取得できます。
これは bobheadxi/deployments
を使うと簡単です。
今回のコード例ではこれらを使用します。
push したときの処理
push したときには、デプロイをし、デプロイ後にそのブランチに対応する PR が存在するかチェックし、存在していたらコメントを残します。
コード例は以下のようになります。
.github/workflows/deploy-preview.yml1name: Deploy preview23on:4push:5branches: # branches other than main6- '*'7- '!main'89env:10ENV_NAME: preview-${{ github.ref_name }} # preview-[branch name]1112jobs:13deploy:14name: Deploy1516runs-on: ubuntu-latest1718permissions:19deployments: write # bobheadxi/deployments needs this2021outputs:22url: ${{ steps.deploy-url.outputs.url }}2324steps:25- name: Start deployment26uses: bobheadxi/deployments@v127id: deployment28with:29step: start30token: ${{ github.token }}31env: ${{ env.ENV_NAME }}3233# Authenticate and deploy steps...3435- name: Deploy36id: deploy-url37run: # Output the deployment URL3839- name: Update deployment status40uses: bobheadxi/deployments@v141if: always()42with:43step: finish44token: ${{ github.token }}45status: ${{ job.status }}46env: ${{ steps.deployment.outputs.env }}47env_url: ${{ steps.deploy-url.outputs.url }}48deployment_id: ${{ steps.deployment.outputs.deployment_id }}4950check-if-pr-exists:51needs: deploy5253name: Check if associated PR exists5455runs-on: ubuntu-latest5657permissions:58pull-requests: read5960outputs:61pr-number: ${{ steps.get-pr-number.outputs.pr-number }}6263steps:64- name: Get associated PRs65id: prs66run: >67echo "pulls=$(gh api -H "Accept: application/vnd.github.v3+json" -X GET68/repos/${{ github.repository }}/pulls69-f state=open70-f head=${{ github.repository_owner }}:${{ github.ref_name }})" >> $GITHUB_OUTPUT71env:72GH_TOKEN: ${{ github.token }}7374- name: Get PR number75id: get-pr-number76run: |77if [ "${{ fromJSON(env.PULL) }}" ]; then78echo "pr-number=${{ fromJSON(env.PULL).number }}" >> $GITHUB_OUTPUT;79else80echo "pr-number=0" >> $GITHUB_OUTPUT;81fi82env:83PULL: ${{ toJSON(fromJSON(steps.prs.outputs.pulls)[0]) }}8485comment:86needs:87- deploy88- check-if-pr-exists8990name: Comment on PR9192if: needs.check-if-pr-exists.outputs.pr-number != 09394runs-on: ubuntu-latest9596permissions:97contents: read # actions/checkout needs this98issues: write # peter-evans/create-or-update-comment needs this99pull-requests: write # peter-evans/create-or-update-comment needs this100101steps:102- name: Checkout103uses: actions/checkout@v3104105- name: Find Comment106uses: peter-evans/find-comment@v2107id: fc108with:109issue-number: ${{ needs.check-if-pr-exists.outputs.pr-number }}110comment-author: github-actions[bot]111body-includes: preview112113- name: Get datetime for now114id: datetime115run: echo "value=$(date)" >> $GITHUB_OUTPUT116env:117TZ: Asia/Tokyo118119- name: Create or update comment120uses: peter-evans/create-or-update-comment@v2121with:122issue-number: ${{ needs.check-if-pr-exists.outputs.pr-number }}123comment-id: ${{ steps.fc.outputs.comment-id }}124body: |125:eyes: Visit the **preview** for this PR (updated for commit ${{ github.sha }}):126[![View the preview deployment at ${{ needs.deploy.outputs.url }}](https://img.shields.io/badge/-View_Preview-eee)](${{ needs.deploy.outputs.url }})127<sub>(:clock3: updated at ${{ steps.datetime.outputs.value }})</sub>128edit-mode: replace
長いな…
deploy
check-if-pr-exists
comment
の 3 つの job があり、直列になっています。
deploy
では実際のデプロイの処理を GitHub のデプロイ開始、終了の処理で挟み込んでいます。
check-if-pr-exists
では、指定の author、ブランチ名で開いている PR が存在するかをチェックし、存在していればそのような PR の最初のものの番号を、なければ 0 を返します。
needs
に deploy
を指定することで、デプロイ完了時にはじめて PR の存在チェックをするようにします。
comment
は、check-if-pr-exists
で取得した PR 番号と deploy
で取得したデプロイ先の URL にもとづき、PR が存在すれば URL をコメントに残します。
PR を開いたときのワークフローでコメントを残すので、そのコメントの ID を peter-evans/find-comment
で取得し、そのコメントを新しいコメント内容で更新するようにしています。
これで main
以外のブランチに push したときに自動でデプロイされ、完了時に PR が開かれていればコメントされます。
また、GitHub の Environments 欄にこんな感じにデプロイが表示されたりします。
PR を開いたときの処理
PR を開いたときには、デプロイが完了しているか確認し、結果によって異なるコメントを PR に残します。
デプロイ完了時にコメントを残す処理は push のワークフローに定義しているため、ここでは考えなくても大丈夫です。
コード例は以下のようになります。
.github/workflows/comment-on-pr.yml1name: Comment on PR about deployment23on:4pull_request:5types:6- opened78env:9ENV_NAME: preview-${{ github.head_ref }} # preview-[branch name]1011jobs:12check-if-deployment-exists:13name: Check if a successful deployment exists1415runs-on: ubuntu-latest1617permissions:18deployments: read1920env:21GH_TOKEN: ${{ github.token }}2223outputs:24state: ${{ steps.get-status.outputs.state }}25url: ${{ steps.get-status.outputs.url }}2627steps:28- name: Get associated deployments29id: get-deployments30run: >31echo "deployments=$(gh api -H "Accept: application/vnd.github.v3+json"32/repos/${{ github.repository }}/deployments?environment=${{ env.ENV_NAME }})" >> $GITHUB_OUTPUT3334- name: Get statuses of the deployment35id: get-statuses36run: >37echo "statuses=$(gh api -H "Accept: application/vnd.github.v3+json"38/repos/${{ github.repository }}/deployments/${{ env.DEP_ID }}/statuses)" >> $GITHUB_OUTPUT39env:40DEP_ID: ${{ fromJSON(steps.get-deployments.outputs.deployments)[0].id }}4142- name: Get latest status of the deployment43id: get-status44run: |45echo "state=${{ fromJSON(env.STATUS).state }}" >> $GITHUB_OUTPUT46echo "url=${{ fromJSON(env.STATUS).environment_url }}" >> $GITHUB_OUTPUT47env:48STATUS: ${{ toJSON(fromJSON(steps.get-statuses.outputs.statuses)[0]) }}4950comment:51needs: check-if-deployment-exists5253name: Comment on PR5455if: needs.check-if-deployment-exists.outputs.state != null5657runs-on: ubuntu-latest5859permissions:60contents: read61issues: write62pull-requests: write6364steps:65- name: Checkout66uses: actions/checkout@v36768- name: Get datetime for now69id: datetime70run: echo "value=$(date)" >> $GITHUB_OUTPUT71env:72TZ: Asia/Tokyo7374- name: Comment on PR if the deployment has completed75if: needs.check-if-deployment-exists.outputs.state == 'success'76uses: peter-evans/create-or-update-comment@v277with:78issue-number: ${{ github.event.pull_request.number }}79body: |80:eyes: Visit the **preview** for this PR (updated for commit ${{ github.sha }}):81[![View the preview deployment at ${{ needs.check-if-deployment-exists.outputs.url }}](https://img.shields.io/badge/-View_Preview-eee)](${{ needs.check-if-deployment-exists.outputs.url }})82<sub>(:clock3: updated at ${{ steps.datetime.outputs.value }})</sub>8384- name: Create comment if the deployment has not completed85if: needs.check-if-deployment-exists.outputs.state == 'in_progress'86uses: peter-evans/create-or-update-comment@v287with:88issue-number: ${{ github.event.pull_request.number }}89body: |90:hourglass_flowing_sand: preview deployment is ongoing...
こちらは check-if-deployment-exists
と comment
の 2 つの job があり、直列になっています。
check-if-deployment-exists
では GitHub API を使って、対応する deployments を取得し、最新のものの statuses を取得し、その最新のものの state とデプロイ先の URL を取得しています。
取得している情報としては「この PR を出しているブランチにひもづく最新の deployment は今どういう状態か(進行中か完了しているか)」です。
これらの情報を次の job comment
に渡し、デプロイが成功 success
であれば URL をコメント、未完了 in_progress
であればデプロイ中だと示すコメントを残します。
これで PR を開いたときにコメントを残してくれるようになります。
以上で、プレビューデプロイを作成し PR にコメントする処理を GitHub Actions で実行することができるようになりました。
他にも、PR がマージされたらプレビューデプロイを削除したり、リリースを作成したら production 環境にデプロイしたりといった Action を作ることができます。
この記事が参考になったらうれしいです。
ではまた