[chef] Changes to Attributes in Chef 11

Chronological Thread 
  • From: Daniel DeLeo < >
  • To:
  • Subject: [chef] Changes to Attributes in Chef 11
  • Date: Wed, 31 Oct 2012 09:20:56 -0700


I'd like to make you all aware of some changes to attributes that are set to be released with Chef 11.


You can no longer use code like:

   node[:foo] = "bar"

Instead, use:

   node.set[:foo] = "bar"
   node.set.foo = "bar"

## Background

In bygone versions of Chef, there was only a single level of attribute precedence, which corresponds to today's "normal" attributes. In order to support compatibility for cookbooks, when multiple layers of attributes were added, the node attribute code supported compatibility with the previous implementation by making code like this:

    node[:foo] = "bar"

…set an attribute at the "normal" level. The right way to do that with the new implementation is:

    node.set[:foo] = "bar"

This had a number of consequences. Since there was ambiguity between whether the ultimate intention of a line of code was to read or write a value (up to the last method call, where this ambiguity is resolved), the code for attributes was very complicated and difficult to understand. You would also see that if you looked at an intermediate level of the attributes tree, you would get a Chef::Node::Attribute object, which would be somewhat opaque unless you call #to_hash on it. And finally, the implementation had a few bugs and surprising behaviors, such as:

* iteration or calling a value repeatedly would not work:

    chef > i = node[:network][:interfaces] ; nil
     => nil 
    chef > i.en0.to_hash
     => {"addresses"=>{"20:c9:d0:45:45:63"=> # snipped for brevity
    chef > i.en0.to_hash
    ArgumentError: Attribute en0 is not defined!

* You could accidentally set attributes by calling an undefined method:

    chef > node.some_attribute?(:foo)
     => :foo 
    chef > node[:some_attribute?]
     => :foo 

## Chef 11 Changes

In Chef 11, we've rewritten the attributes implementation to be much simpler, and behave as you'd expect in the vast majority of cases. In order to do so, we needed to remove the ability to write normal level attributes without first specifying which level you wish to write to. Code such as this:

    node[:foo] = "bar"

Will now raise a `Chef::Exceptions::ImmutableAttributeModification` error. Put another way, read and write are now separate operations, and reads return an immutable, merged view of your attributes. 

This change allowed us to fix the bugs/surprises I detailed above. Here's the same code run under Chef 11:

    chef > i = node[:network][:interfaces] ; nil
     => nil 
    chef > i.en0
     => {"addresses"=>{"20:c9:d0:45:45:63"=> # snip for brevity
    chef > i.en0
     => {"addresses"=>{"20:c9:d0:45:45:63"=> # snip for brevity

    chef > node.some_attribute?(:foo)
    NoMethodError: Undefined node attribute or method `some_attribute?' on `node'

Also notice that when reading attribute values, you don't need to call #to_hash to get something readable back; instead you get a plain Hash (well, actually a Chef::Node::ImmutableMash, which inherits from Mash which inherits from Hash).

Finally, when setting an attribute using method call syntax, you need to use `attribute="value"` form instead of `attribute("value")` form. That is, do this:

    # Works:
    node.set.my_attribute = "value"

…instead of:

    # Does not work:
    node.set.my_attribute "value"

## Impact and What to Look For

All modern cookbook code should work with the new attribute implementation out of the box. You are most likely to see problems if you have cookbooks written before Chef 0.8 or in a pre-0.8 style. For example, an old version of the nginx attributes file looked like this:

    nginx Mash.new unless attribute?("nginx")
    nginx[:version] = "1.0.5"
    nginx[:dir]     = "/etc/nginx"

This code is using the implicit normal attribute setting that is retired in Chef 11, and running this cookbook under Chef 11 will cause an error. If you have code like this in your attributes files, the first thing to do is to consider whether these attributes should, in fact, be normal level attributes. Leaving them at the normal level means that you need to use override attributes to set different values via roles or environments, and that these values will be persisted across multiple chef-client runs. In the above example, these settings are really cookbook level defaults, so you'd want to change them like so:

    default[:nginx][:version] = "1.0.5"
    default[:nginx}[:dir]     = "/etc/nginx"

After making this change, existing nodes that have run the older version of the recipe will need to have the nginx attributes removed from their normal attributes. For a small number of nodes, you can make this change with `knife node edit`. For a larger number of nodes, you can automate this process with `shef` (incidentally renamed `chef-shell` in Chef 11) or `knife exec`.

## What's Not Changed
Hopefully, the above examples make this clear, but to be explicit: you can still set and access attributes using element reference with strings (e.g., `node.default["foo"] = "bar"`), element reference with symbols (e.g., `node.default[:foo] = "bar"`), and method calls (e.g., `node.default.foo = "bar"`).

## Documentation
It is our goal for the Chef 11 release is to exhaustively document all breaking changes (where we cannot avoid them in the first place). Changes that have been accepted and merged are documented here:


## Further Changes

We are considering an additional change to attributes to address CHEF-2936. Right now we're evaluating the impact of the proposed solution. I'll send a similar post to this one if the proposed patch is merged.

Let us know if you have further questions.

Whip up some awesome,

Daniel DeLeo

  • [chef] Changes to Attributes in Chef 11, Daniel DeLeo, 10/31/2012

Archive powered by MHonArc 2.6.16.