Skip to content

Commit

Permalink
Merge pull request #16 from chaselee/patch-2
Browse files Browse the repository at this point in the history
Allow specific dynos to be restarted and viewing of dyno list and status
  • Loading branch information
daemonsy committed Jun 11, 2015
2 parents 8678016 + 6f831f2 commit ccd1108
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 12 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -46,9 +46,10 @@ Use `hubot help` to look for the commands. They are all prefixed by heroku. (e.g
Some commands (hubot help will be a better source of truth):

- `hubot heroku info <app>` - Returns useful information about the app
- `hubot heroku dynos <app>` - Lists all dynos and their status
- `hubot heroku releases <app>` - Latest 10 releases
- `hubot heroku rollback <app>` <version> - Rollback to a release
- `hubot heroku restart <app>` - Restarts the app
- `hubot heroku restart <app> <dyno>` - Restarts the specified app or dyno/s (e.g. `worker` or `web.2`)
- `hubot heroku migrate <app>` - Runs migrations. Remember to restart the app =)
- `hubot heroku config <app>` - Get config keys for the app. Values not given for security
- `hubot heroku config:set <app> <KEY=value>` - Set KEY to value. Case sensitive and overrides present key
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -24,7 +24,8 @@
"homepage": "https://github.com/daemonsy/hubot-heroku",
"dependencies": {
"heroku-client": "^1.9.0",
"lodash": "^2.4.1"
"lodash": "^2.4.1",
"moment": "^2.10.3"
},
"devDependencies": {
"chai": "^1.10.0",
Expand Down
45 changes: 40 additions & 5 deletions src/scripts/heroku-commands.coffee
Expand Up @@ -9,9 +9,10 @@
#
# Commands:
# hubot heroku info <app> - Returns useful information about the app
# hubot heroku dynos <app> - Lists all dynos and their status
# hubot heroku releases <app> - Latest 10 releases
# hubot heroku rollback <app> <version> - Rollback to a release
# hubot heroku restart <app> - Restarts the app
# hubot heroku restart <app> <dyno> - Restarts the specified app or dyno/s (e.g. worker or web.2)
# hubot heroku migrate <app> - Runs migrations. Remember to restart the app =)
# hubot heroku config <app> - Get config keys for the app. Values not given for security
# hubot heroku config:set <app> <KEY=value> - Set KEY to value. Case sensitive and overrides present key
Expand All @@ -24,6 +25,7 @@ Heroku = require('heroku-client')
heroku = new Heroku(token: process.env.HUBOT_HEROKU_API_KEY)
_ = require('lodash')
mapper = require('../heroku-response-mapper')
moment = require('moment')

module.exports = (robot) ->
respondToUser = (robotMessage, error, successMessage) ->
Expand Down Expand Up @@ -56,6 +58,33 @@ module.exports = (robot) ->
heroku.apps(appName).info (error, info) ->
respondToUser(msg, error, "\n" + objectToMessage(mapper.info(info)))

# Dynos
robot.respond /heroku dynos (.*)/i, (msg) ->
appName = msg.match[1]

msg.reply "Getting dynos of #{appName}"

heroku.apps(appName).dynos().list (error, dynos) ->
output = []
if dynos
output.push "Dynos of #{appName}"
lastFormation = ""

for dyno in dynos
currentFormation = "#{dyno.type}.#{dyno.size}"

unless currentFormation is lastFormation
output.push "" if lastFormation
output.push "=== #{dyno.type} (#{dyno.size}): `#{dyno.command}`"
lastFormation = currentFormation

updatedAt = moment(dyno.updated_at)
updatedTime = updatedAt.format('YYYY/MM/DD HH:mm:ss')
timeAgo = updatedAt.fromNow()
output.push "#{dyno.name}: #{dyno.state} #{updatedTime} (~ #{timeAgo})"

respondToUser(msg, error, output.join("\n"))

# Releases
robot.respond /heroku releases (.*)$/i, (msg) ->
appName = msg.match[1]
Expand Down Expand Up @@ -91,13 +120,19 @@ module.exports = (robot) ->
respondToUser(msg, error, "Success! v#{release.version} -> Rollback to #{version}")

# Restart
robot.respond /heroku restart (.*)/i, (msg) ->
robot.respond /heroku restart ([\w-]+)\s?(\w+(?:\.\d+)?)?/i, (msg) ->
appName = msg.match[1]
dynoName = msg.match[2]
dynoNameText = if dynoName then ' '+dynoName else ''

msg.reply "Telling Heroku to restart #{appName}"
msg.reply "Telling Heroku to restart #{appName}#{dynoNameText}"

heroku.apps(appName).dynos().restartAll (error, app) ->
respondToUser(msg, error, "Heroku: Restarting #{appName}")
unless dynoName
heroku.apps(appName).dynos().restartAll (error, app) ->
respondToUser(msg, error, "Heroku: Restarting #{appName}")
else
heroku.apps(appName).dynos(dynoName).restart (error, app) ->
respondToUser(msg, error, "Heroku: Restarting #{appName}#{dynoNameText}")

# Migration
robot.respond /heroku migrate (.*)/i, (msg) ->
Expand Down
47 changes: 47 additions & 0 deletions test/fixtures/dynos.json
@@ -0,0 +1,47 @@
[
{
"attach_url": "rendezvous://rendezvous.runtime.heroku.com:5000/{rendezvous-id}",
"command": "forever server.js",
"created_at": "2012-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "web.1",
"release": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"version": 11
},
"size": "1X",
"state": "up",
"type": "web",
"updated_at": "2015-01-01T12:00:00Z"
},
{
"attach_url": "rendezvous://rendezvous.runtime.heroku.com:5000/{rendezvous-id}",
"command": "forever server.js",
"created_at": "2012-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "web.2",
"release": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"version": 11
},
"size": "1X",
"state": "crashed",
"type": "web",
"updated_at": "2015-01-01T12:00:00Z"
},
{
"attach_url": "rendezvous://rendezvous.runtime.heroku.com:5000/{rendezvous-id}",
"command": "celery worker",
"created_at": "2012-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "worker.1",
"release": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"version": 11
},
"size": "2X",
"state": "up",
"type": "worker",
"updated_at": "2015-06-01T12:00:00Z"
}
]
51 changes: 46 additions & 5 deletions test/heroku-commands-spec.coffee
Expand Up @@ -25,12 +25,13 @@ describe "Heroku Commands", ->
it "exposes help commands", ->
commands = room.robot.commands

expect(commands).to.have.length(8)
expect(commands).to.have.length(9)

expect(commands).to.include("hubot heroku info <app> - Returns useful information about the app")
expect(commands).to.include("hubot heroku dynos <app> - Lists all dynos and their status")
expect(commands).to.include("hubot heroku releases <app> - Latest 10 releases")
expect(commands).to.include("hubot heroku rollback <app> <version> - Rollback to a release")
expect(commands).to.include("hubot heroku restart <app> - Restarts the app")
expect(commands).to.include("hubot heroku restart <app> <dyno> - Restarts the specified app or dyno/s (e.g. worker or web.2)")
expect(commands).to.include("hubot heroku migrate <app> - Runs migrations. Remember to restart the app =)")
expect(commands).to.include("hubot heroku config <app> - Get config keys for the app. Values not given for security")
expect(commands).to.include("hubot heroku config:set <app> <KEY=value> - Set KEY to value. Case sensitive and overrides present key")
Expand All @@ -50,6 +51,21 @@ describe "Heroku Commands", ->
done()
, duration)

describe "heroku dynos <app>", ->
it "lists all dynos and their status", (done) ->
mockHeroku
.get("/apps/shield-global-watch/dynos")
.replyWithFile(200, __dirname + "/fixtures/dynos.json")

room.user.say "Damon", "hubot heroku dynos shield-global-watch"

setTimeout(->
expect(room.messages[1][1]).to.equal("@Damon Getting dynos of shield-global-watch")
expect(room.messages[2][1]).to.include("@Damon Dynos of shield-global-watch\n=== web (1X): `forever server.js`\nweb.1: up 2015/01/01 07:00:00")
expect(room.messages[2][1]).to.include("\nweb.2: crashed 2015/01/01 07:00:00")
expect(room.messages[2][1]).to.include("\n\n=== worker (2X): `celery worker`\nworker.1: up 2015/06/01 08:00:00")
done()
, duration)

describe "heroku releases <app>", ->
it "gets the 10 recent releases", (done) ->
Expand Down Expand Up @@ -94,7 +110,7 @@ describe "Heroku Commands", ->
done()
, duration)

describe "heroku restart <app>", ->
describe "heroku restart <app> <dyno>", ->
it "restarts the app", (done) ->
mockHeroku
.delete("/apps/shield-global-watch/dynos")
Expand All @@ -108,6 +124,32 @@ describe "Heroku Commands", ->
done()
, duration)

it "restarts all dynos of a process", (done) ->
mockHeroku
.delete("/apps/shield-global-watch/dynos/web")
.reply(200, {})

room.user.say "Damon", "hubot heroku restart shield-global-watch web"

setTimeout(->
expect(room.messages[1][1]).to.equal("@Damon Telling Heroku to restart shield-global-watch web")
expect(room.messages[2][1]).to.equal("@Damon Heroku: Restarting shield-global-watch web")
done()
, duration)

it "restarts specific dynos", (done) ->
mockHeroku
.delete("/apps/shield-global-watch/dynos/web.1")
.reply(200, {})

room.user.say "Damon", "hubot heroku restart shield-global-watch web.1"

setTimeout(->
expect(room.messages[1][1]).to.equal("@Damon Telling Heroku to restart shield-global-watch web.1")
expect(room.messages[2][1]).to.equal("@Damon Heroku: Restarting shield-global-watch web.1")
done()
, duration)

describe "heroku migrate <app>", ->
beforeEach ->
mockHeroku
Expand Down Expand Up @@ -150,7 +192,7 @@ describe "Heroku Commands", ->
expect(room.messages[1][1]).to.equal("@Damon Getting config keys for shield-global-watch")
expect(room.messages[2][1]).to.equal("@Damon CLOAK, COMMANDER, AUTOPILOT, PILOT_NAME")
done()
)
, duration)

describe "heroku config:set <app> <KEY=value>", ->
mockRequest = (keyPair) ->
Expand Down Expand Up @@ -234,4 +276,3 @@ describe "Heroku Commands", ->
expect(room.messages[2][1]).to.equal("@Damon Heroku: CLOAK_ID has been unset")
done()
, duration)

0 comments on commit ccd1108

Please sign in to comment.