UNPKG

hubot-heroku

Version:

Run heroku commands via hubot without direct access to Heroku

220 lines (164 loc) 7.18 kB
# Description: # Exposes Heroku commands to hubot # # Dependencies: # "heroku-client": "^1.9.0" # "hubot-auth": "^1.2.0" # # Configuration: # HUBOT_HEROKU_API_KEY # # 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> <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 # hubot heroku config:unset <app> <KEY> - Unsets KEY, does not throw error if key is not present # # Author: # daemonsy Heroku = require('heroku-client') heroku = new Heroku(token: process.env.HUBOT_HEROKU_API_KEY) _ = require('lodash') mapper = require('../heroku-response-mapper') moment = require('moment') useAuth = (process.env.HUBOT_HEROKU_USE_AUTH or '').trim().toLowerCase() is 'true' module.exports = (robot) -> auth = (msg, appName) -> role = "heroku-#{appName}" hasRole = robot.auth.hasRole(msg.envelope.user, role) isAdmin = robot.auth.hasRole(msg.envelope.user, 'admin') if useAuth and not (hasRole or isAdmin) msg.reply "Access denied. You must have this role to use this command: #{role}" return false return true respondToUser = (robotMessage, error, successMessage) -> if error robotMessage.reply "Shucks. An error occurred. #{error.statusCode} - #{error.body.message}" else robotMessage.reply successMessage rpad = (string, width, padding = ' ') -> if (width <= string.length) then string else rpad(width, string + padding, padding) objectToMessage = (object) -> output = [] maxLength = 0 keys = Object.keys(object) keys.forEach (key) -> maxLength = key.length if key.length > maxLength keys.forEach (key) -> output.push "#{rpad(key, maxLength)} : #{object[key]}" output.join("\n") # App Info robot.respond /heroku info (.*)/i, (msg) -> appName = msg.match[1] return unless auth(msg, appName) msg.reply "Getting information about #{appName}" 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] return unless auth(msg, appName) 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.utc().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] return unless auth(msg, appName) msg.reply "Getting releases for #{appName}" heroku.apps(appName).releases().list (error, releases) -> output = [] if releases output.push "Recent releases of #{appName}" for release in releases.sort((a, b) -> b.version - a.version)[0..9] output.push "v#{release.version} - #{release.description} - #{release.user.email} - #{release.created_at}" respondToUser(msg, error, output.join("\n")) # Rollback robot.respond /heroku rollback (.*) (.*)$/i, (msg) -> appName = msg.match[1] version = msg.match[2] return unless auth(msg, appName) if version.match(/v\d+$/) msg.reply "Telling Heroku to rollback to #{version}" app = heroku.apps(appName) app.releases().list (error, releases) -> release = _.find releases, (release) -> "v#{release.version}" == version return msg.reply "Version #{version} not found for #{appName} :(" unless release app.releases().rollback release: release.id, (error, release) -> respondToUser(msg, error, "Success! v#{release.version} -> Rollback to #{version}") # Restart robot.respond /heroku restart ([\w-]+)\s?(\w+(?:\.\d+)?)?/i, (msg) -> appName = msg.match[1] dynoName = msg.match[2] dynoNameText = if dynoName then ' '+dynoName else '' return unless auth(msg, appName) msg.reply "Telling Heroku to restart #{appName}#{dynoNameText}" 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) -> appName = msg.match[1] return unless auth(msg, appName) msg.reply "Telling Heroku to migrate #{appName}" heroku.apps(appName).dynos().create command: "rake db:migrate" attach: false , (error, dyno) -> respondToUser(msg, error, "Heroku: Running migrations for #{appName}") heroku.apps(appName).logSessions().create dyno: dyno.name tail: true , (error, session) -> respondToUser(msg, error, "View logs at: #{session.logplex_url}") # Config Vars robot.respond /heroku config (.*)$/i, (msg) -> appName = msg.match[1] return unless auth(msg, appName) msg.reply "Getting config keys for #{appName}" heroku.apps(appName).configVars().info (error, configVars) -> listOfKeys = configVars && Object.keys(configVars).join(", ") respondToUser(msg, error, listOfKeys) robot.respond /heroku config:set (.*) (\w+)=('([\s\S]+)'|"([\s\S]+)"|([\s\S]+\b))/im, (msg) -> keyPair = {} appName = msg.match[1] key = msg.match[2] value = msg.match[4] || msg.match[5] || msg.match[6] # :sad_panda: return unless auth(msg, appName) msg.reply "Setting config #{key} => #{value}" keyPair[key] = value heroku.apps(appName).configVars().update keyPair, (error, configVars) -> respondToUser(msg, error, "Heroku: #{key} is set to #{configVars[key]}") robot.respond /heroku config:unset (.*) (\w+)$/i, (msg) -> keyPair = {} appName = msg.match[1] key = msg.match[2] value = msg.match[3] return unless auth(msg, appName) msg.reply "Unsetting config #{key}" keyPair[key] = null heroku.apps(appName).configVars().update keyPair, (error, response) -> respondToUser(msg, error, "Heroku: #{key} has been unset")