Serverspecのテストケースをロール単位で管理する
serverspec-initでできるデフォルトのRakefileだとホスト名ごとにディレクトリ掘ってその中にテストケースのspecファイルを入れていくのだけど、サーバ複数あるとspecファイルコピペしないといけないのイマイチなので、Chefのロール単位ぐらいで管理できるとよいかなぁというかんじ。
調べてみたところserverspec作者のmizzyさんのブログにサンプルコードあったので、これを参考にやってみた。
serverspec のテストをホスト間で共有する方法 - Gosuke Miyashita
今回のディレクトリ構成は大体こんなかんじでロールごとにディレクトリ掘って、その中にcookbookごとにspecファイル入れてくイメージ。
. ├── Rakefile ├── spec │ ├── ap │ │ ├── mysqlclient_spec.rb │ │ ├── nodejs_spec.rb │ │ ├── rvm_spec.rb │ │ └── sqlite_spec.rb │ ├── base │ │ ├── build-essential_spec.rb │ │ └── git_spec.rb │ ├── db │ │ └── mysql_spec.rb │ └── spec_helper.rb
Rakefileはこんなかんじで、はじめにホストの定義を書いて、それに対応するrakeタスクを機械的に生成する。ホスト定義は台数増えてきたら別ファイルに分けた方がよいかもしんない。
require 'rake' require 'rspec/core/rake_task' hosts = [ { :name => 'commitm-dev', :roles => %w( base ap db ) }, { :name => 'commitm-ap01', :roles => %w( base ap ) } ] hosts = hosts.map do |host| { :name => host[:name], :short_name => host[:name].split('.')[0], :roles => host[:roles] } end desc "Run serverspec to all hosts" task :serverspec => 'serverspec:all' namespace :serverspec do task :all => hosts.map { |h| 'serverspec:' + h[:short_name] } hosts.each do |host| desc "Run serverspec to #{host[:name]}" RSpec::Core::RakeTask.new(host[:short_name].to_sym) do |t| ENV['TARGET_HOST'] = host[:name] t.pattern = 'spec/{' + host[:roles].join(',') + '}/*_spec.rb' end end end
short_nameのところはnameがFQDNのときにタスク名がFQDNになるのめんどいという意図だろうけど、今回の例だとあんまり意味ないが一応入れてある。
ちなみに参考元のブログ記事ではspec_helperも記載されてるようなのだけど、spec_helperが旧バージョンっぽい香りがしたので、そのまま使わず。今回使ってるserverspec 2.8.2のserverspec-initで生成されたテンプレベースで、request_ptyの設定だけいじったら動いた。request_pty自体は今回のロール分割の話とは本質的に関係ないのだけど、参考までにspec_helper.rbのコードも貼っておく。
require 'serverspec' require 'net/ssh' set :backend, :ssh if ENV['ASK_SUDO_PASSWORD'] begin require 'highline/import' rescue LoadError fail "highline is not available. Try installing it." end set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false } else set :sudo_password, ENV['SUDO_PASSWORD'] end host = ENV['TARGET_HOST'] options = Net::SSH::Config.for(host) options[:user] ||= Etc.getlogin set :host, options[:host_name] || host set :ssh_options, options set :request_pty, true # Disable sudo # set :disable_sudo, true # Set environment variables # set :env, :LANG => 'C', :LC_MESSAGES => 'C' # Set PATH # set :path, '/sbin:/usr/local/sbin:$PATH'
request_ptyについて、ssh先がEC2のAmazon Linuxだとデフォルトで/etc/sudoersでrequirettyが設定されててsudo制限されてることが原因のようで、テンプレのままだとsudoがこけた。Linux側の設定をいじらなくてもserverspecのspec_helper側でrequest_ptyをtrueにするとうまく動くっぽい。ちなみにこのオプションtrueにすると標準出力と標準エラー出力が1つになるので、テストで標準出力や標準エラー出力をチェックしてる場合は注意。
Rakefileの確認のためにrake -Tするとホストごとにタスクが定義されてるのが分かる。
$ rake -T rake serverspec # Run serverspec to all hosts rake serverspec:commitm-ap01 # Run serverspec to commitm-ap01 rake serverspec:commitm-dev # Run serverspec to commitm-dev
あとは実行したいタスクを指定して実行すればOK。
$ rake serverspec:commitm-dev
うん。よさげなかんじじゃないでしょうか。