7
votes

I am trying to implement a wrapper cookbook by taking inspiration from How to Write Reusable Chef Cookbooks, Gangnam Style. I wish to install tomcat 7 on my node without manager app. I have created a wrapper cookbook with the following attributes/default.rb file:

default["tomcat"]["base_version"] = 7
default["tomcat"]["deploy_manager_apps"] = false

The default attributes provided in tomcat/attributes/default.rb are:

default["tomcat"]["base_version"] = 6
#other attributes
default["tomcat"]["deploy_manager_apps"] = true
default["tomcat"]["user"] = "tomcat#{node["tomcat"]["base_version"]}

I wish to override these values across all attributes. However attributes such as ["tomcat"]["user"] are not getting overriden. The above still has the value of tomcat6 in node["tomcat"]["user"].

Do I have to override all the attributes which refer to ["tomcat"]["base_version"]}"? If my attributes/default.rb were loaded before tomcat cookbook's default.rb this would have worked fine.

I am using Berkshelf, Vagrant and Chef solo for development. In metadata.rb of my cookbook, I have mentioned depends "tomcat".

My custom cookbook is located at https://github.com/vaibhavguptaIITD/hcentive-ops/tree/master/hc-tomcat and tomcat community cookbook is located at https://github.com/opscode-cookbooks/tomcat.

3

3 Answers

9
votes

This is due to how/when ruby code is evaluated during a Chef run. In a typical Chef run, the attribute files are evaluated first, in the dependency order dictated by the run_list as mentioned here: Chef 11 In-Depth: Attributes Changes.

Chef detects the dependency on the tomcat cookbook and loads/evaluates it's attributes first. So default["tomcat"]["user"] = "tomcat#{node["tomcat"]["base_version"]} is set to tomcat6 because at the time, the value of node["tomcat"]["base_version"] is 6.

Later, Chef evaluates your wrapper cookbook and properly sets the node["tomcat"]["base_version"] attribute to 7, however node["tomcat"]["user"] is never reevaluated.

So you will need to set the value for node["tomcat"]["user"] in your wrapper cookbook if you would like to change it's value.

3
votes

This is not a bug in chef-client parse order. If we reversed it then from the wrapper cookbook you could never read the default values set in the base class since those values would not have been parsed yet.

It also allows your attributes set in the default precedence level to have priority over the cookbook you are wrapping. If we reversed the topological sort that would force wrapper cookbooks to use the override level. If you have wrapper cookbooks on top of wrapper cookbooks then now you've run out of standard attribute precedence levels. Eventually you run out of precedence levels and you've made a mess.

The order that attributes are parsed with dependents-first instead of parents-first gets these precedence issues correct so that everyone can just use the default level in their wrapper cookbooks.

The blog post here goes into the derived attributes problem in more detail and proposes a solution:

https://coderanger.net/derived-attributes/

I've cut an issue against chef-client to add support for lazy attributes officially here (the poise-derived repo referenced off of that blog is abandoned and should not be used):

https://github.com/chef/chef/issues/10345

1
votes

I ran into this same problem. It makes sense to me also to set a base variable and set other variables from it like:

default["apache"]["apache_docroot"] = '/var/www'

#other attributes:
default['apache']['webapp1_docroot'] = "#{node['apache']['apache_docroot']/webapp1}"

to get: /var/www/webapp1

As was pointed out, what happens is Chef find your depends cookbooks and loads their attributes first. Seems wrong in some sense. Why not load my parent

override["apache"]["apache_docroot"] = '/net1/websites'

first, then the depends will work fine. They are lower and will not get overridden.

I found a way to get around this problem. It's not great but it works. You end up doing:

  • Load depend tomcat *.rb
  • Load parent wrapper *.rb
  • Reload depend tomcat specific.rb

You can use this command node.from_file to reload attributes from another file:

puts "*** RUNNING bundle-apache-java-tomcat-example default.rb"

# Reload bundle-apache-java-jboss::default attributes to reset var's depending on apache_docroot value
node.from_file(run_context.resolve_attribute( "bundle-apache-java-tomcat", "default" ) )

this is loading cookbook: bundle-apache-java-tomcat, attribs file: default.rb

side note: I ended up leaving this in my cookbook but I wanted to set website attributes a 'simpler' way using a hash. I cannot set one attrib from another while I am initializing a hash at the same time, but I left that code in there in case I might still need it.

I created 2 new bundle cookbooks that I hope makes it easy to set up multiple web sites. You can set or not set a proxy link from apache to tomcat also.

https://github.com/stant/bundle-apache-java-tomcat-example (how to use main one) https://github.com/stant/bundle-apache-java-tomcat (main one)