6

In learning chef, I see conflicting patterns for wrapper cookbooks. For example.

Some cookbooks use default.rb and others use customize.rb for overrides.

attributes/default.rb

attributes/customize.rb 

Which is best practice?

Also, some wrapper cookbooks have parameters like this, in the recipes/default.rb file :

normal['foo']['bar'] = 42

Whereas others have

default['foo']['bar'] = 42

And some have

node.set['foo']['bar'] = 42

Additionally some cookbooks use symbols, and other strings

['foo']['bar']
[:foo][:bar]

Which style should I use?

Update

Looks like chef has released an official style guide, which addresses at least part of the question (e.g. symbol vs strings). https://docs.chef.io/ruby.html#

spuder
  • 17,437
  • 19
  • 87
  • 153
  • Don't forget using `:` or `"` like `default[:foo][:bar] = 42` and `default["foo"]["bar"] = 42`! Does the fun ever start!? See: https://docs.chef.io/attributes.html for more info. In all seriousness though, you can name your attributes file whatever you want, in fact you can split your attributes into multiple files for clarity, Chef will load all of them. If you only have one, I would keep it `default.rb` as that's what most cookbooks use – Display Name is missing Feb 11 '15 at 23:37

2 Answers2

10

I haven't seen any formal best practice on how to override attributes in wrapper cookbooks, I think one has to pick a style and as long as all your cookbooks are consistent within your organization, you should be good. What follows is my person opinions.


Attributes File

We initially followed the style of separating attribute files similar to that attributes/customize.rb format you described. However, we found it's much simpler to just keep all attributes in attributes/default.rb, especially since our attributes stay under 50 lines of code.

Override Precedence

We always use the lowest attribute precedence when overriding attributes in our wrapper cookbook. We stick to default whenever possible, because the order of precedence dictates your cookbook's default wins out over the wrapped cookbooks default.

Attribute Access Style

Most cookbooks you see out there use the string access format.

default['foo']['bar'] = 42

In fact there's a foodcritic rule specifically discouraging the use of symbol access. Foodcritic also encourages using a consistent style for accessing attributes. If you're deciding on a style, keep in mind that the community has pretty much decided on the string access format.

However, there is another way. Attributes in Chef are not a Hash, they're actually a Mash. Mash happens to support accessing attributes as methods, so:

default.foo.bar = 42

We prefer that syntax because it's more compact.

However, be cautious, as Tejay Cardon points out, Chef implemented this with method_missing, so you could run into issues if the attribute name is the same as a current (or future) node object method. We preface all our cookbooks with our org name, so collision is unlikely, but when we override community cookbooks we might run into issues. Thankfully testing should catch any such lapses.

Cautionary Note

Overriding attributes in a wrapper cookbook will not work in surprising ways, particularly when string interpolation is involved. This is explained very well in this blog post. The main gist is if a cookbook defines attributes as:

default['version'] = '1.0'
default['url'] = "http://example.com/#{node['version']}.zip"

And in your wrapper cookbook you override version:

default['version'] = '2.0'

The default['url'] will still resolve to http://example.com/1.0.zip, not http://example.com/2.0.zip as you might expect. This happens because the url attribute is interpolated before your override happens. The blog post goes into more depth and offers a potential solution.

Wrap Up

At the end of the day, there are multiple ways to do things in Chef and the community is still maturing. There's some best practices around writing reusable cookbooks, etc, but the practices are still evolving.

The best thing you can do is try the various styles and figure out which one works best for you and your team. Once you've decided on a style, I'd suggest putting together a style guide (I've used this Angular style guide as inspiration). You can also enforce your style guide with custom foodcritic rules. We've had success with this approach.

Community
  • 1
  • 1
Arthur Maltson
  • 5,760
  • 4
  • 30
  • 33
  • I'd upvote this 10 times if I could. Thank you for the wonderful clarification of something that has confused me for ages! – Aegix Aug 07 '15 at 22:31
  • Found an official chef style guide. Question has been updated with link. https://docs.chef.io/ruby.html# – spuder Sep 02 '15 at 17:14
3

There is very little in the way of best practices out there, sadly, but there are a few gotchas to be careful about.

Attribute loading order

Attributes files are loaded (Executed) in a very specific, albeit not very clear, order.

  1. The required cookbooks are resolved based on the runlist and metadata for each cookbook referenced in the runlist, and all dependencies.
  2. Once a list of involved cookbooks has been compiled, they are ordered lexigraphically.
  3. The attribute files from each cookbook are then ordered lexigraphically.
  4. Execution starts at the to of the list, however, if an include_attribute statement is encountered, that attribute file will be immediately executed (and removed from the list so that it is not executed a second time.)

This is important because if the same attribute is set at the same level ie default by more than one attributes file, then the last file to execute will determine which value wins. __So always include_attribute the attribute file from your library cookbook at the top of the attribute file for your wrapper cookbook. Then set attributes at the default level (so that they can more easily be overridden elsewhere).

One of these things is not like the others

Normal attributes (node.set or node.normal) are a funny beast. They persist between chef runs. So once you set an attribute to normal, it will stick around until you either remove it, or change it. Sometimes this is a good thing, like a uuid you want to remember, but sometimes it is an unexpected side effect.

Style

symbols and strings, and methods, oh my.

There is a LOT of debate regarding node.attribute, node[:attribute], and node['attribute'] access styles. Just read up on foodcritic 001 if you want to see both sides of the coin. In my opinion, node['attribute'] has won the majority of community cookbook developers, but it is a pretty slim majority. node.attribute is almost universally frowned upon because it can have evil side effects. Ultimately, you use what you like best, but if your library cookbooks are fairly consistent, I'd suggest you follow their convention.

Where to override

Personally, I try hard to never set an attribute outside of an attributes file. It too often leads to headaches. It's just easier to keep all attribute set operations in the attributes files, so you can easily hunt them down. But there are times when you either cannot do it in the attributes file, or it just makes more sense in the recipe. As with most programing rules, you just have to weigh consistency with what feels right.

Tejay Cardon
  • 4,193
  • 2
  • 16
  • 31
  • I've to disagree on one point, every attribute file is read and loaded in lexical order [doc here](http://docs.chef.io/attributes.html#attribute-files), there's no filter to corresponding recipe. I've cookbooks with attributes files not matching any recipe and they're loaded. – Tensibai Feb 12 '15 at 16:17
  • 1
    I stand corrected. I ran a bunch of tests on this about 6 months ago, and I'm confident that (using chefspec) it was only loading those with a corresponding recipe. But just now I got different results. Either I'm nuts or either chef or chefspec changed something. (I'd easily believe any of those three) – Tejay Cardon Feb 12 '15 at 18:36
  • I highly suspect a chefspec behavior. Our actual test coverage is very limited but I'll try to find time to test this (interesting edge case behavior we may fall in at some point). Forgot to tell the answer is crystal clear ;) – Tensibai Feb 12 '15 at 18:43
  • Yea, I find that attribute files that don't have the same name as a recipe are usually included in another attributes file with `include_attributes` somewhere. So they would still be evaluated. – Tejay Cardon Feb 12 '15 at 19:36
  • Oh, small precision again: the cookbook load order is not lexical but the runlist order with dependencies in order an loaded bottom up. The dependency solver may came in the path when a cookbook is included twice or more as it will be loaded only once (the sooner for override, so the last time in the runlist in fact) – Tensibai Feb 12 '15 at 19:46
  • Yea, I'm not totally clear on the cookbook load order. I'd have to write some tests to really get my head around it. What's this bottom up thing all about? – Tejay Cardon Feb 12 '15 at 20:13
  • It's a chef 11 improvement for deterministic load order from this ticket https://tickets.opscode.com/browse/CHEF-3376. Too long to resume here. I'm quite sure there's some other doc over it, probably at docs.chef.io but didn't searched it – Tensibai Feb 12 '15 at 20:36
  • @TejayCardon while I definitely agree that string access has won out from a community prospective, I'm wondering why you said "`node.attribute` is almost universally frowned upon because it can have evil side effects"? – Arthur Maltson Feb 13 '15 at 18:52
  • @ArthurMaltson the method style access is based on the ruby `method_missing`. As a result, you can encounter collisions between an attribute and a real method. It also puts constraints on any monkey patching, and can break with any new methods added to the node object. Finally, it leaves a great deal of ambiguity regarding which methods are methods, and which are attributes. – Tejay Cardon Feb 14 '15 at 02:06
  • Huh, I was hoping it was more of an `OpenStruct` like concept, but yeah, since they're using `method_missing` those are all very good points. I guess in our case all of our cookbooks are prefaced with our org name, so the chance of collision is unlikely. However, there is definitely a chance for collision when we override node attributes. Thanks, something to consider... we always have tests to fall back on. I like the terseness of the method access approach... – Arthur Maltson Feb 14 '15 at 02:55