heroku/heroku-apps

View on GitHub
src/commands/ps/scale.js

Summary

Maintainability
B
5 hrs
Test Coverage
'use strict'

let cli = require('heroku-cli-util')
let co = require('co')
const {compact} = require('lodash')

let emptyFormationErr = (app) => {
  return new Error(`No process types on ${cli.color.app(app)}.
Upload a Procfile to add process types.
https://devcenter.heroku.com/articles/procfile`)
}

function * run (context, heroku) {
  let app = context.app

  function parse (args) {
    return compact(args.map((arg) => {
      let change = arg.match(/^([\w-]+)([=+-]\d+)(?::([\w-]+))?$/)
      if (!change) return
      let quantity = change[2][0] === '=' ? change[2].substr(1) : change[2]
      if (change[3]) change[3] = change[3].replace('Shield-', 'Private-')
      return {type: change[1], quantity, size: change[3]}
    }))
  }

  let changes = parse(context.args)
  if (changes.length === 0) {
    let formation = yield heroku.get(`/apps/${app}/formation`)

    const appProps = yield heroku.get(`/apps/${app}`)
    const shielded = appProps.space && appProps.space.shield
    if (shielded) {
      formation.forEach((d) => {
        d.size = d.size.replace('Private-', 'Shield-')
      })
    }

    if (formation.length === 0) throw emptyFormationErr(app)
    cli.log(formation.map((d) => `${d.type}=${d.quantity}:${d.size}`).sort().join(' '))
  } else {
    yield cli.action('Scaling dynos', {success: false}, co(function * () {
      let formation = yield heroku.request({method: 'PATCH', path: `/apps/${app}/formation`, body: {updates: changes}})
      const appProps = yield heroku.get(`/apps/${app}`)
      const shielded = appProps.space && appProps.space.shield
      if (shielded) {
        formation.forEach((d) => {
          d.size = d.size.replace('Private-', 'Shield-')
        })
      }
      let output = formation.filter((f) => changes.find((c) => c.type === f.type))
        .map((d) => `${cli.color.green(d.type)} at ${d.quantity}:${d.size}`)
      cli.action.done(`done, now running ${output.join(', ')}`)
    }))
  }
}

let cmd = {
  variableArgs: true,
  description: 'scale dyno quantity up or down',
  help: `Appending a size (eg. web=2:Standard-2X) allows simultaneous scaling and resizing.

Omitting any arguments will display the app's current dyno formation, in a
format suitable for passing back into ps:scale.`,
  examples: `$ heroku ps:scale web=3:Standard-2X worker+1
Scaling dynos... done, now running web at 3:Standard-2X, worker at 1:Standard-1X.

$ heroku ps:scale
web=3:Standard-2X worker=1:Standard-1X`,
  needsAuth: true,
  needsApp: true,
  run: cli.command(co.wrap(run))
}

module.exports = [
  Object.assign({topic: 'ps', command: 'scale'}, cmd),
  Object.assign({topic: 'dyno', command: 'scale'}, cmd),
  Object.assign({topic: 'scale', hidden: true}, cmd)
]