Cookbooks
This chapter covers different aspects related to cookbooks.
Cookbooks are the fundamental unit of configuration in chef. Cookbooks determine what gets deployed on the client.
Basics of Cookbooks
A cookbook is the basic unit of configuration and policy definition in chef. It defines a complete scenario for the deployment and configuration of an application.
As an example, a cookbook for Apache or Tomcat would provide all details to install and configure a fully configured Apache or Tomcat server.
A complete cookbook is one that contains all the components required to support the installation and configuration of an application or component.
We use Ruby as the reference library in chef. For writing specific resources we can use extended DSL (Domain Specific Language).
Let’s discuss the structure and content of a cookbook in detail.
Cookbook Directory Structure
Figure 7-1 shows the typical directory structure of any cookbook.
Figure 7-1. Directory structure of a cookbook
The following sections cover all aspects related to the cookbook.
Figure 7-2 shows the working of a cookbook.
Figure 7-2. Working of a cookbook
Recipes are the configuration units in chef that are actually deployed on the client and are used to configure the system. They are written in Ruby DSL. Recipes are normally a collection of resources with a bit of Ruby code.
A recipe
Working with Recipes
In this section we discuss the various approaches that are useful while creating recipes.
Using Data Bags in a Recipe
Figure 7-3. Contents of a data bag
We can access the created data bag in the recipe using the following syntax:
item = data_bag_item("application", "my_application")
where application is the name of the data bag and my_application is the object name.
We can use a Ruby hash to access data bag items.
item["repository"] #=> "git://github.com/test/my_application.git"
We can also create a data bag using knife as shown in Figure 7-4.
Figure 7-4. Creating a databag
Encrypting Data Bag items
Any item created in a data bag is not encrypted by default, but for creating any sort of secured environment we should encrypt our data bag items. We can achieve the same by creating a secret key. Here we use open SSL to create a secret key.
openssl rand –base64 512 | tr –d '
' > Secret_key
The foregoing command will create a secret key named Secret_key by generating a random number.
Store Keys on Nodes
We can store the encryption key in a file and copy the file to the node that requires it (see Figure 7-5). We need to pass the location of the file inside an attribute.
Figure 7-5. Storing keys
The EncryptedDataBagItem.load method expects the secret key as the third argument; we can use EncryptedDataBagItem.load_secret to use the secret file contents and then pass them.
Using Search Results in a Recipe
Chef server maintains an index of your data (environments, nodes, roles). Search index easily allows you to query the data that is indexed and then use it within a recipe.
There is a specified query syntax that supports range, wildcard, exact, and fuzzy.
Search can be done from various places in chef; it can be within a recipe, it can be from the management console.
The search engine in a chef installation is based on Apache Solr.
We can use the result of a search query in a recipe. The following code shows an example of using a simple search query in a recipe:
search(:node, "attribute:value")
The result of a search query can be stored in variable and then can be used anywhere within a recipe.
The search query in Figure 7-6 will return the servers with the role dbserver and then will render the template for all those servers.
Figure 7-6. Searching in a recipe
Recipes are written in Ruby, so anything that can be done within Ruby can be done within a recipe. We cover some of the important concepts in the following sections.
Assign a Value to a Variable
We use the ‘=’ operator to assign a value.
The following code shows an example of the ‘=’ operator.
package_name = "apache2"
It will create a variable named package_name with value “apache2”.
Using the Case Statement
We use the case statement when we need to compare an expression and, based upon it, execute certain code.
Figure 7-7 shows a piece of code that demonstrates the use of case statement within a recipe.
Figure 7-7. Using case statement
Check for a Condition
We use the ‘if’ expression to check the ‘true or false’ condition in a chef recipe.
Figure 7-8 shows a piece of code that checks if the node platform is Ubuntu or Debian and will execute the code accordingly.
Figure 7-8. Checking for a condition
Unless Expression
We use the ‘unless’ expression to execute a piece of code when the result is FALSE.
Figure 7-9 shows an example. If the platform version is anything other than 5.0, then the code will be executed.
Figure 7-9. Using ‘unless’ statement
Include Recipes in Recipes
A recipe can be included in any other recipe by using the include_recipe keyword.
The resources of the recipe that is included will be executed in the same order in which they appear in the original recipe. A recipe can be included in another recipe using the following syntax:
include_recipe "apache2::mod_ssl"
We can include a recipe within a recipe any number of times, but it will be processed only for the first time and after that it will be ignored.
Apply Recipes to Run List
If we need to apply any recipe then it needs to be added to the run list using a suitable name which is defined by the cookbook directory structure.
For example, a cookbook might have the following structure:
cookbooks/
mysql/
recipes/
default.rb
server.rb
One is the default recipe which has the same name as that of the cookbook and other recipe is server.rb. Figure 7-10 shows the syntax of a run list.
Figure 7-10. Specifying the run list
Exception Handlers and Log Files
We can write the output of a recipe to a log file. This can be achieved using the Chef::Log. There can be various levels of logging which include debug, warn, info, fatal, and error.
The following code can be used to capture some information:
Chef::Log.info('some useful information')
Tags
A tag describes a node in a customized way and then we can group our nodes based on that description.
You can check whether or not your machines have a tag. You can also add or remove tags at any point by using the following command.
tag('mytag')
Tagging can be done using various modes including from knife and from within a recipe. We can use the following code to check whether or not a machine is tagged:
tagged?('mytag')
To return true or false use
tagged?[array of nodes]
We can use the following untag command to remove the tag from any node:
untag('mytag')
See Figure 7-11 for an example.
Figure 7-11. Example
The output would be as follows:
[Wed, 12Jul 2014 22:23:45 +0000] INFO: Hey I'm a server
[Wed, 12 Jul 2014 22:23:45 +0000] INFO: I don't have a tag.
Recipe DSL
Recipe DSL is mainly Ruby DSL and we use it to declare resources within a recipe.
We use the methods in the recipe DSL to find out the value of a specific parameter, and then, based on that value, chef takes an action. Anything that can be done with Ruby can be done in a recipe.
We can use attributes, search results, and data bags in the recipe DSL. We can also use the helper methods available. The helper methods are
Let’s discuss these four helper methods in detail.
Platform?
We use Ohai to detect the value of the node[‘platform’] parameter during every chef run. We use the platform method in a recipe to run platform-specific actions.
We can use the “platform” method in a recipe with the following syntax:
platform?("parameter","parameter")
We can provide one or more than one parameter in a comma-separated list. Typically, we use this method along with conditional Ruby (Case, if, or elseif) (e.g., see Figure 7-12).
Figure 7-12. Using platform? method in a recipe
With this method we can create a single cookbook which can be used on multiple operating systems (OSs).
platform_family?
We use the platform_family? method when we want to execute some actions for a specific platform family. We use Ohai to detect the platform_family? attribute. The actions will be executed if one of the listed parameters matched the node['platform_family'].
We can use it in the recipe with the following syntax:
platform_family?("parameter","parameter")
We can provide more than one value of parameters using a comma-separated list.
We also use this method along with conditional Ruby, so that a single cookbook is used on multiple platforms (e.g., see Figure 7-13).
Figure 7-13. Using platform_family? method in a recipe
value_for_platform?
We use this method in a recipe with a hash of a particular value based on node['platform'] and node['platform_version']. Ohai provides the value during each chef run.
We can use it in the recipe with the following syntax:
value_for_platform( ["platform"] => { ["version"] => value } )
We can provide one or more than one value for platform in a comma-separated list, and version specifies the version of that platform.
If each value only has a single platform, then the syntax is as shown in Figure 7-14.
Figure 7-14. Using value_for_platform? method in a recipe
It there is more than one platform, the syntax will be as shown in Figure 7-15.
Figure 7-15. Using value_for_platform? method in a recipe
The code shown in Figure 7-16 will set the value of package_name variable as httpd if that platform is CentOS or Red Hat and Apache2 if platform is Debian or Ubuntu.
Figure 7-16. Using value_for_platform? method in a recipe
value_for_platform_family?
We use this method in a recipe with a hash to select value based on node['platform_family'] attribute detected by Ohai.
We can use it in a recipe with the following syntax:
value_for_platform_family({ platform_family => value })
We can provide one or more than one platform_family using a comma-separated list.
If there is a single platform for each value, then the syntax is as shown in Figure 7-17.
Figure 7-17. Using value_for_platform_family? method in a recipe
If the value has more than one platform, then the syntax is as shown in Figure 7-18.
Figure 7-18. Using value_for_platform_family? method in a recipe
The code shown in Figure 7-19 will set the value of the package variable to httpd-devel if the platform_family is Red Hat or Fedora or Suse and the value would be “apache-dev” if the platform_family is Debian.
Figure 7-19. Using value_for_platform_family? method in a recipe
Resources are the chunks of Ruby blocks that you declare in your recipe and that actually help in configuring the system. A resource helps us to define the actions that we want our recipe to do. These actions can be anything like installing a package or starting a service. The action is completed with the help of a provider.
During a chef run the resources are identified and mapped to a provider. The provider then executes that action. The resources define the current state of system and the state in which we want the system to be. Providers are used to define the steps to bring the system into that state.
An action is decoupled from the steps required to complete that action, which means that a provider exists for each of the paths that are required to complete the action. This is important because a single action may require different steps, depending on the platform of the system on which the action is being taken.
For example, “install a package” is a single action. To install a package onto various platforms, the steps required for each of those platforms may be different and may require different providers. On a Red Hat or CentOS machine a provider will use the Yum package provider to get the package installed and on a Debian or an Ubuntu machine, a provider will use the APT package installer.
The Chef::Platform class maps providers to platforms (and platform versions).
We use Ohai to get the information about the platform. Based upon that data we can identify the correct provider and then execute the action.
For example, see the resource shown in Figure 7-20.
Figure 7-20. Example of a resource
Resources Syntax
A resource has four components.
The syntax for a resource is as follows:
type "name" do
attribute "value"
action :type_of_action
end
The code in Figure 7-21 can be used to install version 1.16.1 of the tar package.
Figure 7-21. Example of a resource used for installing a package
There are predefined actions and attributes for each resource in chef and there is a default value for most of the attributes.
Some attributes are available to all resources; these are sometimes referred to as “meta” attributes and they are commonly used to send notifications to other resources or to set up conditional execution rules.
There is a default value for each action. We need to specify only the nondefault behaviors of actions and attributes.
Attributes associated with resources are not the same as attributes associated with nodes.
Resources
We can use a number of inbuilt chef resources in configuration. We discuss some of the important chef resources here.
Cookbook_file
We use this resource if we want to transfer any file with our cookbook. It transfers the files available in the files/default subdirectory of the cookbook to any specified path. We can use this resource on any platform.
We can use the cookbook_file resource in a recipe as shown in Figure 7-22.
Figure 7-22. Using the cookbook_file resource
For example,
cookbook_file "test" do
path "/root/test"
action :create
end
This would copy the test file to /root/test.
where
The following actions are available with this resource:
There are many attributes available with the resource. Some of the important attributes are
cron
We use this resource to manage the cron entries. This resource requires access to a crontab program to work properly.
We can use the cron resource can be used in a recipe as shown in Figure 7-23.
Figure 7-23. Using the cron resource
For example,
cron "test"do
hour "3"
minute "30"
command "/bin/test"
end
This would run a program at the specified interval.
where
The following actions are available in the cron resource:
Some of the important attributes available with the cron resource are:
Directory
We use this resource to manage a directory. Chef should have the permission to the directory that will be managed.
We can use the directory resource in a recipe with the syntax shown in Figure 7-24.
Figure 7-24. Using the directory resource
where
The following actions are available in the directory resource:
Some of the important attributes available with this resource are
env
The env resource is a Windows-specific resource and we use it to manage environment variables.
We can use the env resource in a recipe with the syntax shown in Figure 7-25.
Figure 7-25. Using the env resource
For example,
env "Test"do
value "C:\test\test.exe"
end
It would create an environment variable named Test with the provided value.
where
The following actions are available in this resource:
Some of the important attributes available with this resource are
execute
We use the execute resource in case we want to execute a command. Idempotency is not maintained by this resource by default. If we want to maintain the idempotency we need to use the not_if and only_if parameters.
We can use the execute resource in a recipe with the syntax shown in Figure 7-26.
Figure 7-26. Using the execute resource
For example,
execute "environment"do
command "source /etc/environment"
end
This would reload the /etc/environment file.
where
The following actions are available with this resource:
Some of the important attributes available with this resource are
file
We use this resource in case we need to manage the files that are present on a node.
We can use the file resource in a recipe (as shown in Figure 7-27).
Figure 7-27. Using the file resource
where
The screenshot in Figure 7-28 shows how we can use the file resource.
Figure 7-28. Using the file resource
The following actions are available in the file resource:
Some of the important attributes available with this resource are
package
We use this resource to manage packages. The resource uses the native functionality of the OS to install the packages on the nodes. Although there are OS-specific chef resources that can be used to manage packages, we recommend using the package resource wherever possible.
We can use the package resource in a recipe with the syntax shown in Figure 7-29
Figure 7-29. Using the package resource
where
The following actions are available with the package resource:
Some of the important attributes that are available with this resource are
powershell_script
This is a Windows-specific resource. We use this resource if we want execute a script using powershell. This resource is similar to many chef resources with small tweaks. This resource will create a temporary file rather than running it inline. The commands executed with this resource are not idempotent by default; we can use the not_if and only_if meta parameters to maintain the idempotency.
We can use the powershell_script resource in a recipe with the syntax shown in Figure 7-30.
Figure 7-30. Using the powershell_script resource
where
The screenshot in Figure 7-31 shows how to use the powershell_script resource.
Figure 7-31. Using the powershell_script resource
The following actions are available in the powershell_script resource:
Some of the important attributes available with this resource are
remote_file
We use this resource if we want to transfer a file from a remote location to the node. The functioning of this resource is similar to that of the file resource.
We can use it in a recipe with the syntax shown in Figure 7-32
Figure 7-32. Using the remote_file resource
where
The screenshot in Figure 7-33 shows how we can use this resource in a recipe.
Figure 7-33. Using the remote_file resource
The following actions are available in the remote_file resource:
Some of the important attributes available with this resource are
script
We use this resource to execute the scripts. We can choose the interpreter we want to use. It creates a temporary file and executes the file rather than running it inline.
The commands that are executed by this resource are not idempotent by nature; to maintain idempotency we need to use the not_if and only_if meta parameters.
We can use script in a recipe with the syntax shown in Figure 7-34
Figure 7-34. Using the script resource
where
The following actions are available with this resource:
Some of the important attributes available with this resource are
service
We use this resource to manage any service on a node.
The syntax for using the service resource in a recipe is shown in Figure 7-35.
Figure 7-35. Using the service resource
where
The following actions are available with this resource:
Some of the important attributes available with this resource are
Template
We use this resource if we want to manage the contents of a file. It stores files in an erb (Embedded Ruby) template. We normally store templates in the template/default available in the cookbook subdirectory.
We can use the template resource in the recipe as shown in Figure 7-36.
Figure 7-36. Using the template resource
For example,
template "/etc/nginx.conf"do
source "nginx.conf.erb"
end
We use this resource to configure a file from a template
where
Following are the actions that are available with this resource:
Some of the important attributes available with this resource are
Attribute are used to override the settings on any node. During each chef run, the attributes on a node are compared to those in the cookbook and then, depending upon the precedence level, the settings are done.
The attribute file is located in the attributes subdirectory of the cookbook. During each chef run, the attributes in the attributes files are evaluated against the node object.
For example, the mysql cookbook contains the following attribute file (called default.rb):
default["mysql"]["data_dir"] = "/opt/local/lib/mysql"
default["mysql"]["port"] = ""3306"
The use of the node object is implicit here. The following example is equivalent to the previous one:
node.default["mysql"]["data_dir"] = "/etc/apache2"
node.default["mysql"]["port"] = '3306'
Chef server keeps the attributes indexed so that they are easy to access.
Attributes Types
The following types of attributes are available:
Attributes Methods
Various attribute methods can be used in the attribute files of the cookbook or directly in a recipe. The various attribute methods that are available are
There is one more method, attribute?, that is available and is used to check the value of an attribute.
We can use the attribute?() method in an attributes file as shown in Figure 7-37.
Figure 7-37. Using attributes
Similarly, we can use it in a recipe as shown in Figure 7-38.
Figure 7-38. Using attributes in recipes
Precedence
During a chef run, saved attributes are retrieved from the chef server and are merged with the attributes on the local system. The attribute type and the source of the attribute determine which attribute values have priority over others.
In general, use the default attributes as much as possible (or even all the time).
The merge order for attribute precedence will do most of the work, yet leaving many levels of precedence available for the situations in which they are truly necessary.
Attribute values are applied in the following order (from low to high priority): 1 being the lowest and 15 being the highest.
In other words, an automatic attribute takes precedence over a forced override attribute, a forced override attribute takes precedence over an override attribute, an override attribute takes precedence over a normal attribute, and so on.
Definitions
Definitions are used to create new resources by combing the existing resources. Usually we use definitions when we are repeating a pattern of resources. Definition is not a resource or a lightweight resource but is actually a collection of two or more resource declarations.
There is a separate directory where we create definitions.
A definition is never declared into a cookbook.
There are mainly three components of a definition.
If we do not set any value for a parameter, we use the default value.
The syntax of a definition is as shown in Figure 7-39.
Figure 7-39. Using definitions
Figure 7-40 shows an example of a definition with name apache_site with ‘action’ as a parameter and ‘enable’ as its argument.
Figure 7-40. Using definitions
Libraries
A library is a way to increase chef functionality. We can implement a Ruby class directly and then use it in our recipe. There is a separate directory for library where we create libraries. A library once defined is available to be used anywhere in the cookbook.
The basic syntax of a library is shown in Figure 7-41.
Figure 7-41. Defining libraries
This syntax can be used in a recipe with the code shown in Figure 7-42.
Figure 7-42. Using libraries
For example, we could create a simple library that extends Chef::Recipe::, as shown in Figure 7-43.
Figure 7-43. Creating a library
We can use this library in a recipe as shown in Figure 7-44.
Figure 7-44. Using Library in recipe
Metadata
We use metadata to store certain information about the cookbook. We use the file metadata.rb to provide this information. The file is located in the cookbook directory.
The following things can be specified in the cookbook. Figure 7-45 shows the metadata file of the build-essential cookbook.
Figure 7-45. Metadata
A metadata can be used to specify the following important things: