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.
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:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
### 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:
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
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 .
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.