serverspecの高度なヒント

  • 84
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

ホスト間でserverspecテストを共有する方法

serverspec-init コマンドは、このようなテンプレートファイルを生成します。

|-- Rakefile
`-- spec
    |-- spec_helper.rb
    `-- www.example.jp
        `-- httpd_spec.rb

このディレクトリ構造では、ホストごとにテストを書かないといけません。
しかし、このように役割ごとにテストを書くことができます。

|-- Rakefile
`-- spec
    |-- app
    |   `-- ruby_spec.rb
    |-- base
    |   `-- users_and_groups_spec.rb
    |-- db
    |   `-- mysql_spec.rb
    |-- proxy
    |   `-- nginx_spec.rb
    `-- spec_helper.rb

このようにRakefileにホストの関係に役割を書く必要があります。

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

hosts = [
  {
    :name  => 'proxy001.example.jp',
    :roles => %w( base proxy ),
  },
  {
    :name  => 'proxy002.example.jp',
    :roles => %w( base proxy ),
  },
  {
    :name  => 'app001.example.jp',
    :roles => %w( base app ),
  },
  {
    :name  => 'app002.example.jp',
    :roles => %w( base app ),
  },
  {
    :name  => 'db001.example.jp',
    :roles => %w( base db ),
  },
  {
    :name  => 'db002.example.jp',
    :roles => %w( base db ),
  },
]

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

そして、次のようにspec_helper.rbを変更する必要があります。

spec_helper.rb
require 'serverspec'
require 'net/ssh'

include Serverspec::Helper::Ssh
include Serverspec::Helper::DetectOS

RSpec.configure do |c|
  c.host  = ENV['TARGET_HOST']
  options = Net::SSH::Config.for(c.host)
  user    = options[:user] || Etc.getlogin
  c.ssh   = Net::SSH.start(c.host, user, options)
  c.os    = backend.check_os
end

実行方法も rake spec では動きません。以下のようにします。

  • 一台ずつ実行する場合
$ rake serverspec:サブドメイン
例)
$ rake serverspec:proxy001
$ rake serverspec:proxy002
$ rake serverspec:app001
$ rake serverspec:app002
$ rake serverspec:db001
$ rake serverspec:db002
  • 全台実行する場合
$ rake serverspec:all

このままではどのテストが実行されているのかわかりにくいので、オプションに-t(--trace)を付けて以下のようにするとよい。

$ rake serverspec:all -t

--

ホスト固有のプロパティを使用する方法

serverspecはホスト固有のプロパティを操る単純なメカニズムをサポートしています。

この例では、ホスト固有のプロパティは、YAMLファイルに書かれていることを前提としています。

db001.example.jp:
  :roles:
    - base
    - db
  :server_id: 101
db002.example.jp:
  :roles:
    - base
    - db
  :server_id: 102

YAMLファイルに次のような役割ごとに分けられたテストをRakefileに書くことができます。

Rakefile
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'

properties = YAML.load_file('properties.yml')

desc "Run serverspec to all hosts"
task :serverspec => 'serverspec:all'

namespace :serverspec do
  task :all => properties.keys.map {|key| 'serverspec:' + key.split('.')[0] }
  properties.keys.each do |key|
    desc "Run serverspec to #{key}"
    RSpec::Core::RakeTask.new(key.split('.')[0].to_sym) do |t|
      ENV['TARGET_HOST'] = key
      t.pattern = 'spec/{' + properties[key][:roles].join(',') + '}/*_spec.rb'
    end
  end
end

set_property でホスト固有のプロパティの設定をspec_helper.rbに書くことができます。

spec_helper.rb
require 'serverspec'
require 'pathname'
require 'net/ssh'
require 'yaml'

include Serverspec::Helper::Ssh
include Serverspec::Helper::DetectOS
include Serverspec::Helper::Properties

properties = YAML.load_file('properties.yml')

RSpec.configure do |c|
  c.host  = ENV['TARGET_HOST']
  set_property properties[c.host]
  options = Net::SSH::Config.for(c.host)
  user    = options[:user] || Etc.getlogin
  c.ssh   = Net::SSH.start(c.host, user, options)
  c.os    = backend.check_os
end

このように propertyというハッシュ変数によってホスト固有のプロパティを使うことができます。

require 'spec_helper'

describe file('/etc/my.cnf') do
  it { should contain "server-id = #{property[:server_id]}" }
end

--

PATH環境変数

このようにspec_helper.rbか、他の場所でPATH環境変数を追加することができます。

RSpec.configure do |c|
  c.path = '/sbin:/usr/sbin'
  ...

この場合、コマンドはPATH=/sbin:/usr/sbinのように展開されます。

ブロックスコープのPATH環境変数

** この機能は実験的なものです。将来的に変更されるかもしれません。 **

このようにブロックスコープ内でだけPATH環境変数を設定することもできます。

describe package('jekyll') do
  let(:path) { '/usr/local/rbenv/shims' }
  it { should be_installed.by('gem') }
end

ブロックスコープのpre_commandパラメータ

** この機能は実験的なものです。将来的に変更されるかもしれません。 **

このようにpre_commandパラメータを設定することができます。

describe service('httpd') do
  let(:pre_command) { 'source ~/.zshrc' }
  it { should be_running }
end

この場合、コマンドは source ~/.zshrc && service httpd status のように展開されます。