Recipes
Recipes are the core component of getting things done. They are scripts, written in Ruby, that provide the instructions to be executed on end hosts when the Chef client is run. Recipes are placed in the recipes
directory inside of a cookbook, and each recipe is designed to achieve a specific purpose, such as provisioning accounts, installing and configuring a database server, and custom software deployments.
Recipes combine configuration data with the current state of the host to execute commands that will cause the system to enter a new state. For example, a PostgreSQL database server recipe would have the goal of installing and starting a PostgreSQL server on any host that runs the recipe. Let's look at a few possible starting states and the expected behavior:
- A host without PostgreSQL installed would begin at the state of not having the service; then, it will execute the commands required to install and configure the service
- Hosts with an existing but outdated PostgreSQL service would be upgraded to the latest version of the database server
- Hosts with a current installation of PostgreSQL would have its PostgreSQL installation untouched
- In all cases, the configuration on the disk would be updated to match the configuration stored in the Chef server
To achieve these goals, recipes use a combination of resources, configuration data, and Ruby code to determine what to execute on the end host. Each host-level resource—files, configuration files, packages, users, and so on—is mapped to a Chef resource in a recipe. For example, consider the recipe that we saw earlier in the book that was used to demonstrate that the Chef-solo installation was functional:
file "#{ENV['HOME']}/example.txt" do action :create content "Greetings #{ENV['USER']}!" end
This is a complete recipe; it has one step to create a file, and that is all it does. The file being created on the end host needs a name; here, it will be named ENV['HOME']/example.txt
, which is Ruby's way of representing $HOME/example.txt
. In addition to a name, we are instructing Chef to create the file (we could just as easily instruct Chef to delete the file) and to put the contents Greetings $USER into the file, replacing what is in there.
We could extend our recipe to ensure that a specific user existed on the host and create a file with the owner set to the newly created user:
user "smith" do action :create system true comment "Agent Smith" uid 1000 gid "agents" home "/home/matrix" shell "/bin/zsh" end file "/tmp/agent.txt" do action :create content "Hello from smith!" owner "smith" group "agents" mode "0660" end
Each recipe is a script that is run from beginning to end, assuming that nothing causes it to abort. Also, each recipe can access the node's attribute data and leverage resources to compile templates, create directories, install packages, execute commands, download files, and do just about anything that you can do from a shell as an administrator. If you can't accomplish what you need to do using the existing Chef resources, you can either create your own custom resources, or you can execute an arbitrary user-defined shell script.