In my last post, Automating Cookbook Testing with Test-Kitchen, Berkshelf, Vagrant and Guard, we successfully got our local development environment up and running in a simple automated fashion. However, we aren't actually doing anything interesting yet. Our Strider CD application is not getting installed or configured and even if we did have cookbook code written, we haven't got any way to validate it's configured properly.

This post will cover integrating Foodcritic with Guard to enable automated lint checking of Chef cookbook code.

Step 1: Install Foodcritic

If there is only one tool you take away from these posts to implement on your own, please let it be Foodcritic. Having an automated tool tell you when you are using the wrong attribute syntax or that you are cargo-culting an archaic method of sending notifications is one of the most helpful things you can do today. That's because it's super simple to get started with Foodcritic.

Let's go ahead and add the Foodcritic gem to our cookbook's Gemfile now:

source 'https://rubygems.org'

gem 'berkshelf'

group :development do  
  gem 'test-kitchen'
  gem 'kitchen-vagrant'
  gem 'guard'
  gem 'guard-kitchen'
  gem 'foodcritic'
end  

Now let's get Foodcritic into our vendor/bundle directory with Bundler:

$~/chef-repo/cookbooks/strider-cd: bundle install
Fetching gem metadata from https://rubygems.org/.......  
Fetching gem metadata from https://rubygems.org/..  
Resolving dependencies...  
Using i18n (0.6.9)  
Using multi_json (1.8.2)  
Using activesupport (3.2.16)  
Using addressable (2.3.5)  
...
Installing foodcritic (3.0.3)  
...
Using bundler (1.3.5)  
Your bundle is complete!  
It was installed into ./vendor/bundle  

Now we can check out our empty cookbook and see if we have any syntactic errors already!

$~/chef-repo/cookbooks/strider-cd: bundle exec foodcritic .
FC008: Generated cookbook metadata needs updating: ./metadata.rb:3  

NOTE: You may see nothing output at all. If this happens, check your Gemfile.lock file to see what version Bundler is expecting for the foodcritic gem. Due to the order in which we are adding gems to our Gemfile, some Foodcritic dependencies may have a conflict with what's already installed in vendor/bundle. You can usually resolve this by deleting your Gemfile.lock file and running bundle install again.

Well, that's interesting. We haven't even written anything, but Foodcritic is already complaining to us! If you look at the response from Foodcritic, you can see it's pretty informative.

FC008 is the Foodcritic rule that was violated. You can read about this specific rule at http://acrmp.github.io/foodcritic/#FC008, but it seems from the summary that follows is pretty clear what triggered the error.

Generated cookbook metadata needs updating: ./metadata.rb:3`  

If we look at our metadata.rb file at line 3, you'll notice that our maintainer_email setting is not filled out properly. Line 2 is also incorrect, even though it wasn't called out specifically. Let's fix both lines now.

maintainer       'Jane Doe'  
maintainer_email 'you@example.com'  

Now if we run bundle exec foodcritic . again, we should see zero errors.

Step 2: Automate Foodcritic

While having a linting tool available to run at will is pretty handy, automating it is always handier. Luckily Guard is already managing our Test-Kitchen tooling, so we'll just add Foodcritic to that list as well.

As with our guard-kitchen gem, we'll add the guard-foodcritic gem to our Gemfile help manage Foodcritic with Guard.

source 'https://rubygems.org'

gem 'berkshelf'

group :development do  
  gem 'test-kitchen'
  gem 'kitchen-vagrant'
  gem 'guard'
  gem 'guard-kitchen'
  gem 'guard-foodcritic'
  gem 'foodcritic'
end  

And then we'll get guard-foodcritic into our vendor/bundle directory with Bundler

$~/chef-repo/cookbooks/strider-cd: bundle install

Next we'll need to update our Guardfile with the watchers for Foodcritic by running bundle exec guard init foodcritic. The latest version of guard-foodcritic available when this post was written does not include matchers for metadata.rb or any templates, so we'll open up the Guardfile and add them manually

# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'kitchen' do  
  watch(%r{test/.+})
  watch(%r{^recipes/(.+)\.rb$})
  watch(%r{^attributes/(.+)\.rb$})
  watch(%r{^files/(.+)})
  watch(%r{^templates/(.+)})
  watch(%r{^providers/(.+)\.rb})
  watch(%r{^resources/(.+)\.rb})
end

guard "foodcritic" do  
  watch(%r{attributes/.+\.rb$})
  watch(%r{providers/.+\.rb$})
  watch(%r{recipes/.+\.rb$})
  watch(%r{resources/.+\.rb$})
  watch(%r{^templates/(.+)})
  watch('metadata.rb')
end  

If Guard is running when you update the Guardfile, it will reload the configuration and you should see it attempt to validate against Foodcritic. If Guard is not running, you can restart it by running bundle exec guard start.

There are just two more manual changes needed to our Guardfile to customize it for single-cookbook development. By default, guard-foodcritic attempts to test against all cookbooks found in a cookbooks directory in your current path. Since we are only working with one cookbook for our example, we can modify the behavior by adding :cookbook_paths => "." to tell Foodcritic to test the current directory only and :all_on_start => false to instruct guard-foodcritic not to run against all cookbooks.

guard "foodcritic", :cookbook_paths => ".", :all_on_start => false do  
  watch(%r{attributes/.+\.rb$})
  watch(%r{providers/.+\.rb$})
  watch(%r{recipes/.+\.rb$})
  watch(%r{resources/.+\.rb$})
  watch(%r{^templates/(.+)})
  watch('metadata.rb')
end  

In my next post, we'll cover writing some tests with Chefspec and automating them with Guard.


This blog is the second post in a series covering automated cookbook development for Chef. You can find links to all current posts below.

  1. Automating Cookbook Testing with Test-Kitchen, Berkshelf, Vagrant and Guard
  2. Check Yo Self Before You Wreck Yo Self with Foodcritic & Guard
  3. Continuous ChefSpec Validation with Guard
  4. Serverspec, Guard and Test Kitchen: Testing Servers Like a Boss