Opscode
Home     Introduction to Chef     Cookbooks     Blog     GitHub     Tickets 

Recipes

Recipes are the fundamental configuration units in Chef. You write recipes to encapsulate collections of Resources, which are executed in the order they are defined, and configure some aspect of your system.

Rules of the Kitchen

When you are writing recipes, there are a few things you should remember:

  1. Resources are executed in the order they appear.
  2. Resources are compiled before they are executed - see Anatomy of a Chef Run for more information.
  3. You can make sure another recipe is executed at any point in your recipe with the include_recipe command.
  4. All the attributes of the current node are available via node.

Namespacing

Recipes are contained in Cookbooks, and they are namespaced by the Cookbook that contains the recipe. Each cookbook likely has a default recipe, which is accessed directly by the name of the cookbook. Any other recipe will be joined to the cookbook name by ::.

For example, a directory structure like:

  • cookbooks
    • apache
      • recipes
        • default.rb
        • ssl.rb

Would have two recipes - apache and apache::ssl. Currently, you are limited to one level deep for namespacing - file a ticket if you think this is insufficient.

A simple recipe

Lets create a recipe that will make sure that rsync is installed on all our systems. We'll use the Package resource to do it:

cookbooks/rsync/recipes/default.rb

Is enough code to ensure that the rsync package is installed on every system.

A more advanced recipe

Lets say we want to create a recipe to manage sudo. The first thing we do is create a new Cookbook, called sudo. Then open up the default.rb recipe (see the section on namespacing below for more details) - this is where we are going to record all the actions we need to take to get sudo configured.

Install the sudo package

First, we need to install the package for sudo. In default.rb, we will add a Package Resource:

Adding the Sudo Package Resource

We set this package to 'upgrade', instead of 'install', because Sudo has had a stable interface for ages, and the only cause for new releases are usually security related - and we don't want to wait to install them.

Populate the sudoers file

The next resource we want to manage is the /etc/sudoers file. We're going to populate the sudoers file from a template, based on some attributes that we're going to set up as well. First, in our sudo cookbooks attributes directory, we'll add two new attributes - one for users we want to have sudo privileges, and one for groups.

attributes/sudoers.rb
Attributes files

See the Attributes page for an explanation of these files, and what they do. But one thing that's great about this recipe is that you can use the Knife command or the Management Console to talk to the Chef Server and add new users and groups to the sudoers file on your nodes. (Just add more users to the Array's)

Next, we'll add our template resource to the recipe, resulting in our recipe being:

Note that the two attributes we created earlier are passed to this template as sudoers_group and sudoers_users. Lets add our template now:

templates/default/sudoers.erb
Template files

You can check out the Templates page for more information about how to utilize templates. The thing that's important to call out here is that the attributes we passed to the template via variables show up in the template as instance variables of the template, accessible with an @ in front of the variable name.

Results

If we run this recipe, it will make sure that:

  1. The sudo package is installed.
  2. The sudoers file has all the users and groups authorized to sudo on this node.

You could now use the Chef Server Web UI to manage your nodes, updating the list of authorized users for each host.

Including other Recipes

Chef manages resources in the order they are specified. The only way to state a dependency is at the recipe level, and you do that through including other recipes at certain points in your own.

For example, lets say we were making a recipe to install this wiki. It happens to be running Confluence, which requires Apache, Tomcat, and Java. We could write one humongous recipe that does all of those things in order.. but that sucks, and we might very well have other places where we need Apache or Java.

A simple recipe might be something like:

cookbooks/jira/recipes/default.rb

By using include_recipe, we can easily manage each recipe as if it stands alone - we just state what other recipes it needs. Chef is smart enough to know when it has included a recipe already - it won't do the same thing twice.

Metadata

In order to ensure that a cookbook's recipes are included properly in another, you need to use Cookbook Metadata "depends" method to tell the server to send the included cookbook to the client.

Searching

Chef has the ability to create Search - a way for you to store arbitrary data in the Chef Server for use in your Recipes. Chef helps you out by creating a search index for all of the Nodes under management by default - and you can use this index in your recipes.

Taking our sudo example from above, lets say we want to dynamically include all the users that are authorized on any node on every system. (I don't recommend you do this, but it's a fine example!)

Every authorized user recipe

What we did here was pretty simple - we got back all the authorized sudo users for every node, and just made a new list of users based on the aggregate set.

You can do all kinds of things with this functionality - think about configuring tools like Nagios, or anything that requires a high level view of your entire infrastructure. (Which database is active?)

Here's another search which will return all nodes which have the recipe "foo-client":

Every node with the foo-client recipe

We've got big plans for this search interface - its really only in it's most prototypical stages. Stay tuned - we think you're going to love it.

Using Ruby In Recipes

As mentioned, Chef's recipe DSL is pure Ruby. We use some examples throughout this page, but let's take a look at what's going on. First, a simple example. We will create a file using the File resource, and then print a log message using the Ruby method in Chef's internal logging class.

At first glance, it looks like this will create the file /tmp/create_me, and then print a log message. Let's run chef to see what happens.

Note the log message is printed before the file resource. During the Load Recipes phase of the Chef run, Chef will add a new resource named file["/tmp/create_me"] to the Resource Collection array, and then evaluate and execute the Ruby code that prints the log message. Then, during the Convergence phase of the Chef run, the resources in the collection are executed. If we look at the debug output of the Chef run, we see that it is in convergence:

If the log message should be printed after the file is created, we can use a Ruby Block.

And then when we run chef:

The ruby_block resource is added to the resource collection, and then executed during convergence.

Execute Resource at Compile Time

We can also reverse this, and execute the resource during compile time instead. This is a trick posted on the Opscode Blog sometime back to make sure a gem was installed via Chef before it was used in a recipe. Using the earlier file example, let's run it at compile time.

And when we run chef:

The key here is that we're setting the action to nothing, and then calling the create action with the run_action method call to the resource. In the blog post, we do a similar thing with the gem resource.

Tags

To use tags in your recipe simply

If you want to test if a machine is tagged

will return true or false. tagged? Takes an array as an argument.

To remove a tag

So all in all

Will output

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

Copyright © 2009 Opscode, Inc. All Rights Reserved.