Github ActionのCI中にファイルに変更が発生したら自動でPull Requestを作成する仕組みを作った

TL;DR

久々に年末年始に長い休みが取れたこともあり時間ができたので、せっかくだしなんかやろうと思って、pushしたらなんらかのトリガーを元にGithub ActionのCIを回してファイルを更新-> PRを作るまでを実装してみた。

Motivation / Background

手元でやってpushするのはめんどいなーということってあると思うんだけど、例えばrspecからドキュメント自動生成 -> PR作成はCIに任せたいとか、既存のOpenAPIの設定からTypeScriptの型を作りたいみたいなのを手元でやりたくないみたいなのに使えそう。

今後の仕事で活かすこともできそうだし作ろうと思った。

Railsの準備

去年仕事でも触ったopenapiをrspecから自動生成する系のgemを使ってみることにした。

まずは適当にRailsのプロジェクトを作ってopenapiをrspec実行したら自動生成できるようにしておく。

この辺のrails new的な記事はそのうち書く予定。一年に一回やるかどうかのrails new。。。ちなみに今回はGithub Actionでなんかしらの自動生成させたいだけなので、別にRailsじゃなくても構わない。

想定するフロー

今回作りたいフローとしては以下。

  1. mainのソースコードを更新する
  2. gitにpushする
  3. 対象のディレクトリ以下のファイルが更新されていればドキュメントを自動生成をCI上でまわす
  4. CI上で発生した差分を新規のブランチにcommit
  5. 新規のブランチからmainにPR

自分の作ったブランチにPRしたいじゃんという気持ちもよぎったけど、これからレビューされるものよりmainから作って自動PRするほうがいいなと思った。ちなここのフックはmainにpushされた時点とかでなくても変えられるのでなんとでもって感じ。

実装

早速Github ActionのWorkflowを実装していく。今回はrequest_specに変更があったらドキュメントを生成したいので、専用のファイルを作った。

name: "API: Generate Openapi Schema"

on:
  push:
    branches:
      - main
    paths:
      - "app/controllers/api/**"
      - "spec/requests/api/**"
      - ".github/workflows/api_generate_openapi.yaml"
      - ".github/workflows/openapi-generater.yaml"
  workflow_dispatch:

jobs:
  ci:
    name: Generate Openapi Schema
    uses: ./.github/workflows/openapi-generater.yaml
    with:
      app_name: api

workflow_dispatchを入れることで、あとで手動でworkflowを実行できるようにしておく。

こっちが本丸。

name: "Generate Openapi Schema"

on:
  workflow_call:
    inputs:
      app_name:
        description: 対象アプリケーション名(controller配下のディレクトリ名)。
        type: string
        required: true
jobs:
  ci:
    name: CI
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout
        uses: actions/checkout@v3

  # ~~~アプリケーション設定なので省略~~~

     # ファイルに変更を入れる
      - name: Run test
        run: OPENAPI=1 bundle exec rspec

     # ブランチを定義
      - name: Define new branch name
        id: define_new_branch_name
        run: |
          new_branch_name=update-api-schema/$(echo "${ISSUE_NUM}-$(TZ=UTC-9 date '+%Y%m%d%H%M%S')")
          echo "new_branch_name=$new_branch_name" >> $GITHUB_OUTPUT

     # ブランチを作成
      - name: Create branch
        uses: EndBug/add-and-commit@v9
        with:
          new_branch: ${{ steps.define_new_branch_name.outputs.new_branch_name }}

     # 発生した差分をコミット
      - name: Commit updates
        uses: EndBug/add-and-commit@v7
        with:
          branch: ${{ steps.define_new_branch_name.outputs.new_branch_name }}
          message: "Update ${{ inputs.app_name }} Schema: ${{ steps.get_version.outputs.LATEST_VERSION }}"
          add: "['doc/openapi/${{ inputs.app_name }}.yaml']"

     # mainにむけてPull Requestを作成
      - name: Create PR
        run: |
          gh pr create \
            --head $NEW_BRANCH_NAME \
            --base $BASE_BRANCH_NAME \
            --title "$ISSUE_TITLE" \
            --body "Update API Schema"
        env:
          GH_TOKEN: ${{ github.token }}
          NEW_BRANCH_NAME: ${{ steps.define_new_branch_name.outputs.new_branch_name }}
          ISSUE_TITLE: "Update ${{ inputs.app_name }} Schema: ${{ steps.get_version.outputs.LATEST_VERSION }}"
          BASE_BRANCH_NAME: ${{ github.event.repository.default_branch }}

↑を.github/workflow直下に設定したのち、workflowを実行すると以下のようなPRが作成される。

解説

reusing workflow

今回はreusing workflowを使って、複数のworkflowファイルから呼び出される前提の実装をしている。これなら上だと「api」ディレクトリのopenapiを作成しているが、例えば「api_internal」みたいな内部向けAPIのディレクトリを切って、そこのみドキュメントを作りたいみたいなニーズにも応えられる。

なのでこのまま実装を手元のworkflowに貼り付けても動作しないので、自社サービスに使う時は注意してほしいかな。

set-output

今回実装していて気づいたけど、github actionでset-outputは非推奨-> 今年の五月に削除されるとのこと。

最初Classmethodさんの記事を参考にやってたんだけど、コードが古くてWarningがでていたので、set-output使ってる人たちはお気をつけて。

変換ツールを作ってくれている親切な人がいるのでありがたくお世話になった。

インストールして以下を実行すれば変換される。

% npx set-env-to-github_env                                                                    
Migrate ./.github/workflows/openapi-generater.yaml
Migration is completed`

まとめ

Github Action、privateリポジトリだと有料だから敷居高く感じちゃうけど、publicリポジトリだと無課金でたくさん試せるので、勉強がてらこうして便利なworkflow作っていくのもよいなと思った。