How to capture Chef exceptions - ruby

I'm working on a Chef recipe right now and I need to update a data bag with some information depending on the result of a code. Basically I need to update a data bag with succeed or failed.
The code looks like this:
begin
node[:fileDeploy].each do |f|
if f[:deploy]
cookbook_file "#{f[:subdirectory]}/#{f[:targetFilename]}" do
owner users['tomcatUser']
group users['tomcatGroup']
mode "0755"
cookbook node[:cookbookName]
source "#{f[:sourceFilename]}"
end
end
end
# Update data bag: succeeded
rescue Chef::Exceptions::FileNotFound => e
# Update data bag: failed
end
The problem is that even though there is a missing file, the rescue block is not excecuted and the data bag is not updated accordingly.
So, when I run the command sudo chef-client on the server, it ends up with the exception Chef::Exceptions::FileNotFound but it is not being handled by the rescue block. does it make sense? Any help?

Your rescue block doesn't catch the exception because the code raising the exception is not executed in the scope of the exception handler.
In your code, you declare a cookbook_file resource. The declaration goes fine and the resource is scheduled for execution during the convergence phase. Your rescue block could catch an exception that would occur during declaration of the resource, not when it is actually executed.
Please see About the chef-client Run to learn more about the two phases of a chef run, namely the generation of the resource collection and the later convergence.
Now for your desired result, you could check the condition that the source file exists during convergence and decide accordingly.
Generally, handling errors is rather hard in Chef. This is by design, as you generally should design your system that it is rather independent from other parts. So if you need a directory or file to be present, you ought to create that one explicitly using the appropriate resource. You can then use notifications to notify other resources to run a certain action if the current resource "changed" (whatever that means for the specific resource).
The code below tries to achieve something similar to what you apparently want. It still doesn't catch an exception during convergence but tries to not raise one in the first place but checks the required conditions and runs the appropriate resources.
node[:fileDeploy].each do |f|
if f[:deploy]
cookbook_file "#{f[:subdirectory]}/#{f[:targetFilename]}" do
owner users['tomcatUser']
group users['tomcatGroup']
mode "0755"
cookbook node[:cookbookName]
source f[:sourceFilename]
only_if{ Dir.exist?(File.base_name(f[:sourceFilename]) }
notifies :run, "execute[success #{f[:sourceFilename]}]", :immediately
end
# This one gets notified (and run) when the cookbook_file is successful
execute "success #{f[:sourceFilename]}" do
action :nothing
command "do whatever you like"
end
# This one runs only if the created file doesn't exist by now
execute "error #{f[:subdirectory]}/#{f[:targetFilename]}" do
action :run
command "do whatever you like"
creates "#{f[:subdirectory]}/#{f[:targetFilename]}"
end
end
end

Related

Ensure to run code after rails `delayed job` fails or successess

Is there any way we can ensure certain code to run event after the delayed job is failed or succeeds just like we can write ensure block in exception handling?
What's wrong with the following approach?
def delayed_job_method
do_the_job
ensure
something
end

Prevent exceptions in Rubygem that cannot communicate with its associated web service?

I am using a RubyGem (DeathByCaptcha) that makes HTTP calls to deathbycaptcha.com. Every so often the HTTP request times out or fails for some other unknown reason, and my Ruby scripts exits with an exception. I am trying to automate repeated instances of this method ("decode") and I am trying to determine if there is a way to prevent an error in this method from exiting the whole script.
EDIT: Since I'm bound to get flamed on here, I will mention upfront that the purpose of this is to determine the effectiveness of different captcha options on my website's registration page with common captcha-breakers, because I have had problems with spam signups.
Here is how to prevent the exception from exiting the script.
tries = 0
begin
# risky failing code
rescue
sleep(1) # sleep n seconds
tries += 1
retry if tries <= 3 # retry the risky code again
end
You would need to catch the exception that is raised and somehow handle it.
You are looking for something like
begin
# Send HTTP request
rescue WhateverExceptionClassYouGet > error
# Do something with the error
end

Handling Errno::ETIMEDOUT exception in capistrano

I am using a block to dynamically generate a list of servers I want to push code to. Occasionally a few of the machines are unreachable, resulting in a "Errno::ETIMEDOUT". This stop the entire deployment process. How do I rescue this and run some custom code using capistrano?
You'd have to wrap your server list generator with a try catch block.
begin
# ... Get server list
rescue Errno::ETIMEDOUT
# ... Handle cases when connection times out
end

Ruby, can you call a method from inside the same method?

I'm trying to compare 2 files. If only 1 file appears, it will create a copy.
Is it then possible to re-call the method, when using begin..rescue..end?
def differ()
begin
file_today = read_file("/etc/hosts.deny")
file_yesterday = read_file("/etc/hosts.deny_old")
content = Diffy::Diff.new(file_yesterday, file_today)
rescue
copy_log
differ #call itself?!O_o Well, after the copy has been created!
end
return content
end
It is "differ #call itself?!O_o Well, after the copy has been created!" that I cant get to work.
You can use the retry keyword in your rescue clause to restart it.
Edit: Here's some more information from the free edition of Programming Ruby:
"The redo statement causes a loop to repeat the current iteration. Sometimes, though, you need to wind the loop right back to the very beginning. The retry statement is just the ticket. retry restarts any kind of iterator loop. Retry will reevaluate any arguments to the iterator before restarting it."
Edit: I realized that this behavior for retry has been deprecated in 1.9. Just know that retry is usually used to re-execute a code block that raised an exception. Make sure you've fixed whatever caused the exception before you retry - otherwise you end up in an infinite loop!

Ruby - Error Handling - Good Practices

This is more of an opinion oriented question. When handling exceptions in nested codes such as:
Assuming you have a class that initialize another class to run a job. The job returns a value, which is then processed by the class which initially called it.
Where would you put the exception and error logging? Would you define it on the initialization of the job class in the calling class, which will handle then exception in the job execution or on both levels ?
if the job handles exceptions then you don't need to wrap the call to the job in a try catch.
but the class that initializes and runs the job could throw exceptions, so you should handle exceptions at that level as well.
here is an example:
def some_job
begin
# a bunch of logic
rescue
# handle exception
# log it
end
end
it wouldn't make sense then to do this:
def some_manager
begin
some_job
rescue
# log
end
end
but something like this makes more sense:
def some_manager
begin
# a bunch of logic
some_job
# some more logic
rescue
# handle exception
# log
end
end
and of course you would want to catch specific exceptions.
Probably the best answer, in general, for handling Exceptions in Ruby is reading Exceptional Ruby. It may change your perspective on error handling.
Having said that, your specific case. When I hear "job" in hear "background process", so I'll base my answer on that.
Your job will want to report status while it's doing it's thing. This could be states like "in queue", "running", "finished", but it also could be more informative (user facing) information: "processing first 100 out of 1000 records".
So, if an error happens in your background process, my suggestion is two-fold:
Make sure you catch exceptions before you exit the job. Your background job processor might not like a random exception coming from your code. I, personally, like the idea of catching the exception and saving it to the database, for easy retrieval later. Then again, depending on your background job processor, maybe it handles error reporting for you. (I think reque does, for example).
On the front end, use AJAX (or something) to occasionally check in to how the job is doing. Say every 10 seconds or something. In additional to getting the status of the job, also make sure you return this additional information to the user (if appropriate).

Resources