経緯
- Capistrano3 割と使ってて、特に asonas/capistrano3-puppet · GitHub はギョームでバッチリ使ってる
- でも、ギョームで使ってるのに自動テストがないので気持ちが悪い。Capoistrano(3)のプラグインってほとんどテスト書かれてないのでは...
- 何も参考に出来ないので、どう書けば良いのか...
- Docker使えばsshロギンしてチェックアウトして実際動作で確認できるのでは
Cap 3 からはタスクがタダの Rake Task になったので、Rake Taskのテストを書くのと同じようにテストできるかもしれないが、大量の stubbing が必要になる感じがしたので今回は e2e な感じのテストを雑に書いた。
以下が流れ。
SSH + Puppet ができるコンテナを作る Dockerfile を用意する
FROM rastasheep/ubuntu-sshd:14.04 MAINTAINER Uchio KONDO <udzura@udzura.jp> ENV HOME /root RUN apt-get -y install puppet RUN apt-get -y install git ADD dot.gitconfig /root/.gitconfig ADD puppet_repo /var/repo/puppet RUN git init /var/repo/puppet RUN cd /var/repo/puppet && git add . && git commit -m 'sample commit' RUN mkdir /root/.ssh ADD id_dsa.pub /root/.ssh/authorized_keys RUN chmod 700 /root/.ssh RUN chmod 600 /root/.ssh/authorized_keys RUN mkdir /var/puppet
最終的にこんな感じになった。
ここの sshd は素直に動いたので使った。秘密/公開鍵は専用のものをリポジトリに入れておもむろにADDできるようにしておく。
あと、テスト用の簡単なpuppetプロジェクトをADDしたのちコンテナ内でgitリポジトリにした。これは、Capistranoのデプロイ対象を同じコンテナ内のリポジトリにすることで高速にしようとしている。あとテスト用のpuppetプロジェクトも一緒に管理できるので便利と思う。
RSpec のビフォーフィルターで当該コンテナを立ちあげる。
config.before :all do Dir.chdir './spec/misc' do run 'bundle install --path vendor/bundle' end run 'docker build -t cap3puppet/base spec/misc' run 'docker run -d -P --hostname \'cap3puppet.dev\' --name test_sshd cap3puppet/base' end config.after :all do run 'docker stop test_sshd' run 'docker rm test_sshd' end
素直でよい。
run の定義は下記のような感じ。Open3.capture3という最高のメソッドをラップしている。便利のため、runやrun_and_captureに任意の環境変数を渡せるようにする。
require 'open3' module CommandRunner def __env @__env ||= { 'PATH' => '/bin"/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin', } end def run(cmd, options={}) sout, *_ = run_and_capture cmd, options return sout end def run_and_capture(cmd, options={}) ext_env = options[:ext_env] || {} return Open3.capture3 __env.merge(ext_env), cmd end end RSpec.configure do |config| config.include CommandRunner end
後述するWerckerのbase boxがUbuntu saucyっぽくて、素直にやるとRubyが1.9系に... options={} が無念っぽい。
RSpecの中からcapして出力を検査する
sshdが動作するdocker containerが自分のものになったので、そこに対して cap deploy(今回は cap production provision というコマンドでエイリアスしている)を実行してテストにする。
spec/misc ディレクトリの中に完全に動く Capistrano3 project を配置して、その中で実行するのがポイント。そこでの設定例は https://github.com/asonas/capistrano3-puppet/compare/8025d43...070e781#diff-939ca037997ba3d51fcaf6e9b2b07f8bR1 とか https://github.com/asonas/capistrano3-puppet/compare/8025d43...070e781#diff-7b8dd0f8c713ba0d737de06490b4bf89R1 のノリで。
RSpecのexampleは以下のように。
describe 'cap basic tasks' do describe '$ cap -T' do it 'should contain provison task' do Dir.chdir './spec/misc' do stdout, stderr, exitinfo = run_and_capture('bundle exec cap -T') expect(stdout).to match(/^cap provision/) expect(stderr).to be_empty expect(exitinfo.exitstatus).to be 0 end end end describe '$ cap production provision' do let(:hostname) { have_boot2docker? ? run('boot2docker ip 2>/dev/null').chomp : 'localhost' } let(:port) { run('docker port test_sshd 22').split(':')[1].chomp rescue "22" } let(:ext_env) do { 'HOSTNAME' => hostname, 'PORT' => port } end it 'should run a provisioning successfully' do Dir.chdir './spec/misc' do stdout, stderr, exitinfo = run_and_capture("bundle exec cap production provision", ext_env: ext_env) expect(stdout).to include('Running /usr/bin/env puppet apply --modulepath=modules manifests/site.pp') expect(stdout).to include('Notice: Scope(Class[Sample]): hello, world!') expect(exitinfo.exitstatus).to be 0 end end end end
今回は、capコマンドが吐き出すログが意図したようになっているか?と、コマンド自体の正常終了、という検査だけにとどめているが、capを実行した結果ファイルシステムがどうなっているか、等のテストを書いてもいいと思う。Capistrano3に同梱されている capistrano/sshkit · GitHub とか使えそう。Serverspecと組み合わせるとかもできると思うけどやり過ぎかな...。
boot2dockerを利用する際のハック
コードを見れば分かる通り随所にboot2dockerを用いた場合での分岐が見られる。普通に同じホストでdockerを動かすのと違って、以下の2点に気を使う必要がある。
詳しくは当該diffで「have_boot2docker?」をC-fすれば分かると思う。
ここまでで、あとはRakefileを適当に書けば、手元で rake spec が動くところまでできている。
Wercker に仕掛けよう
あとはWerckerに仕掛けるだけである。
Werckerは、Dockerを用いたCIに対応している。
wercker-labs/docker というbase boxがあるんでそれにルビーを入れればrspecが走る。でも、パッケージのbundlerを使ったら1.9.3になってしまう...。update-alternativesとか使えばいいんかな?
box: wercker-labs/docker build: steps: - install-packages: packages: build-essential ruby2.0 ruby2.0-dev bundler - script: name: docker version code: | docker -v - script: name: bundle install code: | bundle install --path vendor/bundle - script: name: spec runner code: | bundle exec rake spec
あとは
WerckerのWeb UIからプロジェクトを登録し、プルリクエストを作成すれば便利最高が発生する。