この記事は Fringe81 Advent Calendar 2019 の23日目の投稿です。
前回の技術書典7に社内有志でサークル参加しました。そのときに作った冊子のPDF生成自動化についてやったことと反省点とかいけてないなーと思ったことについて思い出しながら書いていきます。
ことのはじまり
各自が適宜ブランチ切って作業していくという雑なスタイルを採用したはいいが、他のメンバーの成果物をレビューするときに
- fetch して
- checkout して
- PDF生成して
という手間を踏むのが大変だなと感じていました。
知人に自身の所属企業で技術書典の出展とりまとめみたいなことをしている人がいて、その人はAWSでやっているということを書いていたので、その人に話をききつつ自分はGCPのサービス群で自動PDF生成に挑戦しようと思ったのでした。
やったこと
下準備
- ふつうに Re:VIEW プロジェクトを作って textlint / rake pdf できる状態を作る
- GitHubにリポジトリ作ってPushしとく
ここらは割愛。Re:VIEWとかrextlintのドキュメントみながらふつうにやったはず。
ビルド用のコンテナイメージを作る
- Re:VIEWをやっている
- textlint したい
とかを考慮してなんかこんな感じのものを作って build して push しました。
FROM vvakame/review:3.2
# setup
RUN apt-get update && \
apt-get install -y --no-install-recommends \
texlive-fonts-extra && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN npm install -g \
textlint \
textlint-plugin-review \
textlint-rule-max-ten \
textlint-rule-no-mix-dearu-desumasu \
textlint-rule-prh \
textlint-filter-rule-comments
cloudbuild.yaml を書く
こんな感じのもの
steps:
- name: 'gionxy/review:3.2'
entrypoint: bash
args:
- -c
- |
cd articles && textlint *.re && rake pdf
id: pdf-build
artifacts:
objects:
location: 'gs://[bucket_name]/$SHORT_SHA'
paths: ['articles/[booktitle].pdf']
次のようなことが書いてあります:
-
gionxy/review:3.2
使ってbash -c "cd articles && textlint *.re && rake pdf"
してね! - そのあと
articles/[booktitle].pdf
をgs://[bucket_name]/$SHORT_SHA
以下に置いてね!
ここで $SHORT_SHA
はGitのコミットID(短いもの)に置換されます。
反省点
例の記事にひきづられてなんとなく1コンテナで完結するように新しいイメージを作ってしまったんですが、よく考えればCloudBuildで使うんだからRe:VIEWコンテナとtextlintするコンテナは別でよかった。stepsを2つにして textlint → Re:VIEWビルド みたいに。
Cloud Build とリポジトリを接続
https://cloud.google.com/cloud-build/docs/run-builds-on-github?hl=ja を参考に Cloud Build アプリをリポジトリに追加した。
こうすることで、全コミットに対してトリガーが発動するようになりました。今はわかりませんが、8月時点ではたとえばPRオープン時のみ起動、というような設定はなかったと思います。
PRに対してはChecks APIで結果が通知されるようになりますが、成功か失敗かくらいしか伝達されない非常にシンプルなもので、具体的な失敗の原因はリンクを踏んでコンソールを確認しないといけませんでした。
Cloud Build の結果を Cloud Functions で受け取る
最低限ビルドが成功したときの成果物URLくらいは通知されたかったので完了を受け取るそう方法がないか調べたところ、cloud-builds
というPubSubトピックに情報を投げてるようだったので、そのイベントを拾って動作するスクリプトを Cloud Functions に置きました。
import os
import sys
import json
import base64
import urllib.request
def check(event, context):
if event['attributes']['status'] == 'SUCCESS':
main(event, context)
def main(event, context):
messages = json.loads(base64.b64decode(event['data']).decode('utf-8'))
substitutions = messages['substitutions']
repo_name = substitutions['REPO_NAME']
commit_sha = substitutions['COMMIT_SHA']
short_sha = substitutions['SHORT_SHA']
url = f'https://api.github.com/search/issues?q=is:pr+repo:gion-xy/{repo_name}+sha:{commit_sha}'
headers = dict(Authorization=f'token {os.environ["TOKEN"]}')
search_request = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(search_request) as res:
search_response = json.loads(res.read())
if search_response['total_count'] == 0:
print(f'{short_sha}: not PR commit', file=sys.stderr)
exit(0)
pull_request_info = search_response['items'][0]
if pull_request_info['state'] != 'open':
print(f'{short_sha}: PR is not open', file=sys.stderr)
exit(0)
pull_request_number = pull_request_info['number']
body = f'PDFはこちら: https://storage.googleapis.com/f81-techbookfest7/{short_sha}/1chome-lab-1.pdf'
post_request = urllib.request.Request(
f'https://api.github.com/repos/gion-xy/{repo_name}/issues/{pull_request_number}/comments',
data=json.dumps(dict(body=body)).encode(),
headers=headers
)
with urllib.request.urlopen(post_request) as res:
body = res.read()
commit_sha
が含まれるPRを探すというなんというかすごく泥臭いスクリプトになりました。
この状態でPRを更新するとCI成功時にPRにコメントがつくようになった。やりましたね。Botからやるみたいなのよくわからなくて自分のトークン使って投稿してます。
SlackにGitHub連携を入れてPRへのコメントが通知されるようにすればSlackからすぐに結果に飛べて便利。これは全てがうまくいって自画自賛している様子です。
まとめ
Cloud BuildとGitHubを連携させてRe:VIEW文書の自動PDF生成を行う方法についてみてきました。
Cloud BuildのGitHub Appは当時出たてで機能も少なかったためにいろいろ工夫をする必要がありましたが、いまだとドキュメントを見る限りPRに対してのみ反応できるようになっている雰囲気があり、より楽になっているのではないでしょうか。
技術書典8は残念ながら(社内有志としても個人としても)サークル参加を見送ることになりましたが、一般参加はする予定なので、この記事がサークル参加するみなさんの助けになったりすることがあれば嬉しいなと思います。というか現環境でこういうもの組むとやっぱり楽になってるのかどうかというところは知りたいですね。