How to use SSH password login
Customize your spec_helper.rb like this.
require 'highline/import'
if ENV['ASK_LOGIN_PASSWORD']
options[:password] = ask("\nEnter login password: ") { |q| q.echo = false }
else
options[:password] = ENV['LOGIN_PASSWORD']
end
set :ssh_options, options
How to share Serverspec tests among hosts
serverspec-init
generates template files like this.
|-- Rakefile
`-- spec
|-- spec_helper.rb
`-- www.example.jp
`-- sample_spec.rb
This directory structure forces you to write tests per host.
But you can write tests per role like this.
|-- Rakefile
`-- spec
|-- app
| `-- ruby_spec.rb
|-- base
| `-- users_and_groups_spec.rb
|-- db
| `-- mysql_spec.rb
|-- proxy
| `-- nginx_spec.rb
`-- spec_helper.rb
Rakefile should be like this.
require 'rake'
require 'rspec/core/rake_task'
hosts = %w(
proxy001.example.com
proxy002.example.com
app001.example.com
app002.example.com
db001.example.com
db002.example.com
)
task :spec => 'spec:all'
namespace :spec do
task :all => hosts.map {|h| 'spec:' + h.split('.')[0] }
hosts.each do |host|
short_name = host.split('.')[0]
role = short_name.match(/[^0-9]+/)[0]
desc "Run serverspec to #{host}"
RSpec::Core::RakeTask.new(short_name) do |t|
ENV['TARGET_HOST'] = host
t.pattern = "spec/{base,#{role}}/*_spec.rb"
end
end
end
You can spec_helper.rb generated by serverspec-init as-is.
How to use Serverspec tests as shared behaviors
RSpec allows shared behaviors. Using this allows an alternate method of writing specs that can be run on multiple servers.
First, create a directory to hold the shared examples…typically ‘support’ or ‘shared’ In that directory, create a subdirectory to hold like behaviors. For example, if you are using Puppet, Ansible, Itamae, etc and you want to write shared behavior for your ‘database’ module/recipe/whatever.
mkdir -p serverspec/shared/database
then serverspec/shared/database/init.rb:
shared_examples 'database::init' do
describe package('mysql-community-server') do
it { should be_installed }
end
end
Now to use that shared behavior on multiple hosts, create some specs for each server:
serverspec/specs/foo.bar.com_spec.rb:
require 'spec_helper'
describe 'foo.bar.com' do
include_examples 'database::init'
end
and maybe bacon.bar.com also has apache:
require 'spec_helper'
describe 'bacon.bar.com' do
include_examples 'database::init'
include_examples 'apache::init'
end
Note that you can also pass parameters for greater reuse:
shared_examples 'deploy_user' do |deploy_user|
describe user(deploy_user) do
it { should exist }
it { should have_home_directory("/home/#{deploy_user}") }
end
end
# then later
include_examples 'deploy_user', 'the_user'
You can see examples in rubyisbeautiful/serverspec_examples.
RSpec shared examples docs will also help.
How to use host specific properties
Serverspec supports a simple mechanism to handle host specific properties.
This example assumes that host specific properties are in YAML file.
db001.example.jp:
:roles:
- base
- db
:server_id: 101
db002.example.jp:
:roles:
- base
- db
:server_id: 102
You can write Rakefile for role separated tests like this with the YAML file.
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'
properties = YAML.load_file('properties.yml')
desc "Run serverspec to all hosts"
task :spec => '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
You can write spec_helper.rb to set host specific properties with set_property
.
require 'serverspec'
require 'net/ssh'
require 'yaml'
properties = YAML.load_file('properties.yml')
host = ENV['TARGET_HOST']
set_property properties[host]
...
And you can use host specific properties with property
like this.
require 'spec_helper'
describe file('/etc/my.cnf') do
it { should contain "server-id = #{property[:server_id]}" }
end
PATH environment variable
You can add PATH environment variable in spec_helper.rb or other places like this.
set :path, '/sbin:/usr/local/sbin:$PATH'
Block scoped PATH environment variable
This feature is experimental.So this may be changed in the future.
You can also set PATH environment variable only in a block scope like this.
describe package('jekyll') do
let(:path) { '/usr/local/rbenv/shims' }
it { should be_installed.by('gem') }
end
Block scoped pre_command parameter
This feature is experimental.So this may be changed in the future.
You can set pre_command parameter like this.
describe service('httpd') do
let(:pre_command) { 'source ~/.zshrc' }
it { should be_running }
end
In this case, a command is expanded to source ~/.zshrc && service httpd status
.
Parallel execution
If you have a lot of hosts, running tests on all of them can take some
time. To speed up tests, they can be executed in parallel by passing
-j 10
and -m
flags to rake
.
How to control sudo
You can disable sudo completely like this.
# In spec_helper.rb
set :disable_sudo, true
Or you can disable sudo temporarily like this.
# In spec_helper.rb
RSpec.configure do |c|
c.around :each, sudo: false do |example|
set :disable_sudo, true
example.run
set :disable_sudo, false
end
...
# In xxxxx_spec.rb
describe command('whoami'), :sudo => false do
its(:stdout) { should match 'vagrant' }
end
describe command('whoami') do
its(:stdout) { should match 'root' }
end
Another way to disable sudo temporarily.
describe command('whoami') do
let(:disable_sudo) { true }
its(:stdout) { should match 'vagrant' }
end
describe command('whoami') do
its(:stdout) { should match 'root' }
end
You can pass specific options to sudo.
For example, run command under appuser
with simulate initial login.
# In spec_helper.rb
set :sudo_options, [ '-u', 'appuser', '-i' ] # By Array
or
set :sudo_options, '-u appuser -i' # By String
Or only in a block scope.
describe command('~/.rbenv/shims/gem list') do
let(:sudo_options) { '-u appuser -i' }
its(:stdout) { should contain('bundler') }
end
How to get OS information
You can get OS information of the target host by os
helper method.
os[:family] # redHat, ubuntu, debian and so on
os[:release] # OS release version
os[:arch] # i386 or x86_64
You can use this feature like this.
if os[:family] == 'redhat'
# RedHat bases OS related environment spec
if os[:arch] == 'i386'
# 32bit environment spec
else
end
elsif ['debian', 'ubuntu'].include?(os[:family])
# debian related environment spec
end
Or like this.
describe file('/usr/lib64'), :if => os[:arch] == 'x86_64' do
it { should be_directory }
end