Chef Cookbook Testing using Vagrant

There came a time in our team where we started churning out cookbooks by the hour. Now the question that needed to be answered was - How does one QA a chef cookbook. We could either have had one team member solely responsible for writing out rspecs and testing the cookbooks on a virtual environment, but what happens when the only team members who can be used to perform the above task as busy creating the cookbooks themselves. We needed a faster and smoother answer. In came Vagrant.

Why Vagrant?

Vagrant does everything that one would need to do to set up a virtual environment without wasting time in installations, snapshots, teardowns etc. Vagrant doesn’t create a virtual machine instance completely from scratch. Instead, it imports a base image (read .box files) for a VM and builds off of that. This simplifies things greatly for Vagrant users since they don’t have to spend time specifying tedious details such as memory capacity, hard disk capacity, network controllers, etc, and also allows customize-able bases to build projects from. Vagrant will isolate dependencies and their configuration within a single disposable, consistent environment, without sacrificing any of the tools you are used to working with (editors, browsers, debuggers, etc.). Read More.

Vagrant gives us one file "Vagrantfile" that needs to be configured with the environment that needs to be set and voila! everything works like you want it to. The standard (read easiest) way to use vagrant is as below.

  1. Create a directory where you want to store all your vagrant data, say vagrant. Then download the boxes that you need in your test environment. The box packages are available at vagrantbox.es.
  2. Run the vagrant init command in the vagrant directory. This command would create the Vagrantfile. The file by default sets up a single VM environment.
  3. Add a box, depending on the flavour that the cookbooks need to be tested for.
    vagrant box add centos64 https://github.com/2creatives/vagrant-centos/releases/download/v6.4.2/centos64-x86_64-20140116.box
  4. Consider a production system running different version of CentOS for different microservices. In order to set up a testing environment that would test cookbooks on all images of the production set-up. Customize the Vagrantfile as below:
    test_vms = {
        :centos56 => {
            :hostname => "centos56",
            :ipaddress => "x.x.x.x",
            :run_list => "recipe[cookbook_1],recipe[cookbook_2]"
        },
        :centos58 => {
            :hostname => "centos58",
            :ipaddress => "x.x.x.x",
            :run_list => "recipe[cookbook_1],recipe[cookbook_2]"
        },
        :centos6 => {
            :hostname => "centos64",
            :ipaddress => "x.x.x.x",
            :run_list => "recipe[cookbook_1],recipe[cookbook_2]"
        }
    }
    Vagrant::Config.run do |global_config|
        test_vms.each_pair do |name, setup|
            global_config.vm.define name do |config|
                config.vm.share_folder("v-root", "/vagrant", ".", :disabled => true)
                config.vm.box = name.to_s
                config.vm.boot_mode = :headless
                config.vm.host_name = name
                config.vm.network :bridged, setup[:ipaddress]
                config.vm.provision :chef_solo do |chef|
                    chef.cookbooks_path = "cookbooks" #path relative to the location of Vagrantfile
                    chef.node_name = name
                    chef.log_level = :info
                    chef.run_list = options[:run_list].split(",").flatten
                end
            end
        end
    end
  5. There are two types of adapters that can be used to setup config.vm.network :bridged and :hostonly. Using :bridged adapter the vms would be able to talk to each other and using :hostonly they would behave as individual vms on separate networks.
  6. Another thing to keep in mind while testing in a self contained cloud environment would be that if there are cookbooks that try to access a web server / yum repo server / gem server in the same environment then it would be essential that there be a cookbook that stops the iptables and is the first to run in the run_list each time.
  7. Now, just run vagrant up and all the vms would be provisioned as configured in the Vagrantfile. Also remember that that vagrant up would try to recreate the entire environment each time the command is run. So, say you hit an error in a cookbook you ran. You change the cookbook but you don't want to rebuild the entire environment again, run vagrant provision <vm_name>. This command would only re-provision the mentioned vm.
  8. Coming to teardown. The easiest way to teardown a vagrant environment is by running the command vagrant destroy -f This would force destroy all the vms created by the vagrant up command.