Attributes represent information about your node. In addition to the information that can be automatically generated by
ohai
, you can set attributes in Chef recipes or in separate attribute files.
Attribute files are located in the attributes folder of a cookbook. Similar to recipes, the default attribute file is called:
Figure 8-1 shows the format of an attribute when it is specified in a cookbook attribute file.
Attributes can also be set directly in recipes. Figure 8-2 shows the format of an attribute when it is set in a recipe. You must precede the attribute name with
node.
when you set an attribute directly in a recipe.
Because attributes can be defined in multiple places, all attribute values are composed together during a Chef run according to the priority levels as shown in Figure 8-3. Attributes defined by
ohai
have the highest priority, followed by attributes defined in a recipe, then attributes defined in an attribute file. In other words, recipe attributes have a higher priority than those defined in attribute file, and will override them by default. Attributes defined by ohai
trump everything else.
We’ll talk more about precedence levels later in this chapter. However, when setting an attribute in a cookbook, it should (almost) always be a default attribute with default precedence.
Motd-Attributes Cookbook
Let’s experiment with attributes in a cookbook called
motd-cookbook
. This is an attribute-driven version of the motd
cookbook we created in Chapter 7. We’re going to go through the cookbook creation steps quickly in this chapter. If you need a refresher on what each of these commands mean and the expected output, refer back to Chapter 7.
First, generate the
motd-attributes
cookbook using either the chef generate cookbook
command in the Chef Development Kit or the knife cookbook create
command in Chef Client, depending on which is installed on your Chef Development workstation.
Chef Development Kit:
Chef Client:
Next, modify the .kitchen.yml file to use our favorite box image, as shown in Example 8-1.
Create a recipe that uses the
template
resource to generate a /etc/motd file, but this time, we’ll use some attributes in the template file. The initial recipes/default.rb file you create should resemble Example 8-2.
Generate a template file to generate /etc/motd.
Chef Development Kit:
Chef Client - Linux/Mac OS X:
Chef Client - Windows:
Create a template
/etc/motd
that uses some node attributes generated by ohai
, as shown in Example 8-3.
Perform a
kitchen converge
to apply the cookbook to your node, and then when you run kitchen login
it should display the following, with the expected values for node["ipaddress"]
and node["hostname"]
. (Reminder: Once you have issued these commands and verified the output is correct, make sure you run the exit
command to get back to the host prompt directory with your cookbook source.)Setting Attributes
Chef Development Kit:
Chef Client - Linux/Mac OS X:
Chef Client - Windows:
By default, the Chef Development Kit does not create an attributes directory until you tell it to generate an attribute file. Chef Client, on the other hand, always creates the directory, but leaves it up to you to create the default.rb attribute file by hand.
Now, let’s set an attribute in our attributes file following the form outlined in Figure 8-1. Before you edit attributes/default.rb the file will have no content, as shown in Example 8-4.
By convention, when attributes are set in a cookbook’s attribute file, the values are expected to be namespacedunder a top-level key matching the cookbook name. Then all the key/value pairs are contained within the top-level key; for example, the
default['motd-attributes']['company']
value is the string 'Chef'
in this example, as the cookbook name in the metadata.rb file is motd-attributes
. Also, following the form outlined in Figure 8-1, the attribute uses the default
precedence level.
Let’s also set an attribute value in our recipe, as seen in Example 8-5, following the form in Figure 8-2.
Update the motd.erb template as shown in Example 8-6. You can access attributes from any source under the
node
object: attribute file values, values set in recipes, or values set automatically by ohai
. They’re all just the corresponding node
values.
Performing another Chef run and checking the message of the day should produce the following output (again, make sure to
exit
back out to the host prompt):Basic Attribute Priority
Now let’s experiment with the basics of attribute priorities by trying to reset values set elsewhere. As shown in Example 8-7, modify the recipe/default.rb so that it tries to reset the value of a higher priority automatic attribute set by
ohai
, and a lower priority attribute defined in the attribute file.
Perform a Chef run and check to see the resulting values:
You should notice:
- The
node['motd-attributes']['company']
value set in the recipe ‘My Company’ has a higher prioritythan the value ‘Chef’ set in the attribute file, so this is the value displayed in the template. - The
node['ipaddress']
value set in the recipe'1.1.1.1'
has a lower priority than the automatic value10.0.2.15
set byohai
, so the value set in the recipe is ignored, and the template displays the higher priority value.
These priorities represent how the variables are intended to be used. Values set in an attribute file are intended to be defaults that can be overridden by a recipe. On the other hand, the automatic values set by
ohai
should never be overridden as they represent important information about a system, such as the fact that it shouldn’t be easy to reset the node’s IP address, for example.Include_Recipe
You might wonder why there is a need for this priority mechanism with Chef attributes. It’s because like in most other programming languages, a Chef recipe can reference other Chef recipe files using an “include” statement:
include_recipe
. So when your Chef code is processed during a Chef run, it could possibly include a chain of references to multiple recipe files that might even be in other cookbooks, as shown in Figure 8-4. Because you can use include_recipe
, Chef code might contain conflicting attribute assignments, and there needs to be some guidelines for how these conflicts are resolved.NOTE
A rule of thumb many Chef coders find useful is that a recipe file shouldn’t be longer than a “screenful” of code—between one dozen and two lines. Anything longer gets difficult to understand at a glance because you have to scroll up or down to see all the code. When your recipe code starts getting this long, consider breaking it up into multiple recipe files, stitching them together with
include_recipe
statements.
In order to use
include_recipe
, you reference a recipe in the exact same form that you reference a recipe in a run list, in the form "<cookbook>::<recipe>"
. For example, "motd-attributes::message"
.
Let’s add an
include_recipe
statement to our motd-attributes
cookbook. Generate a new recipe called message
.
Chef Development Kit:
Chef Client - Linux/Mac OS X:
Chef Client - Windows:
Now edit the recipes/message.rb file containing the recipe as shown in Example 8-8. The message recipe will be used to set additional values related to the message of the day.
TIP
Note that our .kitchen.yml file includes only a reference to the
default
recipe in its run list. We never intend for external consumers of our cookbook to refer to this message
recipe directly. We’re just using the message
recipe to organize our code, and it is assumed that include_recipe
will be used inside the default
recipe to include any other necessary code:
Some Chef coders use the convention of putting an underscore prefix (“_”) in the name of recipes that are “private”—recipes used merely to organize Chef code into smaller, more understandable chunks. They name the recipe file
"_message.rb"
to make this intent more clear in the cookbook file structure.
Also, you need to add an
include_recipe
statement to your default.rb. The include_recipe
statement ensures that the message.rb file gets processed during the Chef run. Otherwise, only the recipe file default.rb will be evaluated when the run list is motd-attributes::default
. We won’t remove any of the attribute values we set earlier, in order to illustrate a few more attribute priority concepts.include_recipe
statements can be present anywhere in a recipe file. When the Chef code is evaluated, the include_recipe
statement is replaced with an expansion of the recipe code that is referenced, as shown in Figure 8-5.
Perform a
kitchen converge
and inspect the resulting message. When there is a duplicate attribute value set at the same priority level, the last attribute value setting wins. In this case, with the include_recipe
expansion, node.default["motd-attributes"]["company"]
is set twice; the last value set before the template
resource is what is used—“the greatest company in the universe”:Attribute Precedence
Figure 8-6 shows the three most commonly used levels of precedence that can be used in attribute definitions:
When setting an attribute in a cookbook, it should (almost) always be a default attribute. There is one exception to this rule, where it makes sense to use
override
precedence with environments. We’ll explore the reasoning behind this in Chapter 15.
Other than the special case of Chef environments, attribute precedence is most frequently used when you want to customize Chef, which is beyond the scope of this book. When you want to implement a new feature in Chef, such as a custom resource, being able to tinker with Chef’s powerful attribute precedence engine lets you add new features to the language that follow the general expectations for basic attribute priority outlined in this chapter. For more information on this topic, refer to Customizing Chef, by Jon Cowie.
Debugging Attributes
If you need to debug where attributes are being set, the node object exposes a helpful
node.debug_value()
method. Let’s say, for example, you did not know that ohai
set node['ipaddress']
using automatic precedence. You could determine this by using node.debug_value()
.
Modify recipes/default.rb as shown in Example 8-10. Because we have access to the full power of the Ruby language in a Chef recipe, we also make use of the “pretty printer for Ruby objects” function included with the core Ruby library. This function will print out the contents of a Ruby object in a more readable format. The
node.debug_value()
returns the raw contents of an object; pp
just makes the output look nicer.
When you run
kitchen converge
, you should see the following output:
From here, you could sort out that the
node['ipaddress']
attribute was set to the value of "10.0.2.15"
at the automatic
precedence level and set to the value "1.1.1.1"
at the default
precedence level. So Chef did register that you set the value to "1.1.1.1"
, but the override took precedence.NOTE
You might notice that there are more precedence levels in the
node.debug_level()
output than we have discussed so far in this book. We are intentionally simplifying attribute precedence in this book. If you follow the guideline of using default precedence, unless there is a specific need, you rarely have to deal with the full complexity of attribute precedence with basic Chef cookbook code.
A lot of the complexity of attributes arose as the precedence feature was introduced in Chef and cookbooks needed to be written to be backward-compatible with older versions. With Chef 11, most cookbooks follow the default precedence guideline.
If you’d like to learn more about the details of attribute precedence, refer to the aforementioned Customizing Chef.
Debugging when an attribute is set in two or more places at the same precedence level is a little more difficult to trace, but still not too complicated. You just need to sprinkle
node.debug_value()
statements before and after include_recipe
calls.
For example, say we didn’t know that the
motd-attributes::message
recipe set node.default['motd-attributes']['message']
. We could figure this out by sprinkling more node.debug_value()
calls in our code. Change recipes/default.rb as shown in Example 8-11, adding a call to node.debug_value('motd-attributes', 'message')
before and after include_recipe
.
Run
kitchen converge
again, and the output of node.debug_value()
should resemble the output shown in Figure 8-7, with the second-to-last dump of node.debug_value()
being before include_recipe
and the last being after include_recipe
.
From this output, you could sort out that something in
motd-attributes::message
recipe set the attribute node['motd-attributes']['company']
to "the best company in the universe"
using the default
precedence, overriding what was set earlier.Summary
In this chapter, we presented an overview of attributes and how Chef uses attributes to capture the state of a node. Attributes can be defined in multiple places:
- Automatically via
ohai
- Attribute files
- Recipes
- Other cookbooks
Because attributes can be defined in multiple places, Chef defines a priority scheme for how attribute values are composed from multiple sources. You can tweak this priority scheme by manually providing different levels of precedence, including automatic, default, and override. In general, you should use the default precedence, unless there is a specific need to extend or customize Chef, letting Chef choose the default priority in which attribute values are merged together.
In the next chapter, we’ll be introducing Chef Server. Chef Server is a useful central repository for all shared information necessary to manage multiple nodes effectively.
No comments:
Post a Comment