Chef Blogs

Test Ohai Plugins with ChefSpec and InSpec

Franklin Webber | Posted on | Chef InSpec | community | cookbooks | Products and Projects

You can test your custom Ohai plugins with ChefSpec using the chefspec-ohai gem, and with InSpec in Test Kitchen. This frees you from the traditionally difficult task of debugging a failing Ohai plugin.

In this post, I’ll focus solely on testing an Ohai plugin. If you are interested in a complete tutorial that walks through the process of creating a cookbook with the plugin and all the tests, check out the Learn Chef tutorial for building an Ohai plugin, or watch the embedded video below:

The History of Testing Ohai Plugins

Ohai collects data about your system. The data is captured through various plugins that are defined and shipped with Ohai. Additionally, it supports the ability to load custom plugins that you define.

Testing those plugins has always been hard. To ensure the plugin works would require you to deliver it to your target node and then execute ohai or chef-client.

  • On success, you would be able to see the data that the plugin has captured about the node. Yay!
  • On failure, no data will be captured and no output will tell you what went wrong. Boo!

No output? Why doesn’t the run fail?

When an Ohai plugin fails, it does so silently, so that it does not effect the remainder of the ohai execution and the chef-client run. If you were to make a mistake while creating or changing the plugin it would take a long time to troubleshoot the issue.

I have taught numerous chefs how to create Ohai plugins in our Chef Intermediate course. When errors happen, and they sure do happen, we pair up and try to play the game “spot-the-typo”. Hardly a fun game to play!

An Example Ohai Plugin

An ohai plugin is usually a file within your cookbook that you deliver to your node with the ohai cookbook and then properly loaded with the chef-client cookbook.

Here is an example plugin that captures the state of the modules installed with Apache:

# cookbooks/apache/files/default/apache_modules.rb
Ohai.plugin :Apache do
  provides 'apache/modules'

  collect_data :default do
    apache(Mash.new)
    modules_cmd = shell_out('apachectl -t -D DUMP_MODULES')
    apache[:modules] = modules_cmd.stdout
  end
end

Testing the Plugin with ChefSpec

Defining a ChefSpec test will allow us to verify our syntax and logic defined in the plugin.

A new gem contains the necessary helpers to load and unit test the Ohai plugin. Here, we will install it and then require it within our common test helpers files. Then, we will define a new test that we will execute.

1. Install the chefspec-ohai gem:

$ chef gem install chefspec-ohai

2. Require the chefspec-ohai gem in the `spec/spec_helper.rb`:

# cookbooks/apache/spec/spec_helper.rb
require 'chefspec'
require 'chefspec/berkshelf'
require 'chefspec/ohai'

3. Create and define the test file:

# cookbooks/apache/spec/ohai_plugins/apache_modules_spec.rb
require 'spec_helper'

describe_ohai_plugin :Apache do
  let(:plugin_file) { 'files/default/apache_modules.rb' }

  it 'provides apache/modules' do
    expect(plugin).to provides_attribute('apache/modules')
  end

  let(:command) { double('Fake Command',stdout: 'OUTPUT') }

  it 'correctly captures output' do
    allow(plugin).to_receive(:shell_out).with('apachectl -t -D DUMP_MODULES').and_return(command)
    expect(plugin_attribute('apache/modules')).to eq('OUTPUT')
  end
end

Let’s talk about the syntax of an Ohai plugin test:

  • describe_ohai_plugin is a new example group alias that allows you to easily test Ohai plugins
  • let(:plugin_file) { ... } defines the relative path to the cookbook file that contains the Ohai plugin
  • The first example asserts that the plugin provides the correct attribute
  • let(:command) { ... } returns the test double command object which we use in the next example
  • double('Fake Command',stdout: 'OUTPUT') is an RSpec test double that takes a name and a hash of parameters. These parameters are converted to methods on the double object.
  • allow(plugin).to_receive(:shell_out).with('...') is RSpec’s way of allowing messages  to override the shell_out command when used within the plugin; replace the results of it when given the specified command.
  • plugin_attribute('apache/modules') retrieves the value stored in the modules key (Similar to writing node['apache']['modules'])

4. Execute the test suite

$ chef exec rspec spec/ohai_plugins/apache_modules_spec.rb
.[2016-12-02T14:18:59-06:00] WARN: Plugin Definition Error: <files/default>: collect_data already defined on platform default
.

Finished in 0.20469 seconds (files took 1.54 seconds to load)
2 examples, 0 failures

Testing the Ohai Plugin with InSpec

Testing our plugin with ChefSpec allows us to ensure the plugin is defined correctly. It does not verify if the command we wrote works correctly on our given platform / platform version. This is where we can use Test Kitchen.

The ohai community cookbook provides ohai_plugin to assist us with deploying the plugin. Here, we will establish a dependency on the ohai cookbook, create a new recipe, define a Test Kitchen suite, create and execute a new InSpec test.

1. Add the ‘ohai’ community cookbook as a dependency

# cookbooks/apache/metadata.rb
# ... rest of your cookbook metadata ...
depends 'ohai'

2. Create a recipe to deploy the plugin

# cookbooks/apache/recipes/ohai_apache_modules.rb
include_recipe 'apache::default' # the plugin requires the software to be installed
ohai_plugin 'apache_modules'

3. Add a new Test Kitchen suite that includes the recipe in the run list

# cookbooks/apache/.kitchen.yml
# ... REST OF KITCHEN CONFIGURATION ...
suites:
  # ... other test suites ...
  - name: ohai_apache_modules
    run_list:
    - recipe[apache::ohai_apache_modules]
    attributes:

4. Create and define a test that executes `ohai` while loading our plugin

$ mkdir -p test/recipes/ohai_apache_modules
# cookbooks/apache/test/recipes/ohai_apache_modules/default_test.rb

plugin_directory = '/tmp/kitchen/ohai/plugins'

describe command("ohai -d #{plugin_directory} apache") do
  its(:stdout) { should match(/core_module/) }
end
  • The ohai cookbook will store plugins in the specified directory within Test Kitchen.
  • The command resource is invoking the ohai command-line tool with the directory and specifying the plugin we want to view.
  • The standard out should displays all the modules; ‘core_module’ is an example of one of those modules.

5. Execute this new test suite

$ chef exec kitchen verify ohai_apache_modules

Conclusion

Now you can build your Ohai plugins with tests. ChefSpec will verify that you wrote the plugin correctly. InSpec will verify that the command really works on your specified platform. These feedback mechanisms will ensure you maintain your development velocity and are much more fun than playing “spot-the-typo”.