Wrapper Cookbooks and Computed Attributes

I’m sure that I’m missing something very simple and self-evident, but I’m at a
bit of a loss as to how to handle computed attributes using override statements
in wrapper cookbooks. I’m trying to follow what seems like the advice of the
wise men of Chef and use versioned wrapper cookbooks instead of roles, but
doing so seems like it involves sacrificing the ability to override a default
attribute for a cookbook and have that overridden default value be used in
computing other attributes.

For example, say I have a cookbook called “example”. In its
attributes/default.rb file, it has these two lines:

default[‘example’][‘version’] = '1.0’
default[‘example’][‘module’] = “example-#{example[‘version’]}”

By default, the value of node[‘example’][‘module’] should be properly set to
"example-1.0". If I set the version to “1.1” using a role, I can access
node[‘example’][‘module’] in a recipe, and its value should be “example-1.1”,
which is great. However, if I set up a wrapper cookbook named
"mycompany-example" and have its attributes/default.rb file contain the line
"override[‘example’][‘version’] = ‘1.1’", accessing node[‘example’][‘module’]
in a recipe will give me a value of “example-1.0”, even though accessing
node[‘example’][‘version’] would correctly be ‘1.1’. The ideal outcome would
be to have the module value be “example-1.1”, and I’m lost as to how to make
that happen.

If I’m reading the docs properly, it seems like attribute files are evaluated
in runlist order, so I imagine it’d be possible to just have
"mycompany-example" in the runlist, with no mention of the “example” cookbook,
and then add an “include_attribute ‘example’” line after the override in the
attributes file. However, the docs also say that dependencies specified in
metadata are loaded before the cookbook that depends on them, and I have the
"example" cookbook specified as a dependency in the "mycompany-example"
cookbook’s metadata.rb, making it so that the “example” cookbook’s attributes
file has already been loaded by the time I try to override an attribute that it
uses to compute other attributes. I suppose I could remove that dependency,
but that seems wrong.

Does anyone have any experience with problems like this? Any suggestions, even
if they’re to throw out this approach and go with something different, would be
greatly appreciated.

which version of chef you are running? you can do it in both way, chef 11
has elaborate and reliable attribute precedence methods. You have to use
different precedence levels, at different part of the code.

i like roles, we have berkshel which maintains all community cookbooks with
versions , all custom cookbooks are under site-cookbooks/ , and all of them
starts with pd-* , all of them includes pd-base, which provides a library
method VERSION, pretty much like rubygems, same is available as node
attribute to. Which you can exploit in your recipes. We do have roles and
different levels of attribute precedence. We use strict attribute
conventions, (default always, use override only if its needed ), we use
chefspec to test role+recipe together.

But again, it really depends upon your workflow. How you manage cookbooks?
What tools you use etc. If you are using something like knife-spork &
berkshef/librarian, how comfortable you/your team with chef & ruby etc. I

On Wed, Aug 14, 2013 at 8:05 PM, Justin Locsei justin.locsei@gmail.comwrote:

I'm sure that I'm missing something very simple and self-evident, but I'm
at a
bit of a loss as to how to handle computed attributes using override
statements
in wrapper cookbooks. I'm trying to follow what seems like the advice of
the
wise men of Chef and use versioned wrapper cookbooks instead of roles, but
doing so seems like it involves sacrificing the ability to override a
default
attribute for a cookbook and have that overridden default value be used in
computing other attributes.

For example, say I have a cookbook called "example". In its
attributes/default.rb file, it has these two lines:

default['example']['version'] = '1.0'
default['example']['module'] = "example-#{example['version']}"

By default, the value of node['example']['module'] should be properly set
to
"example-1.0". If I set the version to "1.1" using a role, I can access
node['example']['module'] in a recipe, and its value should be
"example-1.1",
which is great. However, if I set up a wrapper cookbook named
"mycompany-example" and have its attributes/default.rb file contain the
line
"override['example']['version'] = '1.1'", accessing
node['example']['module']
in a recipe will give me a value of "example-1.0", even though accessing
node['example']['version'] would correctly be '1.1'. The ideal outcome
would
be to have the module value be "example-1.1", and I'm lost as to how to
make
that happen.

If I'm reading the docs properly, it seems like attribute files are
evaluated
in runlist order, so I imagine it'd be possible to just have
"mycompany-example" in the runlist, with no mention of the "example"
cookbook,
and then add an "include_attribute 'example'" line after the override in
the
attributes file. However, the docs also say that dependencies specified in
metadata are loaded before the cookbook that depends on them, and I have
the
"example" cookbook specified as a dependency in the "mycompany-example"
cookbook's metadata.rb, making it so that the "example" cookbook's
attributes
file has already been loaded by the time I try to override an attribute
that it
uses to compute other attributes. I suppose I could remove that
dependency,
but that seems wrong.

Does anyone have any experience with problems like this? Any suggestions,
even
if they're to throw out this approach and go with something different,
would be
greatly appreciated.

For example, say I have a cookbook called "example". In its
attributes/default.rb file, it has these two lines:

default['example']['version'] = '1.0'
default['example']
['module'] = "example-#{example['version']}"

By default, the value of node['example']['module'] should be properly set
to
"example-1.0". If I set the version to "1.1" using a role, I can access
node['example']['module'] in a recipe, and its value should be
"example-1.1",
which is great. However, if I set up a wrapper cookbook named
"mycompany-example" and have its attributes/default.rb file contain the
line
"
override['example']['version'] = '1.1'", accessing
node['example']['module']
in a recipe will give me a value of "example-1.0", even though accessing
node['example']['version'] would correctly be '1.1'. The ideal outcome
would
be to have the module value be "example-1.1", and I'm lost as to how to
make
that happen.

You are running into this:
http://tickets.opscode.com/browse/CHEF-4234

TL;DR

In your wrapper cookbook either repeat the computed node attribute...

node.set
['example']['version'] = '1.1'
# not DRY but works
node.set['example']
['module'] = "example-#{example['version']}"

...or use this to re-evaluate the original attributes file in your wrapper
cookbook (see
http://docs.opscode.com/chef/essentials_cookbook_recipes.html#reload-attributes
):

node.set
['example']['version'] = '1.1'
# now re-evaluate the default attributes file from the example cookbook
node.from_file(run_context.resolve_attribute("example", "default.rb"))

Also notice that set is enough -- you don't need to override. Not sure
if that was intended in your example...

Cheers,
Torben

we have to do this entire thing inside a ruby_block resource.

Thank you for your responses, Ranjib and Torben.

I'm using Chef 11, and am still a little new to using Chef server, as my
previous experience was just using Chef solo for a few small-scale
projects. I'm also using Berkshelf to manage cookbooks, and will likely be
adding knife-spork to the workflow as well; I'm still trying to figure out
which tools will work best. I have read up on the different levels of
attribute precedence in Chef 11, and am using them in most of my code to do
what I want. However, even with the well-defined system of attribute
precedence, it doesn't seem like there's a way to override an attribute in
an attribute file that's already been loaded and have it be used to compute
other attribute in that already-loaded file, as confirmed by the ticket
that Torben linked to.

Speaking of Torben, thank you for the links and possible solutions. It's
good to know that the non-DRY version is possible, if not exactly optimal,
so that might be a fallback if nothing else works. However, the ruby_block
(thanks for the clarification Ranjib) used to re-evaluate an arbitrary
cookbook's attributes seems like it might be the way to go, as I've just
tried that out with my wrapper cookbooks and it does exactly what I want.

Thanks for the clarification about using node.set as well. I was using
override in the attribute file mainly because I had seen examples of
people using override as part of the wrapper-cookbook pattern, but the
docs do seem to indicate that set / normal have a higher precedence
than default, and should be enough to bend the wrapped cookbook to a
wrapper's will.

Thanks again for your help!

On Thu, Aug 15, 2013 at 1:26 AM, Torben Knerr ukio@gmx.de wrote:

For example, say I have a cookbook called "example". In its
attributes/default.rb file, it has these two lines:

default['example']['version'] = '1.0'
default['example']
['module'] = "example-#{example['version']}"

By default, the value of node['example']['module'] should be properly set
to
"example-1.0". If I set the version to "1.1" using a role, I can access
node['example']['module'] in a recipe, and its value should be
"example-1.1",
which is great. However, if I set up a wrapper cookbook named
"mycompany-example" and have its attributes/default.rb file contain the
line
"
override['example']['version'] = '1.1'", accessing
node['example']['module']
in a recipe will give me a value of "example-1.0", even though accessing
node['example']['version'] would correctly be '1.1'. The ideal outcome
would
be to have the module value be "example-1.1", and I'm lost as to how to
make
that happen.

You are running into this:
http://tickets.opscode.com/browse/CHEF-4234

TL;DR

In your wrapper cookbook either repeat the computed node attribute...

node.set
['example']['version'] = '1.1'
# not DRY but works
node.set['example']
 ['module'] = "example-#{example['version']}"

...or use this to re-evaluate the original attributes file in your wrapper
cookbook (see
http://docs.opscode.com/chef/essentials_cookbook_recipes.html#reload-attributes
):

node.set
['example']['version'] = '1.1'
# now re-evaluate the default attributes file from the example cookbook
node.from_file(run_context.resolve_attribute("example", "default.rb"))

Also notice that set is enough -- you don't need to override. Not sure
if that was intended in your example...

Cheers,
Torben

--
Music : www.moremonks.com
Photos : www.flickr.com/photos/moremonks