Your SlideShare is downloading. ×
What Makes a Good Chef Cookbook? (May 2014 Edition)
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

What Makes a Good Chef Cookbook? (May 2014 Edition)

777
views

Published on

What makes a good or bad Chef cookbook? …

What makes a good or bad Chef cookbook?
Presented at the Boston Chef meetup on May 22, 2014.

Published in: Technology, Self Improvement

1 Comment
2 Likes
Statistics
Notes
No Downloads
Views
Total Views
777
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
20
Comments
1
Likes
2
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 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
  • 3. Finding a Good Cookbook LMGTCFY
  • 4. Do judge a cookbook by its cover
  • 5. 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
  • 6. 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
  • 7. if node.run_list.recipes.include?('foo::bar') ... end Poking run_list and environment if node.chef_environment == 'production' ... end • Use feature flags!
  • 8. template "/etc/whatever.conf" do ... not_if { foo } end Compile vs. Execute Errors if foo template "/etc/whatever.conf" do ... end end not the same thing as
  • 9. 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
  • 10. execute '/i/will/run/every/time' do action :run # because I don't have a guard here end Missing guards
  • 11. 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.
  • 12. Fear of LWRPs • Missed abstraction opportunities • No good example to put here; they’re all 200 lines long (thus proving my point)
  • 13. remote_file 'whatever.tar.gz' do source 'http://hardcoded.url.com/' end Hardcoded Strings
  • 14. Excess Conditions & Recipe Length • https://github.com/opscode- cookbooks/mysql/blob/v3.0.12/recipes/server.rb • (We’ve since refactored this)
  • 15. Good Cookbooks...
  • 16. Put control flow in attributes • Especially for cross-platform cookbooks • Set common set of attributes, write common behavior in recipe context
  • 17. 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" Control Flow in Attributes
  • 18. node['postgresql']['server']['packages'].each do |pg_pack| package pg_pack end Common Recipe Code • Easy to support more platforms without modifying recipe
  • 19. default.rb server.rb _suse.rb_fedora.rb_windows.rb Separate recipes by OS • If things you do are very different per platform, separate them into different recipes
  • 20. “Public” versus “Private” recipes • ‘_’ faux-namespacing
  • 21. 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!
  • 22. 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
  • 23. 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
  • 24. 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.
  • 25. 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
  • 26. Run System Commands Safely • system • backticks •Chef::Mixin::ShellOut •shell_out •shell_out!
  • 27. $ chef-apply -s Chef::Recipe.send(:include, Chef::Mixin::ShellOut) cmd = shell_out!("echo -n Ohai, world") log cmd.stdout ^D Recipe: (chef-apply cookbook)::(chef-apply recipe) * log[Ohai, world] action write Example Recipe Context
  • 28. unless node.chef_environment('pigsty') include_recipe 'bacon::default' end Feature Flags Example if node['foo']['bar']['can_haz_bacon'] include_recipe 'bacon::default' end • Instead:
  • 29. 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
  • 30. actions :create, :delete attribute :instance_name, :kind_of => String, :name_attribute => true attribute :console_log_level, :kind_of => String, :required => true attribute :datasources, :kind_of => Hash, :default => {} . . . default_action :create Repetition == LWRP Candidate • Perfect for abstracting! • Resource interface:
  • 31. 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
  • 32. 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
  • 33. 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
  • 34. 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
  • 35. Wrap-Up
  • 36. 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
  • 37. Make your code aromatic • Keep recipes small • Keep recipes simple • Use a consistent style • Use Foodcritic
  • 38. 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
  • 39. 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...
  • 40. 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%
  • 41. Thank You! E: jdunn@getchef.com G: https://github.com/juliandunn T: @julian_dunn W: www.juliandunn.net

×