How do you abort/end a Chef run?

Jordan Dea-Mattson picture Jordan Dea-Mattson · Jan 12, 2013 · Viewed 36.4k times · Source

Under certain conditions, I need to abort/end a Chef run with a non-zero status code, that will then propagate back through our deployment chain and eventually to Jenkins resulting in a big, fat red ball.

What is the best way to do this?

Answer

jtimberman picture jtimberman · Jan 12, 2013

For the readers coming to this question and answer in the future that may not be familiar with Chef, a Chef run "converges" the node, or brings it in line with the policy declared in the recipe(s) it is running. This is also called "convergence." This has two phases, "compile" and "execute." The compile phase is when Chef evaluates ("compiles") the recipes' Ruby code, looking for resources to add to the Resource Collection. Once that is complete, it "executes" the actions for each resource to put it into the state desired. System commands are run, etc.

Erik Hollensbe wrote an excellent walk through of how this works in 2013.

Now, for the answer:

There are several ways to end a Chef run, or exit a Chef recipe, depending on how you want to go about it, since Chef recipes are Ruby code.

If your goal is to stop processing a recipe based on a condition, but continue with the rest of the run, then use the return Ruby keyword. For example:

file '/tmp/ponies' do
  action :create
end

return if platform?('windows')

package 'bunnies-and-flowers' do
  action :install
end

We presume that if the system is Windows, it doesn't have a package manager that can install the bunnies-and-flowers package, so we return from whence we came.

If you wish to abort the Chef run entirely

Tl;dr: Use raise. It's the best practice to abort a Chef run in case of an error condition.

That said, chef-client exits if it encounters an unhandled exception anywhere in the run. For example, if a template resource can't find its source file, or if the user running chef-client doesn't have permission to do something like make a directory. This is why using raise also works to end a run.

Where you put raise matters. If you use it in a ruby_block resource, it will only raise during the execution phase in convergence. If you use it outside of a resource like the return example above, it will happen during the compile phase.

file '/tmp/ponies' do
  action :create
end

raise if platform?('windows')

package 'bunnies-and-flowers' do
  action :install
end

Perhaps we do have a package manager on Windows, and we want this package installed. The raise will result in Chef fatally exiting and giving a stack trace.

In years past, another approach was to use Chef::Application.fatal! - as written by me in this answer. Times have changed and this is NOT RECOMMENDED. Do not do this anymore. If you're doing it, switch to raise and as mentioned, write your own exception handler if your needs are more complicated (see below).

More Graceful error handling

Since recipes are Ruby, you can also gracefully handle error conditions with a begin..rescue block.

begin
  dater = data_bag_item(:basket, "flowers")
rescue Net::HTTPServerException
  # maybe some retry code here?
  raise "Couldn't find flowers in the basket, need those to continue!"
end

data_bag_item makes an HTTP request for a data bag on the Chef Server, and will return a Net::HTTPServerException if there's a problem from the server (404 not found, 403 unauthorized, etc). We could possibly attempt to retry or do some other handling, and then fall back to raise.

Reporting Errors

Simply exiting and tossing a stack trace is fine if you're running Chef from the command-line. However, if you're running it in cron or as a daemon across a few, or even dozens or hundreds of machines, this isn't a great way to keep sanity when there's problems.

Enter Chef's report/exception handler feature. You can use a handler for your Chef runs. All report handlers are run at the end of a Chef run. Exception handlers are run at the end of an aborted Chef run. The status of the run is tracked, and can be checked in the handler, so you can write one that handles both kinds of run (successful/completed or unsuccessful/aborted).

The documentation tells you how to write one. It also includes a list of available open source handlers that you can use for a variety of services, including:

  • Email over SMTP
  • IRC
  • Graphite
  • HipChat

And many more.