# Formatter objects know best about how to create and format content.
# The Base class defines several variables and methods that can be used in subclasses.
# Nearly all of them can be overridden. Subclasses can also extend functionality and
# call on helpers.
# Dear Reader,
# There is a brittle, un-OOP pattern in this class, but it gets the job done
# because sometimes "working" is better than "elegant" or "correct".
# Your exercise, should you choose to take it on, is to devise a better way
# for a formatter to know which content to generate based on the message type.
# THIS IS ESPECIALLY IMPORTANT FOR SUBCLASSES THAT OVERRIDE METHODS!
# Hit me with your best shot.
require 'tmpdir'
require 'nagios-herald/logging'
require 'nagios-herald/util'
require 'nagios-herald/formatter_loader'
module NagiosHerald
class Formatter
include NagiosHerald::Logging
include NagiosHerald::Util
attr_accessor :content # all the content required to generate a message
attr_accessor :sandbox # @sandbox is the place to save attachments, possibly a tempdir
attr_accessor :state_type
def initialize(options)
@content = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) } # autovivify
@content[:attachments] = []
@content[:html]
@content[:subject] = ""
@content[:text]
@nagios_url = options[:nagios_url]
@sandbox = get_sandbox_path
@state_type = get_nagios_var("NAGIOS_SERVICESTATE") != "" ? "SERVICE" : "HOST"
end
def self.formatters
@@formatters ||= {}
end
# Public: When subclassed formatters are instantiated, add them to the @@formatters hash.
# The key is the downcased and snake_cased name of the class file (i.e. check_disk);
# the value is the actual class (i.e. CheckDisk) so that we can easily
# instantiate formatters when we know the formatter name.
# Learned this pattern thanks to the folks at Chef and @jonlives.
# See https://github.com/opscode/chef/blob/11-stable/lib/chef/knife.rb#L79#L83
#
# Returns the formatters hash.
def self.inherited(subclass)
subclass_base_name = subclass.name.split('::').last
subclass_base_name.gsub!(/[A-Z]/) { |s| "_" + s } # replace uppercase with underscore and lowercase
subclass_base_name.downcase!
subclass_base_name.sub!(/^_/, "") # strip the leading underscore
formatters[subclass_base_name] = subclass
end
# Public: Concatenates text content.
#
# section - The content section name whose text we'll concatenate
# text - The text we want to concatenate
#
# Example:
#
# add_text("state_detail", "Service is somewhere in Kansas")
#
# Returns the concatenated HTML for the given section.
def add_text(section, text)
# Ensure our key is a symbol, regardless if we're passed a string or symbol
section = section.to_sym
if @content[:text][section].nil? or @content[:text][section].empty?
@content[:text][section] = text
else
@content[:text][section] += text
end
end
# Public: Concatenates HTML content.
#
# section - The content section name whose HTML we'll concatenate
# text - The HTML we want to concatenate
#
# Example:
#
# add_html("state_detail", "Service is somewhere in Kansas")
#
# Returns the concatenated HTML for the given section.
def add_html(section, html)
# Ensure our key is a symbol, regardless if we're passed a string or symbol
section = section.to_sym
if @content[:html][section].nil? or @content[:html][section].empty?
@content[:html][section] = html
else
@content[:html][section] += html
end
end
# Public: Add an attachment's path to an array.
#
# path - The fully qualified path for a file attachment
#
# Example:
#
# add_attachment("/tmp/file-to-attach.txt")
#
# Returns the array of attachment paths.
def add_attachment(path)
#@attachments << path
@content[:attachments] << path
end
#
# format the content
#
# Public: Appends a newline in text and HTML format.
#
# section - The content section name that needs the line break
#
# Example
#
# line_break(additional_info)
#
# Appends text and HTML output to the appropriate sections in @content
def line_break(section)
add_text(section, "\n")
add_html(section, "
")
end
# Public: Formats the information about the host that's being alerted on.
# Generates text and HTML output.
def host_info
section = __method__
text = ""
html = ""
notification_type = get_nagios_var("NAGIOS_NOTIFICATIONTYPE")
hostname = get_nagios_var("NAGIOS_HOSTNAME")
service_desc = get_nagios_var("NAGIOS_SERVICEDESC")
text += "Host: #{hostname} "
html += "
Host: #{hostname} "
if !service_desc.nil? and !service_desc.empty?
text += "Service: #{service_desc}\n"
html += "Service: #{service_desc}
"
else
# we need a trailing newline if no service description
line_break(section)
end
add_text(section, text)
add_html(section, html)
line_break(section)
end
# Public: Formats information about the state of the thing being alerted on
# where 'thing' is either HOST or SERVICE.
# Generates text and HTML output.
def state_info
section = __method__
text = ""
html = ""
state = get_nagios_var("NAGIOS_#{@state_type}STATE")
duration = get_nagios_var("NAGIOS_#{@state_type}DURATION")
last_duration = get_nagios_var("NAGIOS_LAST#{@state_type}STATE")
attempts = get_nagios_var("NAGIOS_#{@state_type}ATTEMPT")
max_attempts = get_nagios_var("NAGIOS_MAX#{@state_type}ATTEMPTS")
text += "State is now: #{state} for #{duration} (was #{last_duration}) after #{attempts} / #{max_attempts} checks\n"
if state.eql? 'OK' or state.eql? 'UP'
html += "State is now: #{state} for #{duration} (was #{last_duration}) after #{attempts} / #{max_attempts} checks
"
else
html += "State is now: #{state} for #{duration} (was #{last_duration}) after #{attempts} / #{max_attempts} checks
"
end
add_text(section, text)
add_html(section, html)
line_break(section)
end
# Public: Formats information about the notification.
# Provides information such as the date and notification number.
# Generates text and HTML output.
def notification_info
section = __method__
text = ""
html = ""
date = get_nagios_var("NAGIOS_LONGDATETIME")
number = get_nagios_var("NAGIOS_NOTIFICATIONNUMBER")
text += "Notification sent at: #{date} (notification number #{number})\n\n"
html += "Notification sent at: #{date} (notification number #{number})
"
add_text(section, text)
add_html(section, html)
end
# Public: Formats information provided plugin's output.
# Generates text and HTML output.
def additional_info
section = __method__
text = ""
html = ""
output = get_nagios_var("NAGIOS_#{@state_type}OUTPUT")
if !output.nil? and !output.empty?
text += "Additional Info: #{unescape_text(output)}\n\n"
html += "Additional Info: #{output}
"
add_text(section, text)
add_html(section, html)
end
end
# Public: Formats information provided plugin's *long* output.
# Generates text and HTML output.
def additional_details
section = __method__
text = ""
html = ""
long_output = get_nagios_var("NAGIOS_LONG#{@state_type}OUTPUT")
if !long_output.nil? and !long_output.empty?
text += "Additional Details: #{unescape_text(long_output)}\n"
html += "Additional Details:
#{unescape_text(long_output)}