Skip to content

Instantly share code, notes, and snippets.

@b-boyd
Created May 21, 2014 03:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save b-boyd/7ddd1a163dca537b340a to your computer and use it in GitHub Desktop.
Save b-boyd/7ddd1a163dca537b340a to your computer and use it in GitHub Desktop.
mailer-handler.rb - TCP Mailer handler for Sensu
#!/opt/sensu/embedded/bin/ruby
# A TCP Mailer handler for Sensu, based on https://github.com/sensu/sensu-community-plugins/blob/master/handlers/notification/mailer.rb.
#
# This expects the email address to be defined with an 'mail_to' attribute in the check,
# so different checks can send to different addresses.
#
# Requires sensu-generichandler.rb (https://gist.github.com/b-boyd/d52abc1c3baf6ea9e33d)
# and expects it in "../lib/". This mailer-handler.rb can be copied to
# /opt/sensu/handler/bin/.
#
# Example handler configuration, mailer.json:
# {
# "mailer": {
# "smtp_domain": "mydomain.com",
# "mail_from": "sensu@mydomain.com",
# "smtp_port": 25,
# "smtp_address": "smtp.mydomain.com"
# },
# "handlers": {
# "mailer": {
# "severities": [
# "ok",
# "warning",
# "critical",
# "unknown"
# ],
# "type": "tcp",
# "socket": {
# "port": 2035,
# "host": "127.0.0.1"
# }
# }
# }
# }
#
# Don't forget an init script (https://gist.github.com/b-boyd/821433dd903fd45d998e)
# and to rotate/purge the /var/log/sensu/sensu-mailer-handler.log file.
#
# Add a sensu check to ensure the process is running and/or the port is listening
# and use the remediator handler to automatically restart.
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
require 'rubygems'
require 'sensu/cli'
require 'sensu/base'
require 'sensu-generichandler'
gem 'mail', '~> 2.5.4'
require 'mail'
require 'timeout'
class MailerServer
$STOP_SIGNALS = %w[INT TERM]
def self.run(options={})
server = self.new(options)
EM.run do
server.start
server.trap_signals
end
end
def initialize(options={})
base = Sensu::Base.new(options)
base.setup_process
@logger = base.logger
@settings = base.settings
end
def start
begin
host = @settings['handlers']['mailer']['socket']['host']
rescue
@logger.error("@settings['handlers']['mailer']['socket']['host'] not defined")
return
end
begin
port = @settings['handlers']['mailer']['socket']['port'].to_i
rescue
@logger.error("@settings['handlers']['mailer']['socket']['port'] not defined")
return
end
mailer_client = MailerClient.new
EM.start_server(host, port, MailerReceiver) do |conn|
conn.handler = mailer_client
conn.logger = @logger
end
end
def trap_signals
@signals = Array.new
$STOP_SIGNALS.each do |signal|
Signal.trap(signal) do
@signals << signal
end
end
EM::PeriodicTimer.new(1) do
signal = @signals.shift
if $STOP_SIGNALS.include?(signal)
@logger.warn('received signal', {
:signal => signal
})
EM.stop
end
end
end
end
# Receive data over a socket that should go to Mail
class MailerReceiver < EM::Connection
attr_accessor :handler
attr_accessor :logger
def receive_data(data)
begin
handler.handle(data, logger)
rescue Exception => e
logger.info(e.message)
end
close_connection
end
end
class MailerClient < Sensu::GenericHandler
@event
@logger
def handle(data, logger)
@logger = logger
if ! is_json?(data)
@logger.error("Invalid JSON data - #{data}")
return
end
@event = JSON.parse(data)
self.filter
client = @event['client']['name']
output = @event['check']['output']
send_email(client, output)
end
def send_email(client, output)
# If a 'mail_to' isn't defined for this check
if ! @event['check']['mail_to']
@logger.info("No mail_to defined")
return
end
mail_to = @event['check']['mail_to']
mail_from = settings['mailer']['mail_from']
smtp_address = settings['mailer']['smtp_address'] || 'localhost'
smtp_port = settings['mailer']['smtp_port'] || '25'
smtp_domain = settings['mailer']['smtp_domain'] || 'localhost.localdomain'
smtp_username = settings['mailer']['smtp_username'] || nil
smtp_password = settings['mailer']['smtp_password'] || nil
smtp_authentication = settings['mailer']['smtp_authentication'] || :plain
if @event['check']['status'] == 0
status = "RESOLVED"
elsif @event['check']['status'] == 1
status = "WARNING"
elsif @event['check']['status'] == 2
status = "CRITICAL"
else
status = "UNKNOWN"
end
subject = "#{status} - " + @event['check']['name'] + " on #{client}"
text_body = "#{output}\n\n"
text_body += <<-BODY
Host: #{@event['client']['name']}
Check Name: #{@event['check']['name']}
Timestamp: #{Time.at(@event['check']['issued'])}
Command: #{@event['check']['command']}
Occurrences: #{@event['occurrences']}
BODY
html_body = "<b>#{output}</b><br><br>"
html_body += <<-BODY
Host: #{@event['client']['name']}<br>
Timestamp: #{Time.at(@event['check']['issued'])}<br>
Check Name: #{@event['check']['name']}<br>
Command: #{@event['check']['command']}<br>
Occurrences: #{@event['occurrences']}<br>
BODY
Mail.defaults do
delivery_options = {
:address => smtp_address,
:port => smtp_port,
:domain => smtp_domain,
:openssl_verify_mode => 'none',
:enable_starttls_auto => true
}
unless smtp_username.nil?
auth_options = {
:user_name => smtp_username,
:password => smtp_password,
:authentication => smtp_authentication
}
delivery_options.merge! auth_options
end
delivery_method :smtp, delivery_options
end
begin
timeout 10 do
Mail.deliver do
to mail_to
from mail_from
subject subject
text_part do
body text_body
end
html_part do
content_type 'text/html; charset=UTF-8'
body html_body
end
end
@logger.info('mail -- sent alert for ' + short_name + ' to ' + mail_to.to_s)
end
rescue Timeout::Error
@logger.error('mail -- timed out while attempting to ' + @event['action'] + ' an incident -- ' + short_name)
end
end
def short_name
@event['client']['name'] + '/' + @event['check']['name']
end
def bail(msg)
raise msg + ': ' + @event['client']['name'] + '/' + @event['check']['name']
end
def is_json?(str)
begin
!!JSON.parse(str)
rescue
false
end
end
end
begin
options = Sensu::CLI.read
MailerServer.run(options)
rescue exit
rescue Exception => e
@logger.error("#{e}. #{e.backtrace}")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment