What Makes a Good Cookbook?


Published on

What makes a good Chef cookbook?

  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • 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."
  • What Makes a Good Cookbook?

    1. 1. What Makes a Good Cookbook? Julian C. Dunn Senior Consultant Chef Software, Inc. <jdunn@getchef.com>
    2. 2. Finding a Good Cookbook
    3. 3. Do judge a cookbook by its cover
    4. 4. 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
    5. 5. 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
    6. 6. 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
    7. 7. Not declarative execute “yum install httpd” do not_if “rpm -qa | grep -x httpd” end • Also, the Chef recipe with 100 bash resource declarations
    8. 8. Missing guards execute "I will run every time" do action :run # because I don't have a guard here end
    9. 9. Fear of LWRPs • Missed abstraction opportunities • No good example to put here; they’re all 200 lines long (thus proving my point)
    10. 10. Hardcoded Strings remote_file 'whatever.tar.gz' do source 'http://hardcoded.url.com/’ end
    11. 11. Excess Conditions & Recipe Length • https://github.com/opscode-cookbooks/mysql/blob/v3.0.1 • (We’ve since refactored this)
    12. 12. Good Cookbooks...
    13. 13. Put control flow in attributes • Especially for cross-platform cookbooks • Set common set of attributes, write common behavior in recipe context
    14. 14. Control Flow in Attributes 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"
    15. 15. Common Recipe Code node['postgresql']['server']['packages'].each do |pg_pack| package pg_pack end • Easy to support more platforms without modifying recipe
    16. 16. 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
    17. 17. “Public” versus “Private” recipes • ‘_’ faux-namespacing
    18. 18. 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!
    19. 19. 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
    20. 20. 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
    21. 21. 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.
    22. 22. 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
    23. 23. 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
    24. 24. Repetition == LWRP Candidate • Perfect for abstracting! • Resource interface: 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
    25. 25. 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
    26. 26. 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.
    27. 27. 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
    28. 28. 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
    29. 29. Wrap-Up
    30. 30. 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
    31. 31. Make your code aromatic • Keep recipes small • Keep recipes simple • Use a consistent style • Use Foodcritic
    32. 32. 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
    33. 33. 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...
    34. 34. Thank You! E: jdunn@getchef.com G: https://github.com/juliandunn T: @julian_dunn W: www.juliandunn.net