Designing IA for AI - Information Architecture Conference 2024
What Makes a Good Chef Cookbook? (May 2014 Edition)
1. What Makes a Good
Cookbook?
Julian C. Dunn
Senior Consulting Engineer Engineering Team Lead
Chef Software, Inc.
<jdunn@getchef.com>
2. $ whoami
• Consulting Engineer
Engineering Team Lead at Chef
• System Administrator
• Reformed Java Developer
• Writes silly code like this
•https://github.com/juliandunn/
doge-chef-formatter
10. missing_attrs = %w{
postgres
}.select do |attr|
node['postgresql']['password'][attr].nil?
end.map { |attr| "node['postgresql']['password']['#{attr}']" }
if !missing_attrs.empty?
Chef::Application.fatal!([
"You must set #{missing_attrs.join(', ')} in chef-solo mode.",
"For more information, see https://github.com/opscode-
cookbooks/postgresql#chef-solo-note"
].join(' '))
end
Too Clever for Its Own Good
11. Chef::Application.fatal!([
"You must set #{missing_attrs.join(', ')} in chef-solo mode.",
"For more information, see https://github.com/opscode-
cookbooks/postgresql#chef-solo-note"
].join(' '))
Poking at Chef Internals
• Other abuses: Messing with run_context and
run_state
14. execute 'yum install httpd' do
not_if 'rpm -qa | grep -x httpd'
end
Not declarative
• Also, the Chef recipe with 100 bash or
powershell_script resource declarations
16. default['mydaemon']['port'] = '1433'
# don't you mean the integer 1433?
default['mydaemon']['knob'] = 'disabled'
# don't you mean false?
Not using native Ruby data types
• If you use native data types you can validate
people’s input.
17. Fear of LWRPs
• Missed abstraction opportunities
• No good example to put here; they’re all 200 lines
long (thus proving my point)
26. loaded_recipes = if run_context.respond_to?(:loaded_recipes)
run_context.loaded_recipes
else
node.run_state[:seen_recipes]
end
node['mysql']['client']['packages'].each do |name|
resources("package[#{name}]").run_action(:install)
end
Do not abuse compile-time
•.run_action(:must_die)
• Use sparingly, if at all!
27. if some_error_condition
fail "Helpful error message"
# rather than Chef::Application.fatal!("error")
end
Avoid poking Chef Internals
•Chef::Application.fatal is for use by Chef
itself
•fail or raise is better
28. Attributes only where necessary
• “Let’s create a node attribute for each of the 15,000
tunables in this daemon”
• Not necessary if you never touch 14,975 of those
knobs
29. git clone git://github.com/foozolix/foozolix.git
cd foozolix && ./configure
make
make install
Give people options for installation
• At least give people a way to install from packages.
• “Compile from source” should be banned in most
cases.
30. Be declarative
• Know and use built-in Chef resources
• Know where to find LWRPs to avoid
batch/execute/powershell_script
• Consider log resource versus Chef::Log
•Shows up in reporting as an updated resource instead of having to trawl
through client.log
•Set an idempotency guard!
•Log at the right log level
31. Run System Commands Safely
• system
• backticks
•Chef::Mixin::ShellOut
•shell_out
•shell_out!
34. node['jboss']['instances'].each do |instance|
link "/etc/init.d/#{instance['name']}" do
to "/etc/init.d/jbossas"
end
template "/etc/sysconfig/#{instance['name']}" do
source "jbossas.sysconfig.erb"
owner node['jboss']['server']['user']
group node['jboss']['server']['group']
mode "00644"
variables(
:jbossconf => instance['name']
)
action :create
end
template "#{node['jboss']['server']['home']}/bin/standalone.sh" do
source "standalone.sh.erb"
owner node['jboss']['server']['user']
group node['jboss']['server']['group']
mode "00755"
action :create
end
link "#{node['jboss']['server']['home']}/bin/#{instance['name']}.sh" do
to "#{node['jboss']['server']['home']}/bin/standalone.sh"
end
end
Repetition == LWRP Candidate
36. jboss_instance "petstore" do
instance_name "can_haz_cheezburgerz"
console_log_level "DEBUG"
datasources {'db1' => 'jdbc://whatever:5432/db1'}
end
Repetition == LWRP Candidate
• Write/debug hard logic once
• Clear consumer interface
• Parameter validation & sanity checking
• Non-JBoss experts can invoke without knowing gnarly details
37. module MyCookbook
module Helper
# returns Windows friendly version of the provided path,
# ensures backslashes are used everywhere
def win_friendly_path(path)
path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR) if path
end
end
end
Write helper libraries
• Create reusable helper functions in pure Ruby
• Move repetitive detail out of recipe context.
• http://tinyurl.com/chef-libraries
38. Keep Recipes Small
• < 100 lines
• If longer than this, consider breaking up functionality
• Example: nagios::server recipe does too much
•Installs Nagios
•Configures Nagios
•Pokes around in data bags for config items
39. Use Community Helpers
• Chef Sugar - http://code.sethvargo.com/chef-sugar/
• Chef Cutlery - https://github.com/realityforge/chef-cutlery
• Attribute Validator -
https://github.com/clintoncwolfe/attribute-validator
• You can also crib the ideas if you want to avoid external
dependencies
41. Testing
• I didn’t mention testing once in this talk!
• I’m assuming you will write tests for your cookbooks.
• A whole other talk...
• ... including good/bad things to test
42. Make your code aromatic
• Keep recipes small
• Keep recipes simple
• Use a consistent style
• Use Foodcritic
43. Beware Expertise Bias
• Hide gnarly details from recipe context
•Libraries
•LWRPs
•Attributes
• Resist urge to be overly clever - not everyone’s an expert
•Akin to the one-line sed/awk script
•http://tinyurl.com/the-expertise-bias
44. Learn from Software Developers
• Everything I told you about information hiding, design
patterns, testing, etc.
• Ops can learn from devs as well!
• Maybe we should call it OpsDev...
45. Don’t Yet Know Chef?
• 2-Day Chef Fundamentals
Training in Boston
• June 16-17
• New Horizons, 75 Federal
St., Suite 1205
• Use code MEETUP to save
10%