Just getting started with using chef
recently. I gather that attributes are stored in one large monolithic hash named node
that's available for use in your recipes and templates.
There seem to be multiple ways of defining attributes
- Directly in the recipe itself
- Under an attributes file - e.g.
attributes/default.rb
- In a JSON object that's passed to the
chef-solo
call. e.g. chef-solo -j web.json
Given the above 3, I'm curious
- Are those all the ways attributes can be defined?
- What's the order of precedence here? I'm assuming one of these methods supercedes the others
- Is #3 (the
JSON
method) only valid for chef-solo
?
- I see both
node
and default
hashes defined. What's the difference? My best guess is that the default
hash defined in attributes/default.rb
gets merged into the node
hash?
Thanks!
- Your last question is probably the easiest to answer. In an attributes file you don't have to type 'node' so that this in attributes/default.rb:
default['foo']['bar']['baz'] = 'qux'
Is exactly the same as this in recipes/whatever.rb:
node.default['foo']['bar']['baz'] = 'qux'
In retrospect having different syntaxes for recipes and attributes is confusing, but this design choice dates back to extremely old versions of Chef.
- The -j option is available to chef-client or chef-solo and will both set attributes. Note that these will be 'normal' attributes which are persistent in the node object and are generally not recommended to use. However, the 'run_list', 'chef_environment' and 'tags' on servers are implemented this way. It is generally not recommended to use other 'normal' attributes and to avoid
node.normal['foo'] = 'bar'
or node.set['foo'] = 'bar'
in recipe (or attribute) files. The difference is that if you delete the node.normal
line from the recipe the old setting on a node will persist, while if you delete a node.default
setting out of a recipe then when your run chef-client on the node that setting will get deleted.
What happens in a chef-client run to make this happen is that at the start of the run the client issues a GET to get its old node document from the server. It then wipes the default, override and automatic(ohai) attributes while keeping the 'normal' attributes. The behavior of the default, override and automatic attributes makes the most sense -- you start over at the start of the run and then construct all the state, if its not in the recipe then you don't see a value there. However, normally the run_list is set on the node and nodes do not (often) manage their own run_list. In order to make the run_list persist it is a normal attribute.
The choice of the word 'normal' is unfortunate, as is the choice of 'node.set' setting 'normal' attributes. While those look like obvious choices to use to set attributes users should avoid using those. Again the problem is that they came first and were and are necessary and required for the run_list. Generally stick with default and override attributes only. And typically you can get most of your work done with default attributes, those should be preferred.
- There's a big precedence level picture here:
https://docs.chef.io/attributes.html#attribute-precedence
That's the ultimate source of truth for attribute precedence.
- That graph describes all the different ways that attributes can be defined.
The problem with Chef Attributes is that they've grown organically and sprouted many options to try to help out users who painted themselves into a corner. In general you should never need to touch automatic, normal, force_default or force_override levels of attributes. You should also avoid setting attributes in recipe code. You should move setting attributes in recipes to attribute files. What this leaves is these places to set attributes:
- in the initial -j argument (sets normal attributes, you should limit using this to setting the run_state, over using this is generally smell)
- in the role file as default or override precedence levels (careful with this one though because roles are not versioned and if you touch these attributes a lot you will cause production issues)
- in the cookbook attributes file as default or override precedence levels (this is where you should set most of your attributes)
- in environment files as default or override precedence levels (can be useful for settings like DNS servers in a datacenter, although you can use roles and/or cookbooks for this as well)
You also can set attributes in recipes, but when you do that you invariably wind up getting your next lesson in the two-phase compile-converge parser that runs through the Chef Recipes. If you have recipes that need to communicate with each other its better to use the node.run_state which is just a hash that doesn't get written as node attributes. You can drop node.run_state[:foo] = 'bar' in one recipe and read it in another. You probably will see recipes that set attributes though so you should be aware of that.
Hope That Helps.
When writing a cookbook, I visualize three levels of attributes:
- Default values to converge successfully --
attributes/default.rb
- Local testing override values -- JSON or .kitchen.yml (have you tried chef_zero using ChefDK and Kitchen?)
- Environment/role override values -- link listed in lamont's answer: https://docs.chef.io/attributes.html#attribute-precedence