Skip to content
This repository has been archived by the owner on Jan 22, 2019. It is now read-only.

Commit

Permalink
Fix #24: Add an Amazon task to see instances infos
Browse files Browse the repository at this point in the history
  • Loading branch information
athieriot committed Jul 10, 2013
1 parent 01bf6af commit 03eed28
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .albot.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@
},
"changelog": {
"gistId": ""
},
"amazon": {
"key": "",
"secret": "",
"region": ""
}
}
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ You will just need Coffeescript

pulls [ <url> | without <filter> | with <filter> | recent [<unit>] | last [<number> | <filter>]] List all Pull Requests of the organisation
deploy <project> [ | <alias>] [<branch>] Deploy your projects with the configured command
changelog <project> [ | <alias>] <pr> <number> | <since> <number> <period> | <between> <tag-range> ["save"] List changelog for a given PR, period, range
changelog <project> [ | <alias>] <pr> <number> | <since> <number> <period> | <between> <tag-range> [save] List changelog for a given PR, period, range
amazon [ instances [ with <term> ] ] Display various informations about your Amazon infrastructure
help Display a list of available commands
server Start albot to listen on Hipchat instead of the command line

Expand Down Expand Up @@ -146,6 +147,23 @@ Available in all flavour

$ albot changelog webapp between 43..45 save

### Amazon

Display substantial informations about your Amazon infrastructure.
Like the list of your EC2 instances.

$ albot amazon instances

Will display all the instances available on your account.

Interestingly, if you have DNS configured in Route53, this route will be displayed instead of the Public DNS.

If some of your instances are behind an ElasticLoadBalancer, more details will be provided for it.

You can, as always, filter the result list

$ albot amazon instances with live

### Server

Albot can also be use as an Hipchat bot.
Expand Down
24 changes: 0 additions & 24 deletions TODO

This file was deleted.

119 changes: 119 additions & 0 deletions lib/amazon.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
Configuration = require './configuration'
_ = require('underscore')._
_.str = require 'underscore.string'
_.mixin _.str.exports()

Async = require 'async'

Utils = require './utils'
AwsHelpers = require './aws_helpers'

amazon = (fallback, keyword, filter, filterValue) ->

Route53 = new Configuration.Amazon.aws.Route53()
Ec2 = new Configuration.Amazon.aws.EC2()
Elb = new Configuration.Amazon.aws.ELB()

params = {}

if (keyword == 'instances')
AwsHelpers.getAllRecordSets Route53, (err, recordSets) ->
if (not recordSets?)
Utils.fallback_printError fallback, err
else
dnsRoutePairs = mappingDnsWithRoute(recordSets)

findInstancesBehindLoadBalancer Elb, dnsRoutePairs, (err, idInstances) ->
if (not idInstances?)
Utils.fallback_printError fallback, err
else
if (filter == 'with')
display fallback, Ec2, params, dnsRoutePairs, idInstances, filterValue
else
display fallback, Ec2, params, dnsRoutePairs, idInstances


display = (fallback, Ec2, params, dnsRoutePairs, idInstances, filterValue) ->
AwsHelpers.getInstancesByParams Ec2, params, (err, results) ->
list = _.map results, (instance) ->

tag = _.findWhere instance.Tags, {"Key": "Name"}
security = instance.SecurityGroups[0]
role = if instance.IamInstanceProfile? then " / Role: " + instance.IamInstanceProfile.Arn.split('/')[1] else ""
route = _.findWhere dnsRoutePairs, {"Dns": instance.PublicDnsName}
title =
if (route?)
route.Route
else if instance.PublicDnsName
instance.PublicDnsName
else
"No route or public dns"

behinds = _.findWhere idInstances, {"Id": instance.InstanceId}
lb = if behinds? and behinds.LoadBalancer? then " - behind: #{behinds.LoadBalancer.LoadBalancerName}" else ""

{
title: title
#TODO: Should remove the trailing dot
url: "http://#{title}"
infos: if tag? then tag.Value + ' / ' + instance.InstanceType else instance.InstanceType
comments: if security? then "Security: #{security.GroupName}#{role}"
status: instance.State.Name == 'running'
tails: if behinds? then _.map _.pluck(behinds.Routes, 'Route'), (route) -> "via #{route}#{lb}"
}

Utils.fallback_printList fallback, list, (list) ->
if (filterValue?)
_.filter list, (o) ->
stuff = o.title + o.infos + o.comments
stuff += _.reduce o.tails, (memo, tail) ->
memo.concat(tail)
, []

_.str.include(stuff, filterValue)
else
list

mappingDnsWithRoute = (recordSets) ->
_.reduce recordSets, (memo, recordSet) ->
if (not _.isEmpty(recordSet.ResourceRecords))
memo.concat _.map recordSet.ResourceRecords, (record) ->
{ Dns: record.Value, Route: recordSet.Name }
else
memo.concat [
{ Dns: recordSet.AliasTarget.DNSName, Route: recordSet.Name }
]
, []

filterByUrl = (dnsRoutePairs, url) ->
if (url?)
_.filter dnsRoutePairs, (name) -> name.Route.indexOf(url) > -1
else
dnsRoutePairs

findInstancesBehindLoadBalancer = (Elb, routes, callback) ->
names = _.reduce routes, (memo, dnsRoutePair) ->
match = dnsRoutePair.Dns.match("^(.*lb)-")

if match then memo.concat(match[1]) else memo
, []

AwsHelpers.getLoadBalancersByNames Elb, _.uniq(names), (err, lbDescriptions) ->
if (not lbDescriptions?)
callback err
else
Async.reduce lbDescriptions, [], (memo, description, cb) ->
cb null, memo.concat _.map description.Instances, (instance) ->
{
Id: instance.InstanceId,
LoadBalancer: description,
Routes: _.where(routes, {"Dns": description.DNSName + "."})
}
, (err, idInstances) ->
callback null, idInstances

module.exports = {
name: "Amazon",
description: "[ instances [ with -term- ] ] Display various informations about your Amazon infrastructure",
action: amazon
}
71 changes: 71 additions & 0 deletions lib/aws_helpers.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Configuration = require './configuration'
_ = require('underscore')._

Async = require 'async'

prepareFilters = (name, value, sensitive) ->
if (_.isString(value) or _.isArray(value))
if (sensitive)
value = [
"\*" + value.toUpperCase() + "\*",
"\*" + value.toLowerCase() + "\*"
]
else if (_.isString(value))
value = ["\*" + value + "\*"]

{"Filters": [{
"Name": name,
"Values": value
}]}

#TODO: More than 100 instances
getInstancesByParams = (Ec2, params, callback) ->
Ec2.describeInstances params, (err, data) ->
if (not data?)
callback(err)
else
callback null, _.reduceRight data.Reservations, (memo, reservation) ->
memo.concat(reservation.Instances)
, []

getAllRecordSets = (Route53, callback) ->
Route53.listHostedZones {}, (err, hostedZones) ->
if (not hostedZones?)
callback(err)
else
Async.concat hostedZones.HostedZones, (hz, cb) ->
getAllRecordSetsAcc Route53, hz, [], null, null, cb
, callback

getAllRecordSetsAcc = (Route53, hz, acc, recordName, recordType, callback) ->
if (recordName? and recordType?)
params = {"HostedZoneId": hz.Id, "StartRecordName": recordName, "StartRecordType": recordType}
else
params = {"HostedZoneId": hz.Id}

Route53.listResourceRecordSets params, (err, records) ->
if (not records?)
callback(err, [])
else
all = acc.concat records.ResourceRecordSets

if (records.IsTruncated)
getAllRecordSetsAcc(Route53, hz, all, records.NextRecordName, records.NextRecordType, callback)
else
callback(null, all)

getLoadBalancersByNames = (Elb, names, callback) ->
params = {"LoadBalancerNames": names}

Elb.describeLoadBalancers params, (err, loadBalancers) ->
if (not loadBalancers?)
callback err
else
callback null, loadBalancers.LoadBalancerDescriptions

module.exports = {
prepareFilters: prepareFilters,
getInstancesByParams: getInstancesByParams,
getAllRecordSets: getAllRecordSets,
getLoadBalancersByNames: getLoadBalancersByNames
}
2 changes: 1 addition & 1 deletion lib/changelog.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,6 @@ module.exports = {
name: "Changelog",
description: "-project- [ | -alias-] -pr- -number-
| -since- -number- -period- | -between- -tag-range-
[\"save\"] List changelog for a given PR, period, range ",
[save] List changelog for a given PR, period, range ",
action: changelog
}
1 change: 1 addition & 0 deletions lib/commands.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ list = {
pulls: require('./pulls'),
deploy: require('./deploy'),
changelog: require('./changelog'),
amazon: require('./amazon'),
help: {
name: "Help"
description: "Display a list of available commands",
Expand Down
29 changes: 28 additions & 1 deletion lib/configuration.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Path = require 'path'
HipchatApi = require 'hipchat'
GithubApi = require 'github'
Winston = require 'winston'
AWS = require 'aws-sdk'

userHome = () ->
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE
Expand Down Expand Up @@ -35,6 +36,11 @@ Nconf
"deploy": {
"args": [],
"env": []
},
"amazon": {
"key": "",
"secret": "",
"region": ""
}
}

Expand All @@ -55,6 +61,23 @@ initLogger =

@logger

initAws =
(aws) ->
if (aws?)
@aws = aws
else
credentials =
{
accessKeyId: Nconf.get("amazon").key,
secretAccessKey: Nconf.get("amazon").secret,
region: Nconf.get("amazon").region
}

AWS.config.update credentials
@aws = AWS

@aws

module.exports =
Nconf: Nconf,
Nickname: Nconf.get('nickname'),
Expand All @@ -69,8 +92,12 @@ module.exports =
Channel: Nconf.get('hipchat').channel,
Frequency: Nconf.get('hipchat').frequency
},
Amazon: {
initAws: initAws,
aws: @aws || initAws()
},
Winston: {
initLogger: initLogger,
logger: @logger || initLogger
logger: @logger || initLogger()
},
Version: JSON.parse(Fs.readFileSync(Path.resolve(__dirname, '../package.json'), 'utf8')).version
4 changes: 2 additions & 2 deletions lib/server.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ server = (frequency, testCallback) ->
freq = if _.isString(frequency) then frequency else Hipchat.Frequency

Hipchat.Rooms.history Hipchat.Channel, (error, lines) ->
if (error?) then console.log("An error occured while fetching history: #{JSON.stringify(error)}")
if (error?) then Winston.logger.error("An error occured while fetching history: #{JSON.stringify(error)}")
else if(lines)
Cache.store(lines.messages)

intervalId = setInterval () ->
Hipchat.Rooms.history Hipchat.Channel, (error, lines) ->
if (error?) then console.log("An error occured while fetching history: #{JSON.stringify(error)}")
if (error?) then Winston.logger.error("An error occured while fetching history: #{JSON.stringify(error)}")
else if (lines)
Async.each lines.messages, (line, cb) ->
if (not Cache.cached(line))
Expand Down
Loading

0 comments on commit 03eed28

Please sign in to comment.