Puppet Module Testing

Intro

Prep

Code!

Introduction

of myself

Introduction

of the agenda

Getting started

The Git repo

Getting started

[vagrant] git checkout vagrant_start

GIT tips n tricks

  • Creating an empty branch:
    $ git checkout --orphan NEWBRANCH
  • Checking out a specific file from a branch/tag
    $ git checkout REFERENCE -- FILENAME

Vagrant

http://vagrantup.com

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


 ____ _____ _____ __  __
 |  _ \_   _|  ___|  \/  |
 | |_) || | | |_  | |\/| |
 |  _ < | | |  _| | |  | |
 |_| \_\|_| |_|   |_|  |_|

          
http://rspec-puppet.com/

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
          
http://docs.travis-ci.com/user/languages/ruby/

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