Content for blog.jerryaldrichiii.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

149 lines
5.5 KiB

---
layout: post
title: Using Chef Infra Node Attributes in Chef InSpec
category: chef_infra
tags: [chef, cookbooks, attributes, inspec]
summary: Don't use Chef Infra 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:
```ruby
# attributes/default.rb
default['my_cookbook']['users'] = %w(jerryaldrichiii bobsmith janedoe)
```
```ruby
# 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:
```ruby
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:
```ruby
# 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?").