GitHub Actions で OpenAPI の自動生成コードの Pull Request を複数リポジトリへ飛ばしてみた

f:id:shinnosuke-K:20210825220031p:plain

こんにちは、2021年に新卒で入社したエンジニアの岸田(@mwudo)です。

所属している ATOM 事業部で API のリファクタリングやテストを書いたり、機能実装などを日々行っています。

この記事では、配属されて最初に取り組んだ業務の一つの GitHub Actions を使って OpenAPI の自動生成コードの PR (Pull Request) を作成する流れについて紹介します。

なぜやることになったのか

配属される直前にある issue が立ちました。

f:id:shinnosuke-K:20210903111841p:plain
HRは有名なモンスターを狩るゲーム内のレベル

いくつかクエストがあり、その一つがこれから紹介するものです。(難易度はたぶん村の星4ぐらい)

今回の対象となるリポジトリ周りについて簡単に説明します。

OpenAPI で記述された yaml ファイルを管理するリポジトリがあり、それを submodule としてバックエンドとフロントエンドのリポジトリに取り込む運用をしています。

なので、OpenAPI の定義を更新するたびに、各リポジトリで submodule を更新して、自動生成コマンドを走らせることになります。

この運用でも問題はないと思いますが、エンジニアは総じて面倒くさいことは自動化させたいと思う人種(個人的見解)なので、GiHub Actions でやってみたということです。

動作の流れ

では、どのように自動生成コードの PR が作成されるのかざっくりとした図と流れを示します。

f:id:shinnosuke-K:20210902174048j:plain

  1. OpenAPI 定義を管理するリポジトリで作成した PR 内のコメント欄に /generate と打つ
  2. 各リポジトリへ自動生成コードの PR を作成するイベントが発火
  3. 各リポジトリで submodule を更新し、コードを生成して PR 作成

ここからはそれぞれを詳しく説明していきます。

特定のコメントで GitHub Actions を起動

こちらの記事がよくまとまっていて参考になりました。

akaimo.hatenablog.jp

記事の中でも記載されていますが、以下の設定してあげると if の条件によってジョブが実行されるかどうか決定されます。

name: Requester
on:
  issue_comment:
    types:
      - created
jobs:
  request-pr-creation:
    if: (github.event.issue.pull_request != null) && github.event.comment.body == '/generate'
    runs-on: ubuntu-latest

/generate の部分を変更することで好みのコメントにすることもできます。

(タイポすると動かないので、ミスると地味に恥ずかしい)

token の設定

他のリポジトリへイベント通知させるためには、token 情報を設定する必要があります。

いろいろ調べてみて、最終的にGiHub App Tokenを使用しています。*1

github.com

これを使うためには Organization 下で GitHub App を作成する必要があります。*2

ちなみにここからの作業は Organization の場合、Owner 権限 を持つ方でないとできないので注意してください。

(僕は所属する部の部長さんに画面を共有してもらいながら一緒に行いました)

GitHub App を作成する - GitHub Docs

Permissions の設定項目は2つのみ変更します。

  • Contents → Read & write
  • Metadata → Read Only

設定が完了したら、対象となるレポジトリへインストールします。対象は今回のフローを適用する全てのリポジトリです。

GitHub Appのインストール - GitHub Docs

インストールが完了したら、各レポジトリの secrets に変数を設定します。(ここが一番面倒くさい)

リポジトリの settingsecrets で、以下の変数を設定

変数名 値の在り処
APP_ID 作成した Github App の App ID
PRIVATE_KEY Github App で作られる Private keys *3

ここまでの設定してようやく token を作成することができます。

- name: Genarate token
  id: generate_token
  uses: tibdex/github-app-token@v1.0.2
  with:
    app_id: ${{ secrets.APP_ID }}
    private_key: ${{ secrets.PRIVATE_KEY }}

作成した token は steps の id 紐付いているので、
${{ steps.generate_token.outputs.token}} で使うことができます。

ブランチ情報の取得

今回のフローは PR がマージする前に行われるため、検討するべき事がありました。

  • submodule を更新するためには PR のブランチ情報が必要
  • 関係するリポジトリのブランチ戦略上、マージ先のブランチが固定されない

なので、ブランチ情報を取得する必要があり、GitHub の API を使って情報を取得します。

こちらの記事を参考に作成しました。

buildersbox.corp-sansan.com

- name: get upstream branch
  id: upstreambranch
  run: |
    PR=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" ${{ github.event.issue.pull_request.url }})
    echo "::set-output name=branch::$(echo $PR | jq -r '.head.ref')"
    echo "::set-output name=base::$(echo $PR | jq -r '.base.ref')"

branch は 作成した PR のブランチ、base が マージ先のブランチになります。

指定したリポジトリへイベントを通知

イベントの通知には Repository Dispatch を使用しました。

- name: Notify to backend
  uses: peter-evans/repository-dispatch@v1
  with:
    repository: backend
    token: ${{ steps.generate_token.outputs.token}}
    event-type: generate-code
    client-payload: '{
      "url": "${{ github.event.issue.pull_request.html_url }}",
      "user": "${{ github.event.comment.user.login }}",
      "branch": "${{ steps.upstreambranch.outputs.branch }}",
      "base": "${{ steps.upstreambranch.outputs.base }}",
    }'

client-payload で値を設定してあげると、通知先の GitHub Actions で使うことができます。

この設定を通知したいリポジトリ分追加します。

イベントを受信してコードの自動生成

submodule の更新とコードの自動生成です。(token 作成も忘れずに)

Repository Dispatch からイベントの通知が来るので、通知をトリガーとして GitHub Actions を起動させます。

submodule が絡む関係で checkout の処理が通常とは少し異なります。

on:
  repository_dispatch:
    types: [generate-code ]

# token の作成
- name: Genarate token
  id: generate_token
  uses: tibdex/github-app-token@v1.0.2
  with:
    app_id: ${{ secrets.APP_ID }}
    private_key: ${{ secrets.PRIVATE_KEY }}
      
- uses: actions/checkout@v2
  with:
  token: ${{ steps.generate_token.outputs.token}}
  ref: ${{ github.event.client_payload.base }}
  fetch-depth: 0
  submodules: true

# submodule を更新
- name: update submodule
  run: cd { submoduleのディレクトリ } && git pull origin ${{ github.event.client_payload.branch }} && cd ..

submodule を更新する方法の一つのgit submodule foreach の場合、複数 submodule を持っていたらすべての submodule で実行します。

バックエンドのリポジトリは submodule を2つ持ってるので、foreach だと特定の submodule を更新することができないです。

もっといい方法があると思ったり、たまにエラーが起きたりがありますが、概ね動いているのでこれでやっています。

コードの自動生成の部分について、フロントエンドとバックエンドの言語が違うので、ここだけ処理がリポジトリで違ってくると思います。

まず バックエンドの自動生成について見ていきます。言語は Go です。

- uses: actions/setup-go@v2.1.3
  with:
    go-version: '^1'

- name: Install swagger and Generate Code
  run: go install github.com/go-swagger/go-swagger/cmd/swagger@latest && make generate-code

Go のコードを生成できる CLI(今回は go-swagger) をインストールして、自動生成します。(具体的なコマンドは Makefile に書いているはず)


次にフロントエンドの自動生成について、yarn の例を示します。(npmでも似たようなものになるはず)

- uses: actions/setup-node@v2

- name: Install packages and Generate Code
  run: yarn install && yarn api:generate

- name: Remove package
  run: rm -rf ./node_modules

フロントの場合は、yarn install 時に作成される node_modules を消すことをお忘れなく(具体的な自動生成コマンドはここでは省略)

PR 作成

最後に PR を作成して完了です。

- name: Create Pull Request
  uses: peter-evans/create-pull-request@v3.10.0
  with:
    title: 'APIコードの更新 by ${{ github.event.client_payload.user }}'
    token: ${{ secrets.GITHUB_TOKEN }}
    branch: ${{ github.event.client_payload.branch }}
    base: ${{ github.event.client_payload.base }}
    commit-message: 'update submodule and generated code'
    body: |
      ${{ github.event.client_payload.url }}

f:id:shinnosuke-K:20210902125101p:plain
GitHub Actions で作成される 自動生成コードのPR

惜しいところは、コメントに PR の URL を挿入するとメンションが飛ぶはずなのですが、Private リポジトリかつ PR 作成者が bot の関係でうまくメンションが飛びませんでした。(後からコメントを編集すると飛ぶ)

おわりに

この仕組を作ることで、git pull するだけで OpenAPI の自動生成コードを手元に用意できるのは個人的には楽だと感じています。

似たようなことをやりたいと思っている人の参考になれば嬉しいです。

次は Go に関連した記事を書ければと思っております!!

*1:GitHub で用意されている GITHUB_TOKEN は権限が厳しすぎるため、使えませんでした。一応、Personal Access Token (PAT) を使えば可能でしたが、PAT は個人に付与される情報なので、作成した人が異動や退職された場合の管理コストを考えて採用は見送りました。

*2:GitHub App のステータスを Public にすると GitHub Marketplace に公開されるので必ず Private

*3:Private keys がある場所は、Github Appの設定の General の下の方にある項目で Generate