Testing Chef cookbook

  • Tutorial

The infrastructure as code concept allows us to apply solutions from the development world to infrastructure. Individual infrastructure components in projects are often repeated. When integrating such components, the most convenient option is general cookbooks. The cookie code is constantly changing, bugs are fixed, new functionality appears. With the help of testing, we track regressions, control backward compatibility and implement new features faster.
In this article we will get acquainted with testing tools, write a simple cookbook and a test for it.

What are we testing

First, we need to decide what we will test.

  1. Ruby Style Guide . In the Ruby world, there is no style guide from the language developers, but there is a style guide from the community . Since the cookie code is written in ruby, it’s good to follow the practices that are already in the community;
  2. Cookbook linter. During the existence of chef, a large number of patterns and anti-patterns have accumulated, they must be taken into account when writing a cookbook;
  3. Integration Testing . After making changes, it is necessary to check the efficiency of the cookbooks, border conditions and integration with other cookbooks.

Ruby Style Guide

To check ruby ​​against the style guide, the Rubocop utility is used .
The utility is installed with the gem install rubocop command; you can check the code with the ' rubocop command. ', most errors can be fixed automatically, for this you need to use the -a option .
If you want to disable some checks for some files, you need to create a .rubocop.yml file and specify exceptions in it.

Example. For all subdirectories in the test directory, disable the method length check for the spec_helper.rb file :
    - 'test/**/spec_helper.rb'

Cookbook linter

Cookbook style and detection of known errors is carried out using the Foodcritic utility . The utility is installed by the gem install foodcritic command , launched by the foodcritic command <path to the cookbook>
Example of work:
foodcritic .
FC017: LWRP does not notify when updated: ./providers/default.rb:32

If Foodcritic found any problems, there are instructions on how to fix them on the project website http://acrmp.github.io/foodcritic/ .

Integration testing

For integration testing, Chef released the Test Kitchen utility , http://kitchen.ci . This utility prepares the test environment and runs your tests. In a simple case, Test Kitchen starts a virtual machine, runs a chef-client in it, and after passing chef-run, it runs the tests.
Virtual machines run through Vagrant. As a hypervisor, you can use everything that supports Vagrant. In addition to using virtual machines, Test Kitchen can work with public and private clouds.
Tests can be written using various frameworks. Out of the box, Bats, shUnit2, RSpec, and Serverspec are supported. Test language Bash (Bats, shUnit2) or Ruby (RSpec, Serverspec). At the same time, the same cookie can be tested under different operating systems and with different test suites.

Testing with Test Kitchen

In order to write tests and generally work with chef cookbooks in the modern world, the Chef Development Kit is used . ChefDK installs its own installation of the ruby ​​language and all the gems needed for chef to work. Thus, the installation of chef will not depend on your system ruby, this will avoid many problems with cross-dependencies of gem-s and so on.
Chef itself and most of the necessary gems are included in the supply of ChefDK. If some
gem is missing, then it can be installed with the command: chef gem install <gem name> .
You can download ChefDK for your platform here: https://downloads.chef.io/chef-dk/


For training in testing, we will write a simple deploy-user cookbook that will create the deployer user and the home directory / home / deployer. In real life, to create users, you can (and should) use ready-made community cookies .
To generate the skeleton of a cookbook, we use the command: chef generate cookbook deploy-user . At the output, we get the deploy-user directory with a cookie.
how to generate a skeleton?
Historically an empty cookie could be created by the team
knife cookbook create 
- This command creates a cookbook in the directory specified in the knife settings, does not create a test framework, does not create a chefignore file, does not create a git repository, but creates a lot of extra directories for all chef entities. Although, in the 2010th year, and it was very cool =)
berks cookbook 
- creates a skeleton using the berkshelf utility (it's like a bundler in the ruby ​​world)
chef generate cookbook 
- creates a skeleton using the chef utility from ChefDK.
The berks cookbook and chef generate do roughly the same thing. The difference is in the little things that we will not consider in the framework of this article. In any case, you can always add / remove what you need.
In the end, you can write a simple Thor / Rack task that will take into account all your wishes.

Create the attributes directory and the default.rb file in it. In the default.rb file, define the variables with the user name and its shell.
attributes / default.rb :
default['deploy-user']['username'] = 'deployer'
default['deploy-user']['shell'] = '/bin/bash'

In the recipes / default.rb file, call the standard user resource and pass it our
recipes / default.rb parameters :
user node['deploy-user']['username'] do
  shell node['deploy-user']['shell']
  supports manage_home: true

For our example, such a simple cookbook will be enough.


The entire configuration of Test Kitchen is described by a single .kitchen.yml file. We already have this file, it was generated by the chef utility .
Let's go through the contents of .kitchen.yml.
The config is divided into sections, each section is responsible for different aspects of the testing process.

Default .kitchen.yml :
  name: vagrant
  name: chef_zero
  - name: ubuntu-12.04
  - name: centos-6.5
  - name: default
      - recipe[deploy-user::default]

The driver section describes the parameters of the driver for working with virtual machines. In the simple case, we use Vagrant.

In the provisioner section, we indicate who will execute the code of the tested cookie. In the case of Chef, there are two options, chef-solo and chef-zero. In the modern world, the use of chef-solo is not required without special need. You can also specify some additional options, for example, which version of Chef to install. If your cookbook does not work with Chef 12, you can explicitly specify the version of require_chef_omnibus: 11.18.6 .

The platforms section describes which OS our cookie will be tested on. For example, you can add Ubuntu 14.04 like this: ' - name: ubuntu-14.04 '.

IMPORTANTEach driver and platform has its own vagrant boxes by default. If you need to change the default settings, then the driver must pass the appropriate command.

Example: Parallels Desktop hypervisor and custom box.
  - name: ubuntu-14.04
      provider: parallels
      box: express42/ubuntu-14.04

With such an entry ( company / image ), the image is taken from the service https://atlas.hashicorp.com , you can simply specify the box url through the box_url option .

Finally, the suites section describes a set of recipes that must be completed before running the tests. We have one suite named default. It describes a run-list with one deploy-user :: default recipe . This is the recipe in which we described the creation of the user.

Work with Test Kitchen

Now let's see what we can do with our kitchen.

You can view the list of machines and their condition with the kitchen list command . Note that we have described only one suite, so only two machines will be created: default-ubuntu-1404 and default-centos-70. If we described another suite, then the number of cars would double. The final number of machines is equal to: the number of suites multiplied by the number of platforms

With the kitchen converge command, we will start creating virtual machines and launch all suites for all platforms. You can run one suite on one platform like this: kitchen converge default-ubuntu-1404 . To remove all machines and return everything as there was a kitchen destroy command .
how to turn off the virtual machine
Fun fact, with Test Kitchen, you cannot turn off machines without removing them. There is an epic saga on Github about this in several actions issue https://github.com/test-kitchen/test-kitchen/issues/350

After executing the kitchen converge, we will get the running virtual machines with the default.rb recipe executed from our deploy-user cookbook .

If there are errors in the cookbook code, chef-run will abort and the place in the code that caused the error will be shown. We believe that chef-run was successful =). Next, let's check whether the suite really worked correctly and did what we expected from it. Using the kitchen login default-ubuntu-1404 command, we ’ll enter one of the machines.

Run the getent passwd deployer command :

Indeed, the deployer user is created, the correct home directory is used, and the shell we need is used.

Now check that the user has created the home directory and that it has the correct owner, group and access rights: ls -lah / home / deployer / .
drwxr-xr-x 2 deployer deployer 4096 Mar 15 23:12 .
drwxr-xr-x 4 root     root     4096 Mar 15 23:12 ..
-rw-r--r-- 1 deployer deployer  220 Apr  8  2014 .bash_logout
-rw-r--r-- 1 deployer deployer 3637 Apr  8  2014 .bashrc
-rw-r--r-- 1 deployer deployer  675 Apr  8  2014 .profile

Indeed, the home directory exists and has the rightful owner, group and access rights.

Hooray, now you know how to test cookbooks!

Joke =)


There are two kitchen verify and kitchen test commands to run the tests .

kitchen verify installs the testing framework inside vm and runs your tests. You can edit your tests and run verify again .

kitchen test launches a full test cycle. First , kitchen destroy is executed , if before that the machine was created, then suites are run, tests are run, and at the end, destroy is sometimes performed. By default, destroy is done if the tests succeed. This behavior can be overridden using the kitchen test command options .

Nowadays, cookies are customary to be tested with the Serverspec framework, http://serverspec.org. Serverspec is an RSpec extension that provides convenient primitives for testing servers. With the release of serverspec, people stopped writing tests on pure RSpec (as well as on bash).

If we run kitchen verify now , we will see that they wrote an empty test for us:

         does something (PENDING: Replace this with meaningful tests)
       Pending: (Failures listed here are expected and do not affect your suite's status)
         1) deploy-user::default does something
            # Replace this with meaningful tests
            # /tmp/busser/suites/serverspec/default_spec.rb:8
       Finished in 0.00185 seconds (files took 0.3851 seconds to load)
       1 example, 0 failures, 1 pending
       Finished verifying  (0m36.87s).

Open the file test / integration / default / serverspec / default_spec.rb and write a test for our cookbook:
require 'spec_helper'
describe 'deploy-user::default' do
  describe user('deployer') do
    it { should exist }
    it { should have_home_directory '/home/deployer' }
    it { should have_login_shell '/bin/bash' }
  describe file('/home/deployer') do
    it { should be_directory }
    it { should be_mode 755 }
    it { should be_owned_by 'deployer' }
    it { should be_grouped_into 'deployer' }

The code describes the actions that we did on the machine manually.
The deployer user must exist, have a home directory / home / deployer , and have shell / bin / bash .
/ home / deployer should be a directory, have permissions of 755, the owner of deployer and the deployer group .

If we haven’t made a mistake anywhere, then the result of kitchen verify will be like this:
         User "deployer"
           should exist
           should have home directory "/home/deployer"
           should have login shell "/bin/bash"
         File "/home/deployer"
           should be directory
           should be mode 755
           should be owned by "deployer"
           should be grouped into "deployer"
       Finished in 0.10972 seconds (files took 0.306 seconds to load)
       7 examples, 0 failures
       Finished verifying  (0m1.92s).

Hooray, now you know how to test cookbooks!

Bonus Unit testing

In the world of Chef, unit testing also exists. It uses the chefspec tool, http://sethvargo.github.io/chefspec/ . The main difference from testing through Test Kitchen is that the creation of virtual machines and the start of chef-run does not occur. Instead, the call to the resource with the necessary parameters is checked. This can be useful when some resources cannot be tested in the usual way. For example, if the execution of a resource depends on an external system or requires specific equipment. Well, such tests can be run in any CI system. Of the minuses, it is worth noting that in this way it is difficult to test LWRP.

An example of such a test can be found below.

spec / unit / recipes / default_spec.rb
require 'spec_helper'
describe 'deploy-user::default' do
    let(:chef_run) do
      runner = ChefSpec::ServerRunner.new
    it 'converges successfully' do
      chef_run # This should not raise an error
    it 'creates a user deployer with home "/home/deployer" and shell "/bin/bash"' do
      expect(chef_run).to create_user('deployer').with(
        home: '/home/deployer',
        shell: '/bin/bash')

If you add the line at_exit {ChefSpec :: Coverage.report! To the file spec / spec_helper.rb ! } , then after the tests the percentage of coverage will be displayed.

You can run these tests with the command chef exec rspec -c
Finished in 0.51553 seconds (files took 3.34 seconds to load)
2 examples, 0 failures
ChefSpec Coverage report generated...
  Total Resources:   1
  Touched Resources: 1
  Touch Coverage:    100.0%
You are awesome and so is your test coverage! Have a fantastic day!

Useful documentation

Also popular now: