While I was working for Pagoda Box we brought Chef on as a solution for Configuration Management in order to solve our config drift woes. Before long it was a core part of how we did everything on our system and it eventually got woven into the core fabric of both our (then) current and next infrastructure iterations. A lot of our cookbooks had to be written from scratch due to our very specific needs and unique approach to PaaS Web-Hosting, and this was only complicated by the fact that we were running three different Operating Systems on our servers (depending on which the given machine’s task was suited for). As such, most cookbooks which we used/wrote needed to provide adequate support for CentOS, Ubuntu, & SmartOS; and that meant things could get out out hand within the cookbook’s structure very, very quickly. In order to address this issue, I developed a pattern which allowed us to segregate and share configurations between platforms, while also ensuring that the cookbook was easy to navigate. I called it: The Platform Handler Pattern for Chef Cookbooks, and I figured it might prove useful to many out there.

The pattern is pretty straight-forward in its file-structure. A recipe is created for each supported platform, and is named with the value Ohai would return for that platform and a prefix of “platform”. Like so:

recipes
 - default.rb
 - platform_centos.rb
 - platform_fedora.rb
 - platform_mac_os_x.rb
 - platform_smartos.rb
 - platform_ubuntu.rb
 - platform_windows.rb
 - type_linux.rb
 - type_redhat.rb
 - type_unix.rb

The recipes prefixed with “type” are meant to be called at the beginning of the platform recipes to which they are relevant:

#
# Cookbook Name:: platform_handler_pattern
# Recipe:: platform_ubuntu
#

include_recipe "platform_handler_pattern::type_linux"

The advantages of structuring your recipes in this way are readily apparent as you can easily see where you can find the relevant code for a given platform, and where you would find elements common to multiple platforms.

However the true magic of this pattern can be found in it’s very simple and standardized default recipe:

#
# Cookbook Name:: platform_handler_pattern
# Recipe:: default
#

begin
  include_recipe "platform_handler_pattern::platform_#{node[:platform]}"
rescue Chef::Exceptions::RecipeNotFound => e
log "!!!! This Cookbook does not support #{node[:platform]} :( !!!!"
  raise e
end

Once implemented, this pattern then allows for adding and removing support to new platforms without having to change anything in the default recipe. All that necessary is to create a new platform recipe file with the appropriate platform name and then to include any type recipes which might apply to it. (And you can create as many different kinds of types as you see fit for your purposes.)