OpenAPI generate を GitHub Actions で自動化

スキーマファーストで開発する場合、OpenAPIとかで定義を書いてから、サーバサイドのI/FやクライアントSDKを自動生成する。
手動でやるとクライアントSDKの生成を忘れことがよくあるので、自動化したい。
GitHub Actionsで生成〜Pull Request作成までを自動化してみる。

目次

前提

以下のような構成を想定する。
今回はサーバサイドのリポジトリにおける、クライアントSDK用コードの自動生成を試す。

サーバ開発の流れとしては、以下想定。

  1. Openapi.yamlにAPI定義を記載する
  2. Openapi.yamlに定義されたAPI仕様から、サーバサイドのI/Fを自動生成する
  3. I/F仕様に基づき実装する -> サーバサイドの実装完了
  4. Openapi.yamlに定義されたAPI仕様から、クライアントサイドのSDK(Java用)を自動生成する
  5. 生成されたクライアント用のSDKはprivateなmaven repositoryに配置しておく
  6. クライアントサイドは、private maven repositoryからクライアント用SDKを取得し、サーバのAPI呼び出しに利用する

Openapi.yamlを別リポジトリに配置するとか、クライアント用SDKとして複数言語生成した場合どうするとか色々あると思うが、とりあえず全部Java想定で試作する。

やること

  1. openapi.yamlの変更検知
    検知したらGitHub Actionsを起動する
  2. ビルドバージョンを更新
    今回はマイナーバージョンをインクリメントする運用としてみる
  3. openapi generateする
    タスクランナー(Gradle)にgenerateコマンドが用意されている想定
  4. GitHubにCommit & Push
  5. Pull Request作成

Java用GitHub Actions用意

GitHub ActionsのテンプレートはJava環境を用意しておく。

1
2
3
4
5
6
7
8
9
10
11
12
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
server-id: github
settings-path: ${{ github.workspace }}

openapi.yamlの変更検知

対象ブランチにpushされたときを想定で以下のような感じ。
今回はmainブランチにpathに記載のファイルがpushされた場合に起動するように記載。
Pull Requestがマージされたときという書き方でもいい気がする。

1
2
3
4
5
on:
push:
branches: [ main ]
paths:
- "api/openapi.yaml"

ビルドバージョンを更新

Gradle想定なので、jar生成前にbuild.graleに記載のバージョンを更新したい。
そのため、バージョンをgradle.propertiesに記載しておき、build.gradleにはこのプロパティから読み込む設定にする。
GitHub Actionsの中でgradle.propertiesに記載のバージョンを取得&書き換えをおこなう。

まずはgradle.propertiesにバージョン情報記載

1
apiVersion=1.0.0

build.gradleはそのバージョンを読み込むように記載する

1
2
3

version = apiVersion

で、GitHub Actionsは以下のように設定して、gradle.propertiesから現行バージョン取得 & バージョンをパース & マイナーバージョンの書き換え をする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- name: VUP
id: vup
run: |

# プロパティ値読み込み
source gradle.properties

# バージョンを読み込み&マイナーバージョンをインクリメント
# パッチバージョンは0にしておく
NEW_VERSION=`echo $apiVersion | ( IFS=".$IFS" ; read a b c && echo $a.$((b + 1)).0 )`

# 書き換え
sed -i -e "s/apiVersion=${apiVersion}/apiVersion=${NEW_VERSION}/" gradle.properties

# あとで使えるようにset-outputでバージョン情報を保存しておく
echo ::set-output name=VERSION::$NEW_VERSION

openapi generateする

build.gradleでgenerate用のタスクを用意している想定で、以下の通り。
openapi-generator-cliとかをdockerで引っ張ってきてもいい。

1
2
3
4
5
6
7
8
- name: Generate with Gradle
run: |

# 生成済みコードは削除しておく
rm -rf src/*

# 生成タスク実行
./gradlew openapigenerate

GitHubにCommit & Push

生成したコードをCommit & Pushする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- name: Get branch
id: get_branch
run: echo ::set-output name=BRANCH::${GITHUB_REF/refs\/heads\//}
- name: Switch branch
run: git switch -c tmp
- name: Git config
run: |

# GitHub Actionsの実行botは以下のアドレスが使えるらしい
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"


この辺で生成


- name: Push branch
id: push_branch
env:
VERSION: ${{ steps.vup.outputs.VERSION }}
run: |

# ブランチ名決めてブランチ生成
branch_name=f/generate-java-client-v${VERSION}
git branch -m ${branch_name}

# Commit
git add ./src/*
git commit -m "[update]openapigenerate v${VERSION}"

# Push
git push origin ${branch_name}

# ブランチ名とかあとで使えるようにしておく
echo ::set-output name=BRANCH_NAME::$branch_name

git操作はGitHub Actionsで自動生成されているsecrets.GITHUB_TOKENが利用されるので、特に気にしないでOK
エラーが出る場合は、permissionsを確認すること。ここでsecrets.GITHUB_TOKENの権限が設定される。
参考:権限の種類

適当なテンプレートを使うと、この辺が絞られているため、git pushでエラーになることがある。
権限がないと以下のようにリポジトリが見つからないエラーになった。
(privateリポジトリだからnot foundになったのかな?publicだと権限エラーになる?)

1
2
3
Push to branch main
remote: Repository not found.
fatal: repository 'https://github.com/xxxxx/xxxxxxxxxxxxxx/' not found

Pull Request作成

actions/github-scriptというのを使うと、GitHub APIが叩けるので、それを使って Pull Request を作る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- name: Create pull request
uses: actions/github-script@v2
env:
BASE: ${{ steps.get_branch.outputs.BRANCH }}
VERSION: ${{ steps.vup.outputs.VERSION }}
HEAD: ${{ steps.push_branch.outputs.BRANCH_NAME }}
with:
script: |
github.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `generate java client code : v${process.env.VERSION} from ${process.env.HEAD}`,
body: `generate v${process.env.VERSION}`,
head: `${context.repo.owner}:${process.env.HEAD}`,
base: process.env.BASE
})

actions/github-scriptは、Stepで定義した変数をprocess.env.XXXXで読み込めるので、その辺をうまく使っていろいろやる。

おわりに

権限周りでハマってしまい試作に時間かかったが、これでコード自動生成ができた。
実際には、生成後にビルド&単体テストとか入れ込んでからPull Request生成するようにする。
あとはこのPull Requestがマージされたら、jarを生成してpublishするActionを用意すれば、実際に使えそう。
仕様書もReDocとかで生成して、どこかに配置するようにすれば完璧。
OpenAPIに限らず、gRPCとかスキーマ駆動で開発するものには応用できるはず。どんどん作業効率化していきたい。