+++ title = "About Recipes" draft = false gh_repo = "chef-web-docs" aliases = ["/recipes.html", "essentials_cookbook_recipes.html"]
[menu] [menu.infra] title = "About Recipes" identifier = "chef_infra/cookbook_reference/recipes/recipes.md About Recipes" parent = "chef_infra/cookbook_reference/recipes" weight = 10 +++
{{< readfile file="content/reusable/md/cookbooks_recipe.md" >}}
{{< readfile file="content/reusable/md/cookbooks_attribute.md" >}}
{{< note >}}
{{< readfile file="content/reusable/md/notes_see_attributes_overview.md" >}}
{{< /note >}}
In UNIX, a process environment is a set of key-value pairs made available to a process. Programs expect their environment to contain information required for the program to run. The details of how these key-value pairs are accessed depends on the API of the language being used.
If processes is started by using the execute or script resources
(or any of the resources based on those two resources, such as
bash), use the environment
attribute to alter the environment that
will be passed to the process.
bash 'env_test' do
code <<-EOF
echo $FOO
EOF
environment ({ 'FOO' => 'bar' })
end
The only environment being altered is the one being passed to the child process that is started by the bash resource. This will not affect the Chef Infra Client environment or any child processes.
The following sections show approaches to working with recipes.
{{< readfile file="content/reusable/md/data_bag.md" >}}
The contents of a data bag can be loaded into a recipe. For example, a
data bag named apps
and a data bag item named my_app
:
{
"id": "my_app",
"repository": "git://github.com/company/my_app.git"
}
can be accessed in a recipe, like this:
my_bag = data_bag_item('apps', 'my_app')
The data bag item's keys and values can be accessed with a Hash:
my_bag['repository'] #=> 'git://github.com/company/my_app.git'
{{< readfile file="content/reusable/md/data_bag_encryption_secret_key.md" >}}
An encryption key can also be stored in an alternate file on the nodes
that need it and specify the path location to the file inside an
attribute; however, EncryptedDataBagItem.load
expects to see the
actual secret as the third argument, rather than a path to the secret
file. In this case, you can use EncryptedDataBagItem.load_secret
to
slurp the secret file contents and then pass them:
# inside your attribute file:
# default[:mysql][:secretpath] = 'C:\\chef\\any_secret_filename'
#
# inside your recipe:
# look for secret in file pointed to by mysql attribute :secretpath
mysql_secret = Chef::EncryptedDataBagItem.load_secret('#{node['mysql']['secretpath']}')
mysql_creds = Chef::EncryptedDataBagItem.load('passwords', 'mysql', mysql_secret)
mysql_creds['pass'] # will be decrypted
If a cookbook has a dependency on a recipe that is located in another
cookbook, that dependency must be declared in the metadata.rb file for
that cookbook using the depends
keyword.
{{< note >}}
Declaring cookbook dependencies is not required with chef-solo.
{{< /note >}}
For example, if the following recipe is included in a cookbook named
my_app
:
include_recipe 'apache2::mod_ssl'
Then the metadata.rb file for that cookbook would have:
depends 'apache2'
{{< readfile file="content/reusable/md/cookbooks_recipe_include_in_recipe.md" >}}
Attributes sometimes depend on actions taken from within recipes, so it may be necessary to reload a given attribute from within a recipe. For example:
ruby_block 'some_code' do
block do
node.from_file(run_context.resolve_attribute('COOKBOOK_NAME', 'ATTR_FILE'))
end
action :nothing
end
Anything that can be done with Ruby can be used within a recipe, such as
expressions (if, unless, etc.), case statements, loop statements,
arrays, hashes, and variables. In Ruby, the conditionals nil
and
false
are false; every other conditional is true
.
A variable uses an equals sign (=
) to assign a value.
To assign a value to a variable:
package_name = 'apache2'
A case statement can be used to compare an expression, and then execute the code that matches.
To select a package name based on platform:
package 'apache2' do
case node['platform']
when 'centos', 'redhat', 'fedora', 'suse'
package_name 'httpd'
when 'debian', 'ubuntu'
package_name 'apache2'
when 'arch'
package_name 'apache'
end
action :install
end
An if expression can be used to check for conditions (true or false).
To check for condition only for Debian and Ubuntu platforms:
if platform?('debian', 'ubuntu')
# do something if node['platform'] is debian or ubuntu
else
# do other stuff
end
An unless expression can be used to execute code when a condition returns a false value (effectively, an unless expression is the opposite of an if statement).
To use an expression to execute when a condition returns a false value:
unless node['platform_version'] == '5.0'
# do stuff on everything but 5.0
end
A loop statement is used to execute a block of code one (or more) times.
A loop statement is created when .each
is added to an expression that
defines an array or a hash. An array is an integer-indexed collection of
objects. Each element in an array can be associated with and referred to
by an index.
To loop over an array of package names by platform:
['apache2', 'apache2-mpm'].each do |p|
package p
end
A hash is a collection of key-value pairs. Indexing for a hash is done
using arbitrary keys of any object (as opposed to the indexing done by
an array). The syntax for a hash is: key => "value"
.
To loop over a hash of gem package names:
{ 'fog' => '0.6.0', 'highline' => '1.6.0' }.each do |g, v|
gem_package g do
version v
end
end
A recipe must be assigned to a run-list using the appropriate name, as defined by the cookbook directory and namespace. For example, a cookbook directory has the following structure:
cookbooks/
apache2/
recipes/
default.rb
mod_ssl.rb
There are two recipes: a default recipe (that has the same name as the
cookbook) and a recipe named mod_ssl
. The syntax that applies a recipe
to a run-list is similar to:
{
'run_list': [
'recipe[cookbook_name::default_recipe]',
'recipe[cookbook_name::recipe_name]'
]
}
where ::default_recipe
is implied (and does not need to be specified).
On a node, these recipes can be assigned to a node's run-list similar
to:
{
'run_list': [
'recipe[apache2]',
'recipe[apache2::mod_ssl]'
]
}
Use knife to add a recipe to the run-list for a node. For example:
knife node run list add NODENAME "recipe[apache2]"
More than one recipe can be added:
% knife node run list add NODENAME "recipe[apache2],recipe[mysql],role[ssh]"
which creates a run-list similar to:
run_list:
recipe[apache2]
recipe[mysql]
role[ssh]
Use a JSON file to pass run-list details to chef-solo as long as the
cookbook in which the recipe is located is available to the system on
which chef-solo is running. For example, a file named dna.json
contains the following details:
{
"run_list": ["recipe[apache2]"]
}
To add the run-list to the node, enter the following:
sudo chef-solo -j /etc/chef/dna.json
{{< readfile file="content/reusable/md/search.md" >}}
The results of a search query can be loaded into a recipe. For example, a simple search query (in a recipe) might look like this:
search(:node, 'attribute:value')
A search query can be assigned to variables and then used elsewhere in a
recipe. For example, to search for all nodes that have a role assignment
named webserver
, and then render a template which includes those role
assignments:
webservers = search(:node, 'role:webserver')
template '/tmp/list_of_webservers' do
source 'list_of_webservers.erb'
variables(webservers: webservers)
end
{{< readfile file="content/reusable/md/chef_tags.md" >}}
{{< readfile file="content/reusable/md/cookbooks_recipe_tags.md" >}}
Sometimes it may be necessary to stop processing a recipe and/or stop processing the entire Chef Infra Client run. There are a few ways to do this:
- Use the
return
keyword to stop processing a recipe based on a condition, but continue processing a Chef Infra Client run - Use the
raise
keyword to stop a Chef Infra Client run by triggering an unhandled exception - Use a
rescue
block in Ruby code - Use an exception handler
The following sections show various approaches to ending a Chef Infra Client run.
The return
keyword can be used to stop processing a recipe based on a
condition, but continue processing a Chef Infra Client run. For example:
file '/tmp/name_of_file' do
action :create
end
return if platform?('windows')
package 'name_of_package' do
action :install
end
where platform?('windows')
is the condition set on the return
keyword. When the condition is met, stop processing the recipe. This
approach is useful when there is no need to continue processing, such as
when a package cannot be installed. In this situation, it is OK for a
recipe to stop processing.
In certain situations it may be useful to stop a Chef Infra Client run
entirely by using an unhandled exception. The raise
keyword can be used
to stop a Chef Infra Client run in both the compile and execute phases.
{{< note >}}
You may also see code that uses the fail
keyword, which behaves the same
but is discouraged and will result in Cookstyle warnings.
{{< /note >}}
Use these keywords in a recipe---but outside of any resource blocks---to trigger an unhandled exception during the compile phase. For example:
file '/tmp/name_of_file' do
action :create
end
raise "message" if platform?('windows')
package 'name_of_package' do
action :install
end
where platform?('windows')
is the condition that will trigger the
unhandled exception.
Use these keywords in the ruby_block resource to trigger an unhandled exception during the execute phase. For example:
ruby_block "name" do
block do
# Ruby code with a condition, for example if ::File.exist?(::File.join(path, "/tmp"))
raise "message" # for example "Ordering issue with file path, expected foo"
end
end
Use these keywords in a class. For example:
class CustomError < StandardError; end
and then later on:
def custom_error
raise CustomError, "error message"
end
or:
def custom_error
raise CustomError, "error message"
end
Since recipes are written in Ruby, they can be written to attempt to
handle error conditions using the rescue
block.
For example:
begin
dater = data_bag_item(:basket, 'flowers')
rescue Net::HTTPClientException
# maybe some retry code here?
raise 'message_to_be_raised'
end
where data_bag_item
makes an HTTP request to the Chef Infra Server to
get a data bag item named flowers
. If there is a problem, the request
will return a Net::HTTPClientException
. The rescue
block can be used
to try to retry or otherwise handle the situation. If the rescue
block
is unable to handle the situation, then the raise
keyword is used to
specify the message to be raised.
Use node.run_state
to stash transient data during a Chef Infra Client
run. This data may be passed between resources, and then evaluated
during the execution phase. run_state
is an empty Hash that is always
discarded at the end of a Chef Infra Client run.
For example, the following recipe will install the Apache web server, randomly choose PHP or Perl as the scripting language, and then install that scripting language:
package 'httpd' do
action :install
end
ruby_block 'randomly_choose_language' do
block do
if Random.rand > 0.5
node.run_state['scripting_language'] = 'php'
else
node.run_state['scripting_language'] = 'perl'
end
end
end
package 'scripting_language' do
package_name lazy { node.run_state['scripting_language'] }
action :install
end
where:
- The ruby_block resource declares a
block
of Ruby code that is run during the execution phase of a Chef Infra Client run - The
if
statement randomly chooses PHP or Perl, saving the choice tonode.run_state['scripting_language']
- When the package resource has to install the package for the
scripting language, it looks up the scripting language and uses the
one defined in
node.run_state['scripting_language']
lazy {}
ensures that the package resource evaluates this during the execution phase of a Chef Infra Client run (as opposed to during the compile phase)
When this recipe runs, Chef Infra Client will print something like the following:
* ruby_block[randomly_choose_language] action run
- execute the ruby block randomly_choose_language
* package[scripting_language] action install
- install version 5.3.3-27.el6_5 of package php