Puppet integration tests in (about) seven minutes
While puppet-lint
and rspec-puppet
(thanks to Tim Sharpe)
will help ensure your Puppet code is both clean and produces what you’d
expect in the compiled catalog there are times when you’ll want to go
further than unit testing with rspec-puppet
and do some basic
integration tests to ensure the system ends up in the desired state. In
this post, with the assumption that you have Docker installed, I’ll show
a simple way to run basic integration tests against a Puppet
module. Hopefully in about seven minutes.
In order to collect all the shinies we’ll use Docker, in conjunction
with Hashicorps Packer
, as our platform and we’ll write our unit tests
in json
and run them under a Go based test framework. If you want to
read along without copy and pasting you can find the code in the
Basic Puppet integration testing with Docker and Packer
repo.
Packer is “a tool for creating machine and container images for multiple platforms from a single source configuration.” In our case we’ll use it to create a Docker image, install Puppet and our testing tools and then run the test case inside the container to confirm that everything did as we expected.
If you’re on Linux, and installing Packer by hand, it’s as simple as:
mkdir /tmp/puppet-and-packer
cd /tmp/puppet-and-packer/
wget -O packer.zip https://releases.hashicorp.com/packer/0.8.6/packer_0.8.6_linux_amd64.zip
unzip packer.zip
./packer --version
0.8.6
Software installed we’ll be good devopsy people and write our tests first. For our simple
example all we’ll do with puppet is install the strace
package so we’ll write a
simple Goss test to verify it was installed in the
container. You can read more about
Testing with Goss
on this very site.
cat goss.json
{
"package": {
"strace": {
"installed": true
}
}
}
Now we have our test cases we can write our tiny puppet module:
mkdir -p modules/strace/manifests
cat modules/strace/manifests/init.pp
class strace {
package { 'strace': ensure => 'present', }
}
and the site.pp
that uses it
cat site.pp
include strace
Tiny examples created we’ll get to the meat of the configuration, our Packer file.
{
"builders": [{
"type": "docker",
"image": "ubuntu",
"commit": "true"
}],
"provisioners": [{
"type": "shell",
"inline": [
"apt-get update",
"apt-get install puppet curl -y",
"curl -L https://github.com/aelsabbahy/goss/releases/download/v0.0.22/goss-linux-amd64 > /usr/local/bin/goss && chmod +x /usr/local/bin/goss"
]
}, {
"type": "file",
"source": "goss.json",
"destination": "/tmp/goss.json"
}, {
"type": "puppet-masterless",
"manifest_file": "site.pp",
"module_paths": [ "modules" ]
}, {
"type": "shell",
"inline": [
"/usr/local/bin/goss -g /tmp/goss.json validate --format documentation"
]
}]
}
As an aside we see why JSON is a bad format for application config. You
can’t include comments in native JSON. In our example we’ll use two
of Packers three main sections, builders
and provisioners
. Builders
create and generate images from for various platforms, in our case
Docker. Provisioners install and configure software inside our images.
In the above code we’re using three different types.
inline shell
to install our dependencies, such as Puppet and Gossfile
to copy our tests in to the imagepuppet-masterless
to upload our code and run Puppet over it.
With all the parts in place we can now create a container and have it run our tests.
# and now we build the image and run tests inside the container
./packer validate strace-testing.json
./packer build strace-testing.json
==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: ubuntu
...
# puppet output
docker: Notice: Compiled catalog for 5a68ef0b80b1.udlabs.priv in environment production in 0.10 seconds
docker: Info: Applying configuration version '1454970724'
docker: Notice: /Stage[main]/Strace/Package[strace]/ensure: ensure changed 'purged' to 'present'
docker: Info: Creating state file /var/lib/puppet/state/state.yaml
docker: Notice: Finished catalog run in 8.59 seconds
...
# goss output
==> docker: Provisioning with shell script: /tmp/packer-shell193374970
docker: Package: strace: installed: matches expectation: [true]
docker:
docker:
docker: Total Duration: 0.009s
docker: Count: 1, Failed: 0
==> docker: Committing the container
I’ve heavily snipped Packers output and only shown the parts of relevance to this post. You can see the usual Puppet output, here showing the installation of the strace package
docker: Notice: /Stage[main]/Strace/Package[strace]/ensure: ensure changed 'purged' to 'present'
followed by the goss
tests running and confirm all is as expected
docker: Package: strace: installed: matches expectation: [true]
docker: Count: 1, Failed: 0
It’s trivially simple to replace Goss in these examples with InSpec, ServerSpec or TestInfra if you’re more comfortable with those tools. I chose Goss for this post as it’s very simple to install and has no dependency chain. Something that’s very relevant when you’ve only given yourself seven minutes to show people a new, and hopefully useful, technique.
As a closing note, you’ll want to occasionally clean up the old Docker images this testing creates.