Chef is Easy, Chapter 4: Hello Computer

Chef makes creating per-host config files easy

Have you ever had to manage a large number of servers that were almost identical, except that each one had to have some host-specific information in some config file somewhere? Maybe they needed to have the hostname embedded in the config file, or an IP address, or the name of an NFS filesystem on a mount point. Maybe you needed to allocate 2/3 of available system memory into hugepages for a database. Maybe you needed to set your thread max to ([number of CPUs] – 1). And each one had to be managed by hand!

Chef is here to help you out. Chef makes it incredibly easy to access physical details about your system, and take actions based on those results!

Here’s what you’ll need:

  • That same linux test system from the previous three chapters

Our specifications:

  • Modify the helloworld cookbook to print the number of CPUs on the host into the helloworld.txt file
  • Modify the helloworld cookbook to print the IP address of the primary ethernet adapter

About Ohai1

Ohai is Chef’s system profiler. When run, Ohai will introduce you to your system, telling you most everything you could possibly want to know about it. And if it’s missing some key fact? Ohai takes plugins, so you can easily extend it to gather more information. The best part is that you can distribute these Ohai plugins with Chef itself! Ohai can be run stand-alone, but it’s primarily intended to run as part of the chef-client.

Let’s try running Ohai by itself. Warning: On my test system, this produced 1,216 lines of scroll. You probably want to pipe it through less.

ohai | less

Which should render output similar to this:

{
  "languages": {
    "ruby": {

    },
    "lua": {
      "version": "5.1.4"
    },
    "perl": {
      "version": "5.10.1",
      "archname": "x86_64-linux-thread-multi"
    },
    "python": {
      "version": "2.6.6",
      "builddate": "Feb 22 2013, 00:00:18"
    }
  },
  "kernel": {
    "name": "Linux",
    "release": "2.6.32-358.el6.x86_64",
    "version": "#1 SMP Fri Feb 22 00:31:26 UTC 2013",
    "machine": "x86_64",
    "modules": {
      "vboxvideo": {

Every piece of the data returned by Ohai is available to us inside of Chef. For instance, if I wanted to pull the release number of the kernel, which is represented in Ohai’s output like this:

  "kernel": {
    "release": "2.6.32-358.el6.x86_64"
  }

I would refer to it in Chef like this:

node['kernel']['release']

Easy as pie!2

Let’s see how we can get the total number of CPUs from our server. If you use less to search through the Ohai output, you’ll find a section similar to this:

"cpu": {
    "0": {
      "vendor_id": "GenuineIntel",
      "family": "6",
      "model": "58",
      "model_name": "Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz",
      "stepping": "9",
      "mhz": "2273.300",
      "cache_size": "6144 KB",
      "flags": [
        "fpu",
        "vme",
# Snipped for brevity
        "ssse3",
        "lahf_lm"
      ]
    },
    "total": 1,
    "real": 0
  }

Check that out! Ohai already knows all the details about every CPU in the system. It also knows that I have a total of 1 CPU, and (because I’m running this on a VM) 0 “real” CPUs. If I have to support applications with Chef that just don’t work right when run in virtual hosts, I could easily check to make sure that there are enough “real” CPUs to continue with my workload.

In this case, we’re more interested in the “total” number of CPUs. We can access that in Chef with this attribute:

node['cpu']['total']

Let’s try it out!

Accessing attributes generated by Ohai is easy

Let’s modify the template for our helloworld.txt file, and make it print the number of CPUs in the server.

cd ~/chef
vi cookbooks/helloworld/templates/default/helloworld.txt.erb

Your helloworld.txt.erb file should look like this:

# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello <%= node['helloworld']['place'] %>!

This computer has <%= node['cpu']['total'] %> CPUs!

<% if node['helloworld']['encore'] == false -%>
There will be no encore
<% end -%>

Give it a run with the usual:

chef-client --local -o recipe['helloworld']

And then verify file contents:

cat /tmp/helloworld.txt
# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello Minnesota!

This computer has 1 CPUs!

Chef makes it easy to change behavior based on server attributes

We’re almost there, but this is really annoying:

This computer has 1 CPUs!

Proper English says that if we’ve only got 1 CPU, then our template should print the singular word CPU, not the plural CPUs. But in all other cases (including the improbable zero), then our template should definitely use the plural CPUs. Fortunately, Chef makes this really simple to handle in our template. We’ll write a quick bit of logic that says if the computer only has 1 CPU, the template will print “CPU,” but if it has any other amount of CPUs, then the template will print “CPUs.”

One more time, back into the helloworld.erb.txt file.

vi cookbooks/helloworld/templates/default/helloworld.txt.erb
# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello <%= node['helloworld']['place'] %>!

<% if node['cpu']['total'] == 1 -%>
This computer has <%= node['cpu']['total'] %> CPU!
<% else -%>
This computer has <%= node['cpu']['total'] %> CPUs!
<% end -%>

<% if node['helloworld']['encore'] == false -%>
There will be no encore
<% end -%>

Let’s give it a run:

chef-client --local -o recipe['helloworld']

And then verify file contents:

cat /tmp/helloworld.txt
# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello Minnesota!

This computer has 1 CPU!

Perfect!

Chef makes it easy to break up monolithic config files

If you’ve ever had to deal with a enormous, monolithic config files such as the httpd.conf file, php.ini, mysql.ini, tomcat configs, or the redis.conf, the thought of turning them into templates probably makes you wince. Those huge files contain so many things to tune, it can be daunting to think of processing them all into templates. Fortunately, Chef helps you break those config files up into smaller partial templates, so that you can use only the parts of the config file that you need.

Let’s make a new partial template for the helloworld cookbook that will print out the IP address of the node into our /tmp/helloworld.txt file (you can search for this in the Ohai output, or you can just trust me when I tell you that Ohai stores it at node['ipaddress']).

vi cookbooks/helloworld/templates/default/ipaddress.erb

Your new ipaddress.erb file will be pretty simple, and should look like this:

<% #This partial template example prints the IP address of the host -%>
The primary IP address of this computer is: <%= node['ipaddress'] %>

Notice how our comment this time is in a Ruby block. In Chapter 3, we discussed that the <% symbol means "evaluate," while the -%> closing block means “Do not insert a linebreak.” As a result, when this template is processed by Chef, the comment will be evaluated without printing, and there will be no line break at the end of line one.

The upshot is: When the template is processed, the comment won’t be printed in the final output file!

With that complete, let’s modify our master helloworld.txt.erb template to include our partial template:

# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello <%= node['helloworld']['place'] %>!

<% if node['cpu']['total'] == 1 -%>
This computer has <%= node['cpu']['total'] %> CPU!
<% else -%>
This computer has <%= node['cpu']['total'] %> CPU!
<% end -%>

<%= render "ipaddress.erb" -%>

<% if node['helloworld']['encore'] == false -%>
There will be no encore
<% end -%>

Last time for this chapter:

chef-client --local -o recipe['helloworld']

And finally, verify the file contents:

cat /tmp/helloworld.txt
# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello Minnesota!

This computer has 1 CPU!

The primary IP address of this computer is: 10.0.2.15

Chef Is Super, Super Easy

Here’s everything we learned about Chef today:

  • Chef comes with a built-in system profiler called Ohai
  • Ohai captures more than a thousand lines of facts about each system in a manner of seconds
  • All the facts gathered by Ohai are instantly available as attributes
  • It’s easy to change the behavior of templates or recipes based on facts about the computer running Chef
  • Chef templates can include other partial templates with the command.
  • Partial templates let you easily handle large, monolithic configuration files that include large blocks of optional configuration, like the redis.conf file.
  • You still don’t have to know Ruby to use Chef, but if you ever need to do something fancy like a simple if/else, Ruby’s right there.

Next time: We’ve done enough with Hello World. It’s time to graduate, manage something significant, and do a little bit of real work! We’ll create a new cookbook to manage the contents of the /etc/resolv.conf file. We’ll also learn about how to store lists of similar things together in the same place, and how to easily access those lists automatically.

Extra Credit Question: Can you have Chef change the comment at the top of the helloworld.txt so that it says “This file was generated by Chef for ______”, and insert your individual hostname? (Hint: It’s really easy. Also, see the footnote below.) Extra Extra Credit: Can you set up the IP address partial template so it only renders if you declare that you want to, via an attribute? (Hint: As always, really simple. Also, check out what you did with the “encore” attribute from Chapter 3.)


  1. Ohai2u too! 
  2. A little Chef quirk: There are two facts about every system that have to be accessed in a special way: The node name, and the Chef environment. These are accessed as node.name and node.chef_environment, respectively. 

Chef Is Easy, Chapter 3: Hello Encore

Chef makes writing configuration files easy

Most of the files we want to control with Chef are configuration files. Whether they’re for applications such as ntp or apache, or system configuration files like the /etc/hosts file or /etc/resolve.conf, these files tend to be a mix of static content that never changes, and dynamic content that we want to be able to manage on the fly. It’s neat that we’ve got a file with entirely dynamic content, but in day-to-day systems admin that won’t help us very much. What we have now is a good start, but ultimately we need something a little different.

Fortunately, Chef makes creating and managing configuration files incredibly easy.

Here’s what you’ll need:

  • That same linux test system from the previous two chapters

Our specifications:

  • Modify the helloworld cookbook to use a template for managing /tmp/helloworld.txt, instead of a file.
  • Modify the helloworld cookbook to optionally print a second line in the /tmp/helloworld.txt file, based on an attribute.

About Templates

Chef generates files that mix static and dynamic content using templates. Templates are typically stored in cookbooks, and are written in .erb format (embedded ruby). If you’ve ever worked in PHP, Python jinja, or similar, you should be right at home. Templates automatically have direct access to all the attributes in a cookbook.

Using templates is really easy!

Your helloworld cookbook currently has a place to store node['helloworld']['content']. Let’s modify it so that instead of storing the entire content of the hello world file, it only stores the name of the place we’re greeting, in node['helloworld']['place'].

cd ~/chef
vi cookbooks/helloworld/attributes/default.rb

your attributes/default.rb file file should look like this:

# This is an example Chef attributes file

default['helloworld']['place'] = 'Minnesota'

With that saved, let’s make a new template. Templates live in the templates/default/ directory in your cookbook. To make a new template for use with our helloworld cookbook, let’s create a new file there:

vi cookbooks/helloworld/templates/default/helloworld.txt.erb

your helloworld.txt.erb file should look like this:

# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello <%= node['helloworld']['place'] %>!

Now our cookbook can store the name of the place we’re greeting, and has a template to access that name. The last step is to modify the helloworld default recipe at recipes/default.rb, so that it renders /tmp/helloworld.txt as a template instead of as a file.

vi cookbooks/helloworld/recipes/default.rb

your default.rb file should look like this:

#
# Cookbook Name:: helloworld
# Recipe:: default
#
# Copyright 2014, YOUR_COMPANY_NAME
#
# All rights reserved - Do Not Redistribute
#
# This is an example Chef recipe
#
template "/tmp/helloworld.txt" do
  source "helloworld.txt.erb"
end

With everything in place, let’s run chef-client and create our new /tmp/helloworld.txt file!

chef-client --local -o recipe['helloworld']
[2014-03-09T19:26:07+00:00] WARN: No config file found or specified on command line, using command line options.
Starting Chef Client, version 11.10.4
[2014-03-09T19:26:07+00:00] WARN: Run List override has been provided.
[2014-03-09T19:26:07+00:00] WARN: Original Run List: []
[2014-03-09T19:26:07+00:00] WARN: Overridden Run List: 
] resolving cookbooks for run list: ["helloworld"] Synchronizing Cookbooks: - helloworld Compiling Cookbooks... Converging 1 resources Recipe: helloworld::default * template[/tmp/helloworld.txt] action create - update content in file /tmp/helloworld.txt from bf51b4 to a1c699 --- /tmp/helloworld.txt 2014-03-02 19:50:14.137392304 +0000 +++ /tmp/chef-rendered-template20140309-2987-1edtov9 2014-03-09 19:26:07.949765188 +0000 @@ -1,2 +1,6 @@ -Hello Cleveland! +# This file is managed by Chef +# Any changes made by hand will be overwritten + +Hello Minnesota! + [2014-03-09T19:26:08+00:00] WARN: Skipping final node save because override_runlist was given Running handlers: Running handlers complete Chef Client finished, 1/1 resources updated in 0.70047128 seconds

Quickly verify the contents of /tmp/helloworld.txt:

cat /tmp/helloworld.txt
# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello Minnesota!

Adding logic to templates is easy

What if we want to add a switch, so that our template will optionally print a second line, without us having to modify the template? Let’s re-open the template file, and add some more to it.

vi cookbooks/helloworld/templates/default/helloworld.txt.erb

your helloworld.txt.erb file should look like this:

# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello <%= node['helloworld']['place'] %>!

<% if node['helloworld']['encore'] == false -%>
There will be no encore.
<% end -%>

With these changes, the template will now look for an attribute called node['helloworld']['encore']. If that attribute is set to false, then the template will print the line, “There will be no encore.” If the attribute is set to anything else, that line will not be printed.

About the tags used in .erb templates:

  • <% opens up a block of code, and evaluates everything until the closing tag as Ruby. By default, the results are not printed.
  • <%= opens up a block, and prints the result.
  • %> closes a ruby block. By default this may insert a line break.
  • -%> closes a ruby block, and will never insert a line break.

Finally, let’s add an attribute to the cookbook that stores whether or not we will be performing an encore, called `node[‘helloworld’][‘encore’].

vi cookbooks/helloworld/attributes/default.rb

your attributes/default.rb file file should look like this:

# This is an example Chef attributes file

default['helloworld']['place'] = 'Minnesota'

default['helloworld']['encore'] = false

Let’s try it out!

chef-client --local -o recipe['helloworld']
[2014-03-09T19:54:09+00:00] WARN: No config file found or specified on command line, using command line options.
Starting Chef Client, version 11.10.4
[2014-03-09T19:54:09+00:00] WARN: Run List override has been provided.
[2014-03-09T19:54:09+00:00] WARN: Original Run List: []
[2014-03-09T19:54:09+00:00] WARN: Overridden Run List: 
] resolving cookbooks for run list: ["helloworld"] Synchronizing Cookbooks: - helloworld Compiling Cookbooks... Converging 1 resources Recipe: helloworld::default * template[/tmp/helloworld.txt] action create - update content in file /tmp/helloworld.txt from a1c699 to 0eb328 --- /tmp/helloworld.txt 2014-03-09 19:26:07.949765188 +0000 +++ /tmp/chef-rendered-template20140309-3258-16r3mxm 2014-03-09 19:54:10.035386059 +0000 @@ -3,4 +3,5 @@ Hello Minnesota! +There will be no encore. [2014-03-09T19:54:10+00:00] WARN: Skipping final node save because override_runlist was given Running handlers: Running handlers complete Chef Client finished, 1/1 resources updated in 0.425901299 seconds

Again, quickly verify the contents of /tmp/helloworld.txt:

# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello Minnesota!

There will be no encore.

Now let’s flip the switch and see that the template is re-rendered without the optional section.

vi cookbooks/helloworld/attributes/default.rb
# This is an example Chef attributes file

default['helloworld']['place'] = 'Minnesota'

default['helloworld']['encore'] = true

One more time!

chef-client --local -o recipe['helloworld']
[2014-03-09T19:58:58+00:00] WARN: No config file found or specified on command line, using command line options.
Starting Chef Client, version 11.10.4
[2014-03-09T19:58:58+00:00] WARN: Run List override has been provided.
[2014-03-09T19:58:58+00:00] WARN: Original Run List: []
[2014-03-09T19:58:58+00:00] WARN: Overridden Run List: 
] resolving cookbooks for run list: ["helloworld"] Synchronizing Cookbooks: - helloworld Compiling Cookbooks... Converging 1 resources Recipe: helloworld::default * template[/tmp/helloworld.txt] action create - update content in file /tmp/helloworld.txt from 0eb328 to a1c699 --- /tmp/helloworld.txt 2014-03-09 19:54:10.035386059 +0000 +++ /tmp/chef-rendered-template20140309-3392-jzwgr5 2014-03-09 19:58:59.037814780 +0000 @@ -3,5 +3,4 @@ Hello Minnesota! -There will be no encore [2014-03-09T19:58:59+00:00] WARN: Skipping final node save because override_runlist was given Running handlers: Running handlers complete Chef Client finished, 1/1 resources updated in 0.421208067 seconds

And finally, let’s verify the contents of /tmp/helloworld.txt:

# This file is managed by Chef
# Any changes made by hand will be overwritten

Hello Minnesota!

Chef makes it easy to separate What from How

Notice that even though we were updating the contents of the template and the attributes file, we didn’t have to modify the recipe again? Chef separated what we wanted (A config file that can be changed based on attributes, attributes that let us specify the contents and layout of the config file) from how we got it (by rendering a template in a recipe). We only had to work out the ‘how’ once. After that, we were able to focus on ‘what,’ changing out inputs to get a different outcome.

Chef Is Super, Super Easy

Here’s everything we learned about Chef today:
* Adding new attributes to a cookbook is as easy as defining them in attributes/default.rb.
* Templates are stored in cookbooks as .erb files.
* Adding templates to a cookbook is done by creating files in the templates/default/ directory, such as templates/default/foo.txt.erb.
* Templates let us create files that are a mix of static content and dynamic content.
* It’s easy to access attributes directly from templates, without having to pass them through the recipe first.
* It’s easy to make a template change file layout based on attributes.
* You still don’t have to know Ruby to use Chef!

Next time: More templates! We’ll also learn about Ohai, the Chef system profiler! What if we want to do things like access the name of the host that’s running the recipe, or print out the primary IP address? What if we want to break up a huge config file into many smaller, easy-to-manage templates? How can we handle that automatically? Once again, Chef makes all these things easy!

Extra Credit Question: Can you have Chef change the owner and permissions on the /tmp/helloworld.txt file to belong to someone other than root:root and 0644? How will Chef react if you change those permissions out from under it, and then re-run chef-client? Can you turn the owner, group, and permissions for a file into attributes? (Hint: This is all incredibly easy.)

Chef Is Easy, Chapter 2: Hello Cleveland

Chef makes dynamic configuration easy.

It’s cool that we’ve been able to write out a Hello World file, but what if we want that file to change dynamically based on different situations? It would be neat if we could override the string to be anything we want, without touching the actual recipe. It would be even better if we had a standardized way to package everything up into a single archive that we could share with each other, instead of passing around an executable script.

Chef makes this kind of behavior really, REALLY easy.

Here’s what you will need:

  • The same computer you modified in Chapter 01.
  • An internet connection.
  • A text editor. Use whatever you want! All my examples will use vi.
  • The sample recipe we wrote in Chapter 01.

Our Specifications:

  • Have the recipe change the string “Hello world” in our /tmp/helloworld.txt file to whatever we want, without us having to touch the recipe to change the string.
  • Make sure that a file exists at /tmp/helloworld.txt, with the contents of our string.
  • If the /tmp/helloworld.txt file is deleted, replace it with the correct file.
  • If the /tmp/helloworld.txt file is altered, replace it with the correct file.
  • If our string changes, update the /tmp/helloworld.txt file with the new contents.

About Cookbooks

Cookbooks are the “package” for Chef, and can contain many related recipes. Think of them like .tar archives, or like packages pulled from yum or apt, or like .msi installers. Recipes inside cookbooks are more useful than stand-alone recipes, because cookbooks can bundle supporting files in with recipes so that they can do more complex and interesting things.

Cookbooks typically map 1:1 with a particular application, or set of functionality. For instance the mysql cookbook provides ways to install the MySQL server and client, while the database cookbook extends Chef so that you can run queries against MySQL, PostgreSQL, and SQL Server directly from Chef recipes.

Creating cookbooks is really easy!

Cookbooks are built by creating a directory with a particular file structure that can be zipped up and shared with others. Knife comes with a built-in command that will generate new cookbooks for you.

mkdir ~/chef/cookbooks
knife cookbook create helloworld -o ~/chef/cookbooks

The -o flag (output) tells knife the directory in which to create the new cookbook.

At first, the cookbook only has a few files in it, and lots of empty directories. The brand new cookbook structure looks like this:

chef/cookbooks/helloworld/
├── attributes
├── CHANGELOG.md
├── definitions
├── files
│   └── default
├── libraries
├── metadata.rb
├── providers
├── README.md
├── recipes
│   └── default.rb
├── resources
└── templates
    └── default

This brand new cookbook doesn’t have any instructions in it yet, but it’s syntactically valid out of the box. We could run it right now if we wanted to (but what would be the point?)

Putting recipes into cookbooks is easy

Every cookbook has at least one recipe that dictates what the cookbook will do by default, unimaginatively called the “default recipe.” Let’s copy the contents of our stand-alone hello_world.rb recipe from Chapter 01 into the default recipe for our new helloworld cookbook, and make it the default behavior.

# Copy the contents of the old stand-alone hello_world.rb recipe into 
# the default recipe of the new helloworld cookbook
cat chef/hello_world.rb >> chef/cookbooks/helloworld/recipes/default.rb
# Verify the contents
cat chef/cookbooks/helloworld/recipes/default.rb
# Delete the old recipe
rm chef/hello_world.rb

Your chef/cookbooks/helloworld/recipes/default.rb file should look like this:

#
# Cookbook Name:: helloworld
# Recipe:: default
#
# Copyright 2014, YOUR_COMPANY_NAME
#
# All rights reserved - Do Not Redistribute
#
# This is an example Chef recipe

file '/tmp/helloworld.txt' do
  content 'hello world'
end

Running recipes from cookbooks is easy

So far we’ve been running recipes with chef-apply, which is great for simple stand-alone recipes. If you want to do more powerful things, you should use chef-client instead. Chef-client runs in two modes: standard mode, in which it tries to talk to a Chef Server, and local mode, where no server is required1.

Let’s run our recipe from the new cookbook.

cd chef
chef-client --local -o recipe['helloworld']

The -o flag here (override) lets us override the list of recipes for the node, and is different than the -o flag on knife cookbook create. Just like in Chapter 01, because the /tmp/helloworld.txt file was already there with the correct contents, chef-client marked the file as “up to date” and didn’t make any changes.

Storing information in cookbooks is easy

Right now our recipe is just like a script: It hardcodes the contents of the /tmp/helloworld.txt file, and if we want to change the contents of the file, we have to change the recipe. That’s no good, because if we want to have different contents on different nodes, then we have to have one recipe per node. It would be much better if we could pass that information in from an outside source, so we could use the same recipe to generate different helloworld.txt files.

A little bit of terminology: In Chef, every host configured by a chef-client is called a node. Every node in Chef has what are called attributes, which are facts about the node. Some of these attributes, such as the number of CPUs or the amount of memory on the node, are discovered automatically by Chef as it runs. Other attributes are created and set by humans. One way you can create and set attributes yourself is with cookbooks.

Brand new cookbooks don’t set any attributes. Let’s create a new default attributes file for our helloworld cookbook, and set a new default attribute while we’re there.

vi cookbooks/helloworld/attributes/default.rb

Your cookbooks/helloworld/attributes/default.rb file should look like this:

# This is an example Chef attributes file

default['helloworld']['content'] = 'hello world'

A note: Chef uses the location of a file in the cookbook to determine what the file does. This gets confusing sometimes, because you can have a file at /recipes/default.rb that is the default recipe, and a file at /attributes/default.rb that is the default attributes file. It’s common to have two files in a cookbook with the same name, yet those files do different things! No wonder I have to write this blog.

Using attributes in recipes is really easy

If we want to use our new attribute in our default recipe, it’s really easy.

vi cookbooks/helloworld/recipes/default.rb

Modify your cookbooks/helloworld/recipes/default.rb file to look like this:

#
# Cookbook Name:: helloworld
# Recipe:: default
#
# Copyright 2014, YOUR_COMPANY_NAME
#
# All rights reserved - Do Not Redistribute
#
# This is an example Chef recipe

file '/tmp/helloworld.txt' do
  content node['helloworld']['content']
end

Once you’ve saved the file, run the recipe again:

chef-client --local -o recipe['helloworld']

Again, Chef didn’t change anything! Because the content we specified in the attribute was ‘hello world’, and the file at /tmp/helloworld.txt already contained that content, Chef marked the file as “up to date” and didn’t modify anything.

Now let’s modify the attribute file to say something different.

vi cookbooks/helloworld/attributes/default.rb

Your cookbooks/helloworld/attributes/default.rb file should look like this:

# This is an example Chef attributes file

default['helloworld']['content'] = 'Hello Cleveland!'

And then re-run Chef-client:

chef-client --local -o recipe['helloworld']
[2014-03-02T19:05:30+00:00] WARN: No config file found or specified on command line, using command line options.
Starting Chef Client, version 11.10.4
[2014-03-02T19:05:30+00:00] WARN: Run List override has been provided.
[2014-03-02T19:05:30+00:00] WARN: Original Run List: []
[2014-03-02T19:05:30+00:00] WARN: Overridden Run List: 
] resolving cookbooks for run list: ["helloworld"] Synchronizing Cookbooks: - helloworld Compiling Cookbooks... Converging 1 resources Recipe: helloworld::default * file[/tmp/helloworld.txt] action create - update content in file /tmp/helloworld.txt from b94d27 to bf51b4 --- /tmp/helloworld.txt 2014-03-02 18:53:06.434394513 +0000 +++ /tmp/.helloworld.txt20140302-5470-1wbk3c8 2014-03-02 19:05:30.695394034 +0000 @@ -1,2 +1,2 @@ -hello world +Hello Cleveland! [2014-03-02T19:05:30+00:00] WARN: Skipping final node save because override_runlist was given Running handlers: Running handlers complete Chef Client finished, 1/1 resources updated in 0.424438725 seconds

You can quickly verify the contents of /tmp/helloworld.txt:

cat /tmp/helloworld.txt
Hello Cleveland!

Chef makes it easy to separate What from How

From now on whatever string we put in the ['helloworld']['content'] attribute, Chef will write it to /tmp/helloworld.txt. In this chapter, we’re modifying the helloworld cookbook’s attributes/default.rb file to change the attribute. In the real world, attributes can also be set by recipes in other cookbooks, from environments, and from roles. In future chapters, we’ll learn about many different ways to set attributes to make broadly re-usable cookbooks that don’t have to be modified to work in different datacenters or applications.

Screencasts

Chef Is Super, Super Easy

Here’s everything we learned about Chef today:

  • Creating new cookbooks is a one-liner with knife.
  • Cookbooks can contain many recipes, along with supporting files that make the recipes more powerful.
  • You still don’t have to know Ruby to use Chef!
  • Chef-client --local is more powerful than chef-apply.
  • Each host managed by chef-client is called a node.
  • Attributes are facts about nodes. Many attributes are discovered automatically by Chef, but you can also create and set your own attributes with cookbooks.
  • It’s easy to make a recipe change behavior based on attributes.

Next time: Right now we can write out a file with completely dynamic content, and that’s pretty cool. But most of the files we want to manage are probably going to be config files, things that mix static and dynamic content. How do we handle those situations? Chef makes it easy!

Extra Credit Question: Can you set the location of the helloworld.txt file based on an attribute? (Hint: This is really easy.) What happens if you try to write the file to a location where your user account doesn’t have permission? How can you write the file anyway?


  1. Before there was local mode, chef-client required a Chef Server. To run Chef in standalone mode, you instead had to use a different command called ‘chef-solo.’ You can still use chef-solo if you want to, but for a variety of reasons that are mostly under the hood, chef-client --local is the better way to go. Chef-client --local = new hotness. Chef-solo = old & busted. 

Chef Is Easy, Chapter 1: Hello World

So you want to try Chef? Awesome. Chef is easy.

You’ll be up and running in no time. Here’s what you will need:

  • A computer that you want to modify. It should probably be running Linux, OSX, or Windows. It should probably not be your laptop. You will definitely need sudo. This tutorial assumes you’re on linux, change paths accordingly if you’re using something else.
  • An internet connection.
  • A text editor. Use whatever you want! All my examples will use vi.

Let’s get started with a basic Hello World. Cool? Cool!

Our Specifications:

  1. Install Chef.
  2. Make sure that a file exists at /tmp/helloworld.txt, with the contents “hello world”.
  3. If the /tmp/helloworld.txt file is deleted, replace it with the correct file.
  4. If the /tmp/helloworld.txt file is altered, replace it with the correct file.

Installing Chef is really easy!

This one-liner will install Chef on any internet-connected, non-Windows1 computer:

curl -s -L https://www.getchef.com/chef/install.sh | sudo bash

You can verify that Chef installed correctly by running chef-apply -v.

chef-apply -v
Chef: 11.10.4

WordPress.com doesn’t currently allow asciicast embedding, but you can see the installation command in action here.

So easy!

Working with Chef is way easier than writing in shell!

Programs written in Chef are called recipes. Because Chef is written in ruby, recipes end in the .rb extension. Let’s write a simple recipe.

mkdir chef
vi chef/hello_world.rb

Your hello_world.rb file should look like this:

# This is an example Chef recipe

file '/tmp/helloworld.txt' do
  content 'hello world'
end

See the recipe creation in action here.

Now to try it out!

chef-apply chef/hello_world.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[/tmp/helloworld.txt] action create
      - create new file /tmp/helloworld.txt
      - update content in file /tmp/helloworld.txt from none to b94d27
      --- /tmp/helloworld.txt	2014-02-24 22:09:33.074612312 +0000
      +++ /tmp/.helloworld.txt20140224-4530-evbx97	2014-02-24 22:09:33.075612812 +0000
      @@ -1 +1,2 @@
      +hello world

You can quickly verify the contents of /tmp/helloworld.txt:

cat /tmp/helloworld.txt
hello world

Simple!

See the recipe run in action here.

Chef makes it easy to keep your system configured correctly

From now on, every time we run the hello_world.rb recipe, Chef will check to see if the file at /tmp/helloworld.txt exists. If the file doesn’t exist, Chef will try to create it. If the file exists but doesn’t have exactly the right contents, Chef will back up the “wrong” version and overwrite it with a new file that has exactly the right contents.

Think about all the work you’d have to do in a shell script to accomplish that same set of safeguards. Testing file existence, capturing output, conditionals, generating and matching SHA sums. Chef does all the work for you. Easy mode!

Let’s test it by running Chef again with the /tmp/helloworld.txt file in exactly the right state:

chef-apply chef/hello_world.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[/tmp/helloworld.txt] action create (up to date)

Check that out! Everything was exactly how we told Chef it should be, so Chef marked it “up to date” and didn’t do anything! Chef won’t modify your system unless it needs to make a change.

Now let’s delete the /tmp/helloworld.txt file, and make Chef do some work.

rm /tmp/helloworld.txt
chef-apply chef/hello_world.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[/tmp/helloworld.txt] action create
    - create new file /tmp/helloworld.txt
    - update content in file /tmp/helloworld.txt from none to b94d27
        --- /tmp/helloworld.txt	2014-02-24 23:02:20.422016894 +0000
        +++ /tmp/.helloworld.txt20140224-5500-md8mdp	2014-02-24 23:02:20.422016894 +0000
        @@ -1 +1,2 @@
        +hello world
        

Chef saw that the file didn’t exist, so it created the file again!

Now let’s see what happens when we put the wrong contents into the /tmp/helloworld.txt file. This time we’ll need to run sudo chef-apply, so Chef will have permission to properly back up the “wrong” file to to the cache in /var/chef:

echo configuration drift >> /tmp/helloworld.txt
sudo chef-apply chef/hello_world.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[/tmp/helloworld.txt] action create
    - update content in file /tmp/helloworld.txt from 10f193 to b94d27
        --- /tmp/helloworld.txt	2014-02-24 23:09:47.100016815 +0000
        +++ /tmp/.helloworld.txt20140224-5742-bhjdcz	2014-02-24 23:09:53.871016888 +0000
        @@ -1,2 +1,2 @@
        -hello worldconfiguration drift
        +hello world
        

What happened to the old file? It’s in /var/chef/backup/tmp/helloworld.txt.chef-(timestamp).

ls /var/chef/backup/tmp/helloworld.txt.*
/var/chef/backup/tmp/helloworld.txt.chef-20140224230953.872644

See Chef set things right over and over again here.

Screencasts

Chef Is Super, Super Easy

Here’s everything we learned about Chef today:

  • Installing Chef is a one-liner
  • Writing simple programs in Chef is really, really easy.
  • You don’t have to know Ruby to use Chef!
  • You can re-run a Chef recipe as many times as you want to, and get the same result.
  • If your system changes out from under you, Chef will put it back in the right state.
  • If Chef overwrites a file, it will back that file up.
  • In order to do things that require root privileges, Chef has to be run via sudo.
  • Chef can do a lot more than manipulate files. It can create, modify, and delete users, groups, directories, and mount points. It can manage cron jobs, and set ENVARs. It can start and stop services, install packages, send http requests, manage symlinks & hard links, build software RAIDs, download files, check code out from git & subversion, and run scripts in python, powershell, ruby, bash, and many other languages. In short, Chef can handle any systems administration task you can think of, as easily as it can handle writing files.

Next time: It’s cool that we can have our script write a fixed string into a file, but what if we want to automatically change that string on every host that runs the program? We’ll learn about cookbooks, templates, attributes, and running chef-client in local mode.

Extra Credit Question: What if we only want Chef to put the hello world file back if it gets deleted, but not change anything if the file is already there but doesn’t match? (Hint: This is really easy.)


  1. Don’t worry, Windows folks. There’s a Chef Client .msi installer for y’all. You can download it at the getchef.com homepage