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.