This is a text-only version of the following page on https://raymii.org:
---
Title       : 	Chef: overwrite templates in wrapper-cookbooks
Author      : 	Remy van Elst
Date        : 	02-04-2014
URL         : 	https://raymii.org/s/articles/Chef_-_overwrite_templates_in_wrapper_cookbooks.html
Format      : 	Markdown/HTML
---



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

<p class="ad"> <b>Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:</b><br><br> <a href="https://leafnode.nl">I'm developing an open source monitoring app called  Leaf Node Monitoring, for windows, linux & android. Go check it out!</a><br><br> <a href="https://github.com/sponsors/RaymiiOrg/">Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.</a><br><br> <a href="https://www.digitalocean.com/?refcode=7435ae6b8212">You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $100 credit for 60 days. </a><br><br> </p>


### Background on Wrapper Cookbooks

The Chef Cookbook Wrapper Pattern is based upon a design convention where you
customize 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-source
contribution from a user in the Chef community, designed for server
configuration purposes.

A wrapper cookbook is a cookbook that wraps the original library cookbook with
custom modifications or additions such as overriding a Chef attribute, changing
a 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 Chef
developers and the range of available applications, finding an existing
community cookbook that you want to leverage will become more of the norm than
the exception. Therefore, it will be easier to find an existing cookbook that
you can either use as-is or slightly modify for your own purposes. When
possible, it's best to leverage an existing cookbook (assuming that it's
actively being maintained) than trying to create your own custom cookbook from
scratch or forking a cookbook repository.

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

[Source][2]

### Templates in wrapper-cookbooks

To override a template by just defining it again would result in it being
written 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 your
wrapper-cookbook.

One of my clients uses graphite and wants to limit which users can login using
LDAP in Apache. The [graphite][3] cookbook does not support this by default, but
it works for all the other things.

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

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

In the graphite cookbook we see the following piece of code which places the
template 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]", :immediately
    end
    

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

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

    
    
    {
        "tags": [],
        "graphite": {
            "ldap": {
                "password": "passw0rd",
                "server": "ldap.example.org",
                "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 the
basic header boilerplate and include the `graphite` recipe:

    
    
    #
    # Cookbook Name:: wrapper-graphite
    # Recipe:: default
    #
    # Copyright 2014, EXAMPLE COMPANY
    #
    # License: GPLv3
    
    include_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 overrides
the 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][4]. The [resource
collection][5] is the ordered list of resources, from the recipes in your
expanded run list, that are to be run on a node.

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

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

As you can see we need to set all of the variables, even the ones that were
already 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 graphite
recipe, it will do all the things from the graphite recipe, except for the
override we define here.

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

[Here][6] is a blog entry from Opscode about wrapper cookbooks.

   [1]: https://www.digitalocean.com/?refcode=7435ae6b8212
   [2]: http://support.rightscale.com/12-Guides/Chef_Cookbooks_Developer_Guide/04-Developer/06-Development_Resources/Chef_Cookbook_Design_Patterns/Wrapper_Cookbook_Pattern
   [3]: https://github.com/hw-cookbooks/graphite
   [4]: http://docs.opscode.com/essentials_nodes_chef_run.html
   [5]: http://docs.opscode.com/chef/resources.html#run-resources-from-the-resource-collection
   [6]: http://www.getchef.com/blog/2013/12/03/doing-wrapper-cookbooks-right/

---

License:
All the text on this website is free as in freedom unless stated otherwise. 
This means you can use it in any way you want, you can copy it, change it 
the way you like and republish it, as long as you release the (modified) 
content under the same license to give others the same freedoms you've got 
and place my name and a link to this site with the article as source.

This site uses Google Analytics for statistics and Google Adwords for 
advertisements. You are tracked and Google knows everything about you. 
Use an adblocker like ublock-origin if you don't want it.

All the code on this website is licensed under the GNU GPL v3 license 
unless already licensed under a license which does not allows this form 
of licensing or if another license is stated on that page / in that software:

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Just to be clear, the information on this website is for meant for educational 
purposes and you use it at your own risk. I do not take responsibility if you 
screw something up. Use common sense, do not 'rm -rf /' as root for example. 
If you have any questions then do not hesitate to contact me.

See https://raymii.org/s/static/About.html for details.