About Foodcritic

Foodcritic is a helpful lint tool you can use to check your Chef cookbooks for common problems.

It comes with 58 built-in rules that identify problems ranging from simple style inconsistencies to difficult to diagnose issues that will hurt in production.

Various nice people in the Chef community have also written extra rules for foodcritic that you can install and run. Or write your own!

It's easy to get started:

$ gem install foodcritic
$ foodcritic my_cookbook_dir



style attributes

Use strings rather than symbols when referencing node attributes. This warning will be shown if you reference a node attribute using symbols.

Symbols in node attributes.

This example would match the FC001 rule because node[:cookbook][:package] accesses node attributes with symbols

Modified version

This modified example would not match the FC001 rule:

# Don't do this
package node[:cookbook][:package] do
  action :install
end
package node['cookbook']['package'] do
  action :install
end

style strings

When you declare a resource in your recipes you frequently want to reference dynamic values such as node attributes. This warning will be shown if you are unnecessarily wrapping an attribute reference in a string.

Unnecessary string interpolation

This example would match the FC002 rule because the version attribute has been unnecessarily quoted.

Modified version

This modified example would not match the FC002 rule:

# Don't do this
package 'mysql-server' do
  version "#{node['mysql']['version']}"
  action :install
end
package 'mysql-server' do
  version node['mysql']['version']
  action :install
end

portability solo

Chef Server extends the feature-set of a Chef deployment and is probably the most popular configuration. It is also possible to run the Chef client in a standalone mode with Chef Solo. Where your cookbooks make use of features that only exist in a Chef Server based setup you should check whether you are running in solo mode.

Does not check for Chef Solo

This example would match the FC003 rule because it does not check if it is running in Chef Solo before using search which is a server-specific feature.

Modified version

This modified example would not match the FC003 rule:

nodes = search(:node, "hostname:[* TO *] AND chef_environment:#{node.chef_environment}")
if Chef::Config[:solo]
  Chef::Log.warn('This recipe uses search. Chef Solo does not support search.')
else
  nodes = search(:node, "hostname:[* TO *] AND chef_environment:#{node.chef_environment}")
end

style services

This warning is shown if you are starting or stopping a service using the Chef execute resource rather than the more idiomatic service resource. You can read more about the service resource here:

Uses execute to control a service

This example would match the FC004 rule because it uses execute for service control. There is no reason to use execute because the service resource exposes the start_command attribute to give you full control over the command issued.

Modified version

This modified example would not match the FC004 rule:

# Don't do this
execute 'start-tomcat' do
  command '/etc/init.d/tomcat6 start'
  action :run
end
service 'tomcat' do
  action :start
end

style

When writing Chef recipes you have the full power of Ruby at your disposal. One of the cases where this is helpful is where you need to declare a large number of resources that only differ in a single attribute - the canonical example is installing a long list of packages.

Unnecessarily repetitive

This example matches the FC005 rule because all the resources of type package differ only in a single attribute - the name of the package to be upgraded. This rule is very simple and looks only for resources that all differ in only a single attribute. For example - if only one of the packages specified the version then this rule would not match.

Modified version

This modified example would not match the FC005 rule. It takes advantage of the fact that Chef processes recipes in two distinct phases. In the first ‘compile’ phase it builds the resource collection. In the second phase it configures the node against the resource collection.

Don’t worry about changing your recipe if it already does what you want - the amount of Ruby syntactic sugar to apply is very much a matter of personal taste. Note that this rule also isn’t clever enough yet to detect if your resources are wrapped in a control structure and not suitable for ‘rolling up’ into a loop.

# You could do this
package 'erlang-base' do
  action :upgrade
end
package 'erlang-corba' do
  action :upgrade
end
package 'erlang-crypto' do
  action :upgrade
end
package 'rabbitmq-server' do
  action :upgrade
end
# It's shorter to do this
%w{erlang-base erlang-corba erlang-crypto rabbitmq-server}.each do |pkg|
  package pkg do
    action :upgrade
  end
end

correctness files

When setting file or directory permissions via the mode attribute you should either quote the octal number or ensure it is specified to five digits. Otherwise the permissions that are set after Ruby coerces the number may not match what you expect.

File mode won't be interpreted correctly

Modified versions

These modified examples would not match the FC006 rule:

# Don't do this
directory '/var/lib/foo' do
  owner 'root'
  group 'root'
  mode 644
  action :create
end
# This is ok
directory '/var/lib/foo' do
  owner 'root'
  group 'root'
  mode '644'
  action :create
end

# And so is this
directory '/var/lib/foo' do
  owner 'root'
  group 'root'
  mode 00644
  action :create
end

correctness metadata

This warning is shown when you include a recipe that is not in the current cookbook and not defined as a dependency in your cookbook metadata. This is potentially a big problem because things will blow up if the necessary dependency cannot be found when Chef tries to converge your node. For more information refer to the Chef metadata page:

The fix is to declare the cookbook of the recipe you are including as a dependency in your metadata.rb file.

You may also see this warning if foodcritic has not been able to infer the name of your cookbook correctly when the cookbook directory does not match the name of the cookbook specified in the include.

Example depency on another cookbook

Assuming you have a recipe that had the following line:

Adding metadata dependency for Chef

Then to remove this warning you would add the apache2 cookbook as a dependency to your own cookbook metadata in the metadata.rb file at the root of your cookbook.

include_recipe 'apache2::default'
depends 'apache2'

style metadata

This warning is shown if you used knife cookbook create to create a new cookbook and didn’t override the maintainer and maintainer email. You need to set these to real values in metadata.rb or run knife again with the real values.

Maintainer metadata is boilerplate default

Modified version

This modified example would not match the FC008 rule:

# Don't do this
maintainer 'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license 'All rights reserved'
description 'Installs/Configures example'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
version '0.0.1'
maintainer 'Example Ltd'
maintainer_email 'postmaster@example.com'
license 'All rights reserved'
description 'Installs/Configures example'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
version '0.0.1'

correctness

This warning is likely to mean that your recipe will fail to run when you attempt to converge. Your recipe may be syntactically valid Ruby, but the attribute you have attempted to set on a built-in Chef resource is not recognised. This is commonly a typo or you need to check the documentation to see what the attribute you are trying to set is called:

Resource with an unrecognised attribute

This example matches the FC009 rule because punter is not a recognised attribute for the file resource.

Modified version

Checking the documentation we can see the correct attribute is owner.

# Don't do this
file '/tmp/something' do
  punter 'root'
  group 'root'
  mode '0755'
  action :create
end
file '/tmp/something' do
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end

correctness search

The search syntax used is not recognised as valid Lucene search criteria. This is commonly because you have made a typo or are not escaping special characters in the query syntax.

Note that this rule will not identify syntax errors in searches composed of subexpressions. It checks only for literal search strings.

Unescaped search syntax

This example matches the FC010 rule because search metacharacters - in this case the square brackets - have not been properly escaped.

Modified version

With the characters escaped this will no longer match the rule.

# Don't do this
search(:node, 'run_list:recipe[foo::bar]') do |matching_node|
  puts matching_node.to_s
end
search(:node, 'run_list:recipe\[foo\:\:bar\]') do |matching_node|
  puts matching_node.to_s
end

style readme

Supermarket will now render your cookbook README documentation inline - see this example for the mysql cookbook.

Your README needs to be in Markdown format for this to work. This rule will match any cookbook that does not have a README.md file in the root directory.

style readme

Writing cookbook documentation in RDoc has been deprecated in favour of Markdown format. This rule will match any cookbook that has a README.rdoc file in the root directory.

style files

This warning means that you have hard-coded a file download path in your cookbook to a temporary directory. This can be a problem on boxes built with a small /tmp mount point. Chef has its own configuration option file_cache_path you should use instead:

Downloading to a hard-coded temp directory

This example matches the FC013 rule because it hard-codes the download path to /tmp.

Modified version

To remove this warning use the configured file_cache_path:

# Don't do this
remote_file '/tmp/large-file.tar.gz' do
  source 'http://www.example.org/large-file.tar.gz'
end
remote_file "#{Chef::Config[:file_cache_path]}/large-file.tar.gz" do
  source 'http://www.example.org/large-file.tar.gz'
end

style libraries

Your cookbook has a fairly long ruby_block resource. Long ruby_block resources are often candidates for extraction to a separate module or class under the libraries directory.

style definitions lwrp

Chef definitions are an older approach to creating a higher-level abstraction for a group of resources. Unlike Custom Resources they are not first class resources, cannot receive notifications, and do not support why-run mode. You should prefer Custom Resources for new development.

correctness lwrp

This warning means that the LWRP does not declare a default action. You should normally define a default action on your resource to avoid confusing users. Most resources have an intuitive default action.

Resource without a default action

This example matches the FC016 rule because it does not declare a default action.

Modified version

With a default action specified this warning will no longer be displayed.

# Don't do this
actions :create
actions :create

# Chef 0.10.10 or greater
default_action :create

# In earlier versions of Chef the LWRP DSL doesn't support specifying
# a default action, so you need to drop into Ruby.
def initialize(*args)
  super
  @action = :create
end

correctness lwrp

This warning means that the LWRP will not currently trigger notifications to other resources. This can be a source of difficult to track down bugs.

There are several ways of marking that the resource state has changed:

  • Explicitly call new_resource.updated_by_last_action. This is the approach used historically with older versions of Chef.
  • Surround the action in a converge_by block. This is done when implementing Why-Run support and will also ensure notifications are sent. This approach is available from Chef 10.14.0.
  • Add use_inline_resources to the top of the provider file. This means that the resources you define in your action are run in their own self-contained Chef run and your provider will send notifications if any of the nested resources in your actions are updated. This approach is available from Chef 11 and may become the default behaviour in a future version.

Provider that does not send notifications

This example matches the FC017 rule because it does not mark that its state has changed and will therefore not send notifications.

Modified version

Any of the three approaches shown below will ensure that notifications are sent correctly.

# Don't do this
action :create do
  # create action implementation
end
# Approach 1: Using updated_by_last_action
action :create do
  # create action implementation

  # My state has changed so I'd better notify observers
  new_resource.updated_by_last_action(true)
end

# Approach 2: Using converge_by
action :create do
  converge_by("Creating my_resource #{new_resource.name}") do
    # create action implementation
  end
end

# Approach 3: Using use_inline_resources
use_inline_resources
action :create do
  # create action implementation
end

style lwrp deprecated

Provider uses deprecated syntax

This example matches the FC018 rule because it uses the old syntax for indicating it has been updated.

Modified version

This example uses the newer syntax and will not raise the warning.

# Don't do this
action :create do
  # create action implementation

  # My state has changed so I'd better notify observers, but I'm using
  # a deprecated syntax
  new_resource.updated = true
end

# Also don't do this
action :create do
  # create action implementation

  # My state has changed so I'd better notify observers, but I'm using
  # a deprecated syntax
  @updated = true
end
action :create do
  # create action implementation

  # My state has changed so I'd better notify observers
  new_resource.updated_by_last_action(true)
end

style attributes

Node attributes can be accessed in multiple ways in Chef. This warning is shown when a cookbook is not consistent in the approach it uses to access attributes. It is not displayed for variations between cookbooks.

Recipe mixes symbols and strings for accessing node attributes

Modified version

# Don't do this
node[:apache][:dir] = '/etc/apache2'

directory node['apache']['dir'] do
  owner 'apache'
  group 'apache'
  action :create
end
node['apache']['dir'] = '/etc/apache2'

directory node['apache']['dir'] do
  owner 'apache'
  group 'apache'
  action :create
end

correctness

This rule has been deprecated due to the frequency of false positives. See the discussion against issue #30 for more detail.

correctness lwrp

A change introduced in Chef 0.10.6 means that conditions may not work as expected for resources redeclared with the same name. If your LWRP defines a resource and that resource:

  • Has an associated guard which references a resource attribute. AND
  • The resource has a fixed name.

Then you will likely find that only the first resource will be applied. See this ticket for more background:

Resource condition will be evaluated only once

Because the feed_pet resource will have the same name across all instances of your LWRP, the condition will only be checked for the first resource.

Modified version

By making the resource name change for each unique instance of our LWRP instance we avoid this behaviour.

# Don't do this
action :feed do
  execute 'feed_pet' do
    command "echo 'Feeding: #{new_resource.name}'; touch '/tmp/#{new_resource.name}'"
    not_if { ::File.exists?("/tmp/#{new_resource.name}")}
  end
end
action :feed do
  execute "feed_pet_#{new_resource.name}" do
    command "echo 'Feeding: #{new_resource.name}'; touch '/tmp/#{new_resource.name}'"
    not_if { ::File.exists?("/tmp/#{new_resource.name}")}
  end
end

correctness

A change introduced in Chef 0.10.6 means that conditions may not work as expected for resources declared within a loop. If your recipe defines a resource and that resource:

Then you will likely find that only the first resource will be applied. See this ticket for more background:

Resource condition will be evaluated only once

Because the feed_pet resource will have the same name for every iteration of the loop, the condition will only be checked for the first resource.

Modified version

By making the resource name change for each iteration of the loop we avoid this behaviour.

# Don't do this
%w{rover fido}.each do |pet_name|
  execute 'feed_pet' do
    command "echo 'Feeding: #{pet_name}'; touch '/tmp/#{pet_name}'"
    not_if { ::File.exists?("/tmp/#{pet_name}")}
  end
end
%w{rover fido}.each do |pet_name|
  execute "feed_pet_#{pet_name}" do
    command "echo 'Feeding: #{pet_name}'; touch '/tmp/#{pet_name}'"
    not_if { ::File.exists?("/tmp/#{pet_name}")}
  end
end

style

This warning means you have surrounded a resource with an if or unless rather than defining the condition directly on the resource itself. Note that this warning is only raised for single resources as you could reasonably enclose multiple resources in a condition like this for brevity.

Jay Feldblum has expressed criticism of this rule because the effect is that resources are defined unnecessarily and ignored only at run-time. His view is that it is cleaner to use standard Ruby conditionals to avoid defining them in the first place.

Resource enclosed in a condition

This example matches the FC023 rule because it encloses a rule within a condition, rather than using the built-in Chef not_if or only_if conditional execution attributes.

Modified version

You can avoid the warning above with more idiomatic Chef that specifies the condition above as an attribute on the resource:

# Don't do this
if node['foo'] == 'bar'
  service 'apache' do
    action :enable
  end
end
service 'apache' do
  action :enable
  only_if { node['foo'] == 'bar' }
end

portability

This warning is shown when:

  • you have a conditional statement in your cookbook based on the platform of the node
  • and at least two platforms are included as equivalent in your conditional
  • and the conditional does not include a platform known to belong to the same family

If you are using Ohai 0.6.12 or greater you should probably use platform_family instead. Otherwise for the greatest portability consider adding the missing platforms to your conditional.

Case statement has a subset of platform flavours

This example matches the FC024 rule because it includes a case statement that matches more than one flavour of a platform family but omits other popular flavours from the same family.

Modified version

This warning is no longer raised when the other common equivalent RHEL-based distributions have been added to the when.

# The RHEL platforms branch below omits popular distributions
# including Amazon Linux.
case node[:platform]
  when 'debian', 'ubuntu'
    package 'foo' do
      action :install
    end
  when 'centos', 'redhat'
    package 'bar' do
      action :install
    end
end
case node[:platform]
  when 'debian', 'ubuntu'
    package 'foo' do
      action :install
    end
  when 'centos', 'redhat', 'amazon', 'scientific', 'oracle'
    package 'bar' do
      action :install
    end
  end

style deprecated

This warning is shown if: * you have a cookbook that installs a Rubygem for use from Chef * the cookbook uses the compile-time gem install trick which is deprecated from Chef 0.10.10 and is replaced by the first class chef_gem resource.

Manual compile-time installation

This example matches the FC025 rule because it uses the older approach for installing a gem so that it is available in the current run.

Modified version

Use chef_gem to install the gem to avoid this warning.

r = gem_package 'mysql' do
  action :nothing
end

r.run_action(:install)
Gem.clear_paths
chef_gem 'mysql'

correctness

This warning is shown if you have a conditional attribute declared on a resource as a block that contains only a single string.

Conditional attribute returns a string

This example matches the FC026 rule because it returns a string from the block. This will always evalute to true, and often indicates that you are trying to run a command rather than execute a Ruby block as your condition.

Modified version

If the intention is to run the string as an operating system command then remove the block surrounding the command.

# Don't do this
template '/etc/foo' do
  mode '0644'
  source 'foo.erb'
  not_if { 'test -f /etc/foo' }
end
template '/etc/foo' do
  mode '0644'
  source 'foo.erb'
  not_if 'test -f /etc/foo'
end

correctness

This warning is shown if you set an attribute on a Chef resource that is technically accessible but should not be set in normal usage. To avoid this warning allow Chef to set the value of the internal attribute rather than setting it yourself.

Service resource sets internal attribute

This example matches the FC027 rule because it sets the running attribute on a service resource. This attribute should normally be set by the provider itself and not in normal recipe usage.

Modified version

In this particular example you can achieve the same effect by using the service :start action.

# Don't do this
service 'foo' do
  running true
end
service 'foo' do
  action :start
end

correctness

This warning is shown if you attempt to use the platform? Chef built-in method as node.platform?. Because of the way Chef attributes work the later approach will not error but will do the wrong thing which may result in resources you had intended to apply only to a single platform instead being applied to all platforms.

Incorrect attempt to use platform? method

This example matches the FC028 rule because the platform? method is incorrectly prefixed with node.

Modified version

Remove the leading node. from the use of platform? to resolve this warning.

# Don't do this
file '/etc/foo' do
  only_if { node.platform?('commodore64') }
end
file '/etc/foo' do
  only_if { platform?('commodore64') }
end

correctness metadata

This warning is shown if you declare a recipe in your cookbook metadata without including the cookbook name as a prefix.

Recipe declared without cookbook name prefix

This example matches the FC029 rule because the metadata declares a recipe without prefixing it with the name of the current cookbook.

Modified version

This modified example would not match the FC029 rule:

# Don't do this
name 'example'
version '1.2.3'
recipe 'default', 'Installs Example'
name 'example'
version '1.2.3'
recipe 'example::default', 'Installs Example'

annoyances

Pry is a fantastic tool for interactive exploration of a running Ruby program. You can place breakpoints in your cookbook code that will launch a Pry console. This warning is shown when your cookbook code contains these breakpoints, as failing to remove these will cause your Chef run to halt.

This rule currently only checks for use of binding.pry and not the Chef built-in breakpoint resource which is never used outside of chef-shell.

Recipe includes breakpoint

This example matches the FC030 rule because it includes a Pry breakpoint declared with binding.pry.

Modified version

This modified example would not match the FC030 rule:

# Don't do this
template '/etc/foo' do
  source 'foo.erb'
end
binding.pry
template '/etc/foo' do
  source 'foo.erb'
end

correctness metadata

Chef cookbooks normally include a metadata.rb file which can be used to express a wide range of metadata about a cookbook. This warning is shown when a directory appears to contain a cookbook, but does not include the expected metadata.rb file at the top-level.

correctness notifications

Chef notifications allow a resource to define that it should be actioned when another resource changes state.

Notification timing can be controlled and set to immediate, or delayed until the end of the Chef run. This warning is shown when the timing specified is not recognised.

Notification timing is invalid

This example matches the FC032 rule because it specifies an invalid notification timing.

Modified version

This modified example would not match the FC032 rule because the mispelt timing has been corrected.

# Don't do this
template '/etc/foo' do
  notifies :restart, 'service[foo]', :imediately
end
template '/etc/foo' do
  notifies :restart, 'service[foo]', :immediately
end

correctness

This warning is shown when the erb template associated with a template resource cannot be found.

correctness

This warning is shown when one or more variables passed into a template by a template resource are not then used within the template.

This is often a sign that a template still contains hard-coded values that you intended to parameterise.

Unused template variables

This example matches the FC034 rule because it passes two variables to the template, of which only the first is used.

Modified version

This modified example would not match the FC034 rule becuse the template has been updated to include both variables passed through.

template '/etc/foo/config.conf' do
  source 'config.conf.erb'
  variables(
    'config_var_a' => node['config']['config_var_a'],
    'config_var_b' => node['config']['config_var_b']
  )
end


# config.conf.erb
# var_a=<%= @config_var_a %>
template '/etc/foo/config.conf' do
  source 'config.conf.erb'
  variables(
    'config_var_a' => node['config']['config_var_a'],
    'config_var_b' => node['config']['config_var_b']
  )
end

# config.conf.erb
# var_a=<%= @config_var_a %>
# var_b=<%= @config_var_b %>

style

This rule has been deprecated. See the discussion against issue #60 for more detail.

correctness

This warning is shown when a resource notifies another resource to take an action, but the action is invalid for the target resource type.

Invalid notification action

This example matches the FC037 rule because :activate_turbo_boost is not a valid action for services.

Modified version

This modified example would not match the FC037 rule because the action has been corrected.

# Don't do this
template '/etc/foo.conf' do
  notifies :activate_turbo_boost, 'service[foo]'
end
template '/etc/foo.conf' do
  notifies :restart, 'service[foo]'
end

correctness

This warning is shown when a resource action is not valid for the type of resource.

Invalid resource action

This example matches the FC038 rule because :none is not a valid action.

Modified version

This modified example would not match the FC038 rule because the action has been corrected.

# Don't do this
service 'foo' do
  action :none
end
service 'foo' do
  action :nothing
end

correctness

Chef allows you to use varying syntax to refer to node attributes. This warning is shown when you attempt to reference a method on Chef::Node using the same string or symbol syntax reserved for node attributes.

Attempt to access node method as a key

This example matches the FC039 rule because run_state is only accessible as a method and cannot be referenced as an attribute.

Modified version

This modified example would not match the FC039 rule because the run_state is referenced as a method.

# Don't do this
node['run_state']['nginx_force_recompile'] = false
node.run_state['nginx_force_recompile'] = false

style recipe etsy

This warning is shown if you declare an execute resource that uses git. If the command you are attempting to execute is supported by the git resource you should probably use that instead.

Execute resource used to run git command

This example matches the FC040 rule because an execute resource is used where you could instead use a git resource.

Modified version

This modified example would not match the FC040 rule because the execute resource has been replaced by a git resource.

execute 'git clone https://github.com/git/git.git' do
  action :run
end
git '/foo/bar' do
  repository 'git://github.com/git/git.git'
  reference 'master'
  action :sync
end

style recipe etsy

This warning is shown if you use an execute resource to run the curl or wget commands. If you are downloading a file consider using the remote_file resource instead.

Execute resource used to run wget command

This example matches the FC041 rule because an execute resource is used where you could instead use a remote_file resource.

Modified version

This modified example would not match the FC041 rule because the execute resource has been replaced by a remote_file resource.

execute "cd /tmp && wget 'http://example.org/'" do
  action :run
end
remote_file '/tmp/testfile' do
  source 'http://www.example.org/'
end

deprecated

This warning is shown when require_recipe is used. Because require_recipe has been deprecated you should replace any references to to require_recipe with include_recipe.

Use of deprecated require_recipe statement

This example matches the FC042 rule because the deprecated require_recipe statement is used.

Modified version

This modified example would not match the FC042 rule because the require_recipe statement has been replaced with include_recipe.

# Don't do this
require_recipe 'apache2::default'
include_recipe 'apache2::default'

style notifications deprecated

This warning is shown when you use the old-style notification syntax. You should prefer the new-style notification syntax which has the advantage that you can notify resources that are defined later.

Old notification syntax

This example matches the FC043 rule because it uses the older notification syntax.

Modified version

This modified example would not match the FC043 rule because the syntax of the notification has been updated to use the new format.

template '/etc/www/configures-apache.conf' do
  notifies :restart, resources(:service => 'apache')
end
template '/etc/www/configures-apache.conf' do
  notifies :restart, 'service[apache]'
end

style

This warning is shown when, within a cookbook attributes file, you refer to an attribute as you would a local variable rather than as an attribute of the node object. It is valid to do the former, but you should prefer the later more explicit approach to accessing attributes because it is easier for users of your cookbooks to understand.

Referring to an attribute within an attributes file

This example matches the FC044 rule because it refers to the hostname attribute as a bare attribute.

Modified version

This modified example would not match the FC044 rule because the reference to the hostname attribute has been qualified so that the meaning is more apparent.

default['myhostname'] = hostname
default['myhostname'] = node['hostname']

correctness metadata chef12

This warning is shown when your cookbook does not define a name within the cookbook metadata.rb file. It’s a good idea to specify a name in your cookbook metadata to avoid breakage if the name of the containing directory changes. Additionally, Chef 12 requires the name to be included in the metadata.rb file

attributes correctness

It is a common convention in Ruby development to use ||= to assign a value to variable if it is false or nil. Frequently developers with earlier exposure to Ruby attempt to use the same approach to assign a default value to node attributes within Chef.

This doesn’t work correctly because Chef auto-vivifies attributes so a missing attribute is never falsey.

Using assign unless nil with node attributes

This example matches the FC046 rule because it uses assign unless nil (||=) with node attributes.

Modified version

This modified example would not match the FC046 rule because the assign unless nil expression has been replaced with default_unless.

# Don't do this
default['somevalue'] ||= []
default_unless['somevalue'] = []

attributes correctness chef11

From Chef 11 it is no longer possible to set attributes without specifying their precedence level. For more information refer to the list of Breaking Changes in Chef 11.

Assign an attribute value without specifying precedence

This example matches the FC047 rule because it writes to the attribute without specifying the precedence level to set.

This will work in Chef versions < 11 but you should prefer the new syntax.

Modified version

This modified example would not match the FC047 rule because the attribute assignment has been updated to specify a precedence level of normal.

# Don't do this
node['foo'] = 'bar'
node.normal['foo'] = 'bar'

style processes

Normally to execute an operating system command with Chef you would use a built-in resource such as the execute resource.

You might also have a need to spawn processes from Ruby, either inline or within a ruby_block. There are many different ways to do this in Ruby - my favourite reference is Jesse Storimer’s Working with Unix Processes.

Chef comes with a library called Mixlib::ShellOut that provides a more convenient interface and it is idiomatic to use it rather than the backtick ` or %x{} syntaxes.

Uses %x{} to shellout

This example matches the FC048 rule because it uses the %x{} sigil.

Modified version

This modified example would not match the FC048 rule because it is using the Mixlib::ShellOut library.

# Don't do this
result = %x{some_command}
raise 'Problem executing some_command' unless $?.success?
cmd = Mixlib::ShellOut.new('some_command')
cmd.run_command
cmd.error!

style roles

This warning is shown if you declare a name in a role file that does not match the containing file name. Using the same name for both is more consistent.

correctness environments roles

This warning is shown if the name declared in a role or environment file contains characters that are not allowed.

“[Names should be] made up of letters (upper-and lower-case), numbers, underscores, and hyphens: [A-Z][a-z][0-9] and [_-]. Spaces are not allowed.”

Name includes invalid characters

This example matches the FC050 rule because the name includes a space character.

Modified version

This modified example would not match the FC050 rule because the space has been removed from the role name.

# Don't do this
name 'web server'
run_list 'recipe[apache2]'
name 'webserver'
run_list 'recipe[apache2]'

correctness

This warning is shown if a template uses template partials in a way that would cause an infinite loop. For example if two template partials both include each other.

style metadata

This warning is shown if metadata.rb includes the suggests metadata. Suggests metadata was often used to inform users that a cookbook was required for a particular use case, but suggests itself was never implemented in chef-client. Adding suggests has no impact on the chef-client run and should be avoided.

style metadata

This warning is shown if metadata.rb includes the recommends metadata. Recommends metadata was often used to inform users that a cookbook was recommended for a particular use case, but recommends itself was never implemented in chef-client. Adding recommends has no impact on the chef-client run and should be avoided.

correctness metadata

This warning is shown if the metadata does not include the maintainer keyword. Cookbooks should always contain maintainer information so users can determine the maintainer of the cookbook.

correctness metadata

This warning is shown if the metadata does not include the maintainer_email keyword. Cookbooks should always contain maintainer_email so users can contact the maintainer.

correctness

This warning is shown if a library provider does not specify use_inline_resources. use_inline_resources executes provider resources in their own run context, outside the normal recipe run context. Doing so is considered best practice as it avoids issues with notifying when the provider resource is updated, and also avoids the need to uniquely name resources in the provider.

correctness

This warning is shown if a library provider includes use_inline_resources, but declares it’s actions using action_ methods. The implemention of use_inline_resources requires that actions be declared using the action DSL method and not declared as normal ruby methods. Declaring actions as normal methods will break use_inline_resources. It also may produce unexpected behavior in some versions of Chef.

correctness

This warning is shown if a LWRP provider does not specify use_inline_resources. use_inline_resources executes provider resources in their own run context, outside the normal recipe run context. Doing so is considered best practice as it avoids issues with notifying when the provider resource is updated, and also avoids the need to uniquely name resources in the provider.

correctness

This warning is shown if a LWRP provider includes use_inline_resources, but declares it’s actions using action_ methods. The implemention of use_inline_resources requires that actions be declared using the action DSL method and not declared as normal ruby methods. Declaring actions as normal methods will break use_inline_resources. It also may produce unexpected behavior in some versions of Chef.

correctness metadata

This warning is shown if a cookbook includes an invalid version string in the metadata file. Cookbooks that do not follow this format cannot be uploaded to the chef server.

metadata

This warning is shown if a cookbook does not contain a version string in the metadata file. Without a version string cookbooks will be uploaded as version 0.0.0 each time. It is best practice to provide accurate versions that are incremented on each release, which requires specifying the string.


Choosing which rules to run

Foodcritic comes with a bunch of rules built-in. You will probably find some of them useful and others annoying. The trick to only running the rules you actually care about are what foodcritic calls tags.

Tags

Each rule has a number of associated tags. You can filter which rules are actually checked by specifying the tags that you want to apply at the command line.

Only check against correctness rules

As an example, the following arguments will run foodcritic but only showing warnings for rules tagged with the correctness tag.

$ foodcritic -t correctness

Excluding a particular rule

Each rule is tagged with its own code, normally something like FC123. To avoid checking a particular rule use the ~ (tilde) modifier at the command line.

$ foodcritic -t ~FC002

Running only rules tagged with both tags

Let's say we want to only check against rules that are tagged with both style and services. We don't want to check against rules that have only one or the other. Our command line to do this would look like this:

$ foodcritic -t style -t services

Running rules tagged with either tag

Alternatively we might want to run rules that are tagged with either style or services. To do this we separate the rule codes with a comma:

$ foodcritic -t style,services

Saving your tag options

You can create a .foodcritic file within each cookbook to define the rules that you want to have checked when foodcritic runs. $ echo "style,services" > my_cookbook/.foodcritic

When you run foodcritic with no arguments and a .foodcritic file is present in the cookbook directory it will check only the tags you have specified in that file.

Tag Reference

Below is the list of built-in foodcritic rules shown by tag:


Extra Rules

Awesome people have written additional rules that you can use with foodcritic.

CustomInk Foodcritic Rules

https://github.com/customink-webops/foodcritic-rules

  • CINK001 Missing CHANGELOG in markdown format
  • CINK002 Prefer single-quoted strings
  • CINK003 Don't hardcode apache user or group

Etsy Foodcritic Rules

https://github.com/etsy/foodcritic-rules

  • ETSY001 Package or yum_package resource used with :upgrade action
  • ETSY004 Execute resource defined without conditional or action :nothing
  • ETSY005 Action :restart sent to a core service
  • ETSY006 Execute resource used to run chef-provided command
  • ETSY007 Package or yum_package resource used to install core package without specific version number

Lookout Foodcritic Rules

https://github.com/lookout/lookout-foodcritic-rules

  • LKOUT001 Include a chefspec test for every recipe
  • LKOUT002 apt_repository should not download a key over plain http
  • LKOUT003 specify a uid and gid when creating a user
  • LKOUT004 specify a gid when creating a group

Rule API Reference

attribute_access

Find attributes accesses by type.

You might use this method to enforce local style rules on how attributes are accessed.

# All references to attributes using strings
# For example: node['foo']
attribute_access(ast, :type => :string)

# All references to attributes using symbols
# For example: node[:foo]
attribute_access(ast, :type => :symbol)

# All references to attributes using dots (vivified methods)
# For example: node.foo
attribute_access(ast, :type => :symbol)
categorytypenamedescription
param Nokogiri::XML::Node ast

The AST of the cookbook recipe to check

param Hash options

The options hash (see allowed values below)

option Symbol :type

The approach used to access the attributes. One of :string, :symbol, :vivified or :any.

option Boolean :ignore_calls

Exclude attribute accesses that mix strings/symbols with dot notation. Defaults to false.

return Array

The matching nodes if any

checks_for_chef_solo?

Does the specified recipe check for Chef Solo?

You can use this to check for the portability of the recipe between Chef Server and Chef Solo.

# Returns true if the recipe checks for Chef Solo before using
# server-specific functionality.
checks_for_chef_solo?(ast)
categorytypenamedescription
param Nokogiri::XML::Node ast

The AST of the cookbook recipe to check

return Boolean

True if there is a test for Chef::Config[:solo] in the recipe

chef_dsl_methods

The set of methods in the Chef DSL.

You can use this to see if a method reference is part of the Chef DSL or defined in a cookbook.

# Is search a method in the Chef DSL?
chef_dsl_methods.include?(:search)
=> true
categorytypenamedescription
return Array

Array of method symbols

chef_solo_search_supported?

Is the chef-solo-search library available?

Will return true if any cookbook in the cookbook tree relative to the specified recipe includes the chef-solo-search library. You can use this to see if search is available in Chef Solo mode.

# True if chef_solo_search is supported
chef_solo_search_supported?('foo/recipes/default.rb')
categorytypenamedescription
param String recipe_path

The path to the current recipe

return Boolean

True if the chef-solo-search library is available.

cookbook_name

The name of the cookbook containing the specified file.

You can use this method in rules that need to work out if recipe code refers to the current cookbook: for example when looking at included_recipe statements or LWRP usage.

cookbook_name('foo/recipes/default.rb')
=> "foo"
categorytypenamedescription
param String file

The file in the cookbook

return String

The name of the containing cookbook

declared_dependencies

The dependencies declared in cookbook metadata.

You can use this to check if all dependencies have been declared correctly or to find all cookbooks that share a common dependency.

ast = read_ast('postgresql/metadata.rb')
declared_dependencies(ast)
=> ["openssl"]
categorytypenamedescription
param Nokogiri::XML::Node ast

The metadata rb AST

return Array

List of cookbooks depended on

field

The key / value pair in a ruby environment or role file.

# Retrieve the key and value of the 'name' field
field(ast, :name)
categorytypenamedescription
param Nokogiri::XML::Node ast

The environment or role AST

param String field_name

The name of the field to retrieve

return Nokogiri::XML::Node

The matched key / value pair

field_value

Retrieve the value from a ruby environment or role file.

# Retrieve the value of the 'name' field
field_value(ast, :name)
categorytypenamedescription
param Nokogiri::XML::Node ast

The environment or role AST

param String field_name

The name of the field to retrieve

return String

The string value if specified as a literal.

file_match

Create a match for a specified file. Use this if the presence of the file triggers the warning rather than content.

This is an alternative to match where you don’t have a specific AST element to associate the warning with. The call to file_match will typically be the last line in your rule.

file_match('foo/recipes/default.rb')
=> {:filename=>"foo/recipes/default.rb",
 :matched=>"foo/recipes/default.rb",
 :line=>1,
 :column=>1}
categorytypenamedescription
param String file

The filename to create a match for

return Hash

Hash with the match details

find_resources

Find Chef resources of the specified type.

Note that currently this method does not return blockless resources.

# Returns all resources in the AST
find_resources(ast)

# Returns just the packages
find_resources(ast, :type => :package)
categorytypenamedescription
param Nokogiri::XML::Node ast

The AST of the cookbook recipe to check

param Hash options

The options hash (see allowed values below)

option Symbol :type

The type of resource to look for (or :any for all resources)

return Array

AST nodes of Chef resources.

included_recipes

Retrieve the recipes that are included within the given recipe AST.

You can use this method to determine (and validate) recipe runtime dependencies.

# Find all include_recipe statements, discarding the AST nodes to just
# show the recipe names.
included_recipes(ast).keys
=> ["postgresql::client"]

included_recipes(ast, :with_partial_names => false).keys
=> ["postgresql::client"]
categorytypenamedescription
param Nokogiri::XML::Node ast

The recipe AST

param Hash options

:with_partial_names - Include string literals for recipes that have embedded sub-expressions. This defaults to true for backward compatibility.

return Hash

Hash keyed by included recipe name where the value is the AST node of the include_recipe statement.

literal_searches

Searches performed by the specified recipe that are literal strings. Searches with a query formed from a subexpression will be ignored.

ast = read_ast('zenoss/recipes/server.rb')
literal_searches(ast).size
=> 3
categorytypenamedescription
param Nokogiri::XML::Node ast

The AST of the cookbook recipe to check

return Array

The matching nodes

match

Create a match from the specified node.

Return matches when a rule has matched against a recipe. A call to match is typically the last line of your rule.

Ensure that the AST node you are passing to this method has a descendant pos node so that the match can be associated with a line in the file.

# You will frequently use map to apply the match function to an array of
# nodes that you consider matches for your rule.
attribute_access(ast, :type => :string).map{|s| match(s)}
categorytypenamedescription
param Nokogiri::XML::Node node

The node to create a match for

return Hash

Hash with the matched node name and position with the recipe

notifications

Provides convenient access to resource notification details. You can pass either the AST for an individual resource or the entire recipe to this method. Note that a resource may be registered for multiple notifications / subscriptions.

While Chef permits either :immediate or :immediately to be specified in cookbooks, the timing for both will be returned as :immediate to remove the need for callers to switch on both values.

find_resources(ast).select do |resource|
  notifications(resource).any? do |notification|
    ! [:delayed, :immediate].include? notification[:timing]
  end
end
categorytypenamedescription
param Nokogiri::XML::Node ast

The AST to check for notifications.

return Array

A flat array of notification hashes.

  • :type - Either :notifies or :subscribes
  • :resource_type - The type of resource to be notified
  • :resource_name - The name of the resource to be notified. This can be an AST if the resource name is not a string literal.
  • :action - The notification action
  • :timing - Either :delayed or :immediate
  • :style - The syntax used in the cookbook to define the notification, either :old or :new

os_command?

Does the provided string look like an Operating System command? This is a rough heuristic to be taken with a pinch of salt.

categorytypenamedescription
param String str

The string to check

return Boolean

True if this string might be an OS command

read_ast

Read the AST for the given Ruby or erb source file.

Many of the other functions documented here take an ast as an argument. You can also use Nokogiri’s support querying the AST with XPath or CSS to implement your own rules.

# Normally the recipe AST will be passed in to your rule without you
# needing to use read_ast.

# However if you need to you can read in the AST for a cookbook recipe
# directly.
ast = read_ast('apache2/recipes/default.rb')
categorytypenamedescription
param String file

The file to read

return Nokogiri::XML::Node

The recipe AST

resource_action?

Determine if an action is valid for the given resource type.

resource_action?(:service, :restart)
=> true
categorytypenamedescription
param Symbol resource_type

The type of resource

param Symbol action

The name of the action

return Boolean

True if the action is valid for this type of resource.

resource_attribute

Retrieve a single-valued attribute from the specified resource.

# Given resource is a package
resource_attribute(resource, 'action')
=> :install
categorytypenamedescription
param Nokogiri::XML::Node resource

The resource AST to lookup the attribute under

param String name

The attribute name

return String

The attribute value for the specified attribute

resource_attribute?

Is the specified attribute valid for the type of resource? Note that this method will return true if the resource_type is not recognised.

resource_attribute?(:file, :mode)
=> true

resource_attribute?(:file, :size)
=> false

# If the resource is not a Chef built-in then the attribute is always
# valid
resource_attribute?(:my_custom_resource, :whatever)
=> true
categorytypenamedescription
param Symbol resource_type

The type of Chef resource

param Symbol attribute_name

The attribute name

return Boolean

True if the attribute is valid for this type of resource

resource_attributes

Retrieve all attributes from the specified resource.

Use this method for convenient access to the resource attributes without needing to query the AST.

resource_attributes(resource)
=> {:name=>"zenoss", "arch"=>"kernel", "action"=>:install}
categorytypenamedescription
param Nokogiri::XML::Node resource

The resource AST

return Hash

The resource attributes

resource_attributes_by_type

Retrieve the attributes as a hash for all resources of a given type.

Use this if you want to compare the attributes and values used by resources of the same type.

# The values of the Hash (ignored here) are arrays of resource ASTs.
resource_attributes_by_type(ast).keys.sort
=> ["apt_package",
 "apt_repository",
 "execute",
 "package",
 "ruby_block"]
categorytypenamedescription
param Nokogiri::XML::Node ast

The recipe AST

return Hash

Resources keyed by type, with an array for each

resource_name

Retrieve the name attribute associated with the specified resource.

resource_name(resource)
=> "zenoss"
categorytypenamedescription
param Nokogiri::XML::Node resource

The resource AST to lookup the name attribute under

return String

The name attribute value

resource_type

Return the type, e.g. ‘package’ of a given resource.

You could use this if you wanted to take different action in your rule based on the resource type.

resource_type(resource)
=> "yum_package"
categorytypenamedescription
param Nokogiri::XML::Node resource

The resource AST

return String

The type of resource

resources_by_type

Retrieve all resources of a given type.

The key of the hash is the type of resource (as a string). The value is an array of Hashes.

resources_by_type(ast).keys
=> ["yum_key",
 "yum_repository",
 "package",
 "service",
 "yum_package",
 "apt_repository",
 "apt_package"]
categorytypenamedescription
param Nokogiri::XML::Node ast

The recipe AST

return Hash

The matching resources

ruby_code?

Does the provided string look like ruby code? This does not evaluate the expression, instead only checking that it appears syntactically valid.

You can use this method to check that ruby_block resources and recipes themselves look like Ruby code.

# Lots of strings are well-formed Ruby statements, including some strings
# you might not expect:
ruby_code?('System.out.println("hello world");')
=> true

# This operating system command doesn't look like valid Ruby but others
# might.
ruby_code?('find -type f -print')
=> false
categorytypenamedescription
param String str

The string to check for rubiness

return Boolean

True if this string could be syntactically valid Ruby

searches

Searches performed by the specified recipe. In contrast to literal_searches this method returns all searches.

You could use this method to identify all searches that search for a particular type of object, or to identify searches that are valid for a particular Chef version.

categorytypenamedescription
param Nokogiri::XML::Node ast

The AST of the cookbook recipe to check.

return Array

The AST nodes in the recipe where searches are performed

standard_cookbook_subdirs

The list of standard cookbook sub-directories.

You can use this method when you need to traverse cookbooks manually yourself in order to determine directories to descend into.

standard_cookbook_subdirs
=> ["attributes",
 "definitions",
 "files",
 "libraries",
 "providers",
 "recipes",
 "resources",
 "templates"]
categorytypenamedescription
return Array

The standard list of directories.

templates_included

List of templates that are included, including transitive includes.

templates_included(template_paths(filename), template_path)
categorytypenamedescription
param Array all_templates

The set of all templates

param String template_path

The path to look under

return Array

The set of included templates

valid_query?

Is this a valid Lucene query?

Use this method when acting on searches in a recipe in order to check that they are valid before continuing with the rest of your rule.

valid_query?('run_list:recipe[foo::bar]')
=> false

valid_query?('run_list:recipe\[foo\:\:bar\]')
=> true
categorytypenamedescription
param String query

The query to check for syntax errors

return Boolean

True if the query is well-formed