About the Handler DSL¶
Use the Handler DSL to attach a callback to an event. If the event occurs during the chef-client run, the associated callback is executed. For example:
- Sending email if a chef-client run fails
- Sending a notification to chat application if an audit run fails
- Aggregating statistics about resources updated during a chef-client runs to StatsD
on Method¶
Use the on
method to associate an event type with a callback. The callback defines what steps are taken if the event occurs during the chef-client run and is defined using arbitrary Ruby code. The syntax is as follows:
Chef.event_handler do
on :event_type do
# some Ruby
end
end
where
Chef.event_handler
declares a block of code within a recipe that is processed when the named event occurs during a chef-client runon
defines the block of code that will tell the chef-client how to handle the event:event_type
is a valid exception event type, such as:run_start
,:run_failed
,:converge_failed
,:resource_failed
, or:recipe_not_found
For example:
Chef.event_handler do
on :converge_start do
puts "Ohai! I have started a converge."
end
end
Event Types¶
The following table describes the events that may occur during a chef-client run. Each of these events may be referenced in an on
method block by declaring it as the event type.
Event | Description |
---|---|
:run_start |
The start of the chef-client run. |
:run_started |
The chef-client run has started. |
:ohai_completed |
The Ohai run has completed. |
:skipping_registration |
The chef-client is not registering with the Chef server because it already has a private key or because it does not need one. |
:registration_start |
The chef-client is attempting to create a private key with which to register to the Chef server. |
:registration_completed |
The chef-client created its private key successfully. |
:registration_failed |
The chef-client encountered an error and was unable to register with the Chef server. |
:node_load_start |
The chef-client is attempting to load node data from the Chef server. |
:node_load_failed |
The chef-client encountered an error and was unable to load node data from the Chef server. |
:run_list_expand_failed |
The chef-client failed to expand the run-list. |
:node_load_completed |
The chef-client successfully loaded node data from the Chef server. Default and override attributes for roles have been computed, but are not yet applied. |
:policyfile_loaded |
The policy file was loaded. |
:cookbook_resolution_start |
The chef-client is attempting to pull down the cookbook collection from the Chef server. |
:cookbook_resolution_failed |
The chef-client failed to pull down the cookbook collection from the Chef server. |
:cookbook_resolution_complete |
The chef-client successfully pulled down the cookbook collection from the Chef server. |
:cookbook_clean_start |
The chef-client is attempting to remove unneeded cookbooks. |
:removed_cookbook_file |
The chef-client removed a file from a cookbook. |
:cookbook_clean_complete |
The chef-client is done removing cookbooks and/or cookbook files. |
:cookbook_sync_start |
The chef-client is attempting to synchronize cookbooks. |
:synchronized_cookbook |
The chef-client is attempting to synchronize the named cookbook. |
:updated_cookbook_file |
The chef-client updated the named file in the named cookbook. |
:cookbook_sync_failed |
The chef-client was unable to synchronize cookbooks. |
:cookbook_sync_complete |
The chef-client is finished synchronizing cookbooks. |
:library_load_start |
The chef-client is loading library files. |
:library_file_loaded |
The chef-client successfully loaded the named library file. |
:library_file_load_failed |
The chef-client was unable to load the named library file. |
:library_load_complete |
The chef-client is finished loading library files. |
:lwrp_load_start |
The chef-client is loading custom resources. |
:lwrp_file_loaded |
The chef-client successfully loaded the named custom resource. |
:lwrp_file_load_failed |
The chef-client was unable to load the named custom resource. |
:lwrp_load_complete |
The chef-client is finished loading custom resources. |
:attribute_load_start |
The chef-client is loading attribute files. |
:attribute_file_loaded |
The chef-client successfully loaded the named attribute file. |
:attribute_file_load_failed |
The chef-client was unable to load the named attribute file. |
:attribute_load_complete |
The chef-client is finished loading attribute files. |
:definition_load_start |
The chef-client is loading definitions. |
:definition_file_loaded |
The chef-client successfully loaded the named definition. |
:definition_file_load_failed |
The chef-client was unable to load the named definition. |
:definition_load_complete |
The chef-client is finished loading definitions. |
:recipe_load_start |
The chef-client is loading recipes. |
:recipe_file_loaded |
The chef-client successfully loaded the named recipe. |
:recipe_file_load_failed |
The chef-client was unable to load the named recipe. |
:recipe_not_found |
The chef-client was unable to find the named recipe. |
:recipe_load_complete |
The chef-client is finished loading recipes. |
:converge_start |
The chef-client run converge phase has started. |
:converge_complete |
The chef-client run converge phase is complete. |
:converge_failed |
The chef-client run converge phase has failed. |
:audit_phase_start |
The chef-client run audit phase has started. |
:audit_phase_complete |
The chef-client run audit phase is finished. |
:audit_phase_failed |
The chef-client run audit phase has failed. |
:control_group_started |
The named control group is being processed. |
:control_example_success |
The named control group has been processed. |
:control_example_failure |
The named control group’s processing has failed. |
:resource_action_start |
A resource action is starting. |
:resource_skipped |
A resource action was skipped. |
:resource_current_state_loaded |
A resource’s current state was loaded. |
:resource_current_state_load_bypassed |
A resource’s current state was not loaded because the resource does not support why-run mode. |
:resource_bypassed |
A resource action was skipped because the resource does not support why-run mode. |
:resource_update_applied |
A change has been made to a resource. (This event occurs for each change made to a resource.) |
:resource_failed_retriable |
A resource action has failed and will be retried. |
:resource_failed |
A resource action has failed and will not be retried. |
:resource_updated |
A resource requires modification. |
:resource_up_to_date |
A resource is already correct. |
:resource_completed |
All actions for the resource are complete. |
:stream_opened |
A stream has opened. |
:stream_closed |
A stream has closed. |
:stream_output |
A chunk of data from a single named stream. |
:handlers_start |
The handler processing phase of the chef-client run has started. |
:handler_executed |
The named handler was processed. |
:handlers_completed |
The handler processing phase of the chef-client run is complete. |
:provider_requirement_failed |
An assertion declared by a provider has failed. |
:whyrun_assumption |
An assertion declared by a provider has failed, but execution is allowed to continue because the chef-client is running in why-run mode. |
:run_completed |
The chef-client run has completed. |
:run_failed |
The chef-client run has failed. |
:attribute_changed |
Prints out all the attribute changes in cookbooks or sets a policy that override attributes should never be used. |
Examples¶
The following examples show ways to use the Handler DSL.
Send Email¶
Use the on
method to create an event handler that sends email when the chef-client run fails. This will require:
- A way to tell the chef-client how to send email
- An event handler that describes what to do when the
:run_failed
event is triggered - A way to trigger the exception and test the behavior of the event handler
Define How Email is Sent¶
Use a library to define the code that sends email when a chef-client run fails. Name the file helper.rb
and add it to a cookbook’s /libraries
directory:
require 'net/smtp'
module HandlerSendEmail
class Helper
def send_email_on_run_failure(node_name)
message = "From: Chef <chef@chef.io>\n"
message << "To: Grant <grantmc@chef.io>\n"
message << "Subject: Chef run failed\n"
message << "Date: #{Time.now.rfc2822}\n\n"
message << "Chef run failed on #{node_name}\n"
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message message, 'chef@chef.io', 'grantmc@chef.io'
end
end
end
end
Add the Handler¶
Invoke the library helper in a recipe:
Chef.event_handler do
on :run_failed do
HandlerSendEmail::Helper.new.send_email_on_run_failure(
Chef.run_context.node.name
)
end
end
- Use
Chef.event_handler
to define the event handler - Use the
on
method to specify the event type
Within the on
block, tell the chef-client how to handle the event when it’s triggered.
Test the Handler¶
Use the following code block to trigger the exception and have the chef-client send email to the specified email address:
ruby_block 'fail the run' do
block do
fail 'deliberately fail the run'
end
end
etcd Locks¶
The following example shows how to prevent concurrent chef-client runs from both holding a lock on etcd:
lock_key = "#{node.chef_environment}/#{node.name}"
Chef.event_handler do
on :converge_start do |run_context|
Etcd.lock_acquire(lock_key)
end
end
Chef.event_handler do
on :converge_complete do
Etcd.lock_release(lock_key)
end
end
HipChat Notifications¶
Event messages can be sent to a team communication tool like HipChat. For example, if a chef-client run fails:
Chef.event_handler do
on :run_failed do |exception|
hipchat_notify exception.message
end
end
or send an alert on a configuration change:
Chef.event_handler do
on :resource_updated do |resource, action|
if resource.to_s == 'template[/etc/nginx/nginx.conf]'
Helper.hipchat_message("#{resource} was updated by chef")
end
end
end
attribute_changed
event hook¶
In a cookbook library file, you can add this in order to print out all attribute changes in cookbooks:
Chef.event_handler do
on :attribute_changed do |precedence, key, value|
puts "setting attribute #{precedence}#{key.map {|n| "[\"#{n}\"]" }.join} = #{value}"
end
end
If you want to setup a policy that override attributes should never be used:
Chef.event_handler do
on :attribute_changed do |precedence, key, value|
raise "override policy violation" if precedence == :override
end
end