9. Too Clever for Its Own Good
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
10. Poking at Chef Internals
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(' '))
• Other abuses: Messing with run_context and
run_state
11. Compile vs. Execute Errors
template "/etc/whatever.conf" do
...
not_if { foo }
end
not the same thing as
if foo
template "/etc/whatever.conf" do
...
end
end
12. Not declarative
execute “yum install httpd” do
not_if “rpm -qa | grep -x httpd”
end
• Also, the Chef recipe with 100 bash resource
declarations
13. Missing guards
execute "I will run every time" do
action :run
# because I don't have a guard here
end
14. Fear of LWRPs
• Missed abstraction opportunities
• No good example to put here; they’re all 200 lines
long (thus proving my point)
21. Separate recipes by OS
• If things you do are very different per platform,
separate them into different recipes
default.rb
default.rb
server.rb
server.rb
_windows.rb
_windows.rb
_fedora.rb
_fedora.rb
_suse.rb
_suse.rb
23. Do not abuse compile-time
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
•.run_action(:must_die)
• Use sparingly, if at all!
24. Avoid poking Chef Internals
if some_error_condition
fail "Helpful error message"
# rather than Chef::Application.fatal!("error")
end
• Chef::Application.fatal is for use by Chef
itself
• fail or raise is better
25. 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
26. Give people options for installation
git clone
git://github.com/foozolix/foozolix.git./configuremak
emake install
• Sigh.
• At least give people a way to install from packages.
• “Compile from source” should be banned in most cases.
27. Be declarative
• Know and use built-in Chef resources
• Know where to find LWRPs to avoid
batch/execute/powershell_script
• Consider using 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
28. Repetition == LWRP Candidate
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
30. Repetition == LWRP Candidate
jboss_instance "petstore" do
instance_name "can_haz_cheezburgerz"
console_log_level "DEBUG"
datasources {'db1' => 'jdbc://whatever:5432/db1'}
end
• Write/debug hard logic once
• Clear consumer interface
• Parameter validation & sanity checking
• Non-JBoss experts can invoke without knowing gnarly details
31. Write helper libraries
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
• Create reusable helper functions in pure Ruby
• Move repetitive detail out of recipe context.
32. 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
33. Use Community Helpers
• Chef Sugar - http://code.sethvargo.com/chef-sugar/
• Chef Cutlery https://github.com/realityforge/chef-cutlery
• You can also crib ideas if you want to avoid external
dependencies
35. 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
36. Make your code aromatic
• Keep recipes small
• Keep recipes simple
• Use a consistent style
• Use Foodcritic
37. 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
38. 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...
Other things to add:
- shell_out versus shell_out! (and also using shell_out rather than ‘system’ or backticks (ugh))
If you’ve ever been to Chef training, we tell you... don’t invent your own cookbook if someone else has done it before.
But when you Google, or search on GitHub/StackOverflow/Chef Community site and look for a cookbook, how do you know if it’s any good?
More importantly, if you’re writing a cookbook that you hope to publish, or patches for Chef’s community cookbooks, how do you make a “good” cookbook?
Updated recently?
Lots of outstanding pull requests that haven’t been addressed in years?
No docs?
These things all tell you whether the code is maintained and how alive it is. If you work in open source, this should already be known to you -- how to detect how healthy an open source project is. So I won’t spend too much more time on that.
What about what the code looks like inside?
Code smell
From 30,000 feet, how can you detect whether a cookbook smells good or bad?
Not everyone is a Ruby genius.
This is wrong on so many levels, not the least of which... over-abstraction (using a sledgehammer to kill a fly)
Abuse of batch/execute/powershell_script when built-in Chef resources will do
Another example: remote_file over execute+curl/wget
Always ensure idempotence! (Some exceptions where it’s not possible, but doing so will allow you to establish a baseline # of resources updated, close to zero, that shows whether your systems are in compliance with policy.
Being afraid of writing LWRPs, so not encapsulating repetitive logic in an LWRP with inline resources
Example: Previous job’s Jetty cookbook, where it was a giant loop over a giant attribute array for each Jetty instance
Particularly in URLs! Not everyone has Internet access! Make this stuff attributes so people can override it
Too many conditionals, too much control flow, trying to do a “one size fits all” approach
Becomes an arms race of what you can do earlier and earlier
Nothing worse than a cookbook that purports to install “foozolix” and the recipe consists ofgit clone git://github.com/foozolix/foozolix.git./configuremakemake installEspecially if foozolix is available in a distro package!“compile from source” should be banned in most cases
So much of IT is just pattern recognition... the “app” + “instance of app” is a common pattern. (JBoss instance, Endeca “app”, whatever)
But don’t abuse LWRPs. If it’s not repeatable and a consumable interface by external parties (e.g. you’re only going to do it once), then don’t bother LWRP-ing.
TODO: Show refactoring of a tomcat instance into an LWRP
(A discourse here about how small a recipe should be -- basically it should encapsulate atomic functionality that someone will call, all or nothing)
Nothing more irritating than a recipe that does 95% of what you want, and the 5% of it is some side effect that many people don’t want to do -- e.g. Nagios recipe, installing Nagios and all the data bag search stuff is stuffed into one recipe
Caveat: Locks you into particular gems.
ChefSpec 3.0 for unit testing.
ServerSpec for integration testing.
Test Kitchen to tie it all together.
- Make your code smell good both near and far. - Keep recipes simple - Keep recipes small (more recipes in a cookbook are ok) - I don’t care what your style is, just use a consistent one — Rubocop is good for enforcing whatever style you decide on. Nothing worse than mixing and matching syntax (symbols versus strings versus dot-notation is the worst) - Foodcritic is also good for enforcing cookbook correctness -- not just a linter
http://www.psychologytoday.com/blog/everybody-is-stupid-except-you/201008/the-expertise-bias
Imagine you are a DMV worker. It's your first day on the job. Someone asks you a question and you can see why they're confused. You're a bit confused yourself. You help them out.
But now it's your 1,000th day at the DMV. You've had 10,000 people ask you the same question. The answer, which used to seem a bit obscure, is SO OBVIOUS you want to throw up. You can't believe that these people don't know what they're supposed to do! You look at the customer and think: "You filled out Form 332b even though you have an RV and you're left handed?! You're an idiot."