Puppet Module Testing
Intro
Prep
Code!
Introduction
of the agenda
Getting started
The Git repo
Getting started
[vagrant] git checkout vagrant_start
Vagrantfile
$ vagrant init
[vagrant] git checkout vagrant_vagrantfile
Vagrant boxes
vagrant box list
[vagrant] git checkout vagrant_box_and_puppet_structure
Vagrant VM lifecycle
vagrant up
vagrant provision
vagrant destroy
Vagrant machines
Define multiple vms in a single Vagrantfile
config.vm.define :foo do |vm_config|
vm_config.vm.hostname = 'foo.example.com'
end
config.vm.define :bar do |vm_config|
vm_config.vm.hostname = 'bar.example.com'
end
[vagrant] git checkout vagrant_multi_vms
Vagrant networking
Forwarding ports
vm_config.vm.network :forwarded_port, guest: 80, host: 10080
Private networks ports
vm_config.vm.network :private_network, ip: '192.168.127.100'
[vagrant] git checkout vagrant_forward_ports
[vagrant] git checkout vagrant_private_network
Vagrant Plugins
# Install plugins using vagrant plugin install vagrant-hostmanager
Vagrant.require_plugin 'vagrant-hostmanager'
vagrant plugin install vagrant-hostmanager
[vagrant] git checkout vagrant_hostmanager
Vagrant provisioning
vm_config.vm.provision :shell do |shell|
shell.path = 'scripts/puppet_apply.sh'
shell.args = 'foo'
end
[vagrant] git checkout vagrant_provision_shell
Vagrant in the module works
The autofs module
$ cd puppet/modules
$ git clone git://github.com/vStone/puppet-module-testing autofs
$ git add submodule git://github.com/vStone/puppet-module-testing puppet/modules/autofs
Or if checking out:
$ git submodule init
$ git submodule update
## BETTER NOT USE RECURSIVE CHECKOUT WITH THIS MODULE...
[vagrant] git checkout vagrant_puppet_module_start
The autofs module
Basic structure
Some puppet code:
$ git checkout autofs_ppcs_start -- manifests/{init,config,params,service,package}.pp
[autofs] git checkout autofs_ppcs_start
Gemfile
source 'https://rubygems.org'
puppetversion = ENV.key?('PUPPET_VERSION') ? "~> #{ENV['PUPPET_VERSION']}" : ['~> 3']
gem 'puppet', puppetversion
gem 'rake'
gem 'puppet-lint'
Rakefile
require 'rake'
require 'puppet-lint/tasks/puppet-lint'
PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "vendor/**/*.pp"]
PuppetLint.configuration.log_format = '%{path}:%{linenumber}:%{KIND}: %{message}'
PuppetLint.configuration.send('disable_autoloader_layout')
Running rake (with bundler)
$ bundle install --path=vendor
$ bundle exec rake -T
rake lint # Run puppet-lint
$ bundle exec rake
manifests/config.pp:1:WARNING: class not documented
manifests/init.pp:17:WARNING: class inheriting from params class
...
[autofs] git checkout autofs_rake_lint
Ruby $!%^&
Versioning hell.
gem 'mime-types', (RUBY_VERSION =~ /^1.8/ ? '~> 1.25.1' : nil)
Rspec-puppet
Gemfile
gem 'rspec'
gem 'rspec-puppet', '= 0.1.6'
gem 'puppetlabs_spec_helper'
Rakefile
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = 'spec/*/*_spec.rb'
end
task :default => [:spec, :lint]
spec/spec_helper.rb
require 'rspec-puppet'
# mainly sets up fixtures.
fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
RSpec.configure do |c|
c.module_path = File.join(fixture_path, 'modules')
c.manifest_dir = File.join(fixture_path, 'manifests')
end
# also the perfect place for test-wide code.
First (simple) Test
require 'spec_helper'
describe 'autofs' do
describe 'on CentOS' do
let (:facts) { { :osfamily => 'RedHat' } }
it { should include_class('autofs::package') }
it { should include_class('autofs::service') }
it { should include_class('autofs::config') }
end
end
But... it should FAIL!
# TODO: Fix this (deprecated)
describe 'on another OS' do
let (:facts) { { :osfamily => 'Something' } }
it do
expect {
should include_class('autofs::params')
}.to raise_error(Puppet::Error, /osfamily not supported:/)
end
end
Tweaking rspec
# Rakefile
# Note: Different target: Buildbots don't care about verboseness
RSpec::Core::RakeTask.new(:test) do |t|
t.pattern = 'spec/*/*_spec.rb'
t.rspec_opts = File.read('spec/spec.opts').chomp || ""
end
# spec/spec.opts
--format documentation --colour --backtrace
The answer to how to use rspec-puppet further
____ _____ _____ __ __
| _ \_ _| ___| \/ |
| |_) || | | |_ | |\/| |
| _ < | | | _| | | | |
|_| \_\|_| |_| |_| |_|
puppetlabs_spec_helper
Uses stages
- Prep (fixtures)
- Test
- Cleanup
Default target is spec.
# Rakefile
require 'puppetlabs_spec_helper/rake_tasks'
RSpec::Core::RakeTask.new(:spec_verbose) do |t|
t.pattern = 'spec/*/*_spec.rb'
t.rspec_opts = File.read('spec/spec.opts').chomp || ""
end
task :test do
Rake::Task[:spec_prep].invoke
Rake::Task[:spec_verbose].invoke
Rake::Task[:spec_clean].invoke
end
Fixtures Symlink Madness Be GONE!
# .fixtures.yml
fixtures:
symlinks:
autofs: '#{source_dir}'
repositories:
stdlib: git://github.com/puppetlabs/puppetlabs-stdlib.git
Travis-ci
language: ruby
rvm:
- "1.8.7"
- "1.9.3"
before_install:
- sudo apt-get install -qq libaugeas-dev
script: 'bundle exec rake'
env:
- PUPPET_VERSION="2.7"
- PUPPET_VERSION="3.0"
branches:
except:
- master
- gh-pages
rspec-puppet-augeas
Tweak Gemfile/Rspec
--- a/Gemfile
+++ b/Gemfile
@@ -11,3 +11,5 @@ gem 'puppet-lint'
gem 'rspec'
gem 'rspec-puppet', '= 0.1.6'
gem 'puppetlabs_spec_helper'
+gem 'rspec-puppet-augeas'
+gem 'ruby-augeas'
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d3923f8..c206c0b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,8 +1,10 @@
require 'rspec-puppet'
+require 'rspec-puppet-augeas'
fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
RSpec.configure do |c|
c.module_path = File.join(fixture_path, 'modules')
c.manifest_dir = File.join(fixture_path, 'manifests')
+ c.augeas_fixtures = File.join(fixture_path, 'augeas')
end
The Setup
# update an entry if it exists
augeas{"autofs::include::update-${_mount}":
before => Augeas["autofs::include::add-${_mount}"],
changes => "set ${context}/map '${_mapfile}'",
onlyif => "match ${context} size > 0",
}
Fixture(s)
# spec/fixtures/augeas/etc/auto.master
/exists /etc/auto.exists
/remove /etc/auto.remove
The test
describe_augeas 'autofs::include::update-/exists', :lens => 'Automaster', :target => 'etc/auto.master' do
it 'should modify existing value' do
should execute.with_change
aug_get('*[. = "/exists"]/map').should == '/etc/auto.newvalue'
should execute.idempotently
end
end
hiera
Reasons?
- No support for defs in hiera
- Need for wrapper classes
- Test those classes
Gemfile
gem 'hiera-puppet-helper'
spec/spec_helper.rb
require 'hiera-puppet-helper'
module Helpers
def hieraconfig(hierarchy = ['common'])
hierarchy = [hierarchy] if hierarchy.is_a?(String)
r = {
:backends => ['yaml'],
:hierarchy => hierarchy,
:yaml => { :datadir => File.expand_path(File.join(__FILE__, '..', 'fixtures/hiera')) }
}
r
end
end
RSpec.configure do |c|
c.include Helpers
c.module_path = File.join(fixture_path, 'modules')
c.manifest_dir = File.join(fixture_path, 'manifests')
end
autofs::loader
class autofs::loader {
$tree = hiera_hash('autofs::mounts', {})
create_resources_autofs($tree)
}
spec/fixtures/hiera/single.yaml
---
autofs::mounts:
foobar:
purge: false
mount: /data
shares:
foo:
host: foo.example.com
location: /foo
bar:
host: bar.example.com
location: /bar
spec/classes/autofs_loader_spec.rb
require 'spec_helper'
describe 'autofs::loader' do
let (:facts) { { :osfamily => 'RedHat' } }
describe 'single hiera yaml file' do
let :hiera_config do
hieraconfig(['single'])
end
it { should include_class('autofs::loader') }
it { should contain_autofs__include('foobar') }
it { should contain_autofs__mount('foo') }
it { should contain_autofs__mount('bar') }
end
end