Serverspecでrubyのバージョン確認する

出来たサーバに監視周りとかいろいろ周辺のパッケージとか入れて遊んでいこうかなぁと思ったけど、その前にせっかくChefでパッケージのインストールとかも自動化したのだから、流れ的にテストも自動化したいよねぇとか思ってServerspec使い始めた。

試しにまずはServerspecでサーバにインストールされてるrubyのバージョンを確認するというのをやってみた。やりたいことをもう少し具体化するとruby -vコマンド実行した標準出力の文字列をチェックして想定通りのバージョンかをテストすればよい。

Serverspecは現時点の最新版の2.8.2を使いました。

最初に準備として、Gemfileにserverspecを追加する。

source 'https://rubygems.org'

gem "chef", '~> 12.0.3'
gem "knife-solo", '~> 0.4.2'
gem "berkshelf", '~> 3.2.3'
gem "serverspec", '~> 2.8.2'

で、bundle installする。

$ bundle install --path vendor/bundle

次にserverspec-initコマンドでひな形ファイルを生成する。途中で何個か質問があるけど、

$ bundle exec serverspec-init
Select OS type:

  1) UN*X
  2) Windows

Select number: 1

OSの種類を選ぶ。UN*Xなので1。

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

SSHかlocalかを選ぶ。SSHなので1。

Vagrant instance y/n: n

Vagrantインスタンスか。開発環境はVagrantだけど、本番環境はEC2なのでnで素のままで使う。

Input target host name: commitm-dev

テスト対象のホスト名入力する。ホスト名はssh接続先として名前解決できるものと揃えておく。とりあえずvagrant上の開発用の仮想サーバのホスト名を指定した。

 + spec/
 + spec/commitm-dev/
 + spec/commitm-dev/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec

以上でひな形ファイルが生成された。

ちなみに参考までに手元の~/.ssh/configはこんなかんじです。各自環境に合わせてssh鍵認証でパスワードなしでログインできるようにしておけばよい。

Host commitm-dev
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile xxxxx
  IdentitiesOnly yes
  LogLevel FATAL

自動生成されたRakefileの中身を眺めてみる。

require 'rake'
require 'rspec/core/rake_task'

task :spec    => 'spec:all'
task :default => :spec

namespace :spec do
  targets = []
  Dir.glob('./spec/*').each do |dir|
    next unless File.directory?(dir)
    targets << File.basename(dir)
  end

  task :all     => targets
  task :default => :all

  targets.each do |target|
    desc "Run serverspec tests to #{target}"
    RSpec::Core::RakeTask.new(target.to_sym) do |t|
      ENV['TARGET_HOST'] = target
      t.pattern = "spec/#{target}/*_spec.rb"
    end
  end
end

specディレクトリ配下に#{target}でホスト名ごとのフォルダがあって、その中の*_spec.rbをテストするrakeタスクが定義されているようです。

自動生成されたspec/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

# 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'

sshのconfig読んでホスト名とかオプションとかセットしてるよう。あと、sudoのパスワード設定したい場合は環境変数にセットすればよさそうです。特に今は変更は必要ないのでこのままで。

あと自動生成されたテスト用のspecファイルspec/(ホスト名)/sample_spec.rbを眺める。

require 'spec_helper'

describe package('httpd'), :if => os[:family] == 'redhat' do
  it { should be_installed }
end

describe package('apache2'), :if => os[:family] == 'ubuntu' do
  it { should be_installed }
end

describe service('httpd'), :if => os[:family] == 'redhat' do
  it { should be_enabled }
  it { should be_running }
end

describe service('apache2'), :if => os[:family] == 'ubuntu' do
  it { should be_enabled }
  it { should be_running }
end

describe service('org.apache.httpd'), :if => os[:family] == 'darwin' do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end

RSpecの書き方でこんなかんじで書いていけば良さそう。これを参考にテストを書いてみる。

とりあえずsample_spec.rbをruby_spec.rbとリネームして、インストールされてるrubyのバージョンをテストするコードを書いてみる。

require 'spec_helper'

# ruby versions
describe command('ruby -v') do
  let(:disable_sudo) { true }
  its(:stdout) { should match /ruby 2\.2\.0p0.+/ }
end

予めいろいろなリソースタイプが定義されているけど、単純に任意のコマンド実行するにはcommandというリソースタイプを使えばよい。

標準出力はits(:stdout)で取れるので、should matchで正規表現で期待する文字列を書けばよい。
ちなみに古いブログ記事見るとreturn_stdoutというのを使っているサンプルコードがあるようなのだけど、既に廃止されてるようで、試したらNoMethodErrorになった。

あとハマりどころで、Serverspecのテスト実行はデフォルトでsudoされるのだけど、今回チェックしたいのはログインした一般ユーザのrubyバージョンなので、明示的にdisable_sudoをtrueにする。このようにletで設定すると、このテストケースだけ一時的に設定が変更できるよう。

準備出来たのでrakeコマンドでテスト実行。

$ bundle exec rake
(略)
Command "ruby -v"
  stdout
    should match /ruby 2\.2\.0p0.+/

Finished in 1.33 seconds (files took 0.33412 seconds to load)
1 example, 0 failures

うん。ちゃんと動いたっぽい。
こんな感じでテストコード書いてけばよいのね。