Skip to content

Instantly share code, notes, and snippets.

@jerryaldrichiii
Created January 14, 2019 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jerryaldrichiii/635cd680b1394da2eacd32aba795edaa to your computer and use it in GitHub Desktop.
Save jerryaldrichiii/635cd680b1394da2eacd32aba795edaa to your computer and use it in GitHub Desktop.

Chef Node Attributes in InSpec

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

Chef and 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) and infrastructure/application testing (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 Node Attributes

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

Chef 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 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
  miah
  cwolfe
)
# recipies/default.rb
node['my_cookbook']['users'].each do |my_user|
  user my_user
end

Testing the Results of Chef

Trusting Chef to do what Chef 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?

InSpec!

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

users = %w(
  jerryaldrichiii
  miah
  cwolfe
)

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 and 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 in 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 node attribute data source in InSpec.

Perils of Persistence

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

Before we dive in, I would like to take a moment to say that using the Chef node object's data in 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 and InSpec you have to ask yourself, "What am I actually testing?"

To use the example from above, are you testing that Chef 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
  miah
  cwolfe
  awesomee_developer
)

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

In this case InSpec would be testing that Chef can create a set of users, not testing if Chef actually created the users you want.

Summary

Persisting the node object from Chef to InSpec may seem like the most efficient way to test the results of Chef 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 InSpec for testing higher level performance (e.g. "Does this web service return a HTTP 200?" vs "Is NGINX installed?").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment