この記事は CircleCI Advent Calendar 2018 の 12 日目の記事です!(遅刻しました)
昨日は @nozo_moto さんの「VueJS と Firebase と CircleCIで楽々CI CD環境作る話」でした。
経緯 & やりたいこと
私の研究室で公開しているツール1 では CircleCI で自動テストを行っています。
どうせならテストだけでなく,ビルドして GitHub Releases で公開するところまで CI にやらせようという話になり,CircleCI は未経験でしたが,勉強も兼ねて私が実装を担当することにしました。
実現方法を色々と調査しているうちに,CircleCI の以下の現状に気付きました:
- GitHub Releases を管理する統一的な方法が存在していないこと2
- Orb という処理をモジュール化して共有する機能が最近利用可能になったこと
- まだ誰も GitHub Releases を管理する orb を作成・公開していないこと
GitHub Releases の管理というコードは誰が書いても同じようなものになるはずであり,orb として共有べきものです。
まだ誰も作っていないなら,自分が作って公開してしまおう!と思い立ったのが経緯です。
この記事では,CircleCI を触り始めて一週間の素人が,orb を実装して公開するまでの軌跡をつらつらと書いておきます。
これから orb を作る人の参考になれば幸いです。
なお,今回は orb 内部でコマンドラインツールの ghr や github-release を利用しています。
どちらも CLI で GitHub Releases を操作することができる Go 製の OSS です。
作成した Orb
h-matsuo/github-release という名前で orb registry に登録しています。
ソースコードは GitHub に上げています。
Orb に実装する要素の洗い出し
ドキュメントによると,Orb には次の 3 つの要素を含めることができます:
- Executor
- Command
- Job
Executor
Executor では CircleCI ジョブを実行する環境を定義できます。
CircleCI 2.1 で新しく追加された機能です。
今回作成する orb は内部で Go ツールを利用するため,Go がインストールされた環境で実行する必要があります。
default
という名前の executor を定義しておくことで,ユーザに orb の実行環境を示唆できます。
executors:
default:
parameters:
tag:
description: Pick a specific circleci/golang image variant.
type: string
default: "latest"
docker:
- image: circleci/golang:<< parameters.tag >>
上記例では Go のバージョンをパラメータで指定できるようにしています。
デフォルトでは Docker イメージ circleci/golang
の最新版が使われます。
これにより,ユーザは <Orb名>/default
という名前で executor を指定することができるようになります:
orbs:
github-release: h-matsuo/github-release@0.1.3
jobs:
build:
executor: github-release/default
default
executor は aws-cli などの CircleCI 公式の orb のいくつかでも定義されていますので,実行環境を指定したい場合に有効でしょう。
Command
CircleCI で実際に行う一連の処理(step)をまとめたものが command です。
よく使う,あるいは共通の処理を command として切り出すことで再利用が可能となります。
こちらも CircleCI 2.1 から追加されました。
Orb を作成する際は,ユーザに提供したい機能をその単位毎に command として実装するのが自然だと思います。
今回は GitHub Releases の作成および削除機能を提供するため,それぞれ create
/ delete
という名前の command としてロジックを記述します。
commands:
# ここでは delete コマンドのみ記載
delete:
description: Delete an existing release.
parameters:
# GitHub トークンや削除するタグ等のパラメータを指定できるようにする
github-token-variable:
description: Environment variable containing your GitHub personal access token.
type: string
default: $GITHUB_TOKEN
tag:
description: Git tag for the release.
type: string
steps:
- internal__install-deps # github-release コマンドをインストール
- run:
name: "[github-release/delete] Delete the specified release"
command: |
github-release delete \
--security-token << parameters.github-token-variable >> \
--tag << parameters.tag >>
長くなるため詳細なソースコードは省略します(GitHub をご覧ください)。
Job
Job は上記 executor と command をまとめたものです。
つまり,CircleCI の処理を実行する環境と,実際の処理を定義したものが job になります。
実例として CircleCI の公式 orb の一つ docker-publish を見てみます。
この orb は Docker イメージのビルドやレジストリへのプッシュなどを機能として持ちます。
左メニューにこの orb で定義されている job や command の一覧が書いてあります。
ビルドやプッシュはそれぞれ build
/ deploy
という command として実装されていますが,それらを一気に行う publish
という job も定義されていることが分かります。
このように,orb を作成する際は,以下のような場合に job として提供してあげると便利です3 。
- ある command を実行するにあたり必要な前処理・後処理がある
- 複数の command を決まった順序で呼び出すユースケースが存在する
なお,今回作成した orb は非常にシンプルな機能しか持たないため,job は定義していません。
ドキュメントの記述
第三者に利用してもらうためには,ドキュメントもしっかりと書くべきですね。
Description の記述
Orb のソース中では,おおよそすべての要素に description を記述することができます。
description: |
Create / delete GitHub's Releases.
Issues & PRs: https://github.com/h-matsuo/circleci-orb-github-release
上記のような orb そのものの description 以外にも,executor / command / job はもちろん,各 parameter にも description を付けられます。
Usage Examples の記述
ソースのトップレベルに examples
として記述したものは,usage examples としてドキュメント化されます。
examples:
delete:
description: |
Delete an existing release.
Make sure you set $GITHUB_TOKEN to your GitHub personal access token in advance.
usage:
version: 2.1
orbs:
github-release: h-matsuo/github-release@0.1.1
jobs:
build:
description: Delete the release tagged `vX.Y.Z`.
executor: github-release/default
steps:
- github-release/delete:
tag: vX.Y.Z
公式ドキュメントにはこれについての記述が見当たらなかったのですが,利用例があると格段に使いやすくなるので,できるだけ記述した方が良いでしょう。
完成 & 公開
こうして完成したのが h-matsuo/github-release という orb です(GitHub)。
例えば,Gradle プロジェクトでビルドした jar ファイルを GitHub Releases に登録する場合は次のように書けます:
# 注:予めリポジトリへの書き込み権限を持った GitHub Personal Access Token を
# CircleCI の環境変数 $GITHUB_TOKEN として登録しておく必要があります。
version: 2.1
# 利用する orb を指定する
orbs:
github-release: h-matsuo/github-release@0.1.3
jobs:
# `gradle build` で jar ファイル生成
build:
docker:
- image: circleci/openjdk:8-jdk
steps:
- checkout
- run: gradle build
# 生成物を保存しておく
- persist_to_workspace:
root: .
paths:
- ./build/libs/product.jar
# h-matsuo/github-release で GitHub Releases に公開
release:
executor: github-release/default
steps:
# 生成物を取り出す
- attach_workspace:
at: .
# `create` command で公開する
- github-release/create:
tag: vX.Y.Z
title: Version vX.Y.Z
description: This release is version vX.Y.Z .
file-path: ./build/libs/product.jar
workflows:
version: 2
build-and-release:
jobs:
- build
- release:
requires:
- build
Orb の公開手順については,1 日目の @miyajan さんが 非常に詳しくまとめてくださっている のでここでは省略します。
困った点
Private な要素を持てない
Orb に書いた executor / command / job は,すべて公開され orb のユーザが利用できます。
裏返せば,private な command などを定義できない ということです。
Orb 内部で繰り返し行う処理があるとき,それらも command としてまとめたくなります。
このような「orb の内部でしか使わず,ユーザからは直接使ってほしくないような処理」をまとめたいときに少し困りました。
結局,internal__*
というように内部実装向けの command であることを明示する prefix を付けることでお茶を濁しましたが……。
Orb 内でしか使えない private な要素が持てるようになると,コードの見通しが良くなるように思います。
Dynamic な Conditional Step がない
CircleCI は 2.1 から when / unless という step(conditional steps)が追加され,これにより条件分岐が可能となりました。
しかし,残念ながらこれはワークフローが実際に実行される前に評価される 静的なもの であり,実行時の動的な評価を必要とする条件分岐はできません。
Orb のような汎用モジュールを作成する場合,より多くの状況やパターンに対応するため,前の step の結果に応じて次の step の処理を変えたり,条件分岐させたりしたくなります。
現状 CircleCI でそのような動的な分岐をするには,シェルスクリプト等でガリガリと書くしかありません。
またこの場合は処理を一つの step で完結させる必要があり,さらに他の orb や CircleCI のビルトインコマンドを用いることができないためコードの再利用性が低くなってしまいます。
Orb の開発側としては,Ansible の playbook のような動的な条件分岐 ができるようになって欲しいですね。
気を付けたい点
一度公開した Orb は消せない
公式ドキュメント にもあるように,一度 production orb として公開してしまうと,原則取り下げることができません。
GitHub 等ではうっかり credentials を push してしまっても,force push したりリポジトリそのものを非公開にしたりすることで取り下げが可能ですが,CircleCI の orb ではそれができません。
自分が利用している orb がある日使えなくなるのは信頼性に関わるため,orb が消せないという仕様自体は良いと思います。
しかし,ついうっかり credentials を上げてクラウド破産したりしてしまわないように注意が必要です。
おまけ:CircleCI CLI にコントリビュートした話
作成した orb の自動リリース用設定ファイルを書いている途中,circleci
コマンドにバグを発見しました(issue #213)。
ドキュメント によると,スラッシュ(/
)も orb の development バージョンとして有効な文字に含まれているはずなのですが,実際にスラッシュを含むバージョン名で circleci publish
すると怒られるというバグでした:
# dev:myinitials/mybranch というバージョン名で publish
$ circleci orb publish src/orb.yml hoge/fuga@dev:myinitials/mybranch
Error: Invalid orb reference 'hoge/fuga@dev:myinitials/mybranch': Expected a namespace, orb and version in the format 'namespace/orb@version'
コマンドのソースコードを見てみると,バージョン名のバリデーション処理に余計な部分があり,そこを削除するだけで問題なく動くことが分かりました。
そこでプルリクエストを出してみた(#214)ところ,めでたくマージされた……という話でした。
コミット内容もたった 3 行削除しただけなので大したことないのですが,OSS にコントリビュートした経験があまりなかったため非常に嬉しかったです。
これを機に,来年は有名な OSS へもどんどんコントリビュートしていけたらいいなと思っています。
明日は @monkut さんの担当です。よろしくお願いします!
-
自前で実装する方法については,CircleCI 公式ブログの次の記事で紹介されています:Automate GitHub Releases with CircleCI ↩
-
なお 公式ドキュメント では,フレキシブルでなくなるため,orb に job のみを定義することは避けるように書かれています。 ↩