最近 Drone という CI プラットフォームを試しています. Drone は Plugin という形で拡張機能を提供するので Plugin を自作してみた,という話です.

作ったもの

表題の通り,git-with-ssh という Plugin を作った:

この Plugin は SSH による git コマンドの利用を可能にする. 例えば,Drone で GitHub へのプッシュをしたいとき,パーソナル API トークンを使うのではなく Deploy Key を使いたい場合は次のように書くと良い:

この方法は Issue で作者本人が提案しているため,おそらく推奨されている方法なのだろう. 二行追加するだけだから特別な機能は提供しない,と述べてるので CircleCI のような Deploy key を追加する機能は実装されないだろう(少なくとも当分は).

しかし,実際に使い始めてボイラーテンプレート化してしまったので,せっかくだからこれを Plugin にしてみようと考えた. 結果として,自作した Plugin を使うと次のようにかける:

行数は大してかわらないけど,なんか綺麗になったでしょ?(笑)

作る

だいたい公式ドキュメントと,drone-plugin 組織アカウントにある公式のリポジトリのコードとにらめっこすればなんとかなった.

Drone Plugin

Drone Plugin の中身はただの Docker イメージだ. 仕組みは簡単で,単純に ENTRYPOINT を設定し,.drone.ymlsettings 以下の値を PLUGIN_ というプレフィックスをつけて環境変数としておくだけだ. 例えば上記の git-with-ssh の例だと:

PLUGIN_SSH_PRIVATE_KEY
PLUGIN_SSH_HOSTS
PLUGIN_COMMANDS

という環境変数にそれぞれの値が代入される. なのであとは ENTRYPOINT を設定する Dockerfile を定義すれば良い. 公式ドキュメントにはシェルスクリプトと Go 言語で作る場合の方法が載っている. が,別に Docker の ENTRYPOINT として実行できればなんでも良いので Haskell でも Ruby でも作れるだろう.

今回は本家のを参考にするために Go 言語で作った.

main.go と plugin.go

Go で作る場合,main.go と plugin.go に分けるのがデファクトスタンダートみたいだ. main.go には ENTRYPOINT に設定する CLI アプリのインターフェースを記述し,plugin.go には処理のロジックを記述するようだ. ざっくりと雰囲気だけ書くと:

CLI には urfave/cli を使っている. 理由は特に知らない. go build することで実行ファイルが生成される.

脱線: vs. 改行

少し Drone Plugin とは本質的に関係ない話. plugin.go では id_rsa を次のように生成している:

そして,id_rsa の中身をオプションないしは環境変数として Go アプリに渡したい. しかし,次のように単純に渡してみてもうまく動作しない:

これだと id_rsa の中身は aaa\nbbb となる. --ssh_private_key=$'aaa\nbbb' としたら一応動作するが環境変数などが使えなくなるので,内部で明示的に置換すると良いようだ:

Docker イメージ

他の Drone Plugin のリポジトリを参考にすると,次のような Dockerfile を書くと良い:

FROM で指定するベースイメージには普通,plugins/base を使うようだが,これは scratch にちょっとだけ毛が生えた程度のイメージで git がない. なので docker:git をベースイメージにした. 次のコマンドを実行することで Docker イメージを作成できる:

Drone を設定する

せっかくなので Drone で Docker イメージのビルドなどを CI してみる. Drone Cloud という OSS は無料で使える Drone のクラウドサービスがあるのでこれに設定する.

テストはあとで考えるとして,Go のビルドと Docker イメージのビルドを CI で回す. また,master のプッシュだけは Docker イメージの自動プッシュも実現したい. Drone は他の CI サービスみたいに YAML ファイルで設定ファイルを記述する:

Docker Hub への操作には docker という Plugin を用いた. パスワードのような,ハードコーディングすべきではない文字列は Drone の Secret という仕組みをを用いる. from_secret: key とすることで,Drone の Web UI で設定した key という名の Secret を参照してくれる. 僕はパスワード系の Secret を PR では参照できないようにしているので,when.event.exclude.pull_request とすることで PR の CI では Secret を参照しているステップが動作しないようにしている.

テストをどうするか

plugin.go はただ単にファイルを作ってるだけなのでユニットテストなどはしてもしょうがない. 悩んだ末,最初のシェルコマンドで実行して生成されるファイルと自作 Plugin で生成されるを比較することにした:

expected な id_rsa をわざわざ test ステップで生成するのではなく,GitHub に直接おいても良いが,なんか id_rsa という名前のファイルをパブリックリポジトリに置くのはどうなのかなぁと思ってやめた. このテストのために --home というオプションで任意のディレクトリに SSH の設定 .ssh を生成してくれるようにした. デフォルトは /root だが.

おまけ: バッチ

公式の Drone Plugin のリポジトリをみると README にいろんなバッジがあった. ので,真似して git-with-ssh にも設定してみた:

付けたのは4つ:

  1. Drone のビルド結果
  2. Go Doc
  3. Go Report Card
  4. MicroBadger

1つ目は Drone のビルド結果のバッジ. Drone のバッジは settings の一番下から取得できる.

Go Doc は指定した Go のリポジトリからドキュメントを生成してくれるサービスである. 依存パッケージとかも解析していい感じに表示してくれる. すごい.

Go Report Card も同様に Go のリポジトリを指定することで動作する. こっちは go fmt がちゃんとかかってるかや linter の結果などをチェックしてくれる. すごい.

MicroBadger は Docker Hub にあるイメージを静的検査してくれる. イメージサイズや生成時間はもちろん,Docker イメージのレイヤ構造も出してくれる. これで Dockerfile をわざわざ探さなくても良いのですごい助かる.

おしまい

久しぶりにサンプルじゃない Go のアプリケーションを作ってみた. 楽しかった〜.