Using Chef Infra Node Attributes in Chef InSpec

Chef Infra, Chef InSpec, Node Attributes, what are they!?!

Chef Infra and Chef InSpec are open source products made by Chef Software and each fulfill separate needs in their respective problem spaces. That doesn’t mean they shouldn’t be used together though. Pairing configuration management (Chef Infra) and infrastructure/application testing (Chef InSpec) is a wonderful thing. It is made even more delightful when the same company (and in most cases the same humans) work on the tools to pair them.

That being said, convenience and in some cases developer intuition can lead to unintended and sometimes dangerous consequences. This blog post was created to highlight those consequences.

Chef Infra Node Attributes

In order to understand the potential consequences, we must first understand Chef Infra node attributes and their purpose.

Chef Infra utilizes the concept of a node object. This node object is a data store for both information about a system (provided by Ohai) and user defined information (variables, metadata, etc). It is very common to use this node object to drive the behavior of a Chef Infra cookbook (the collection of code used to define the desired state of a system).

Here is an example that creates users based on a list defined as a node attribute:

# attributes/default.rb
default['my_cookbook']['users'] = %w(jerryaldrichiii bobsmith janedoe)
# recipies/default.rb
node['my_cookbook']['users'].each do |my_user|
  user my_user
end

Testing the Results of Chef Infra

Trusting Chef Infra to do what Chef Infra does is great and all, but how would you verify it actually did what you expected?

Using ChefSpec you could test that the run_list compiled correctly (and the correct attributes were set), but how do you verify it actually created the users you specified?

Chef InSpec!

Chef InSpec is built for testing just that. Below is the Chef InSpec to test the example above:

users = %w(jerryaldrichiii bobsmith janedoe)
users.each do |my_user|
  describe user(my_user) do
    it { should exist }
  end
end

DRY Code

Now, some of you reading the above might notice that the list of users is repeated between Chef Infra and Chef InSpec. In order to follow the DRY (Don’t Repeat Yourself) methodology you might look to remove this repetition.

Couldn’t we just use the node object from Chef Infra in Chef InSpec? You might ask.

In short, you absolutely can! There are even patterns defined in the community to do just that! See:

http://www.hurryupandwait.io/blog/accessing-chef-node-attributes-from-kitchen-tests https://github.com/chef-cookbooks/audit#using-chef-node-data

In practice, while seemingly counter intuitive, you might want to avoid doing using the Chef Infra node attribute data source in Chef InSpec.

Perils of Persistence

Persisting (or saving) the data sources from Chef Infra and using those same data sources in Chef InSpec can have perilous consequences.

Before we dive in, I would like to take a moment to say that using the Chef Infra node object’s data in Chef InSpec isn’t categorically wrong. In fact, there may be very valid use cases to do just that! Just keep the potential problems below in mind if you choose to go down that path.

Asking Yourself, “What am I actually testing?”

When sharing the same data source between Chef Infra and Chef InSpec you have to ask yourself, “What am I actually testing?”

To use the example from above, are you testing that Chef Infra can create users or that a certain list of users are created? While these two questions may seem the same on the surface, they are different.

Using our example above we can demonstrate the difference. Let’s say that another developer comes along and tries to modifies the list of users.

Here is an example of these changes:

# attributes/default.rb
default['my_cookbook']['users'] = %w(
  jerryaldrichiii
  bobsmith
  janedoe
  awesomee_developer
)

As you can see, awesome is spelled incorrectly. If we had used the same data source (the Chef Infra node object) between Chef InSpec and Chef Infra, Chef InSpec would not have caught this, Chef Infra would have created the user awesomee_developer and Chef InSpec would have verified that the awesomee_developer user existed.

In this case Chef InSpec would be testing that Chef Infra can create a set of users, not testing if Chef Infra actually created the users you want. Chef Infra is perfectly capable of creating users. You most likely were intending that the test would test that a defined set of a users were created. Thus, you should hard code those values both in the Chef Infra cookbook and the Chef Infra tests.

Summary

Persisting the node object from Chef Infra to Chef InSpec may seem like the most efficient way to test the results of Chef Infra on the surface. In some cases, it may be, but if you choose to do that you must keep in mind the dangers that come with it. Mainly, it allows for potentially dangerous code changes that won’t be caught by your automated testing.

Instead of persisting the node object, consider other methods of getting the data. For example: Using inspec.command('command').stdout, hardcoding the input attributes in your test framework (e.g. Test Kitchen), and/or moving attribute related tests to ChefSpec and using Chef InSpec for testing higher level performance (e.g. “Does this web service return a HTTP 200?” vs “Is NGINX installed?”).