Skip to main content Logo (IEC resistor symbol)logo

Quis custodiet ipsos custodes?
Home | About | All pages | RSS Feed | Gopher

Chef: overwrite templates in wrapper-cookbooks

Published: 02-04-2014 | Author: Remy van Elst | Text only version of this article

Table of Contents

This article describes how to use a template in a wrapper-cookbook in Chef.

If you like this article, consider sponsoring me by trying out a Digital OceanVPS. With this link you'll get $100 credit for 60 days). (referral link)

Background on Wrapper Cookbooks

The Chef Cookbook Wrapper Pattern is based upon a design convention where youcustomize an existing library cookbook by using a separate wrapper cookbook,which wraps the original cookbook with any related configuration changes.

A library cookbook is an existing cookbook, typically an open-sourcecontribution from a user in the Chef community, designed for serverconfiguration purposes.

A wrapper cookbook is a cookbook that wraps the original library cookbook withcustom modifications or additions such as overriding a Chef attribute, changinga Chef template, converting a Chef attribute to a user-definable input, etc.

As the Chef community continues to grow, both in the number of active Chefdevelopers and the range of available applications, finding an existingcommunity cookbook that you want to leverage will become more of the norm thanthe exception. Therefore, it will be easier to find an existing cookbook thatyou can either use as-is or slightly modify for your own purposes. Whenpossible, it's best to leverage an existing cookbook (assuming that it'sactively being maintained) than trying to create your own custom cookbook fromscratch or forking a cookbook repository.

Although forking a repository may initially seem like the easiest way to modifyan existing cookbook, it will likely cause you more headaches over time as youtry to maintain and upgrade your codebase over time. Therefore, it's recommendedthat you spend the extra time and effort to integrate a Wrapper Cookbook Patterninto your development routine because you will have a more manageable upgradepath for integrating future bug fixes and new functionality.


Templates in wrapper-cookbooks

To override a template by just defining it again would result in it beingwritten two times every Chef run, which is not what we want. Using this method,you can override the template from the default cookbook with a template in yourwrapper-cookbook.

One of my clients uses graphite and wants to limit which users can login usingLDAP in Apache. The graphite cookbook does not support this by default, butit works for all the other things.

Graphite itself has support for LDAP login, however, the client has experiencewith Apache LDAP and wants to use that so that other admins can manage it aswell.

So what we want to do is overwrite the default apache template from the graphitecookbook with our template which has the LDAP data.

In the graphite cookbook we see the following piece of code which places thetemplate for the graphite website in the apache sites-available folder:

template "#{node['apache']['dir']}/sites-available/graphite" do  source "graphite-vhost.conf.erb"  mode 0755  variables(:timezone => node['graphite']['timezone'],            :debug => node['graphite']['web']['debug'],            :base_dir => node['graphite']['base_dir'],            :doc_root => node['graphite']['doc_root'],            :storage_dir => node['graphite']['storage_dir'],            :cluster_servers => node['graphite']['web']['cluster_servers'],            :carbonlink_hosts => node['graphite']['web']['carbonlink_hosts'],            :memcached_hosts => node['graphite']['web']['memcached_hosts'],     )  notifies :reload, "service[apache2]", :immediatelyend

We are going to override this template with extra variables for the LDAPconnection in Apache.

Add a few node attributes, or place them in a data bag, whatever you like, forthe LDAP:

{    "tags": [],    "graphite": {        "ldap": {            "password": "passw0rd",            "server": "",            "enabled": true,            "binddn": "uid=graphite,ou=Applications,dc=example,dc=org",            "accessgroup": "cn=graphite_users,ou=Groups,dc=example,dc=org",            "apachefilter": "uid?sub?(ObjectClass=*)",            "userdn": "ou=Users,dc=example,dc=org",            "port": 636        }    }}

We are going to use these in the apache template.

Create a new cookbook:

knife cookbook create wrapper-graphite

Copy the template over from the graphite cookbook to the wrapper cookbook:

cp cookbooks/graphite/templates/default/graphite-vhost.conf.erb cookbooks/wrapper-graphite/templates/default/graphite-vhost.conf.erb

Add the LDAP settings to the template graphite-vhost.conf.erb in the wrapper-cookbook folder:

<Location />  Order deny,allow  Deny from All  AuthName "Authorization Required"  AuthType Basic  # Needed for require-valid-user  AuthzLDAPAuthoritative off  AuthBasicProvider ldap  AuthLDAPUrl "ldap://<%- @ldap_server %>/<%- @ldap_basedn %>?<%- @ldap_apachefilter %>"  AuthLDAPBindDN "<%- @ldap_binddn %>"  AuthLDAPBindPassword "<%- @ldap_password %>"  Require ldap-group <%- @ldap_accessgroup %>  Satisfy any</Location>

Then edit your recipe, cookbooks/wrapper-graphite/recipes/default.rb. Add thebasic header boilerplate and include the graphite recipe:

## Cookbook Name:: wrapper-graphite# Recipe:: default## Copyright 2014, EXAMPLE COMPANY## License: GPLv3include_recipe "graphite"

Don't forget to also add it to the metadata.rb file:

depends         "graphite"

Add the following magic to the wrapper cookbook. This is the part that overridesthe template in the normal cookbook with the template from the wrapper cookbook:

begin    r = resources(:template => "#{node['apache']['dir']}/sites-available/graphite")    r.cookbook "wrapper-graphite"    r.source "graphite-vhost.conf.erb"    r.mode 0755    r.variables(:timezone => node['graphite']['timezone'],        :debug => node['graphite']['web']['debug'],        :base_dir => node['graphite']['base_dir'],        :doc_root => node['graphite']['doc_root'],        :storage_dir => node['graphite']['storage_dir'],        :cluster_servers => node['graphite']['web']['cluster_servers'],        :carbonlink_hosts => node['graphite']['web']['carbonlink_hosts'],        :memcached_hosts => node['graphite']['web']['memcached_hosts'],        :ldap_enabled => node['graphite']['ldap']['enabled'],        :ldap_server => node['graphite']['ldap']['server'],        :ldap_port => node['graphite']['ldap']['port'],        :ldap_binddn => node['graphite']['ldap']['binddn'],        :ldap_basedn => node['graphite']['ldap']['basedn'],        :ldap_accessgroup => node['graphite']['ldap']['access_group'],        :ldap_password => node['graphite']['ldap']['password'],        :ldap_apachefilter => node['graphite']['ldap']['apachefilter']    )    r.notifies :reload, "service[apache2]", :immediately    rescue Chef::Exceptions::ResourceNotFound        Chef::Log.warn "could not find template to override!"end

This works because a chef-client run has multiple phases. The resourcecollection is the ordered list of resources, from the recipes in yourexpanded run list, that are to be run on a node.

During the resource collection phase we can manipulate attributes of theresources in the resource collection.

Because Chef uses a two-phase execution model (compile, then converge), you canmanipulate the results of that compilation in many different ways beforeconvergence happens.

As you can see we need to set all of the variables, even the ones that werealready declared in the original cookbook. If you don't do this, it will bork.

Now by adding the wrapper-graphite recipe to a node instead of the graphiterecipe, it will do all the things from the graphite recipe, except for theoverride we define here.

The big advantage of this approach for me is that I can update the upstreamcookbook at any moment (for example, when a new graphite is releases or theupstream cookbook changes) while my own changes do not need to be backported inthere.

Here is a blog entry from Opscode about wrapper cookbooks.

Tags: articles, chef, deployment, devops, graphite, ldap, ruby