40
What Makes a Good Cookbook? Julian C. Dunn Senior Consultant Chef Software, Inc. <[email protected] >

What Makes a Good Cookbook?

Embed Size (px)

DESCRIPTION

What makes a good Chef cookbook?

Citation preview

Page 1: What Makes a Good Cookbook?

What Makes a Good Cookbook?Julian C. DunnSenior ConsultantChef Software, Inc.<[email protected]>

Page 2: What Makes a Good Cookbook?

Finding a Good Cookbook

Page 3: What Makes a Good Cookbook?
Page 4: What Makes a Good Cookbook?

Do judge a cookbook by its cover

Page 5: What Makes a Good Cookbook?
Page 6: What Makes a Good Cookbook?
Page 7: What Makes a Good Cookbook?
Page 8: What Makes a Good Cookbook?
Page 9: What Makes a Good Cookbook?

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

Page 10: What Makes a Good Cookbook?

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

Page 11: What Makes a Good Cookbook?

template "/etc/whatever.conf" do ... not_if { foo }end

Compile vs. Execute Errors

if foo template "/etc/whatever.conf" do ... endend

not the same thing as

Page 12: What Makes a Good Cookbook?

execute “yum install httpd” do not_if “rpm -qa | grep -x httpd”end

Not declarative

• Also, the Chef recipe with 100 bash resource declarations

Page 13: What Makes a Good Cookbook?

execute "I will run every time" do action :run # because I don't have a guard hereend

Missing guards

Page 14: What Makes a Good Cookbook?

Fear of LWRPs• Missed abstraction opportunities

• No good example to put here; they’re all 200 lines long (thus proving my point)

Page 15: What Makes a Good Cookbook?

remote_file 'whatever.tar.gz' do source 'http://hardcoded.url.com/’end

Hardcoded Strings

Page 16: What Makes a Good Cookbook?

Excess Conditions & Recipe Length• https://github.com/opscode-cookbooks/mysql/blob/v3.0.12/recipes/server.rb

• (We’ve since refactored this)

Page 17: What Makes a Good Cookbook?

Good Cookbooks...

Page 18: What Makes a Good Cookbook?

Put control flow in attributes• Especially for cross-platform cookbooks

• Set common set of attributes, write common behavior in recipe context

Page 19: What Makes a Good Cookbook?

case node['platform']

when "debian", "ubuntu" default['postgresql']['client']['packages'] = %w{postgresql-client libpq-dev} default['postgresql']['server']['packages'] = %w{postgresql} default['postgresql']['contrib']['packages'] = %w{postgresql-contrib}

when "fedora", "amazon" default['postgresql']['client']['packages'] = %w{postgresql-devel} default['postgresql']['server']['packages'] = %w{postgresql-server} default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} default['postgresql']['server']['service_name'] = "postgresql"

when "redhat", "centos", "scientific", "oracle" default['postgresql']['version'] = "8.4" default['postgresql']['dir'] = "/var/lib/pgsql/data"

if node['platform_version'].to_f >= 6.0 default['postgresql']['client']['packages'] = %w{postgresql-devel} default['postgresql']['server']['packages'] = %w{postgresql-server} default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} else default['postgresql']['client']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-devel"] default['postgresql']['server']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-server"] default['postgresql']['contrib']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-contrib"] end default['postgresql']['server']['service_name'] = "postgresql"

end

Control Flow in Attributes

Page 20: What Makes a Good Cookbook?

node['postgresql']['server']['packages'].each do |pg_pack|

package pg_pack

end

Common Recipe Code

• Easy to support more platforms without modifying recipe

Page 21: What Makes a Good Cookbook?

default.rbdefault.rbdefault.rbdefault.rb

server.rbserver.rbserver.rbserver.rb

_suse.rb_suse.rb_suse.rb_suse.rb_fedora.rb_fedora.rb_fedora.rb_fedora.rb_windows.rb_windows.rb_windows.rb_windows.rb

Separate recipes by OS• If things you do are very different per platform, separate them into different recipes

Page 22: What Makes a Good Cookbook?

“Public” versus “Private” recipes• ‘_’ faux-namespacing

Page 23: What Makes a Good Cookbook?

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!

Page 24: What Makes a Good Cookbook?

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

Page 25: What Makes a Good Cookbook?

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

Page 26: What Makes a Good Cookbook?

git clone git://github.com/foozolix/foozolix.git./configuremakemake install

Give people options for installation

• Sigh.

• At least give people a way to install from packages.

• “Compile from source” should be banned in most cases.

Page 27: What Makes a Good Cookbook?

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

Page 28: What Makes a Good Cookbook?

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" endend

Repetition == LWRP Candidate

Page 29: What Makes a Good Cookbook?

actions :create, :delete

attribute :instance_name, :kind_of => String, :name_attribute => trueattribute :console_log_level, :kind_of => String, :required => trueattribute :datasources, :kind_of => Hash, :default => {}

.

.

.

default_action :create

Repetition == LWRP Candidate• Perfect for abstracting!

• Resource interface:

Page 30: What Makes a Good Cookbook?

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

Page 31: What Makes a Good Cookbook?

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.

Page 32: What Makes a Good Cookbook?

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

Page 33: What Makes a Good Cookbook?

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

Page 34: What Makes a Good Cookbook?

Wrap-Up

Page 35: What Makes a Good Cookbook?

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

Page 36: What Makes a Good Cookbook?

Make your code aromatic• Keep recipes small

• Keep recipes simple

• Use a consistent style

• Use Foodcritic

Page 37: What Makes a Good Cookbook?

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

Page 38: What Makes a Good Cookbook?

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...

Page 39: What Makes a Good Cookbook?

Thank You!E: [email protected]: https://github.com/juliandunnT: @julian_dunnW: www.juliandunn.net

Page 40: What Makes a Good Cookbook?