Permalink
Browse files

Merge pull request #467 from Iristyle/master

Improved AWS support
  • Loading branch information...
2 parents 37e9e46 + e62179b commit 8cc9a54c2e8f18f715896c99245b0a69ef4f7757 @tombell tombell committed Jun 8, 2012
Showing with 139 additions and 52 deletions.
  1. +139 −52 src/scripts/aws.coffee
View
@@ -1,78 +1,165 @@
# Queries for the status of AWS services
#
# hubot sqs status - Returns the status of SQS queues.
+# hubot ec2 status - Returns the status of EC2 instances.
#
# Environment variables:
# HUBOT_AWS_ACCESS_KEY_ID - The Amazon access key id
# HUBOT_AWS_SECRET_ACCESS_KEY - The Amazon secret key for the given id
+# HUBOT_AWS_SQS_REGIONS - Comma separated list of regions to query
+# HUBOT_AWS_EC2_REGIONS - Comma separated list of regions to query
#
-#
-# package.json needs to have "aws2js":"0.6.12"
+# package.json needs to have "aws2js":"0.6.12" and "moment":"1.6.2" and "underscore":"1.3.3"
#
# 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*
key = process.env.HUBOT_AWS_ACCESS_KEY_ID
secret = process.env.HUBOT_AWS_SECRET_ACCESS_KEY
-sqs = (require 'aws2js')
- .load('sqs')
- .setCredentials key, secret
+_ = 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')
-module.exports = (robot) ->
- robot.respond /(^|\W)sqs status(\z|\W|$)/i, (msg) ->
+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
- sqs.request 'ListQueues', {}, (error, queues) ->
- if error?
- msg.send 'Failed to list queues'
- return
+ tags = _.flatten [instance.tagSet.item]
+ name = (_.find tags, (t) -> t.key == 'Name').value
- queues.ListQueuesResult.QueueUrl.forEach (url) ->
+ msg.send "#{prefix} [#{state}] - #{name} / #{type} [#{devType} #{arch}] / #{dnsName} / #{region} / #{id} - started #{launchTime} #{suffix}"
- url = url['#']
- name = url.split '/'
- name = name[name.length - 1]
- path = url.replace 'https://queue.amazonaws.com', ''
+getRegionQueues = (region, msg) ->
+ sqs.setRegion(region).request 'ListQueues', {}, (error, queues) ->
+ if error?
+ msg.send 'Failed to list queues for region #{region} - error #{error}'
+ return
- queue =
- Version: '2011-10-01'
- AttributeName: ['All']
+ urls = _.flatten [queues.ListQueuesResult?.QueueUrl ? []]
- sqs.setQueue(path + '/')
- .request 'GetQueueAttributes', queue, (error, attributes) ->
+ 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?
- 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.setQueue(path + '/')
- .request 'ReceiveMessage', queue, (error, result) ->
-
- queueDesc = "[SQS: #{name}] - [#{msgCount}] total msgs" +
- " / [#{inFlight}] in flight"
- if error?
- timestamp = "unavailable - #{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
- sqsmsg = result.ReceiveMessageResult.Message
- if sqsmsg? and sqsmsg.Attribute?
- timestamp = (att for att in sqsmsg.Attribute \
- when att.Name == 'SentTimestamp')
- timestamp = new Date(parseFloat(timestamp[0].Value))
- else
- timestamp = 'none available'
-
- msg.send "#{queueDesc} / oldest msg ~[#{timestamp}]", url
+ 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 ','

0 comments on commit 8cc9a54

Please sign in to comment.