2016年2月14日日曜日

[CentOS6][Serverspec] ロールと変数を使い、コマンドラインでIPアドレスを指定する


Serverspec advanced Tips」を見て、以下のような使い方ができないか考えてみました。
  • テストケースを複数サーバで使いまわす。
  • 全サーバ共通の変数を yaml (spec_all.yml)で定義する。
  • サーバ個別の変数を yaml (spec_<IPアドレス>.yml)で定義する。
  • ロール単位でテストケースを作成する。
  • テスト対象のロールは、yaml で定義し、コマンドラインでファイル名を指定する。
  • コマンドラインでテスト対象のIPアドレスを指定する。
  • コマンドラインでSSHユーザ/パスワードを指定する。

ディレクトリ・ファイル構成は以下のようになります。
[user01@node01 serverspec]$ tree
.
├── Rakefile
├── roles.yml
├── spec
│   ├── 00_common
│   │   └── os_spec.rb
│   ├── 01_private
│   │   └── os_spec.rb
│   ├── 99_sample
│   │   └── sample_spec.rb
│   └── spec_helper.rb
└── vars
    ├── spec_192.168.56.11.yml
    └── spec_all.yml
  • Rakefile
    対象ロール(roles.yml)のテストケース(*_spec.rb)を実行するように定義します。
  • roles.yml
    対象ロールを定義します。ファイル名は任意です。
  • spec ディレクトリ
    この配下にロール単位のディレクトリを作成します。ロール名は任意です。
  • spec/<ロール名>ディレクトリ
    この配下にテストケースを作成します。ファイル名は *_spec.rb です。(*は任意)
  • spec_helper.rb
    対象サーバへのSSH接続や、変数の読み込みなどを行います。
  • vars ディレクトリ
    この配下に変数ファイルを定義します。全サーバ共通の変数は、"spec_all.yml" に定義します。サーバ個別の変数は、"spec_<IPアドレス>.yml" に定義します。


テストケースの実行方法は以下のとおりです。
パラメータは環境変数で渡します。
env TARGET_HOST=192.168.56.11 TARGET_USER=centos TARGET_PASSWORD=p@ssw0rd SUDO_PASSWORD=p@ssw0rd TARGET_ROLES=roles.yml rake spec:all
  • TARGET_HOST
    テスト対象のサーバのIPアドレスを指定する
  • TARGET_USER
    SSH接続に使用するユーザを指定する。このユーザで sudo が使えるようにしておくこと。
  • TARGET_PASSWORD
    SSH接続ユーザのパスワードを指定する
  • SUDO_PASSWORD
    SUDOのパスワードを指定する
  • TARGET_ROLES
    対象ロールファイル名を指定する


Rakefile は以下のとおり
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'

task :spec => 'spec:all'

roles_yaml = YAML.load_file(ENV['TARGET_ROLES'])

namespace :spec do

  short_name = 'all'
  role       = roles_yaml['roles'].join(',')

  desc "Run serverspec to all"
  RSpec::Core::RakeTask.new(short_name) do |t|
    t.pattern = "spec/{#{role}}/*_spec.rb"
  end
end


spec_helper.rb は以下のとおり
require 'serverspec'
require 'net/ssh'
require 'yaml'

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] =  ENV['TARGET_USER']
options[:password] =  ENV['TARGET_PASSWORD']

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'

# sudoersに「Defaults requiretty」が設定されている場合
set :request_pty, true

# 全体共通の値を書いたファイルをつかう。
all_vars = YAML.load_file(
                    File.expand_path("../../vars/spec_all.yml", __FILE__)
            )
# サーバ固有の値を書いたファイルがあればつかう。
host_vars = YAML.load_file(
                    File.expand_path("../../vars/spec_#{host}.yml", __FILE__)
            ) if File.exists?(File.expand_path("../../vars/spec_#{host}.yml", __FILE__))

spec_property = all_vars
spec_property[:host_vars] =  host_vars ||= {}
#puts spec_property.to_yaml
set_property spec_property


roles.yml は以下のとおり
---
roles:
 - 00_common
 - 01_private


spec_all.yml は以下のとおり
# os_basic
:os_rel: "6.7"
:os_lang: "ja_JP.UTF-8"
:os_localtime: "JST"
:os_zone: "Asia/Tokyo"


spec_192.168.56.11.yml は以下のとおり
# private
:system: R&D
:env: Develop
:role: web
:hostname: node01


00_common/os_spec.rb は以下のとおり
require 'rubygems'
require 'spec_helper'

# OS version
describe command('cat /etc/redhat-release') do
  its(:stdout) { should match /#{property[:os_rel]}/ }
end

# LANG
describe command('grep LANG /etc/sysconfig/i18n') do
  its(:stdout) { should match /#{property[:os_lang]}/ }
end

# timezone
describe command('date') do
  its(:stdout) { should match /#{property[:os_localtime]}/ }
end
describe command('grep ZONE /etc/sysconfig/clock') do
  its(:stdout) { should match /#{property[:os_zone]}/ }
end

# selinux
describe selinux do
  it { should be_disabled }
end

# firewall
describe service('iptables') do
  it { should_not be_enabled }
  it { should_not be_running }
end
describe service('ipi6tables') do
  it { should_not be_enabled }
  it { should_not be_running }
end


00_private/os_spec.rb は以下のとおり
require 'rubygems'
require 'spec_helper'

# hostname
describe file('/etc/sysconfig/network') do
  it { should be_file }
  it { should be_mode 644 }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  it { should contain "HOSTNAME=#{property[:host_vars][:hostname]}" }
end


実行結果は以下のとおり。
[user01@node01 serverspec]$ env TARGET_HOST=192.168.56.11 TARGET_USER=centos TARGET_PASSWORD=p@ssw0rd SUDO_PASSWORD=p@ssw0rd TARGET_ROLES=roles.yml rake spec:all
/usr/bin/ruby -I/usr/lib/ruby/gems/1.8/gems/rspec-support-3.4.0/lib:/usr/lib/ruby/gems/1.8/gems/rspec-core-3.4.0/lib /usr/lib/ruby/gems/1.8/gems/rspec-core-3.4.0/exe/rspec --pattern spec/\{00_common,01_private\}/\*_spec.rb

Command "cat /etc/redhat-release"
  stdout
    should match /6.7/

Command "grep LANG /etc/sysconfig/i18n"
  stdout
    should match /ja_JP.UTF-8/

Command "date"
  stdout
    should match /JST/

Command "grep ZONE /etc/sysconfig/clock"
  stdout
    should match /Asia\/Tokyo/

SELinux
  should be disabled

Service "iptables"
  should not be enabled
  should not be running

Service "ipi6tables"
  should not be enabled
  should not be running

File "/etc/sysconfig/network"
  should be file
  should be mode 644
  should be owned by "root"
  should be grouped into "root"
  should contain "HOSTNAME=node01"

Finished in 1.51 seconds (files took 0.43561 seconds to load)
14 examples, 0 failures



これで、一応、想定通りの動きをするようになったと思います。

ただし、上記サンプルでは、"rake -T" でタスク一覧を表示することはできません。