Upon joining TeamQuest, I started finding places to make myself useful as fast as possible. One such place was adding integration tests to existing puppet modules. I have done this a lot for Chef in the past, using vagrant and/or test-kitchen.

It must be said, that any information, or opinions espoused here, is mine, not TeamQuest's. This is a personal blog, and the information provided here is provided "AS IS", with no warranties.

So here's what I've got working.


  • uses the vanilla centos-6.7 bento box
  • installs puppet 3.8
  • lands puppet module dependencies using this trick
  • "installs" the module under test
  • provisions the VM with a test manifest
  • verifies the provision with serverspec tests

A few peculiarities

Serverspec from Windows to Linux

The fact that I wanted to test a CentOS box from a Windows local machine seemed to confound serverspec. I am writing this from a Mac, where it works as well, but I finally landed on using the Rakefile pattern generated by serverspec-init to solve issues with CRLFs and EOFs. Now it handles connecting and running the tests over SSH.

Module naming and custom class definitions

In my module under test, I have a custom class called tqjenkins. It wraps uses of the Puppet Forge hosted rtyler/jenkins, so in order to not conflict, it has a unique name. But the module it is in, just like rtyler/jenkins, isn't named tqjenkins, but is prefixed with the company name. So my module teamquest-tqjenkins provides a class called tqjenkins. You with me?

Well the puppet apply provisioner in Vagrant doesn't like this. It thinks I am just crazy, even though this is how puppet generates module layouts. It can't find the class definition, and fails during puppet provision with the NoClassDefFoundError equivalent, of:

Error: Puppet::Parser::AST::Resource failed with error ArgumentError: Invalid resource type tqjenkins::production

To make a short story longer, shout out to Volcane in #puppet on Freenode, he/she led me down this path. I saw that if I renamed my folder teamquest-tqjenkins to just tqjenkins, everything worked correctly, but that still didn't feel good. So instead, I use a Vagrant synced_folder to install the module, thus stripping the prefix. VoilĂ !

config.vm.synced_folder "teamquest-tqjenkins", "/etc/puppet/modules/tqjenkins"

Here is my Vagrantfile in entirety, in case you're curious:

unless Vagrant.has_plugin?("vagrant-puppet-install")
  raise "Plugin 'vagrant-puppet-install' is required.
         Run 'vagrant plugin install vagrant-puppet-install'"

Vagrant.configure(2) do |config|
  config.vm.box = "bento/centos-6.7"

  # uses vagrant plugin vagrant-puppet-install
  config.puppet_install.puppet_version = "3.8.3"

  # install dependencies (go ahead, mock me copying this from the metadata)
  # a better way to do this would be use something like librarian-puppet
  config.vm.provision :shell do |shell|
    shell.inline = "mkdir -p /etc/puppet/modules;
                    puppet module install puppetlabs-stdlib;
                    puppet module install rtyler/jenkins;
                    puppet module install puppetlabs/concat"

  config.vm.network "forwarded_port", guest: 8080, host: 9090

  # Because we're not "puppet module install" installing our module under test,
  # we need to rename it on the VM so it looks like a typical module
  config.vm.synced_folder "teamquest-tqjenkins", "/etc/puppet/modules/tqjenkins"

  # provision jenkins with puppet
  config.vm.hostname = "teamquest-tqjenkins.example.com"
  config.vm.provision :puppet do |puppet|
    puppet.manifest_file = "test.pp"
    puppet.manifests_path = "test_manifests"
    puppet.module_path = "./"
    puppet.facter = { 'fqdn' => config.vm.hostname }
    # You want more logs? this is how you get more logs
    puppet.options = "--verbose --debug"