Search code examples
chef-infrahandler

Custom Chef handlers


I am trying to understand the Chef documentation on Custom Handlers.

The steps seem easy enough but I still don't understand how it works.

  1. Download the chef_handler cookbook
  2. Create a custom handler
  3. Write a recipe using the chef_handler resource
  4. Add that recipe to a node’s run-list, often as the first recipe in that run-list

Step 1 is no longer necessary as chef_handler cookbook is now part of Chef.

Step 2 creates a handler. The example given is

require 'net/smtp'

module OrgName
  class SendEmail < Chef::Handler
    def report
      if run_status.failed? then
        message  = "From: sender_name <[email protected]>\n"
        message << "To: recipient_address <[email protected]>\n"
        message << "Subject: chef-client Run Failed\n"
        message << "Date: #{Time.now.rfc2822}\n\n"
        message << "Chef run failed on #{node.name}\n"
        message << "#{run_status.formatted_exception}\n"
        message << Array(backtrace).join('\n')
        Net::SMTP.start('your.smtp.server', 25) do |smtp|
          smtp.send_message message, 'sender@example', 'recipient@example'
        end
      end
    end
  end
end

Now comes step 3 which I don't understand, add the following to a recipe

send_email 'blah' do
  # recipe code
end

When I run my recipe it just produces the error message which I expected to begin with:

FATAL: NoMethodError: undefined method `send_email` for cookbook: test, recipe: default :Chef::Recipe

How is this supposed to work? Are there other simple but working examples of custom handlers?

For Chef 15.0.300


Solution

  • The documentation on Chef handlers is a little unclear indeed at first look. Looking into more details, one thing is clear. chef_handler is a Chef resource (since Chef client 14). It supports two handlers by default - JsonFile and ErrorReport.

    There is more documentation on custom handlers. This shows that we need to have some chef_gem installed for the custom handler(s) to work (apart from the above two).

    Simple Email

    A handler that collects exception and report handler data and then uses pony to send email reports that are based on Erubis templates.

    Connecting the dots... then we should have a recipe that installs the chef_gem and enables the handler. I created a separate cookbook, which can be reused by other cookbooks.

    In the below example, I have a cookbook my_handlers:

    recipes/json.rb
    recipes/error_report.rb
    recipes/email.rb
    libraries/send_mail.rb
    

    In my_handlers/recipes/email.rb:

    chef_gem 'chef-handler-mail'
    
    chef_handler 'MailHandler' do
      source 'chef/handler/mail'
      arguments :to_address => 'root'
      action :enable
    end
    

    In my_handlers/libraries/send_mail.rb:

    require 'net/smtp'
    
    module HandlerSendEmail
      class Helper
    
        def send_email_on_run_failure(node_name)
          message = "From: Chef <[email protected]>\n"
          message << "To: Grant <[email protected]>\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('your.smtp.server', 25) do |smtp|
            smtp.send_message message, '[email protected]', '[email protected]'
          end
        end
      end
    end
    

    Now I have something I can reuse in other cookbooks and choose the handler I want to enable.

    Let's say I have a cookbook cookbook1 in which I want to use the email handler on failure:

    In cookbook1/metadata.rb:

    depends 'my_handlers'
    

    In recipes/default.rb:

    # Enable the Chef email handler by calling "my_handler"
    include_recipe 'my_handlers::email'
    
    Chef.event_handler do
      on :run_failed do
        HandlerSendEmail::Helper.new.send_email_on_run_failure(Chef.run_context.node.name)
      end
    end
    
    # Usual resource declarations
    
    ruby_block 'fail the run' do
      block do
        fail 'deliberately fail the run'
      end
    end
    

    If failure occurs (deliberately as above), Chef email handler is invoked and triggers an email indicating failure.