About Attributes
An attribute is a specific detail about a node. Attributes are used by Chef Infra Client to understand:
- The current state of the node
- What the state of the node was at the end of the previous Chef Infra Client run
- What the state of the node should be at the end of the current Chef Infra Client run
Attributes are defined by:
- The state of the node itself
- Attributes passed via JSON on the CLI
- Cookbooks (in attribute files and/or recipes)
- Roles
- Environments
- Policyfiles
During every Chef Infra Client run, Chef Infra Client builds the attribute list using:
- Attributes passed via JSON on the CLI
- Data about the node collected by [Ohai].
- The node object that was saved to the Chef Infra Server at the end of the previous Chef Infra Client run.
- The rebuilt node object from the current Chef Infra Client run, after it is updated for changes to cookbooks (attribute files and/or recipes), roles, and/or environments, and updated for any changes to the state of the node itself.
After the node object is rebuilt, all of the attributes are compared, and then the node is updated based on attribute precedence. At the end of every Chef Infra Client run, the node object that defines the current state of the node is uploaded to the Chef Infra Server so that it can be indexed for search.
So how does Chef Infra Client determine which value should be applied? Keep reading to learn more about how attributes work, including more about the types of attributes, where attributes are saved, and how Chef Infra Client chooses which attribute to apply.
Attribute Persistence
All attributes except for normal attributes are reset at the beginning
of a Chef Infra Client run. Attributes set via chef-client -j
with a
JSON file have normal precedence and are persisted between Chef Infra
Client runs. Chef Infra Client rebuilds these attributes using automatic
attributes collected by Ohai at the beginning of each Chef Infra Client
run, and then uses default and override attributes that are specified in
cookbooks, roles, environments, and Policyfiles. All attributes are then
merged and applied to the node according to attribute precedence. The
attributes that were applied to the node are saved to the Chef Infra
Server as part of the node object at the conclusion of each Chef Infra
Client run.
Attribute Types
Chef Infra Client uses six types of attributes to determine the value that is applied to a node during a Chef Infra Client run. In addition, Chef Infra Client gathers attribute values from up to five locations. The combination of attribute types and sources makes up to 15 different competing values available during a Chef Infra Client run:
Attribute Type | Description |
---|---|
default | A default attribute is automatically reset at the start of every Chef
Infra Client run and has the lowest attribute precedence. Use default
attributes as often as possible in cookbooks. |
force_default | Use the force_default attribute to ensure that an attribute defined in
a cookbook (by an attribute file or by a recipe) takes precedence over a
default attribute set by a role or an environment. |
normal | A normal attribute is a setting that persists in the node object. A
normal attribute has a higher attribute precedence than a default
attribute. |
override | An override attribute is automatically reset at the start of every
Chef Infra Client run and has a higher attribute precedence than
default , force_default , and normal attributes. An override
attribute is most often specified in a recipe, but can be specified in
an attribute file, for a role, and/or for an environment. A cookbook
should be authored so that it uses override attributes only when
required. |
force_override | Use the force_override attribute to ensure that an attribute defined
in a cookbook (by an attribute file or by a recipe) takes precedence
over an override attribute set by a role or an environment. |
automatic | An automatic attribute contains data that is identified by Ohai at the
beginning of every Chef Infra Client run. An automatic attribute
cannot be modified and always has the highest attribute precedence. |
Attribute Sources
Attributes are provided to Chef Infra Client from the following locations:
- JSON files passed via the
chef-client -j
- Nodes (collected by Ohai at the start of each Chef Infra Client run)
- Attribute files (in cookbooks)
- Recipes (in cookbooks)
- Environments
- Roles
- Policyfiles
Notes:
- Many attributes are maintained in the chef-repo for Policyfiles, environments, roles, and cookbooks (attribute files and recipes)
- Many attributes are collected by Ohai on each individual node at the start of every Chef Infra Client run
- The attributes that are maintained in the chef-repo are uploaded to the Chef Infra Server from the workstation, periodically
- Chef Infra Client will pull down the node object from the Chef Infra
Server and then reset all the attributes except
normal
. The node object will contain the attribute data from the previous Chef Infra Client run including attributes set with JSON files via-j
. - Chef Infra Client will update the cookbooks on the node (if required), which updates the attributes contained in attribute files and recipes
- Chef Infra Client will update the role and environment data (if required)
- Chef Infra Client will rebuild the attribute list and apply attribute precedence while configuring the node
- Chef Infra Client pushes the node object to the Chef Infra Server at the end of a Chef Infra Client run; the updated node object on the Chef Infra Server is then indexed for search and is stored until the next Chef Infra Client run
Automatic (Ohai)
An automatic attribute is a specific detail about a node, such as an IP address, a host name, a list of loaded kernel modules, and so on. Automatic attributes are detected by Ohai and are then used by Chef Infra Client to ensure that they are handled properly during every Chef Infra Client run. The most commonly accessed automatic attributes are:
Attribute | Description |
---|---|
node['platform'] | The platform on which a node is running. This attribute helps determine which providers will be used. |
node['platform_version'] | The version of the platform. This attribute helps determine which providers will be used. |
node['ipaddress'] | The IP address for a node. If the node has a default route, this is the IPV4 address for the interface. If the node does not have a default route, the value for this attribute should be nil . The IP address for default route is the recommended default value. |
node['macaddress'] | The MAC address for a node, determined by the same interface that detects the node['ipaddress'] . |
node['fqdn'] | The fully qualified domain name for a node. This is used as the name of a node unless otherwise set. |
node['hostname'] | The host name for the node. |
node['domain'] | The domain for the node. |
node['recipes'] | A list of recipes associated with a node (and part of that node's run-list). |
node['roles'] | A list of roles associated with a node (and part of that node's run-list). |
node['ohai_time'] | The time at which Ohai was last run. This attribute is not commonly used in recipes, but it is saved to the Chef Infra Server and can be accessed using the knife status subcommand. |
Ohai collects a list of automatic attributes at the start of each Chef
Infra Client run. This list will vary from organization to organization,
by server type, and by the platform that runs those servers. All the
attributes collected by Ohai are unmodifiable by Chef Infra Client. Run
the ohai
command on a system to see which automatic attributes Ohai
has collected for a particular node.
Attribute Files
An attribute file is located in the attributes/
sub-directory for a
cookbook. When a cookbook is run against a node, the attributes
contained in all attribute files are evaluated in the context of the
node object. Node methods (when present) are used to set attribute
values on a node. For example, the apache2
cookbook contains an
attribute file called default.rb
, which contains the following
attributes:
default['apache']['dir'] = '/etc/apache2'
default['apache']['listen_ports'] = [ '80','443' ]
The use of the node object (node
) is implicit in the previous example;
the following example defines the node object itself as part of the
attribute:
node.default['apache']['dir'] = '/etc/apache2'
node.default['apache']['listen_ports'] = [ '80','443' ]
Attribute Evaluation Order
Chef Infra Client evaluates attributes in the order defined by the run-list, including any attributes that are in the run-list because of cookbook dependencies.
Use Attribute Files
An attribute is a specific detail about a node, such as an IP address, a host name, a list of loaded kernel modules, the version(s) of available programming languages that are available, and so on. An attribute may be unique to a specific node or it can be identical across every node in the organization. Attributes are most commonly set from a cookbook, by using knife, or are retrieved by Ohai from each node prior to every Chef Infra Client run. All attributes are indexed for search on the Chef Infra Server. Good candidates for attributes include:
- any cross-platform abstraction for an application, such as the path to a configuration file
- default values for tunable settings, such as the amount of memory assigned to a process or the number of workers to spawn
- anything that may need to be persisted in node data between Chef Infra Client runs
In general, attribute precedence is set to enable cookbooks and roles to define attribute defaults, for normal attributes to define the values that should be specific for a node, and for override attributes to force a certain value, even when a node already has that value specified.
One approach is to set attributes at the same precedence level by setting attributes in a cookbook’s attribute files, and then also setting the same default attributes (but with different values) using a role. The attributes set in the role will be deep merged on top of the attributes from the attribute file, and the attributes set by the role will take precedence over the attributes specified in the cookbook’s attribute files.
Another (much less common) approach is to set a value only if an
attribute has no value. This can be done by using the _unless
variants
of the attribute priority methods:
default_unless
set_unless
(normal_unless
is an alias ofset_unless
; use either alias to set an attribute with a normal attribute precedence.)Note
This method was removed in Chef Client 14. Please use
default_unless
oroverride_unless
instead.override_unless
Note
Use the _unless
variants carefully (and only when necessary) because
when they are used, attributes applied to nodes may become out of sync
with the values in the cookbooks as these cookbooks are updated. This
approach can create situations where two otherwise identical nodes end
up having slightly different configurations and can also be a challenge
to debug.
File Methods
Use the following methods within the attributes file for a cookbook or within a recipe. These methods correspond to the attribute type of the same name:
override
default
normal
(orset
, whereset
is an alias fornormal
)_unless
attribute?
attribute?
A useful method that is related to attributes is the attribute?
method. This method will check for the existence of an attribute, so
that processing can be done in an attributes file or recipe, but only if
a specific attribute exists.
Using attribute?()
in an attributes file:
if attribute?('ec2')
# ... set stuff related to EC2
end
Using attribute?()
in a recipe:
if node.attribute?('ec2')
# ... do stuff on EC2 nodes
end
Recipes
A recipe is the most fundamental configuration element within the organization. A recipe:
- Is authored using Ruby, which is a programming language designed to read and behave in a predictable manner
- Is mostly a collection of resources, defined using patterns (resource names, attribute-value pairs, and actions); helper code is added around this using Ruby, when needed
- Must define everything that is required to configure part of a system
- Must be stored in a cookbook
- May be included in another recipe
- May use the results of a search query and read the contents of a data bag (including an encrypted data bag)
- May have a dependency on one (or more) recipes
- Must be added to a run-list before it can be used by Chef Infra Client
- Is always executed in the same order as listed in a run-list
An attribute can be defined in a cookbook (or a recipe) and then used to
override the default settings on a node. When a cookbook is loaded
during a Chef Infra Client run, these attributes are compared to the
attributes that are already present on the node. Attributes that are
defined in attribute files are first loaded according to cookbook order.
For each cookbook, attributes in the default.rb
file are loaded first,
and then additional attribute files (if present) are loaded in lexical
sort order. When the cookbook attributes take precedence over the
default attributes, Chef Infra Client applies those new settings and
values during a Chef Infra Client run on the node.
Roles
A role is a way to define certain patterns and processes that exist across nodes in an organization as belonging to a single job function. Each role consists of zero (or more) attributes and a run-list. Each node can have zero (or more) roles assigned to it. When a role is run against a node, the configuration details of that node are compared against the attributes of the role, and then the contents of that role’s run-list are applied to the node’s configuration details. When a Chef Infra Client runs, it merges its own attributes and run-lists with those contained within each assigned role.
An attribute can be defined in a role and then used to override the default settings on a node. When a role is applied during a Chef Infra Client run, these attributes are compared to the attributes that are already present on the node. When the role attributes take precedence over the default attributes, Chef Infra Client applies those new settings and values during a Chef Infra Client run.
A role attribute can only be set to be a default attribute or an
override attribute. A role attribute cannot be set to be a normal
attribute. Use the default_attribute
and override_attribute
methods
in the Ruby DSL file or the default_attributes
and
override_attributes
hashes in a JSON data file.
Environments
An environment is a way to map an organization’s real-life workflow to what can be configured and managed when using Chef Infra. This mapping is accomplished by setting attributes and pinning cookbooks at the environment level. With environments, you can change cookbook configurations depending on the system’s designation. For example, by designating different staging and production environments, you can then define the correct URL of a database server for each environment. Environments also allow organizations to move new cookbook releases from staging to production with confidence by stepping releases through testing environments before entering production.
Attributes can be defined in an environment and then used to override the default attributes in a cookbook. When an environment is applied during a Chef Infra Client run, environment attributes are compared to the attributes that are already present on the node. When the environment attributes take precedence over the default attributes, Chef Infra Client applies those new settings and values during a Chef Infra Client run.
Environment attributes can be set to either default
attribute level or
an override
attribute level.
Attribute Precedence
Attributes are always applied by Chef Infra Client in the following order:
- A
default
attribute located in a cookbook attribute file - A
default
attribute located in a recipe - A
default
attribute located in an environment - A
default
attribute located in a role - A
force_default
attribute located in a cookbook attribute file - A
force_default
attribute located in a recipe - A
normal
attribute located in a JSON file passed viachef-client -j
- A
normal
attribute located in a cookbook attribute file - A
normal
attribute located in a recipe - An
override
attribute located in a cookbook attribute file - An
override
attribute located in a recipe - An
override
attribute located in a role - An
override
attribute located in an environment - A
force_override
attribute located in a cookbook attribute file - A
force_override
attribute located in a recipe - An
automatic
attribute identified by Ohai at the start of a Chef Infra Client run
where the last attribute in the list is the one that is applied to the node.
Note
The attribute precedence order for roles and environments is reversed
for default
and override
attributes. The precedence order for
default
attributes is environment, then role. The precedence order for
override
attributes is role, then environment. Applying environment
override
attributes after role override
attributes allows the same
role to be used across multiple environments, yet ensuring that values
can be set that are specific to each environment (when required). For
example, the role for an application server may exist in all
environments, yet one environment may use a database server that is
different from other environments.
Attribute precedence, viewed from the same perspective as the overview diagram, where the numbers in the diagram match the order of attribute precedence:
Attribute precedence, when viewed as a table:
Blacklist Attributes
Warning
When attribute blacklist settings are used, any attribute defined in a
blacklist will not be saved and any attribute that is not defined in a
blacklist will be saved. Each attribute type is blacklisted
independently of the other attribute types. For example, if
automatic_attribute_blacklist
defines attributes that will not be
saved, but normal_attribute_blacklist
, default_attribute_blacklist
,
and override_attribute_blacklist
are not defined, then all normal
attributes, default attributes, and override attributes will be saved,
as well as the automatic attributes that were not specifically excluded
through blacklisting.
Attributes that should not be saved by a node may be blacklisted in the client.rb file. The blacklist is a Hash of keys that specify each attribute to be filtered out.
Attributes are blacklisted by attribute type, with each attribute type
being blacklisted independently. Each attribute type—automatic
,
default
, normal
, and override
—may define blacklists by using the
following settings in the client.rb file:
Setting | Description |
---|---|
automatic_attribute_blacklist | A hash that blacklists automatic attributes, preventing blacklisted attributes from being saved. For example: ['network/interfaces/eth0'] . Default value: nil , all attributes are saved. If the array is empty, all attributes are saved. |
default_attribute_blacklist | A hash that blacklists default attributes, preventing blacklisted attributes from being saved. For example: ['filesystem/dev/disk0s2/size'] . Default value: nil , all attributes are saved. If the array is empty, all attributes are saved. |
normal_attribute_blacklist | A hash that blacklists normal attributes, preventing blacklisted attributes from being saved. For example: ['filesystem/dev/disk0s2/size'] . Default value: nil , all attributes are saved. If the array is empty, all attributes are saved. |
override_attribute_blacklist | A hash that blacklists override attributes, preventing blacklisted attributes from being saved. For example: ['map - autohome/size'] . Default value: nil , all attributes are saved. If the array is empty, all attributes are saved. |
Warning
The recommended practice is to use only automatic_attribute_blacklist
for blacklisting attributes. This is primarily because automatic
attributes generate the most data, but also that normal, default, and
override attributes are typically much more important attributes and are
more likely to cause issues if they are blacklisted incorrectly.
For example, automatic attribute data similar to:
{
"filesystem" => {
"/dev/disk0s2" => {
"size" => "10mb"
},
"map - autohome" => {
"size" => "10mb"
}
},
"network" => {
"interfaces" => {
"eth0" => {...},
"eth1" => {...},
}
}
}
To blacklist the filesystem
attributes and allow the other attributes
to be saved, update the client.rb file:
automatic_attribute_blacklist ['filesystem']
When a blacklist is defined, any attribute of that type that is not
specified in that attribute blacklist will be saved. So based on the
previous blacklist for automatic attributes, the filesystem
and
map - autohome
attributes will not be saved, but the network
attributes will.
For attributes that contain slashes (/
) within the attribute value,
such as the filesystem
attribute '/dev/diskos2'
, use an array. For
example:
automatic_attribute_blacklist [['filesystem', '/dev/diskos2']]
Whitelist Attributes
Warning
When attribute whitelist settings are used, only the attributes defined
in a whitelist will be saved and any attribute that is not defined in a
whitelist will not be saved. Each attribute type is whitelisted
independently of the other attribute types. For example, if
automatic_attribute_whitelist
defines attributes to be saved, but
normal_attribute_whitelist
, default_attribute_whitelist
, and
override_attribute_whitelist
are not defined, then all normal
attributes, default attributes, and override attributes are saved, as
well as the automatic attributes that were specifically included through
whitelisting.
Attributes that should be saved by a node may be whitelisted in the client.rb file. The whitelist is a hash of keys that specifies each attribute to be saved.
Attributes are whitelisted by attribute type, with each attribute type
being whitelisted independently. Each attribute type—automatic
,
default
, normal
, and override
—may define whitelists by using the
following settings in the client.rb file:
Setting | Description |
---|---|
automatic_attribute_whitelist | A hash that whitelists automatic attributes, preventing non-whitelisted attributes from being saved. For example: ['network/interfaces/eth0'] . Default value: nil , all attributes are saved. If the hash is empty, no attributes are saved. |
default_attribute_whitelist | A hash that whitelists default attributes, preventing non-whitelisted attributes from being saved. For example: ['filesystem/dev/disk0s2/size'] . Default value: nil , all attributes are saved. If the hash is empty, no attributes are saved. |
normal_attribute_whitelist | A hash that whitelists normal attributes, preventing non-whitelisted attributes from being saved. For example: ['filesystem/dev/disk0s2/size'] . Default value: nil , all attributes are saved. If the hash is empty, no attributes are saved. |
override_attribute_whitelist | A hash that whitelists override attributes, preventing non-whitelisted attributes from being saved. For example: ['map - autohome/size'] . Default value: nil , all attributes are saved. If the hash is empty, no attributes are saved. |
Warning
The recommended practice is to only use automatic_attribute_whitelist
to whitelist attributes. This is primarily because automatic attributes
generate the most data, but also that normal, default, and override
attributes are typically much more important attributes and are more
likely to cause issues if they are whitelisted incorrectly.
For example, automatic attribute data similar to:
{
"filesystem" => {
"/dev/disk0s2" => {
"size" => "10mb"
},
"map - autohome" => {
"size" => "10mb"
}
},
"network" => {
"interfaces" => {
"eth0" => {...},
"eth1" => {...},
}
}
}
To whitelist the network
attributes and prevent the other attributes
from being saved, update the client.rb file:
automatic_attribute_whitelist ['network/interfaces/']
When a whitelist is defined, any attribute of that type that is not
specified in that attribute whitelist will not be saved. So based on
the previous whitelist for automatic attributes, the filesystem
and
map - autohome
attributes will not be saved, but the network
attributes will.
Leave the value empty to prevent all attributes of that attribute type from being saved:
automatic_attribute_whitelist []
For attributes that contain slashes (/
) within the attribute value,
such as the filesystem
attribute '/dev/diskos2'
, use an array. For
example:
automatic_attribute_whitelist [['filesystem', '/dev/diskos2']]
Examples
The following examples are listed from low to high precedence.
Default attribute in /attributes/default.rb
default['apache']['dir'] = '/etc/apache2'
Default attribute in node object in recipe
node.default['apache']['dir'] = '/etc/apache2'
Default attribute in /environments/environment_name.rb
default_attributes({ 'apache' => {'dir' => '/etc/apache2'}})
Default attribute in /roles/role_name.rb
default_attributes({ 'apache' => {'dir' => '/etc/apache2'}})
Normal attribute set as a cookbook attribute
set['apache']['dir'] = '/etc/apache2'
normal['apache']['dir'] = '/etc/apache2' #set is an alias of normal.
Normal attribute set in a recipe
node.normal['apache']['dir'] = '/etc/apache2'
Override attribute in /attributes/default.rb
override['apache']['dir'] = '/etc/apache2'
Override attribute in /roles/role_name.rb
override_attributes({ 'apache' => {'dir' => '/etc/apache2'}})
Override attribute in /environments/environment_name.rb
override_attributes({ 'apache' => {'dir' => '/etc/apache2'}})
Override attribute in a node object (from a recipe)
node.override['apache']['dir'] = '/etc/apache2'
Ensure that a default attribute has precedence over other attributes
When a default attribute is set like this:
default['attribute'] = 'value'
any value set by a role or an environment will replace it. To prevent
this value from being replaced, use the force_default
attribute
precedence:
force_default['attribute'] = 'I will crush you, role or environment attribute'
or:
default!['attribute'] = "The '!' means I win!"
Ensure that an override attribute has precedence over other attributes
When an override attribute is set like this:
override['attribute'] = 'value'
any value set by a role or an environment will replace it. To prevent
this value from being replaced, use the force_override
attribute
precedence:
force_override['attribute'] = 'I will crush you, role or environment attribute'
or:
override!['attribute'] = "The '!' means I win!"
Change Attributes
Attribute precedence levels may be:
- Removed for a specific, named attribute precedence level.
- Removed for all attribute precedence levels.
- Fully assigned attributes.
Remove Precedence Level
A specific attribute precedence level for default, normal, and override attributes may be removed by using one of the following syntax patterns.
For default attributes:
node.rm_default('foo', 'bar')
For normal attributes:
node.rm_normal('foo', 'bar')
For override attributes:
node.rm_override('foo', 'bar')
These patterns return the computed value of the key being deleted for the specified precedence level.
Examples
The following examples show how to remove a specific, named attribute precedence level.
Delete a default value when only default values exist
Given the following code structure under 'foo'
:
node.default['foo'] = {
'bar' => {
'baz' => 52,
'thing' => 'stuff',
},
'bat' => {
'things' => [5, 6],
},
}
And some role attributes:
# Please don't ever do this in real code :)
node.role_default['foo']['bar']['thing'] = 'otherstuff'
And a force attribute:
node.force_default['foo']['bar']['thing'] = 'allthestuff'
When the default attribute precedence node['foo']['bar']
is removed:
node.rm_default('foo', 'bar') #=> {'baz' => 52, 'thing' => 'allthestuff'}
What is left under 'foo'
is only 'bat'
:
node.attributes.combined_default['foo'] #=> {'bat' => { 'things' => [5,6] } }
Delete default without touching higher precedence attributes
Given the following code structure:
node.default['foo'] = {
'bar' => {
'baz' => 52,
'thing' => 'stuff',
},
'bat' => {
'things' => [5, 6],
},
}
And some role attributes:
# Please don't ever do this in real code :)
node.role_default['foo']['bar']['thing'] = 'otherstuff'
And a force attribute:
node.force_default['foo']['bar']['thing'] = 'allthestuff'
And also some override attributes:
node.override['foo']['bar']['baz'] = 99
Same delete as before:
node.rm_default('foo', 'bar') #=> { 'baz' => 52, 'thing' => 'allthestuff' }
The other attribute precedence levels are unaffected:
node.attributes.combined_override['foo'] #=> { 'bar' => {'baz' => 99} }
node['foo'] #=> { 'bar' => {'baz' => 99}, 'bat' => { 'things' => [5,6] }
Delete override without touching lower precedence attributes
Given the following code structure, which has an override attribute:
node.override['foo'] = {
'bar' => {
'baz' => 52,
'thing' => 'stuff',
},
'bat' => {
'things' => [5, 6],
},
}
with a single default value:
node.default['foo']['bar']['baz'] = 11
and a force at each attribute precedence:
node.force_default['foo']['bar']['baz'] = 55
node.force_override['foo']['bar']['baz'] = 99
Delete the override:
node.rm_override('foo', 'bar') #=> { 'baz' => 99, 'thing' => 'stuff' }
The other attribute precedence levels are unaffected:
node.attributes.combined_default['foo'] #=> { 'bar' => {'baz' => 55} }
Non-existent key deletes return nil
node.rm_default("no", "such", "thing") #=> nil
Remove All Levels
All attribute precedence levels may be removed by using the following syntax pattern:
node.rm('foo', 'bar')
Note
node['foo'].delete('bar')
will throw an exception that points to
the new API.Examples
The following examples show how to remove all attribute precedence levels.
Delete all attribute precedence levels
Given the following code structure:
node.default['foo'] = {
'bar' => {
'baz' => 52,
'thing' => 'stuff',
},
'bat' => {
'things' => [5, 6],
},
}
With override attributes:
node.override['foo']['bar']['baz'] = 999
Removing the 'bar'
key returns the computed value:
node.rm('foo', 'bar') #=> {'baz' => 999, 'thing' => 'stuff'}
Looking at 'foo'
, all that’s left is the 'bat'
entry:
node['foo'] #=> {'bat' => { 'things' => [5,6] } }
Non-existent key deletes return nil
node.rm_default("no", "such", "thing") #=> nil
Full Assignment
Use !
to clear out the key for the named attribute precedence level,
and then complete the write by using one of the following syntax
patterns:
node.default!['foo']['bar'] = {...}
node.force_default!['foo']['bar'] = {...}
node.normal!['foo']['bar'] = {...}
node.override!['foo']['bar'] = {...}
node.force_override!['foo']['bar'] = {...}
Examples
The following examples show how to remove all attribute precedence levels.
Just one component
Given the following code structure:
node.default['foo']['bar'] = {'a' => 'b'}
node.default!['foo']['bar'] = {'c' => 'd'}
The '!'
caused the entire ‘bar’ key to be overwritten:
node['foo'] #=> {'bar' => {'c' => 'd'}
Multiple components; one “after”
Given the following code structure:
node.default['foo']['bar'] = {'a' => 'b'}
# Please don't ever do this in real code :)
node.role_default['foo']['bar'] = {'c' => 'd'}
node.default!['foo']['bar'] = {'d' => 'e'}
The '!'
write overwrote the “cookbook-default” value of 'bar'
, but
since role data is later in the resolution list, it was unaffected:
node['foo'] #=> {'bar' => {'c' => 'd', 'd' => 'e'}
Multiple components; all “before”
Given the following code structure:
node.default['foo']['bar'] = {'a' => 'b'}
# Please don't ever do this in real code :)
node.role_default['foo']['bar'] = {'c' => 'd'}
node.force_default!['foo']['bar'] = {'d' => 'e'}
With force_default!
there is no other data under 'bar'
:
node['foo'] #=> {'bar' => {'d' => 'e'}
Multiple precedence levels
Given the following code structure:
node.default['foo'] = {
'bar' => {
'baz' => 52,
'thing' => 'stuff',
},
'bat' => {
'things' => [5, 6],
},
}
And some attributes:
# Please don't ever do this in real code :)
node.role_default['foo']['bar']['baz'] = 55
node.force_default['foo']['bar']['baz'] = 66
And other precedence levels:
node.normal['foo']['bar']['baz'] = 88
node.override['foo']['bar']['baz'] = 99
With a full assignment:
node.default!['foo']['bar'] = {}
Role default and force default are left in default, plus other precedence levels:
node.attributes.combined_default['foo'] #=> {'bar' => {'baz' => 66}, 'bat'=>{'things'=>[5, 6]}}
node.attributes.normal['foo'] #=> {'bar' => {'baz' => 88}}
node.attributes.combined_override['foo'] #=> {'bar' => {'baz' => 99}}
node['foo']['bar'] #=> {'baz' => 99}
If force_default!
is written:
node.force_default!['foo']['bar'] = {}
the difference is:
node.attributes.combined_default['foo'] #=> {'bat'=>{'things'=>[5, 6]}, 'bar' => {}}
node.attributes.normal['foo'] #=> {'bar' => {'baz' => 88}}
node.attributes.combined_override['foo'] #=> {'bar' => {'baz' => 99}}
node['foo']['bar'] #=> {'baz' => 99}
About Deep Merge
Attributes are typically defined in cookbooks, recipes, roles, and environments. These attributes are rolled-up to the node level during a Chef Infra Client run. A recipe can store attribute values using a multi-level hash or array.
For example, a group of attributes for web servers might be:
override_attributes(
:apache => {
:listen_ports => [ 80 ],
:prefork => {
:startservers => 20,
:minspareservers => 20,
:maxspareservers => 40
}
}
)
But what if all of the web servers are not the same? What if some of the web servers required a single attribute to have a different value? You could store these settings in two locations, once just like the preceding example and once just like the following:
override_attributes(
:apache => {
:listen_ports => [ 80 ],
:prefork => {
:startservers => 30,
:minspareservers => 20,
:maxspareservers => 40
}
}
)
But that is not very efficient, especially because most of them are identical. The deep merge capabilities of Chef Infra Client allows attributes to be layered across cookbooks, recipes, roles, and environments. This allows an attribute to be reused across nodes, making use of default attributes set at the cookbook level, but also providing a way for certain attributes (with a higher attribute precedence) to be applied only when they are supposed to be.
For example, a role named baseline.rb
:
name "baseline"
description "The most basic role for all configurations"
run_list "recipe[baseline]"
override_attributes(
:apache => {
:listen_ports => [ 80 ],
:prefork => {
:startservers => 20,
:minspareservers => 20,
:maxspareservers => 40
}
}
)
and then a role named web.rb
:
name 'web'
description 'Web server config'
run_list 'role[baseline]'
override_attributes(
:apache => {
:prefork => {
:startservers => 30
}
}
)
Both of these files are similar because they share the same structure. When an attribute value is a hash, that data is merged. When an attribute value is an array, if the attribute precedence levels are the same, then that data is merged. If the attribute value precedence levels in an array are different, then that data is replaced. For all other value types (such as strings, integers, etc.), that data is replaced.
For example, the web.rb
references the baseline.rb
role. The
web.rb
file only provides a value for one attribute: :startservers
.
When Chef Infra Client compares these attributes, the deep merge feature
will ensure that :startservers
(and its value of 30
) will be applied
to any node for which the web.rb
attribute structure should be
applied.
This approach will allow a recipe like this:
include_recipe 'apache2'
Chef::Log.info(node['apache']['prefork'].to_hash)
and a run_list
like this:
run_list/web.json
{
"run_list": [ "role[web]" ]
}
to produce results like this:
[Tue, 16 Aug 2011 14:44:26 -0700] INFO:
{
"startservers"=>30,
"minspareservers"=>20,
"maxspareservers"=>40,
"serverlimit"=>400,
"maxclients"=>400,
"maxrequestsperchild"=>10000
}
Even though the web.rb
file does not contain attributes and values for
minspareservers
, maxspareservers
, serverlimit
, maxclients
, and
maxrequestsperchild
, the deep merge capabilities pulled them in.
The following sections show how the logic works for using deep merge to perform substitutions and additions of attributes.
Substitution
The following examples show how the logic works for substituting an existing string using a hash:
role_or_environment 1 { :x => '1', :y => '2' }
+
role_or_environment 2 { :y => '3' }
=
{ :x => '1', :y => '3' }
For substituting an existing boolean using a hash:
role_or_environment 1 { :x => true, :y => false }
+
role_or_environment 2 { :y => true }
=
{ :x => true, :y => true }
For substituting an array with a hash:
role_or_environment 1 [ '1', '2', '3' ]
+
role_or_environment 2 { :x => '1' , :y => '2' }
=
{ :x => '1', :y => '2' }
When items cannot be merged through substitution, the original data is overwritten.
Addition
The following examples show how the logic works for adding a string using a hash:
role_or_environment 1 { :x => '1', :y => '2' }
+
role_or_environment 2 { :z => '3' }
=
{ :x => '1', :y => '2', :z => '3' }
For adding a string using an array:
role_or_environment 1 [ '1', '2' ]
+
role_or_environment 2 [ '3' ]
=
[ '1', '2', '3' ]
For adding a string using a multi-level hash:
role_or_environment 1 { :x => { :y => '2' } }
+
role_or_environment 2 { :x => { :z => '3' } }
=
{ :x => { :y => '2', :z => '3' } }
For adding a string using a multi-level array:
role_or_environment 1 [ [ 1, 2 ] ]
+
role_or_environment 2 [ [ 3 ] ]
=
[ [ 1, 2 ], [ 3 ] ]