The intent of this document is to collect the best practices and guidelines to assist Cookbook authors in creating code that is readable, maintainable, and easy to use.
The inspirations for this document, as well as a fallback sources of guidance, in order of precedence, are:
- Andrew Crump's foodcritic
- @bbatsov's Community Driven Ruby Style Guide
- Google's Python Style Guide
- Python's PEP-8
Alternative guidelines are available at:
- Opscode's Cookbook Style Guide Draft
- Opscode's Cookbook Style-Guide Outline
TODO: Integrate Opscode's guides into this one.
-
Layout a Recipe as you would a Ruby program
-
Ruby
require
statements should be placed at the top of a Recipe, just after comments. -
Ruby constant, global and local variable declarations should be placed immediately following
require
statements, separated by a empty line. -
Chef statements & Resources should be placed below all other Ruby statements.
# Recipe:: my_good_example # Cookbook Name:: my_cookbook # Ruby require statement. require 'right_aws' # Ruby variable declaration: apache_config = '/etc/apache.conf' # Chef Resource file apache_conf do action :create end
-
If a Recipe contains more than a few lines of pure Ruby, you might fare better with a Lightweight Resource Provider (LWRP).
-
Better yet, more than a few lines of Ruby in an LWRP might fare better as a Library.
-
Treat an LWRP's Resource definition as an analog of a Ruby method.
# Example of a Ruby method: def my_method(food='taco', price=2) puts "Here's a delicious #{food} for $#{price}!" end # Example of a Resource definition in Chef: attribute :food, :kind_of => String, :default => 'taco' attribute :price, :kind_of => [Integer, Float], :default => 1
-
Pass one-dimensional Ruby structures to Resources.
- Avoid passing in complex structures, such as Hashes or Arrays.
- It's easier to test for the existence of a key from the Recipe before instantiating a Resource Provider.
# Good, defensible example: fqdn_items = data_bag_item('servers', 'fqdn') web_fqdn = fqdn_items['web_fqdn'] apache2_site 'main website' do action :enable server_name web_fqdn end # Bad, ambiguous example: fqdn_items = data_bag_item('servers', 'fqdn') apache2_site 'main website' do action :enable server_name fqdn_items['web_fqdn'] end
-
Organize a Resources parameters for easy program flow interpretation.
- Readers can quickly determine whether the Resource will run, and on what conditions it will run.
# Good, easy to grok example: some_resource 'some_name' do action :some_action not_if{ some_condition } param1 some_paramater1 param2 some_paramater2 end # Bad, several eye scans required: some_resource 'some_name' do param1 some_paramater1 action :some_action param2 some_paramater2 not_if{ some_condition } end
-
Include conditionals within a Resource.
- See also: FC023: Prefer conditional attributes
- See also: Recipe Resources: Conditional Execution
- DRY approach.
# Good service 'apache2' do action [:enable, :start] only_if{ node['webserver'] } end # Bad if node['webserver'] service 'apache2' do action [:enable, :start] end end
-
Prefer
Chef::Log
overlog
.- Avoids ambiguity of
log
Resource getting collected and executed (e.g. two log lines). log
is not usable within LWRPs.log
writes to the log during convergence, not compilation; uselog
in a Recipe when you want to declare a log entry during compilation but trigger the log entry during convergence; useChef::Log
when you want to log normally.
# Good Chef::Log.info("Hey look, I'm a webserver!") # Bad log "Hey look, I'm a webserver!"
- Avoids ambiguity of
Log of 'Bad' usage:
```
INFO: *** Chef 10.12.0 ***
INFO: Processing log[Hey look, I'm a webserver!] action write (style-guide::default line 9)
INFO: Hey look, I'm a webserver!
INFO: Chef Run complete in 0.004116 seconds
```
-
Do not use
log
orChef::Log
within a Resource.- Chef will already log when it's collecting a processing a Resource.
# Good service 'apache2' do action [:enable, :start] end # Bad service 'apache2' do action [:enable, :start] Chef::Log.info('Enabling apache2 service.') end
-
Don't use static Unix-style paths.
- Where's
/etc/ssl
on NTFS? :)
# Good ETC_SSL = ::File.join(::File::SEPARATOR, 'etc', 'ssl') # Bad ETC_SSL = '/etc/ssl'
- Where's
-
You can use
$globals
to pass constants around Recipes.# Example, where configure_web will use the $web_url global we've defined in a prior Recipe. $web_url = 'http://example.com' include_recipe 'www::configure_web'
-
Why put on 5 lines what you can put on one, for under 80 characters!
- File this under personal preference.
# Good, one line concise Resource: gem_package('right_aws'){ action :nothing }.run_action(:install) # Bad, multiple lines when could easily condense to one: g = gem_package 'right_aws' do action :nothing end g.run_action(:install)
-
If logging a Hash, use it's built-in
inspect()
method. This works for Node Mashes also.# Good Chef::Log.debug("This Node's EC2 information is #{node['ec2'].inspect}") # Bad Chef::Log.debug("This Node's EC2 information is #{node['ec2']")
Copyright 2012 Greg Albrecht
GNU Free Documentation License version 1.3
1.1.0