How do you abort/end a Chef run?
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?
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.
The recommended way to abort or edit a Chef run is to raise an exception. Here's an example:
ruby_block "some tricky operation" do
block do
OperationFoo
raise "Operation Foo Failed" if some_condition
end
end
Chef::Application.fatal! should do what your looking for. Here is an example from our code base which might be helpful.
cipher = case key.length
when 16 then "AES-128-ECB"
when 24 then "AES-192-ECB"
when 32 then "AES-256-ECB"
else
Chef::Application.fatal!("AES Key must be 16, 24, or 32 characters in length but key #{key} has length of #{key.length}")
end