class Flapjack::Gateways::Jabber::Interpreter

Constants

CHECK_MATCH_FRAGMENT

Attributes

siblings[RW]

Public Class Methods

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

  @boot_time = opts[:boot_time]

  @should_quit = false

  @messages = []
end

Public Instance Methods

derive_check_ids_for(pattern, tag_name, check_name, options = {}) { |ids, "matching /#{strip}/", num_checks| ... } click to toggle source
# File lib/flapjack/gateways/jabber.rb, line 203
def derive_check_ids_for(pattern, tag_name, check_name, options = {})
  lock_klasses = options[:lock_klasses] || []

  deriver = if !pattern.nil? && !pattern.strip.empty?
    proc {
      checks = begin
        Flapjack::Data::Check.intersect(:name => Regexp.new(pattern.strip))
      rescue RegexpError
        nil
      end

      if checks.nil?
        "Error parsing /#{pattern.strip}/"
      else
        num_checks = checks.count
        if num_checks == 0
          "No checks match /#{pattern.strip}/"
        else
          checks = checks.sort(:name, :limit => options[:limit]) if options[:limit]
          yield(checks.ids, "matching /#{pattern.strip}/", num_checks) if block_given?
        end
      end
    }
  elsif !tag_name.nil? && !tag_name.strip.empty?

    lock_klasses.unshift(Flapjack::Data::Tag)

    proc {
      tag = Flapjack::Data::Tag.intersect(:name => tag_name.strip).all.first

      if tag.nil?
        "No tag '#{tag_name.strip}'"
      else
        checks = tag.checks
        num_checks = checks.count
        if num_checks == 0
          "No checks with tag '#{tag_name.strip}'"
        else
          checks = checks.sort(:name, :limit => options[:limit]) if options[:limit]
          yield(checks.ids, "with tag '#{tag_name}'", num_checks) if block_given?
        end
      end
    }
  elsif !check_name.nil? && !check_name.strip.empty?
    proc {
      checks = Flapjack::Data::Check.intersect(:name => check_name.strip)

      if checks.empty?
        "No check exists with name '#{check_name.strip}'"
      else
        num_checks = checks.count
        checks = checks.sort(:name, :limit => options[:limit]) if options[:limit]
        yield(checks.ids, "with name '#{check_name.strip}'", num_checks) if block_given?
      end
    }
  end

  return if deriver.nil?

  if lock_klasses.empty?
    Flapjack::Data::Check.lock(&deriver)
  else
    Flapjack::Data::Check.lock(*lock_klasses, &deriver)
  end
end
get_check_details(check, at_time) click to toggle source
# File lib/flapjack/gateways/jabber.rb, line 168
def get_check_details(check, at_time)
  start_range = Zermelo::Filters::IndexRange.new(nil, at_time, :by_score => true)
  end_range   = Zermelo::Filters::IndexRange.new(at_time, nil, :by_score => true)

  sched = check.scheduled_maintenances.intersect(:start_time => start_range,
            :end_time => end_range).all.max_by(&:end_time)

  unsched = check.unscheduled_maintenances.intersect(:start_time => start_range,
            :end_time => end_range).all.max_by(&:end_time)

  out = ''

  if sched.nil? && unsched.nil?
    out += "Not in scheduled or unscheduled maintenance.\n"
  else
    if sched.nil?
      out += "Not in scheduled maintenance.\n"
    else
      remain = time_period_in_words( (sched.end_time - at_time).ceil )
      # TODO a simpler time format?
      out += "In scheduled maintenance: #{sched.start_time} -> #{sched.end_time} (#{remain} remaining)\n"
    end

    if unsched.nil?
      out += "Not in unscheduled maintenance.\n"
    else
      remain = time_period_in_words( (unsched.end_time - at_time).ceil )
      # TODO a simpler time format?
      out += "In unscheduled maintenance: #{unsched.start_time} -> #{unsched.end_time} (#{remain} remaining)\n"
    end
  end

  out
end
interpret(room, nick, time, command) click to toggle source
# File lib/flapjack/gateways/jabber.rb, line 269
def interpret(room, nick, time, command)
  msg = nil
  action = nil
  check = nil

  begin
    case command
    when /^help\s*$/
      msg = "commands: \n" +
            "  find (number) checks matching /pattern/\n" +
            "  find (number) checks with tag <tag>\n" +
            "  state of <check>\n" +
            "  state of checks matching /pattern/\n" +
            "  state of checks with tag <tag>\n" +
            "  tell me about <check>\n" +
            "  tell me about (number) checks matching /pattern/\n" +
            "  tell me about (number) checks with tag <tag>\n" +
            "  ACKID <id>[ duration: <time spec>][ comment: <comment>]\n" +
            "  ack <check>[ duration: <time spec>][ comment: <comment>]\n" +
            "  ack checks matching /pattern/[ duration: <time spec>][ comment: <comment>]\n" +
            "  ack checks with tag <tag>[ duration: <time spec>][ comment: <comment>]\n" +
            "  maint <check>[ (start-at|start-in): <time spec>][ duration: <time spec>][ comment: <comment>]\n" +
            "  maint checks matching /pattern/[ (start-at|start-in): <time spec>][ duration: <time spec>][ comment: <comment>]\n" +
            "  maint checks with tag <tag>[ (start-at|start-in): <time spec>][ duration: <time spec>][ comment: <comment>]\n" +
            "  test notifications for <check>\n" +
            "  test notifications for checks matching /pattern/\n" +
            "  test notifications for checks with tag <tag>\n" +
            "  identify\n" +
            "  help\n"

    when /^identify\s*$/
      t    = Process.times
      fqdn = `/bin/hostname -f`.chomp
      pid  = Process.pid
      msg  = "Flapjack #{Flapjack::VERSION} process #{pid} on #{fqdn} \n" +
             "Identifiers: #{@bot.identifiers.join(', ')}\n" +
             "Boot time: #{@boot_time}\n" +
             "User CPU Time: #{t.utime}\n" +
             "System CPU Time: #{t.stime}\n" +
             `uname -a`.chomp + "\n"

    when /^find\s+(\d+\s+)?checks\s+(?:matching\s+\/(.+)\/|with\s+tag\s+(.+))\s*$/im
      limit_checks = Regexp.last_match(1).nil? ? 30 : Regexp.last_match(1).strip.to_i
      pattern     = Regexp.last_match(2)
      tag         = Regexp.last_match(3)

      msg = derive_check_ids_for(pattern, tag, nil, :limit => limit_checks) do |check_ids, descriptor, num_checks|
        resp = "Checks #{descriptor}:\n"
        checks = Flapjack::Data::Check.find_by_ids(*check_ids)
        resp += "Showing first #{limit_checks} results of #{num_checks}:\n" if num_checks > limit_checks
        resp += checks.map {|chk|
          cond = chk.condition
          "#{chk.name} is #{cond.nil? ? '[none]' : cond.upcase}"
        }.join(', ')
        resp
      end

    when /^state\s+of\s+#{CHECK_MATCH_FRAGMENT}\s*$/im
      pattern    = Regexp.last_match(1)
      tag        = Regexp.last_match(2)
      check_name = Regexp.last_match(3)

      msg = derive_check_ids_for(pattern, tag, check_name) do |check_ids, descriptor|
        "State of checks #{descriptor}:\n" +
          Flapjack::Data::Check.intersect(:id => check_ids).collect {|chk|
            cond = chk.condition
            "#{chk.name} - #{cond.nil? ? '[none]' : cond.upcase}"
          }.join("\n")
      end

    when /^tell\s+me\s+about\s(\d+\s+)?+#{CHECK_MATCH_FRAGMENT}\s*$/im
      limit_checks = Regexp.last_match(1).nil? ? 30 : Regexp.last_match(1).strip.to_i
      pattern      = Regexp.last_match(2)
      tag          = Regexp.last_match(3)
      check_name   = Regexp.last_match(4)

      msg = derive_check_ids_for(pattern, tag, check_name, :limit => limit_checks,
              :lock_klasses => [Flapjack::Data::ScheduledMaintenance,
                                Flapjack::Data::UnscheduledMaintenance]) do |check_ids, descriptor, num_checks|
        current_time = Time.now
        resp = "Details of checks #{descriptor}\n"
        resp += "Showing first #{limit_checks} results of #{num_checks}:\n" if num_checks > limit_checks
        resp += Flapjack::Data::Check.intersect(:id => check_ids).collect {|chk|
                  get_check_details(chk, current_time)
                }.join("")
      end

    when /^ACKID\s+([0-9A-F]+)(?:\s*(.*?)(?:\s*duration:.*?(\w+.*))?)$/im
      ackid        = Regexp.last_match(1)
      comment      = Regexp.last_match(2)
      duration_str = Regexp.last_match(3)

      error = nil
      dur   = nil

      if comment.nil? || (comment.length == 0)
        error = "please provide a comment, eg \"#{@bot.alias}: ACKID #{Regexp.last_match(1)} AL looking\""
      elsif duration_str
        # a fairly liberal match above, we'll let chronic_duration do the heavy lifting
        dur = ChronicDuration.parse(duration_str)
      end

      four_hours = 4 * 60 * 60
      duration = (dur.nil? || (dur <= 0)) ? four_hours : dur

      check = Flapjack::Data::Check.intersect(:ack_hash => ackid).all.first

      if check.nil?
        msg = "ERROR - couldn't ACK #{ackid} - not found"
      else
        check_name = check.name

        details = "#{check_name} (#{ackid})"
        if check.in_unscheduled_maintenance?
          msg = "Changing ACK for #{details}"
        else
          msg = "ACKing #{details}"
        end

        action = Proc.new {
          Flapjack::Data::Event.create_acknowledgements(
            @config['processor_queue'] || 'events',
            [check],
            :summary => (comment || ''),
            :acknowledgement_id => ackid,
            :duration => duration,
          )
        }
      end

    when /^ack\s+#{CHECK_MATCH_FRAGMENT}(?:\s+(.*?)(?:\s*duration:.*?(\w+.*))?)?\s*$/im
      pattern      = Regexp.last_match(1)
      tag          = Regexp.last_match(2)
      check_name   = Regexp.last_match(3)
      comment      = Regexp.last_match(4) ? Regexp.last_match(4).strip : nil
      duration_str = Regexp.last_match(5) ? Regexp.last_match(5).strip : '1 hour'
      duration     = ChronicDuration.parse(duration_str)

      msg = derive_check_ids_for(pattern, tag, check_name,
              :lock_klasses => [Flapjack::Data::UnscheduledMaintenance]) do |check_ids, descriptor|

        failing_checks = Flapjack::Data::Check.
          intersect(:id => check_ids, :failing => true)

        if failing_checks.empty?
          "No failing checks #{descriptor}"
        else
          summary = "#{nick}: #{comment.blank? ? 'Set via chatbot' : comment}"

          action = Proc.new {
            Flapjack::Data::Event.create_acknowledgements(
              @config['processor_queue'] || 'events',
              failing_checks,
              :summary  => summary,
              :duration => duration
            )
          }

          "Ack list:\n" + failing_checks.map(&:name).join("\n")
        end
      end

    when /^maint\s+#{CHECK_MATCH_FRAGMENT}\s+(?:start-in:.*?(\w+.*?)|start-at:.*?(\w+.*?))?(?:\s+duration:.*?(\w+.*?))?(?:\s+comment:.*?(\w+.*?))?\s*$/im
      pattern      = Regexp.last_match(1)
      tag          = Regexp.last_match(2)
      check_name   = Regexp.last_match(3)
      start_in     = Regexp.last_match(4) ? Regexp.last_match(4).strip : nil
      start_at     = Regexp.last_match(5) ? Regexp.last_match(5).strip : nil
      duration_str = Regexp.last_match(6) ? Regexp.last_match(6).strip : '1 hour'
      duration     = ChronicDuration.parse(duration_str)
      comment      = Regexp.last_match(7) ? Regexp.last_match(7).strip : 'Test maintenance'

      started = case
      when start_in
        Time.now.to_i + ChronicDuration.parse(start_in)
      when start_at
        Chronic.parse(start_at).to_i
      else
        Time.now.to_i
      end

      msg = derive_check_ids_for(pattern, tag, check_name,
              :lock_klasses => [Flapjack::Data::ScheduledMaintenance]) do |check_ids, descriptor|
        checks = Flapjack::Data::Check.find_by_ids(*check_ids)

        checks.each do |chk|
          sched_maint = Flapjack::Data::ScheduledMaintenance.new(
            :start_time => started,
            :end_time   => started + duration,
            :summary    => comment
          )
          sched_maint.save

          chk.scheduled_maintenances << sched_maint
        end

        "Scheduled maintenance for #{duration/60} minutes starting at #{Time.at(started)} on:\n" + checks.collect {|c| "#{c.name}" }.join("\n")
      end

    when /^test\s+notifications\s+for\s+#{CHECK_MATCH_FRAGMENT}\s*$/im
      pattern    = Regexp.last_match(1)
      tag        = Regexp.last_match(2)
      check_name = Regexp.last_match(3)

      msg = derive_check_ids_for(pattern, tag, check_name) do |check_ids, descriptor|
        summary = "Testing notifications to all contacts interested in checks #{descriptor}"

        checks = Flapjack::Data::Check.find_by_ids(*check_ids)

        action = Proc.new {
          Flapjack::Data::Event.test_notifications(@config['processor_queue'] || 'events',
            checks, :summary => summary)
        }
        "Testing notifications for check#{(checks.size > 1) ? 's' : ''} #{descriptor}"
      end

    when /^(.*)/
      words = Regexp.last_match(1)
      msg   = "what do you mean, '#{words}'? Type 'help' for a list of acceptable commands."

    end

  rescue => e
    Flapjack.logger.error { "Exception when interpreting command '#{command}' - #{e.class}, #{e.message}" }
    Flapjack.logger.debug { e.backtrace.join("\n") }
    msg = "Oops, something went wrong processing that command (#{e.class}, #{e.message})"
  end

  @bot ||= @siblings && @siblings.detect {|sib| sib.respond_to?(:announce) }

  if @bot && (room || nick)
    if room
      Flapjack.logger.info "sending to room #{room}: #{msg}"
      @bot.announce(room, msg)
    else
      Flapjack.logger.info "sending to user #{nick}: #{msg}"
      @bot.say(nick, msg)
    end
  else
    Flapjack.logger.warn "jabber bot not running, won't send #{msg} to #{room || nick}"
  end

  action.call if action
end
receive_message(room, nick, time, msg) click to toggle source
# File lib/flapjack/gateways/jabber.rb, line 161
def receive_message(room, nick, time, msg)
  @lock.synchronize do
    @messages += [{:room => room, :nick => nick, :time => time, :message => msg}]
    @stop_cond.signal
  end
end
start() click to toggle source
# File lib/flapjack/gateways/jabber.rb, line 139
def start
  Zermelo.redis = Flapjack.redis

  @lock.synchronize do

    @bot = self.siblings ? self.siblings.detect {|sib| sib.respond_to?(:announce)} : nil

    until @messages.empty? && @should_quit
      while msg = @messages.pop
        Flapjack.logger.info "interpreter received #{msg.inspect}"
        interpret(msg[:room], msg[:nick], msg[:time], msg[:message])
      end
      @stop_cond.wait_while { @messages.empty? && !@should_quit }
    end
  end
  Flapjack.redis.quit
end
stop_type() click to toggle source
# File lib/flapjack/gateways/jabber.rb, line 157
def stop_type
  :signal
end