How to check a chef recipe with serverspec

I like test-driven development. I’m used to thinking that test-driving chef cookbooks is hard – until I failed an interview because I lacked knowledge of chefspec. So that same date I looked into chefspec and related tools (rubocop, food critic, and serverspec). Chefspec itself is easy. The good news is that it allows some sort of testing. The bad news is that it’s stubbed unit tests, not integration tests. Serverspec to the rescue!

Serverspec allows functional integration tests of the cookbooks. But how do you set it up? I have some experience with vagrant, but honestly I don’t use it extensively in personal development. I do however use virtualbox a lot (along with chef server).

In my /etc/hosts I have written out a basic local network like so:       localhost          piousbox-samsung       sentact.local      zend.local       pi.local           webdevzine.local     sedux.local       cities.local       api.local       sleeper.local      nagios.local       wasya_co.local     wasya_co2.local       bjjc.local         bjjc-angular.local   anything.local    ubuntu15-virgin    centos-virgin    ubuntu14-virgin    ubuntu-virgin   lb_10.ubuntu       lb_10_spec   bjjc_22.ubuntu15   bjjc_22.ubuntu   bjjc_23.ubuntu14   bjjc_23.ubuntu   spec_24.ubuntu14   spec_24.ubuntu   jenkins1.local     jenkins.local   jenkins2.local   jenkins3.local   petclinic.local   nexus.local   centos-virgin.local   jenkins.centos

The above local DNS is not necessary, but I have found it convenient to have.

Next, there is a repo that contains my chef server workstation and a number of other things. Although chef-spec of each recipe goes into that recipe, the serverspecs all go into spec/ folder of my chef workstation. So let’s say I am testing that a recipe installs ruby. Generate the server spec with `bundle exec serverspec-init`. In spec/spec_24.ubuntu/sample_spec.rb I have the following:

require 'spec_helper'

describe command("/usr/local/rbenv/shims/ruby --version") do
its(:stdout) { should match /2.0.0/ }

and in the for the repo I have the instructions to run it:

# vm_spec_24
# verifies ish::install_ruby
knife client delete vm_spec_24 -y ; \
knife node delete vm_spec_24 -y ; \
# sshpass -p "the_password" ssh oink@spec_24.ubuntu "echo the_password | sudo -S rm -rfv /etc/chef" ; \
VBoxManage controlvm "ubuntu14 spec_24" poweroff ; \
VBoxManage snapshot "ubuntu14 spec_24" restore "network ok" && \
VBoxManage startvm "ubuntu14 spec_24" --type headless && \
while ! ping -c1 spec_24.ubuntu &>/dev/null; do :; done ; \
knife bootstrap spec_24.ubuntu -N vm_spec_24 --ssh-user oink --ssh-password the_password -r "recipe[ish::install_ruby]" --sudo --use-sudo-password -y --environment vm_samsung && \
SUDO_PASSWORD=the_password TARGET_HOST=spec_24.ubuntu be rspec spec/spec_24.ubuntu

What the script does is:

  1. Deletes chef node and client
  2. (does not) delete the target machine’s chef identity – that’s the quick and dirty way, but the following step takes care of the same task cleaner.
  3. reverts the VM to a good clean configuration. Only the static network is configured.
  4. waits for the machine to boot
  5. bootstraps the machine with chef
  6. runs the serverspec

Voila! This answers my need for local semi-automatic testing using serverspec. Hope this helps! The disadvantage of this is that it doesn’t quite fit into CI/CD pipelines, but I will address that in a later post.