Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 172 lines (143 sloc) 6.272 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
# Description:
# Queries for the status of AWS services
#
# Dependencies:
# "aws2js": "0.6.12"
# "underscore": "1.3.3"
# "moment": "1.6.2"
#
# Configuration:
# HUBOT_AWS_ACCESS_KEY_ID
# HUBOT_AWS_SECRET_ACCESS_KEY
# HUBOT_AWS_SQS_REGIONS
# HUBOT_AWS_EC2_REGIONS
#
# Commands:
# hubot sqs status - Returns the status of SQS queues
# hubot ec2 status - Returns the status of EC2 instances
#
# Notes:
# It's highly recommended to use a read-only IAM account for this purpose
# https://console.aws.amazon.com/iam/home?
# SQS - requires ListQueues, GetQueueAttributes and ReceiveMessage
# EC2 - requires EC2:Describe*, elasticloadbalancing:Describe*, cloudwatch:ListMetrics,
# cloudwatch:GetMetricStatistics, cloudwatch:Describe*, autoscaling:Describe*
#
# Author:
# Ethan J. Brown

key = process.env.HUBOT_AWS_ACCESS_KEY_ID
secret = process.env.HUBOT_AWS_SECRET_ACCESS_KEY

_ = require 'underscore'
moment = require 'moment'
aws = require 'aws2js'
sqs = aws
  .load('sqs', key, secret)
  .setApiVersion('2011-10-01')
ec2 = aws
  .load('ec2', key, secret)
  .setApiVersion('2012-05-01')

getRegionInstances = (region, msg) ->
  ec2.setRegion(region).request 'DescribeInstances', (error, reservations) ->
    if error?
      msg.send "Failed to describe instances for region #{region} - error #{error}"
      return

    ec2.setRegion(region).request 'DescribeInstanceStatus', (error, allStatuses) ->
      statuses = if error? then [] else allStatuses.instanceStatusSet.item

      instances = _.flatten [reservations?.reservationSet?.item ? []]
      instances = _.pluck instances, 'instancesSet'
      instances = _.flatten _.pluck instances, 'item'

      msg.send "Found #{instances.length} instances for region #{region}..."

      for instance in instances
        do (instance) ->
          status = _.find statuses, (s) ->
            instance.instanceId == s.instanceId

          suffix = ''
          state = instance.instanceState.name
          excl = String.fromCharCode 0x203C
          dexcl = excl + excl

          switch state
            when 'pending' then prefix = String.fromCharCode 0x25B2
            when 'running' then prefix = String.fromCharCode 0x25BA
            when 'shutting-down' then prefix = String.fromCharCode 0x25BC
            when 'terminated' then prefix = String.fromCharCode 0x25AA
            when 'stopping' then prefix = String.fromCharCode 0x25A1
            when 'stopped' then prefix = String.fromCharCode 0x25A0
            else prefix = dexcl

          if status?
            bad = _.filter [status.systemStatus, status.instanceStatus],
            (s) -> s.status != 'ok'

            if bad.length > 0
              prefix = dexcl
              badStrings = _.map bad, (b) ->
                b.details.item.name + ' ' + b.details.item.status
              concat = (memo, s) -> memo + s
              suffix = _.reduce badStrings, concat, ''

            iEvents = _.flatten [status.eventsSet?.item ? []]
            if not _.isEmpty iEvents then prefix = dexcl
            desc = (memo, e) -> "#{memo} #{dexcl}#{e.code} : #{e.description}"
            suffix += _.reduce iEvents, desc, ''

          id = instance.instanceId ? 'N/A'
          type = instance.instanceType
          dnsName = if _.isEmpty instance.dnsName then 'N/A' \
            else instance.dnsName
          launchTime = moment(instance.launchTime)
            .format 'ddd, L LT'
          arch = instance.architecture
          devType = instance.rootDeviceType

          tags = _.flatten [instance.tagSet.item]
          name = (_.find tags, (t) -> t.key == 'Name').value

          msg.send "#{prefix} [#{state}] - #{name} / #{type} [#{devType} #{arch}] / #{dnsName} / #{region} / #{id} - started #{launchTime} #{suffix}"

getRegionQueues = (region, msg) ->
  sqs.setRegion(region).request 'ListQueues', {}, (error, queues) ->
    if error?
      msg.send 'Failed to list queues for region #{region} - error #{error}'
      return

    urls = _.flatten [queues.ListQueuesResult?.QueueUrl ? []]

    msg.send "Found #{urls.length} queues for region #{region}..."

    urls.forEach (url) ->

      url = url['#']
      name = url.split '/'
      name = name[name.length - 1]
      path = url.replace "https://sqs.#{region}.amazonaws.com", ''

      queue =
        Version: '2011-10-01'
        AttributeName: ['All']

      sqs.setRegion(region).setQueue(path + '/')
      .request 'GetQueueAttributes', queue, (error, attributes) ->
        if error?
          msg.send "Can't read queue attributes [#{name}] (path #{path})" +
            " - #{url} - #{error}"
          return

        info = attributes.GetQueueAttributesResult.Attribute
        for index, attr of info
          switch attr.Name
            when 'ApproximateNumberOfMessages' \
              then msgCount = attr.Value
            when 'ApproximateNumberOfMessagesNotVisible' \
              then inFlight = attr.Value

        queue.MaxNumberOfMessages = 1
        queue.VisibilityTimeout = 0

        sqs.setRegion(region).setQueue(path + '/')
        .request 'ReceiveMessage', queue, (error, result) ->

          queueDesc = "[SQS: #{name}] - [#{msgCount}] total msgs" +
            " / [#{inFlight}] in flight"
          if error?
            timestamp = "unavailable - #{error}"
          else
            sqsmsg = result.ReceiveMessageResult.Message
            if sqsmsg? and sqsmsg.Attribute?
              timestamp = (att for att in sqsmsg.Attribute \
                when att.Name == 'SentTimestamp')
              timestamp = (moment parseFloat timestamp[0].Value)
                .format 'ddd, L LT'
            else
              timestamp = 'none available'

          msg.send "#{queueDesc} / oldest msg ~[#{timestamp}] / #{url}"

defaultRegions = 'us-east-1,us-west-1,us-west-2,eu-west-1,ap-southeast-1,ap-northeast-1'

module.exports = (robot) ->
  robot.respond /(^|\W)sqs status(\z|\W|$)/i, (msg) ->
    regions = process.env?.HUBOT_AWS_SQS_REGIONS ? defaultRegions
    getRegionQueues region, msg for region in regions.split ','
  robot.respond /(^|\W)ec2 status(\z|\W|$)/i, (msg) ->
    regions = process.env?.HUBOT_AWS_EC2_REGIONS ? defaultRegions
    getRegionInstances region, msg for region in regions.split ','
Something went wrong with that request. Please try again.