Chef - 'Wrapper' Cookbooks and Execution Order

Recently, I’ve been experimenting with Chef for Configuration Management.  One of the greatest thing’s about Chef is the concept of ‘Wrapper’ or ‘Application’ cookbooks, which basically make use of existing community cookbook code.  These community cookbooks, wherever they are sourced, are referred to as ‘Library’ cookbooks.  Basically, rather than forking the cookbook and adjusting it yourself, you write a ‘Wrapper’ for the Library cookbook in order to facilitate desired changes.  These changes are typically just overriding attributes, but sometimes you might have to take it a step further.

For example, after creating a few ‘Hello World’ cookbooks, I decided I was going to cook up a Wrapper for gitlab.org’s gitlab cookbook.  I prefer to use the most up-to-date NginX RPM available, as well as utilize the Percona MySQL suite rather than the standard MySQL packages.  For the GitLab cookbook at least, this involved making sure both Repositories were present prior to the resource execution phase.  Additionally, since Gitlab requires mysql-libs and this package is a member of the packages array in the cookbooks default attributes, the ‘packages’ array has to be overridden as well.  Here is an example of the override with a scope limited to the gitlab cookbook, and packages aren’t listed for brevity. This would be included in your wrapper cookbook’s attributes.

override.gitlab["packages"] = %w{packages, without, mysql-libs}

NOTE: I’m putting on my flame-suit; I’m sure there’s something I’m not doing ‘correctly’ here or to proper standards, but this is my first go, and it does work.

There’s a certain amount of Voodoo to get your prerequisites to execute first, for example installing the NginX and Percona repositories.  For the life of me, I couldn’t get the ‘yum-percona’ cookbook to run first when called as an ‘include_recipe’ inside my cookbook.  I know the proper method would be to place the ‘yum_percona’ cookbook further ahead in the node’s run list, but my objective here was to exploit the power of Chef.  What I ended up doing was creating two resources to pull down and install the repo installer RPM. Unfortunately, you can’t just feed the yum_package resource the URL like you could in Bash.  Here’s a little code snippet showing the Voodoo here:

mycookbook-gitlab\default.rb

include_recipe "mycookbook-gitlab::nginx_repo"
include_recipe "gitlab"

mycookbook-gitlab\nginx_repo.rb

# => Download NginX Yum Repo RPM
remote_file "#{Chef::Config[:file_cache_path]}/nginx_repo_latest.rpm" do
source "http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm"
action :nothing
end.run_action(:create)

# => Install NginX Yum Repo from RPM
yum_package "nginx_repo" do
source "#{Chef::Config[:file_cache_path]}/nginx_repo_latest.rpm"
action :nothing
end.run_action(:install)

The key here is the action:nothing, and the end.run_action(:install).  Chef operates in two phases; compile and execution.  The compile phase involves gathering all the resource objects from the cookbook/recipe dependency hierarchy in the node’s run list.  The execution phase is when all said resources are executed.  Obviously, you want the proper yum repositories in place prior to executing a yum install.  This snippet causes the Nginx Repo RPM to be downloaded and installed during the compilation phase. I have seen this in cookbooks before, but wasn’t sure what its purpose was.  These can be used with any resource type, although it’s probably more helpful with some than others. If you find that the descending execution order of your run_list, cookbook, or specific recipe is not being honored, you might need to use this construct to fix it… Or, look for this construct to find the offending blip. If it’s in an upstream community cookbook, override it the way we did here.

Finally, as an FYI, you can make use of the NginX community cookbook to handle the NginX repo installation. By default, it looks to EPEL for the package. You can set the attribute node[‘nginx’][‘repo_source’] = ‘nginx’, which defaults to epel.

comments powered by Disqus