Tim Sharpe

Home :: Projects :: About Me

It's been a long time coming but I'm happy to announce the release of puppet-lint 1.0.0!

Along with a bunch of bugfixes and a rewrite of most of the code, there's some (hopefully) exciting new features in this release.

Automatic fixing of errors

Previewed in the 0.4.0 pre-release, simple problems can now be automatically fixed by puppet-lint (some problems require complex refactoring and so puppet-lint won't attempt anything on your behalf).

In 1.0.0, problems detected by the following checks can be automatically fixed by running puppet-lint with --fix:

  • slash_comments
  • star_comments
  • unquoted_node_name
  • unquoted_resource_title
  • unquoted_file_mode
  • file_mode
  • ensure_not_symlink_target
  • double_quoted_strings
  • only_variable_string
  • variables_not_enclosed
  • quoted_booleans
  • hard_tabs
  • trailing_whitespace
  • arrow_alignment

Complimentary to --fix, puppet-lint now has a --only-checks parameter that you can pass a comma seperated list of checks that you want to run. This works great when doing an initial pass over your code to fix problems. Let's say that you just want to realign all your =>s, run puppet-lint --fix --only-checks arrow_alignment modules/.

Plugin system

From it's inception, puppet-lint has enforced the official style guide and I've had to turn down a lot of good ideas and contributions because they're not a part of that guide. To that end, I've added a plugin system in 1.0.0 so that we can add and distribute custom checks outside of the main code base.

My hope is that together as a community we can come up with some new checks and influence new versions of the style guide. I've written a tutorial that steps through how to write a basic check. If you've got a good idea for a check but aren't up to writing it yourself, create an issue in the puppet-lint repo with the "new check" label and maybe someone else will be able to.

You can find the currently tiny list of community plugins here. They're distributed as Ruby Gems so you can easily install them however you're currently managing puppet-lint (gem, bundler, etc).

Control comments

A much requested feature, you can now disable tests via special comments in your code. Read more about it here.

New checks

A couple of new checks have been added to core puppet-lint in 1.0.0.

  • unquoted_node_name - Checks for unquoted node names
  • puppet_url_without_modules - Checks for fileserver URLs without the modules mountpoint.

What's gone

The class_parameter_defaults check has been removed. This check was based on a misreading of the style guide.

For a more complete list of changes, I'd recommend reading the changelog.


It's been a while, but finally there's a new release of rspec-puppet. Originally slated to be 0.2.0 but promoted to 1.0.0 due to a couple of backwards incompatible changes.

What might cause you problems

create_resource matcher

This deprecated matcher has been removed entirely now. If you still have code using it, you should switch to using the generic contain_<resource> matcher instead.

include_class matcher

This matcher has now been deprecated and will be removed in the next major release. The reason this is on it's way out is that it doesn't support parameterised classes and this has caused a lot of confusion for many users.

If you use include_class anywhere, you'll see the following depreciation notice.

DEPRECATION WARNING: you are using deprecated behaviour that will
be removed from a future version of RSpec.

* include_class is deprecated.
* please use contain_class instead.

So, change:

1 it { should include_class('foo') }

To:

1 it { should contain_class('foo') }

Changes to how parameters are matched

In previous versions of rspec-puppet, parameter values were matched extremely naively, where all values were flattened down to a string before comparison. This means that ['b', 'ba'] would have been equal to ['bb', 'a'] for example. As of 1.0.0, this behaviour has changed and we now compare data structures like arrays and hashes correctly, so you may have to adjust your tests accordingly.

What's new

hiera support

Set the path to your hiera.yaml file in your RSpec.configure block and you're good to go.

1 RSpec.configure do |c|
2   # snip
3   c.hiera_config = '/path/to/your/hiera.yaml'
4 end

compile matcher

This new matcher should the be first thing in any rspec-puppet test suite. It checks that the catalogue will compile correctly and that there are no dependency cycles in the generated graph.

1 it { should compile }

This matcher also has a chain method to enable checking that all dependencies in the catalogue have been met - with_all_deps.

1 it { should compile.with_all_deps }

While at first glance, it might seem that this shouldn't be optional however there are cases where you might not want to test this (if, for example, you are testing a module that notifies a resource in a different module).

relationship tests

Some new additions to the contain_<resource> matcher are the resource relationship tests.

1 it { should contain_file('foo').that_requires('File[bar]') }
2 it { should contain_file('foo').that_comes_before('File[bar]') }
3 it { should contain_file('foo').that_notifies('Service[bar]') }
4 it { should contain_service('foo').that_subscribes_to('File[bar]') }

Regardless of how you define your relationships, either using the metaparameters (require, before, notify and subscribe) or the chaining arrows (->, <-, ~> and <~) these tests will work.

Testing the reverse of the relationship described in your Puppet code will also work with these new methods. Take the following manifest for example:

1 notify { 'foo': }
2 notify { 'bar':
3   before => Notify['foo'],
4 }

Both of the following tests will work:

1 it { should contain_notify('bar').that_comes_before('Notify[foo]') }
2 it { should contain_notify('foo').that_requires('Notify[bar]') }

only_with tests

Also new to the contain_<resource> matcher are the only_with tests. Unlike the with tests which only test that the specified parameters have been defined, only_with tests that these are the only parameters passed to a resource.

Like the with tests, you can specify a single parameter with the only_with_<parameter> method:

1 it { should contain_service('ntp').only_with_ensure('running') }

Or, you can pass it a hash of parameters and values:

1 it do
2   should contain_service('ntp').only_with(
3     'ensure' => 'running',
4     'enable' => true,
5   )
6 end

resource counting matchers

The last new matchers for this release are have_resource_count, have_class_count and the generic have_<resource>_resource_count. As you can guess, these matchers:

Count the total number of resources in the catalogue

1 it { should have_resource_count(3) }

Count the number of classes in the catalogue

1 it { should have_class_count(2) }

Count the number of resources of a particular type in the catalogue

1 it { should have_exec_resource_count(0) }

As always, if you find any bugs or have any suggestions for new functionality, please create an issue here.


Since the first release of puppet-lint, one of the top two feature requests has been the ability to automatically fix trivial style issues (detecting problems is all well and good but no one wants to manually fix the eleventy thousand problems that have accumulated over the years).

To that end, I have just shipped a beta release of puppet-lint (0.4.0.pre1) which includes the experimental fixing support! I highly encourage everyone to give it a try and report any issues they find.

Currently, puppet-lint supports fixing a limited subset of detectable problems. There will probably be more added to this list over time however some problems will always require a human to decide how to proceed.

At this time, puppet-lint do the following for you:

  • Converting // comments into # comments.
  • Quoting unquoted resource titles.
  • Quoting unquoted file mode strings.
  • Converting 3 digit octal file modes into 4 digit modes.
  • Converting double quoted strings without variables into single quoted strings.
  • Converting double quoted strings that only contain a variable into an unquoted variable.
  • Enclosing variables in double quoted strings that haven't been enclosed in braces.
  • Unquoting quoted boolean values.
  • Converting hard tabs into 2 space soft tabs.
  • Removing trailing whitespace.
  • Fixing arrow (=>) alignment in resources and hashes.

Caveat Emptor

Running puppet-lint with fix mode enabled is a potentially destructive action.

I'm going to assume that your manifests are stored in some sort of version control and that you're comfortable discarding the changes puppet-lint makes if you don't like them.

Trying it out

First of all install the new version, either with RubyGems:

1 gem install --pre puppet-lint -v 0.4.0.pre1

or Bundler:

1 gem 'puppet-lint', '0.4.0.pre1'

Then just call puppet-lint with the -f or --fix option

1 $ puppet-lint -f test.pp
2 FIXED: double quoted string containing no variables on line 1
3 FIXED: string containing only a variable on line 3
4 FIXED: indentation of => is not properly aligned on line 5
5 FIXED: unquoted file mode on line 5
6 FIXED: mode should be represented as a 4 digit octal value or symbolic mode on line 5

And the diff of the changes to my test file is

 1 diff --git a/test.pp b/test.pp
 2 index 7cd1e93..4d455cf 100644
 3 --- a/test.pp
 4 +++ b/test.pp
 5 @@ -1,6 +1,6 @@
 6 -$foo = "/tmp/foo"
 7 +$foo = '/tmp/foo'
 8 
 9 -file { "$foo":
10 +file { $foo:
11    ensure => present,
12 -  mode => 444,
13 +  mode   => '0444',
14  }

So, please try it out and create an issue on the repository if you run into any problems!


You might have noticed that there hasn't been a puppet-lint release in quite some time and that I've become a bit lax when it comes to keeping up with bugs and pull requests.

Puppet-lint originally started as something to fill in some time while 10,000 metres above the Tasman Sea and like all good hack projects, I kept adding more and more code onto it to get each release out the door as quickly as possible. This lead to a number of questionable design decisions, the biggest of which was using Puppet's lexer to tokenise the manifests that are being analysed.

Don't get me wrong, the Puppet lexer is fantastic and I doubt I can write a better one, but as it's a part of a separate active project, developing against such a moving target results in constantly adding new corner cases with every Puppet release. This has resulted in a messy code base that I haven't really desired to touch, to the detriment of the project.

So what now?

If you've been following the project on GitHub, you might have noticed a flurry of recent activity in the dust_bunny branch. Over the last week, I made the call to rip out the Puppet lexer and replace it with a simple one of my own. Along with removing the constantly moving target, it has had a couple of other major benefits:

  1. Faster start up time, as we're no longer loading Puppet.
  2. A custom lexer tailored to our needs means we can now access additional data that was previously discarded (like comments).

This has also given me an excuse to start a much needed cleanup of the code to ensure that it is consistent and (hopefully) well documented. Anyone who's tried to add a new check will know what I mean.

Great, why are you telling me?

While everything I've described doesn't sound very interesting from a features point of view, it does represent a massive change to the internals of puppet-lint and there's a very real possibility of introducing regressions.

So, what I need you to do is install the pre-release version of 0.2.0

gem install puppet-lint -v 0.2.0.pre1 --pre

And then run it over your manifests. If you run into any bugs, false positives or negatives etc, please report them on the project issue tracker and tag them with the dust_bunny label.

Once any issues that have arisen as a result of these changes have been resolved, I'll go back through the old issues and get them fixed up before pushing out a proper 0.2.0 release with all the features you've been waiting for.

TL;DR

Lots of internal changes happened in puppet-lint recently and I need you to help test it out and make sure no regressions have snuck in.

Install it, run it, report it. Ta!


I'm going to assume you've got a Puppet module already on GitHub. To save messing around with bundler on your local machine, I recommend installing puppet-lint and rspec-puppet as system gems while you're getting this all set up.

1 $ gem install puppet-lint
2 $ gem install rspec-puppet

rspec-puppet

First of all, let's create a directory structure for your spec files

1 $ mkdir -p spec/classes spec/defines spec/fixtures/manifests
2 $ mkdir -p spec/fixtures/modules/<your module name>
3 $ cd spec/fixtures/modules/<your module name>
4 $ touch spec/fixtures/manifests/init.pp
5 $ for i in files lib manifests templates; do ln -s ../../../../$i $i; done

If you're wondering about that last line, we symlink the contents of the module into spec/fixtures/modules/<your module name> so that we can trick Puppet's autoloader when running the specs.

Next, we need to configure rspec-puppet, so create spec/spec_helper.rb with the following contents

1 require 'rspec-puppet'
2 
3 fixture_path = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures'))
4 
5 RSpec.configure do |c|
6   c.module_path = File.join(fixture_path, 'modules')
7   c.manifest_dir = File.join(fixture_path, 'manifests')
8 end

Now, all we need is a Rake task to fire up the tests. Create a Rakefile in the root directory of your module with the following contents

1 require 'rake'
2 
3 require 'rspec/core/rake_task'
4 
5 RSpec::Core::RakeTask.new(:spec) do |t|
6   t.pattern = 'spec/*/*_spec.rb'
7 end

You can now run rake spec to run your rspec-puppet tests

puppet-lint

Next up, we'll also get some automatic lint testing of your manifests going to ensure you're writing manifests that comply with the Puppet Labs style guide. This is simply a matter adding the following line near the top of your Rakefile

1 require 'puppet-lint/tasks/puppet-lint'

You can now run rake lint to run puppet-lint over your manifests.

Travis CI

Travis CI is a wonderful free continuous integration service that integrates with GitHub, running whatever tests you want against your code every time you push.

To get Travis CI automatically testing your module you need to add a couple of files to the root directory of your module.

First, create a Gemfile which tells bundler which ruby gems your tests need in order to run. If your module needs any additional gems, just add them to the bottom of this file.

 1 source :rubygems
 2 
 3 if ENV.key?('PUPPET_VERSION')
 4   puppetversion = "= #{ENV['PUPPET_VERSION']}"
 5 else
 6   puppetversion = ['>= 2.7']
 7 end
 8 
 9 gem 'rake'
10 gem 'puppet-lint'
11 gem 'rspec-puppet'
12 gem 'puppet', puppetversion

We also need to create .travis.yml which holds our Travis CI test config.

1 rvm: 1.8.7
2 notifications:
3   email:
4     - <your email address>
5 env:
6   - PUPPET_VERSION=2.6.14
7   - PUPPET_VERSION=2.7.11

This basically says, we want to use Ruby 1.8.7, run two sets of tests, one against Puppet 2.6.14 and the other against Puppet 2.7.11 and email the notifications through to your email address.

There's one last thing we need to do and that is create a default rake task that runs both rake spec and rake lint. To do that, add the following to the end of your Rakefile.

1 task :default => [:spec, :lint]

If you haven't already done so, commit and push all this up to GitHub.

Point your browser Travis CI and login with your GitHub account. In your profile page, turn on tests for your module's repository.

Turn on tests

And wait for them to run!

Win

TL;DR

Go check out my logrotate module for a working example.


Whenever I need to setup a new service on one of my hosts, the first thing I do is head to the forge and GitHub to try and find a decent Puppet module that already exists for it.

I almost always leave in disappointment.

Puppet modules are libraries

Much like string.h provides everything you need to manipulate strings in C, your Puppet modules should provide everything needed to manage a service out of the box. By that I mean, I want to pull down your module to enable the functionality I need in Puppet without modifying your module at all.

Package, File, Service

Regrettably, most of the modules out there don't deviate from the basic package, file, service model.

 1 class ntp {
 2   package { 'ntp':
 3     ensure => installed,
 4   }
 5 
 6   file { '/etc/ntp.conf':
 7     ensure  => file,
 8     source  => 'puppet:///modules/ntp/etc/ntp.conf',
 9     owner   => 'root',
10     group   => 'root',
11     mode    => '0444',
12     require => Package['ntp'],
13     notify  => Service['ntp'],
14   }
15 
16   service { 'ntp':
17     ensure => 'running',
18     enable => 'true',
19   }
20 }

While this might be all you need in your homogeneous environment, it's unlikely that this will work in someone elses environment without modifications.

Package, File, Service, Facter

A common extension to this model is to add conditionals using Facter variables to cover a set of different use cases (CentOS vs Debian, etc).

 1 class ntp {
 2   case $::operatingsystem {
 3     Debian: {
 4       $packagename = 'openntp'
 5       $servicename = 'openntp'
 6     }
 7     default: {
 8       $packagename = 'ntp'
 9       $servicename = 'ntp'
10     }
11   }
12 
13   package { $packagename:
14     ensure => installed,
15   }
16 
17   file { '/etc/ntp.conf':
18     ensure  => file,
19     source  => 'puppet:///modules/ntp/etc/ntp.conf',
20     owner   => 'root',
21     group   => 'root',
22     mode    => '0444',
23     require => Package[$packagename],
24     notify  => Service[$servicename],
25   }
26 
27   service { $servicename:
28     ensure => 'running',
29     enable => true,
30   }
31 }

While this is slightly more useful, it doesn't cover the possibility of wanting to override a value on a per-host basis.

Package, File, Service, Facter and... Global Variables? Seriously?

If you ask most people for the "best practice" method of passing parameters to their modules, they'll probably recommend using global variables - where you set a variable in your node definition or ENC and it magically gets picked up inside the various classes that you've included.

 1 node 'foo.example.com' {
 2   $ntp_running = true
 3   $monitor = true
 4   $backup = true
 5   $fml = true
 6 
 7   include ntp
 8 }
 9 
10 class ntp {
11   # trimmed for brevity
12 
13   service { $servicename:
14     ensure => $ntp_running ? {
15       true  => 'running',
16       false => 'stopped',
17     },
18   }
19 }

Do we really need to go into why this is a bad idea? Puppet's variable scoping is confusing enough on it's own.

So what should you be doing

To put it simply, write your Puppet modules like you would write a library for your favorite programming language. Don't know a programming language? You're working towards the almighty "Infrastructure as Code" ideal. Stop making excuses like "I don't know how to program so I didn't know any better" and go and learn a language.

  • Don't make your users edit your code to do the work they want.
  • Don't rely on global variables to pass information to your modules.
  • Make it easy for other people to add features to your module.

While it looks like the 3rd point contradicts the 1st point, it doesn't. There's a big difference between someone sending you a patch to your module to support a distro that it doesn't currently support and someone having to go and edit a template inside your module in order to change a config value that you didn't think anyone would need to change.

An example of a good module

Take a look at the Puppet Labs NTP module. It's not perfect, but it's pretty close.

First off, it's using a parameterised class, so there's no global variables polluting the namespace while still allowing us to change the parameters used to configure NTP without editing the module itself. If you're using an older version of Puppet that doesn't support parameterised classes, a defined type will work just as well.

Secondly, it has support for a good number of different distributions but it has a default case that causes Puppet to abort and let the user know that this module isn't supported on their machine. This is much better than just blindly making changes to a system using values that may or may not work for it.

For the folks using ENCs that don't support parameterised classes or defined types, you're still OK because you can just put this into your role definition like so

1 class mycompany::role::frontend {
2   class { 'ntp':
3     servers => ['ntp.mycompany.com'],
4   }
5 }

How this module could be made even better

The addition of a generic ntp::config type that used either Augeas or parsedfile on the back end to set arbitrary configuration values, i.e.

 1 class { 'ntp':
 2   servers => ['ntp.example.com'],
 3 }
 4 
 5 ntp::config {
 6   'statsdir':
 7     value => '/var/log/ntp';
 8   'statistics':
 9     value => 'rawstats';
10 }

Also, replacing the autoupdate parameter with a version parameter so that it was possible to specify a particular package version, 'latest', 'installed' etc. would be a great addition.

That's all folks

How would you like to see modules written? What currently annoys you about the modules out there? Do you disagree with everything I've written and want the last 10 minutes of your life back? Let me know.


So, I ran into a bit of a problem yesterday. I was working on a Puppet module for the Sensu monitoring framework, part of which involved writing a custom Puppet type to manage the configuration of service checks. Normally this is a trivial matter, however in this case, the checks must be configured on both the server and the client and must therefore be able to notify either the server process or the client process or both to restart after a configuration change.

Obviously, I couldn't just do this:

1 sensu_check { 'mycheck':
2   notify => [
3     Service['sensu-client'],
4     Service['sensu-server'],
5   ],
6 }

As if the host was only running the client process, this would result in an error during the Puppet runs.

One option would be to use the following mess:

 1 if(defined(Service['sensu-client']) && defined(Service['sensu-server'])) {
 2   Sensu_check {
 3     notify => [
 4       Service['sensu-client'],
 5       Service['sensu-server'],
 6     ],
 7   }
 8 } elsif defined(Service['sensu-client']) {
 9   Sensu_check {
10     notify => Service['sensu-client'],
11   }
12 } else {
13   Sensu_check {
14     notify => Service['sensu-server'],
15   }
16 }

Not the most elegant solution in the world. Ideally what I needed was something like the existing autorequire functionality that creates require relationships between your custom type and the named resources automatically but only if the resources exist in the node catalogue.

Unfortunately, such functionality doesn't exist (yet), so I had to go some digging around in Puppet's codebase to implement it. The first thing to do was to establish a way to create arbitrary notify relationships. This turned out to be easily done:

1 Puppet::Type.newtype(:mytype) do
2   def initialize(*args)
3     super
4     self[:notify] = [
5       "Service[myservice]",
6       "Service[myotherservice]",
7     ]
8   end
9 end

Now, all I needed to do was make it so that it only notified the resources that actually existed in the node's manifest

1 Puppet::Type.newtype(:mytype) do
2   def initialize(*args)
3     super
4     self[:notify] = [
5       "Service[myservice]",
6       "Service[myotherservice]",
7     ].select { |ref| catalog.resource(ref) }
8   end
9 end

The moral of this extremely drawn out story? Anything is possible in Puppet if you're willing to get your hands dirty. And this should really be a built in feature *hint*.


1 11:00 <hubot> [puppet/master] merge branch 'mysql-module-refactor' - dave
2 11:00 <hubot> dave is deploying puppet/master to production
3 11:23 <nagios> PROBLEM - MySQL on dbmaster1.initech.com is CRITICAL
4 11:24 <dave> oh fuck

Look familiar?

If you've worked with configuration management systems for a while, a situation like this has probably cropped up and ruined your day. If you're lucky enough to have a homogeneous environment, then you might have a staging environment that you can test changes out on, but what if you don't? A slight mistake in that harmless change you're working on could stop a service on hundreds of machines or worse (purge the mysql-server package and all it's data *cough*).

Why you should be writing unit tests for your Puppet modules

  • Prevent situations like the one above.
  • Catch any problems moving between Puppet releases before it hits production.
  • Now we can do this too

Obligatory XKCD

Getting started

In this article, we'll cover setting up your testing environment and cover how to write unit tests for your Puppet functions. I'm going to make a few assumptions now:

  • You store your Puppet manifests in git.
  • You run a *nix machine as your workstation.
  • You don't mind getting your hands dirty with a bit of simple Ruby.
  • You have Ruby installed (1.8.7).

First of all, we're going to install Bundler to manage the dependencies our Puppet testing rig will have.

1 $ gem install bundler --no-ri --no-rdoc
2 Fetching: bundler-1.0.21.gem (100%)
3 Successfully installed bundler-1.0.21
4 1 gem installed
5 $ bundle --version
6 Bundler version 1.0.21

Next we're going to create a Gemfile in the top level of our Puppet repo with the list of gems we're going to need. Adjust the puppet and facter versions to match your environment.

1 source :rubygems
2 
3 gem 'puppet',       '2.6.12'
4 gem 'facter',       '1.6.0'
5 gem 'rspec-puppet', '0.1.0'
6 gem 'rake',         '0.8.7'

Next we're going to add a couple of things to your .gitignore.

1 vendor/gems/
2 .bundle/

Now we just need to tell bundler to install everything and commit our changes to the repository.

 1 $ bundle install --path vendor/gems
 2 Fetching source index for http://rubygems.org/
 3 Installing rake (0.8.7)
 4 Installing diff-lcs (1.1.3)
 5 Installing facter (1.6.0)
 6 Installing puppet (2.6.12)
 7 Installing rspec-core (2.7.1)
 8 Installing rspec-expectations (2.7.0)
 9 Installing rspec-mocks (2.7.0)
10 Installing rspec (2.7.0)
11 Installing rspec-puppet (0.1.0)
12 Using bundler (1.0.21)
13 Your bundle is complete! It was installed into ./vendor/gems
14 $ git add Gemfile
15 $ git add Gemfile.lock
16 $ git commit -a -m "Bundler setup for testing"

OK, time to configure RSpec. Create a spec directory in the root of your Puppet repository and create a file in the spec folder called spec_helper.rb. Adjust c.module_path and c.manifest_dir to point to your modules and manifests directories in your repository.

1 require 'rspec-puppet'
2 
3 RSpec.configure do |c|
4   c.module_path = "modules"
5   c.manifest_dir = 'manifests'
6 end

The last thing we need to do is create a Rake task to run our tests. Create a Rakefile in the root of your Puppet repository with the following.

1 require 'rake'
2 require 'rspec/core/rake_task'
3 
4 RSpec::Core::RakeTask.new(:test) do |t|
5   t.pattern = 'modules/*/spec/*/*_spec.rb'
6 end
7 
8 task :default => :test

Testing your first function

For the sake of this example, let's create a simple module with a single function (that I'm going to borrow from Puppet Lab's stdlib module).

1 $ mkdir -p modules/misc/lib/puppet/parser/functions
2 $ mkdir -p modules/spec/functions

Download this function and drop it in modules/misc/lib/puppet/parser/functions.

Basically this function should

  • return 0 if you pass it 'f', 'false', 'n', 'no', 0, '', 'undef' or 'undefined'.
  • return 1 if you pass it 't', 'true', 'y', 'yes' or 1.
  • raise Puppet::ParseError if you pass it anything else.

Before we continue, you should now go and read the rspec-puppet README. The tests for this function should live in modules/misc/spec/functions/bool2num_spec.rb.

 1 require 'spec_helper'
 2 
 3 describe 'bool2num' do
 4   it { should run.with_params('true').and_return(1) }
 5   it { should run.with_params('t').and_return(1) }
 6   it { should run.with_params('y').and_return(1) }
 7   it { should run.with_params('yes').and_return(1) }
 8   it { should run.with_params('1').and_return(1) }
 9 
10   it { should run.with_params('false').and_return(0) }
11   it { should run.with_params('f').and_return(0) }
12   it { should run.with_params('n').and_return(0) }
13   it { should run.with_params('no').and_return(0) }
14   it { should run.with_params('0').and_return(0) }
15   it { should run.with_params('undef').and_return(0) }
16   it { should run.with_params('undefined').and_return(0) }
17   it { should run.with_params('').and_return(0) }
18 
19   it { should run.with_params('foo').and_raise_error(Puppet::ParseError) }
20 end

Most of this should be pretty self explanatory, however there is a couple of important things:

  • The spec_helper.rb required on line 1 is the spec_helper.rb in the spec directory at the root of your repository, not from the per-module spec directory.
  • The description on line 3 must be a string and it must be the name of the function that you are testing so that RSpec can set the subject correctly.

Now for the all important running of the tests.

1 $ bundle exec rake
2 /usr/bin/ruby -S rspec modules/misc/spec/functions/bool2num_spec.rb
3 ..............
4 
5 Finished in 2.61 seconds
6 14 examples, 0 failures

Huzzah! Now go and write tests for the rest of your functions. In the next article in this series, we'll cover how to test your Puppet manifests (.pp files).


Sick of having to write file resources to distribute the Augeas lenses that your module depends on? Why not use Puppet's pluginsync functionality to distribute them with the rest of your module?

Under your module's lib directory, create the following directory structure

modules/<module>/lib/
                     augeas/
                            lenses/

Your module should now look like this:

modules/<module>/manifests/
                 templates/
                 lib/
                     puppet/
                     facter/
                     augeas/
                            lenses/

Drop your Augeas lenses into this lenses directory and Puppet will distribute them to all your clients automatically. Now we just need to tell Augeas where to find these lenses.

The easiest way to go about this is to set a default load_path value for Augeas type. To do that, add the following to your site.pp.

1 Augeas {
2   load_path => "/usr/share/augeas/lenses:${settings::vardir}/augeas/lenses",
3 }

Note: Puppet will display an error for each lens during runs as it'll try to load them as Ruby files. It's noisy, but it's not a fatal error. I'm hoping to find an easy way to prevent this.