class Flapjack::Gateways::AwsSns

Constants

SNS_DEFAULT_REGION_NAME

Attributes

sent[RW]

Public Class Methods

new(opts = {}) click to toggle source
# File lib/flapjack/gateways/aws_sns.rb, line 27
def initialize(opts = {})
  @lock = opts[:lock]

  @config = opts[:config]

  # TODO support for config reloading
  @queue = Flapjack::RecordQueue.new(@config['queue'] || 'sns_notifications',
             Flapjack::Data::Alert)

  @sent = 0

  Flapjack.logger.debug("new sns gateway pikelet with the following options: #{@config.inspect}")
end

Private Class Methods

get_signature(secret_key, string_to_sign) click to toggle source
# File lib/flapjack/gateways/aws_sns.rb, line 164
def self.get_signature(secret_key, string_to_sign)
  signature = OpenSSL::HMAC.digest('sha256', secret_key, string_to_sign)

  Base64.encode64(signature).strip
end
string_to_sign(method, host, uri, query) click to toggle source
# File lib/flapjack/gateways/aws_sns.rb, line 170
def self.string_to_sign(method, host, uri, query)
  encoded_query = query.keys.sort.collect {|key|
    k = URI.encode_www_form_component(key)
    v = URI.encode_www_form_component(query[key].to_s)
    "#{k}=#{v}"
  }.join("&")

  [method.upcase,
   host.downcase,
   uri,
   encoded_query
  ].join("\n")
end

Public Instance Methods

start() click to toggle source
# File lib/flapjack/gateways/aws_sns.rb, line 41
def start
  begin
    Zermelo.redis = Flapjack.redis

    loop do
      @lock.synchronize do
        @queue.foreach {|alert| handle_alert(alert) }
      end

      @queue.wait
    end
  ensure
    Flapjack.redis.quit
  end
end
stop_type() click to toggle source
# File lib/flapjack/gateways/aws_sns.rb, line 57
def stop_type
  :exception
end

Private Instance Methods

handle_alert(alert) click to toggle source
# File lib/flapjack/gateways/aws_sns.rb, line 63
def handle_alert(alert)
  Flapjack.logger.debug "Received a notification: #{alert.inspect}"

  region_name = @config["region_name"] || SNS_DEFAULT_REGION_NAME
  hostname = "sns.#{region_name}.amazonaws.com"
  endpoint = "http://#{hostname}/"
  access_key = @config["access_key"]
  secret_key = @config["secret_key"]
  timestamp = Time.at(alert.time).utc.strftime('%Y-%m-%dT%H:%M:%SZ')

  address         = alert.medium.address
  notification_id = alert.id
  message_type    = alert.rollup ? 'rollup' : 'alert'

  aws_sns_subject_template_erb, aws_sns_subject_template =
    load_template(@config['templates'], "#{message_type}_subject",
                  'text', File.join(File.dirname(__FILE__), 'aws_sns'))

  aws_sns_template_erb, aws_sns_template =
    load_template(@config['templates'], message_type, 'text',
                  File.join(File.dirname(__FILE__), 'aws_sns'))

  @alert  = alert
  @check  = alert.check
  bnd     = binding

  begin
    erb_to_be_executed = aws_sns_subject_template
    subject = aws_sns_subject_template_erb.result(bnd).chomp

    erb_to_be_executed = aws_sns_template
    message = aws_sns_template_erb.result(bnd).chomp
  rescue => e
    Flapjack.logger.error "Error while executing the ERB for an AWS SNS message: " +
      "ERB being executed: #{erb_to_be_executed}"
    raise
  end

  if @config.nil? || (@config.respond_to?(:empty?) && @config.empty?)
    Flapjack.logger.error "AWS SNS config is missing"
    return
  end

  errors = []

  [[address, "AWS SNS topic ARN is missing"],
   [access_key, "AWS SNS access key is missing"],
   [secret_key,  "AWS SNS secret key is missing"],
   [notification_id, "Notification id is missing"]].each do |val_err|

    next unless val_err.first.nil? || (val_err.first.respond_to?(:empty?) && val_err.first.empty?)
    errors << val_err.last
  end

  unless errors.empty?
    errors.each {|err| Flapjack.logger.error err }
    return
  end

  query = {'Subject'          => (subject.length > 100) ?
                                 subject[0..99].gsub(/...$/, '...') : subject,
           'TopicArn'         => address,
           'Message'          => message,
           'Action'           => 'Publish',
           'SignatureVersion' => 2,
           'SignatureMethod'  => 'HmacSHA256',
           'Timestamp'        => timestamp,
           'AWSAccessKeyId'   => access_key.upcase}

  # TODO ensure we're not getting a cached response from a proxy or similar,
  # use appropriate headers etc.
  string_sign = self.class.string_to_sign('POST', hostname, "/", query)

  query['Signature'] = self.class.get_signature(secret_key, string_sign)

  req = Net::HTTP::Post.new(endpoint)
  req.set_form_data(query)

  http_response = Net::HTTP.start(hostname) do |http|
    http.request(req)
  end

  Flapjack.logger.debug "server response: #{http_response.inspect}"

  status = http_response.code

  if (status.to_i >= 200) && (status.to_i <= 206)
    @sent += 1
    Flapjack.logger.debug "Sent notification via SNS, response status is #{status}, " +
      "notification_id: #{notification_id}"
  else
    Flapjack.logger.error "Failed to send notification via SNS, response status is #{status}, " +
      "notification_id: #{notification_id}"
  end

rescue => e
  Flapjack.logger.error "Error generating or delivering notification to #{address}: #{e.class}: #{e.message}"
  Flapjack.logger.error e.backtrace.join("\n")
  raise
end