Cloud BuildでAPIドキュメントの生成を自動化

f:id:inari111:20181215134720p:plain

https://cloud.google.com/cloud-build より引用

ピックアップ Advent Calendar 2018 16日目です。
今日はサーバーサイドエンジニアのinari111がお送りします。
Goを毎日書いていますが、Goの話は出てきません… adventar.org

弊社ではProtocol BuffersというIDL(Interface Definition Language, インタフェース定義言語)を採用しています。
以前は、protocコマンドでProtoファイルからAPIのドキュメントを生成 -> GCS(Google Cloud Storage)にアップロードする作業を手動で行っていました。
手動でドキュメント生成をするのは辛く、そして忘れてしまうこともあるので、この記事ではCloud Buildを使って自動化するまでを解説したいと思います。

Cloud Buildとは

ビルド、テスト、デプロイなどを実行してくれる従量課金制なビルドサービスです。 1日あたり120分まで無料枠があります。
各ビルドステップはDockerコンテナで実行され、Dockerイメージは公式ビルダーcloud-builders-community もあるので必要に応じて使うことができます。

ファイル構成

Protoファイルはアプリケーションのソースコードとは別のリポジトリで管理していて、ファイル構成は以下のようになっています。
今回のゴールは、 proto/admin_api/*.proto proto/core/*.proto proto/user_api/*.proto のどれかに変更があったらProtoファイルからAPIドキュメントを生成し、GCSにアップロードをすることです。

.
├── Makefile
├── README.md
├── _doc
│ └── rpc (生成したドキュメントを置く)
├── _docker
│ └── Dockerfile
├── cloudbuild.yaml (ビルドステップを定義)
├── proto
│   ├── README.md
│   ├── admin_api (Admin API用Protoファイル)
│   │   └── aaa.proto
│   ├── core (Admin API, User API両方でimportするProtoファイル)
│   │   └── bbb.proto
│   └── user_api (User API用Protoファイル)
│       └── ccc.proto
└── rtdb
 └── hoge_db_schema.yaml (Realtime DatabaseのSchema)

トリガーを作成する

GitHubを選択 f:id:inari111:20181215121033p:plain

その後、対象のリポジトリの選択をします。

f:id:inari111:20181215121132p:plain

トリガーの設定を書いていきます。

f:id:inari111:20181215121331p:plain

GitHubにpushしたときに proto/admin_api proto/core proto/user_api 下のどれかのProtoファイルに変更があれば ビルドが実行されるようにしました。
proto/**/*.proto と書いても動かなかったので、下記のように記載しています。

cloudbuild.yamlを作成

cloudbuild.yamlを用意します。CircleCIのconfig.yamlみたいなものです。
各stepをこのように定義しました。

steps:
# Dockerビルド
- name: 'gcr.io/cloud-builders/docker'
  dir: "_docker"
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/{image名}', '.']

# Protoファイルからドキュメント生成
- name: 'gcr.io/$PROJECT_ID/{image名}'
  args: ['make', 'gen-proto-doc']

# GCSにアップロードする
- name: 'gcr.io/cloud-builders/gsutil'
  dir: "_doc/rpc"
  args: ['-m', 'cp', 'index.html', '${_RPC_DOC_PATH}/${BRANCH_NAME}_${SHORT_SHA}.html']

# Container Registryにpush
images: ['gcr.io/$PROJECT_ID/{image名}']

各stepを軽く解説します。
1つ目は、Dockerをビルドします。 dir で指定したディレクトリにあるDockerfileを対象とし、 gcr.io/$PROJECT_ID/{image名} というイメージを作成します。

2つ目は、前stepでビルドしたイメージを使い、 make gen-proto-doc を実行します gen-proto-doc

protoc -Iproto --doc_out=_doc/rpc --doc_opt=html,index.html proto/**/*.proto

を実行するようにMakefileに定義していて、Protoファイルからドキュメントを生成することができます。
結果として _doc/rpc/index.htmlが作成されます。

3つ目は gsutilコマンドを使ってGCSにファイルをアップロードします。 BRANCH_NAMESHORT_SHA はcloudbuild.yaml上で使うことができる変数です。デフォルトで使えるものは他にもあるのでこちらを参照してください。https://cloud.google.com/cloud-build/docs/configuring-builds/substitute-variable-values
独自の変数をトリガーの設定ページから登録することもできます。今回はGCSのパスを_RPC_DOC_PATH として登録していました。

最後の images を書くことで Container Registryにpushできます。不要だったら記載する必要はありません。

ローカルで検証

cloud-build-localをインストール

gcloud components install cloud-build-local

cloud-build-localをインストールしたら、ローカルで実行することができます。
--configでビルドリクエストファイルを指定
--dryrun=falseでビルドを実行
--pushはビルド中に作成されたイメージをContainer Registry にpushするかどうかです
変数に _RPC_DOC_PATH を設定しているので --substitutions=_RPC_DOC_PATH=gs://{バケット名}/{パス} を指定する必要があります。
ローカルから実行する場合に限り、 BRANCH_NAMESHORT_SHA の指定も必要です。 ローカルで実行する場合はこんな感じになります。

cloud-build-local --config=cloudbuild.yaml --dryrun=false --push=false --substitutions=_RPC_DOC_PATH=gs://{バケット名}/{パス},BRANCH_NAME=hoge,SHORT_SHA=aaaaa .

↑を実行するとGCS上にアップロードできました。 f:id:inari111:20181215141458p:plain

開発時の検証やテストはcloud-build-localでだいたい事足りました。

ローカルからビルドを送信

実際にローカルからビルドを送信してリモートでビルドを実行した場合は、以下のコマンドを実行します。

gcloud builds submit --config=cloudbuild.yaml --substitutions=_RPC_DOC_PATH=gs://{バケット名}/{パス},BRANCH_NAME=hoge,SHORT_SHA=aaaaa

ブラウザからビルド実行

ブラウザからも実行可能です。

f:id:inari111:20181215142358p:plain

GitHubへのpushをトリガーにビルド実行

これは書くまでもないですね。
ビルドを実行したくなかったら、[skip ci] または [ci skip] をコミットメッセージに書くとビルドがスキップされます。

最後に

  • Cloud Buildを使い、APIドキュメントの生成を自動化することができました
  • 特定のパスのファイルに変更があった場合のみ処理を実行することができるのは便利です
  • 今回は触れませんでしたが、ドキュメントのURLをSlackに通知するのもやっていく予定です
  • 他の会社でもProtocol Buffers採用してほしい