function* run (context) { let host = process.env.HEROKU_STATUS_HOST || 'https://status.heroku.com'; let response = (yield cli.got(host + '/api/v3/current-status', { path: '/api/v3/current-status', json: true, headers: { 'Accept': 'application/vnd.heroku+json;' }, })).body; if (context.flags.json) { cli.styledJSON(response); return; } cli.log(`Production: ${printStatus(response.status.Production)}`); cli.log(`Development: ${printStatus(response.status.Development)}`); response.issues.forEach(function(incident) { cli.log(); cli.styledHeader(`${incident.title} ${cli.color.yellow(moment(incident.created_at).format('LT'))} ${cli.color.cyan(incident.full_url)}`); let padding = _.maxBy(incident.updates, 'update_type.length').update_type.length+1; incident.updates.forEach(u => { cli.log(`${cli.color.yellow(_.padEnd(u.update_type, padding))} ${moment(u.updated_at).format('LT')} (${moment(u.updated_at).fromNow()})`); cli.log(`${u.contents}\n`); }); }); }
function * run (context, heroku) { const fetcher = require('../../lib/fetcher')(heroku) const host = require('../../lib/host') const util = require('../../lib/util') const { app, args, flags } = context let cred = flags.name if (cred === 'default') { throw new Error('Default credential cannot be destroyed.') } let db = yield fetcher.addon(app, args.database) if (util.starterPlan(db)) { throw new Error(`Only one default credential is supported for Hobby tier databases.`) } let attachments = yield heroku.get(`/addons/${db.name}/addon-attachments`) let credAttachments = attachments.filter(a => a.namespace === `credential:${flags.name}`) let credAttachmentApps = Array.from(new Set(credAttachments.map(a => a.app.name))) if (credAttachmentApps.length > 0) throw new Error(`Credential ${flags.name} must be detached from the app${credAttachmentApps.length > 1 ? 's' : ''} ${credAttachmentApps.map(name => cli.color.app(name)).join(', ')} before destroying.`) yield cli.confirmApp(app, flags.confirm, `WARNING: Destructive action`) yield cli.action(`Destroying credential ${cli.color.cmd(cred)}`, co(function * () { yield heroku.delete(`/postgres/v0/databases/${db.name}/credentials/${encodeURIComponent(cred)}`, { host: host(db) }) })) cli.log(`The credential has been destroyed within ${db.name}.`) cli.log(`Database objects owned by ${cred} will be assigned to the default credential.`) }
run: function (context) { if (context.flags.user) { cli.log(`Hello, ${context.flags.user}!`); } else { cli.log('Hello, World!'); } }
run: cli.command(co.wrap(function * (context, heroku) { let mappingResults let didDisplayAnything = false let connection = yield api.withConnection(context, heroku) context.region = connection.region_url let results = yield cli.action('Diagnosing connection', co(function * () { let url = '/api/v3/connections/' + connection.id + '/validations' return yield api.request(context, 'GET', url) })) cli.log() // Blank line to separate each section cli.styledHeader(`Connection: ${connection.name || connection.internal_name}`) if (shouldDisplay(results.data, context.flags)) { didDisplayAnything = true displayResults(results.data, context.flags) } for (let objectName in results.data.mappings) { mappingResults = results.data.mappings[objectName] if (shouldDisplay(mappingResults, context.flags)) { didDisplayAnything = true cli.log() // Blank line to separate each section cli.styledHeader(objectName) displayResults(mappingResults, context.flags) } } if (!didDisplayAnything && !context.flags.verbose) { cli.log(cli.color['green']('Everything appears to be fine')) } })),
function print (apps, user) { if (apps.length === 0) { if (space) cli.log(`There are no apps in space ${cli.color.green(space)}.`) else if (org) cli.log(`There are no apps in organization ${cli.color.magenta(org)}.`) else cli.log('You have no apps.') return } else if (space) { cli.styledHeader(`Apps in space ${cli.color.green(space)}`) listApps(apps) } else if (org) { cli.styledHeader(`Apps in organization ${cli.color.magenta(org)}`) listApps(apps) } else { apps = _.partition(apps, (app) => app.owner.email === user.email) if (apps[0].length > 0) { cli.styledHeader(`${cli.color.cyan(user.email)} Apps`) listApps(apps[0]) } if (apps[1].length > 0) { cli.styledHeader('Collaborated Apps') cli.table(apps[1], { printHeader: false, columns: [ {key: 'name', get: regionizeAppName}, {key: 'owner.email'} ] }) } } }
run: cli.command(co.wrap(function* (context, heroku) { const kolkrabbi = new KolkrabbiAPI(context.version, heroku.options.token) const settings = { pull_requests: { enabled: true } } if (context.flags.autodeploy) { cli.log('Enabling auto deployment...') settings.pull_requests.auto_deploy = true } if (context.flags.autodestroy) { cli.log('Enabling auto destroy...') settings.pull_requests.auto_destroy = true } let app = yield api.getApp(heroku, context.flags.app) yield cli.action( 'Configuring pipeline', kolkrabbi.updateAppLink(app.id, settings) ) }))
function * run (context, heroku) { function favoriteApps () { return heroku.request({ host: 'longboard.heroku.com', path: '/favorites', headers: {Range: ''} }).then((apps) => apps.map((app) => app.app_name)) } function fetchMetrics (apps) { const NOW = new Date().toISOString() const YESTERDAY = new Date(new Date().getTime() - (24 * 60 * 60 * 1000)).toISOString() let date = `start_time=${YESTERDAY}&end_time=${NOW}&step=1h` return apps.map((app) => { let types = app.formation.map((p) => p.type) return { dynoErrors: types.map((type) => heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/formation/${type}/metrics/errors?${date}`, headers: {Range: ''}}).catch(() => null)), routerLatency: heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/router-metrics/latency?${date}&process_type=${types[0]}`, headers: {Range: ''}}).catch(() => null), routerErrors: heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/router-metrics/errors?${date}&process_type=${types[0]}`, headers: {Range: ''}}).catch(() => null), routerStatus: heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/router-metrics/status?${date}&process_type=${types[0]}`, headers: {Range: ''}}).catch(() => null) } }) } let apps, data, metrics yield cli.action('Loading', {success: false}, co(function * () { apps = yield favoriteApps() data = yield { orgs: heroku.request({path: '/organizations'}), notifications: heroku.request({host: 'telex.heroku.com', path: '/user/notifications'}), apps: apps.map((app) => ({ app: heroku.get(`/apps/${app}`), formation: heroku.get(`/apps/${app}/formation`), pipeline: heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => null) })) } metrics = yield fetchMetrics(data.apps) })) process.stderr.write('\r') if (process.stderr.clearLine) process.stderr.clearLine() else cli.console.error('\n') if (apps.length > 0) displayApps(data.apps, metrics) else cli.warn(`Add apps to this dashboard by favoriting them with ${cli.color.cmd('heroku apps:favorites:add')}`) cli.log(` See all add-ons with ${cli.color.cmd('heroku addons')}`) let sampleOrg = _.sortBy(data.orgs.filter((o) => o.role !== 'collaborator'), (o) => new Date(o.created_at))[0] if (sampleOrg) cli.log(`See all apps in ${cli.color.yellow.dim(sampleOrg.name)} with ${cli.color.cmd('heroku apps --org ' + sampleOrg.name)}`) cli.log(`See all apps with ${cli.color.cmd('heroku apps --all')}`) displayNotifications(data.notifications) cli.log(` See other CLI commands with ${cli.color.cmd('heroku help')} `) }
function * run (context, heroku) { const statusHelper = require('./status_helper') const time = require('../../lib/time') const truncate = require('lodash.truncate') let descriptionWithStatus = function (d, r) { const width = () => process.stdout.columns > 80 ? process.stdout.columns : 80 const trunc = (s, l) => truncate(s, {length: width() - (60 + l), omission: '…'}) let status = statusHelper(r.status) let sc = '' if (status.content !== undefined) { sc = cli.color[status.color](status.content) } return trunc(d, sc.length) + ' ' + sc } let url = `/apps/${context.app}/releases` if (context.flags.extended) url = url + '?extended=true' let releases = yield heroku.request({ path: url, partial: true, headers: { 'Range': `version ..; max=${context.flags.num || 15}, order=desc` } }) if (context.flags.json) { cli.log(JSON.stringify(releases, null, 2)) } else if (context.flags.extended) { cli.styledHeader(`${context.app} Releases`) cli.table(releases, { printHeader: false, columns: [ {key: 'version', format: (v, r) => cli.color[statusHelper(r.status).color]('v' + v)}, {key: 'description', format: descriptionWithStatus}, {key: 'user', format: (u) => cli.color.magenta(u.email.replace(/@addons\.heroku\.com$/, ''))}, {key: 'created_at', format: (t) => time.ago(new Date(t))}, {key: 'extended.slug_id'}, {key: 'extended.slug_uuid'} ] }) } else if (releases.length === 0) { cli.log(`${context.app} has no releases.`) } else { cli.styledHeader(`${context.app} Releases`) cli.table(releases, { printHeader: false, columns: [ {key: 'version', label: '', format: (v, r) => cli.color[statusHelper(r.status).color]('v' + v)}, {key: 'description', format: descriptionWithStatus}, {key: 'user', format: (u) => cli.color.magenta(u.email)}, {key: 'created_at', format: (t) => time.ago(new Date(t))} ] }) } }
return function (result) { cli.log(cli.color[color](`${label}: ${result.display_name}`)) if (displayMessages) { cli.log(result.message) if (result.doc_url) { cli.log(result.doc_url) } } }
BuildpackCommand.prototype.displayUpdate = function (buildpacks) { if (buildpacks.length === 1) { cli.log(`Buildpack ${this.action}. Next release on ${this.app} will use ${buildpacks[0].buildpack.url}.`) cli.log(`Run ${cli.color.magenta('git push heroku master')} to create a new release using this buildpack.`) } else { cli.log(`Buildpack ${this.action}. Next release on ${this.app} will use:`) this.display(buildpacks, ' ') cli.log(`Run ${cli.color.magenta('git push heroku master')} to create a new release using these buildpacks.`) } }
function createRemoteSlug(slug) { var lang = `heroku-docker (${ slug.name || 'unknown'})`; cli.log(`creating remote slug...`); cli.log(`language-pack: ${ lang }`); cli.log('remote process types:', modifiedProc); var slugInfo = app.slugs().create({ process_types: modifiedProc, buildpack_provided_description: lang }); return Promise.all([slug.path, slugInfo]) }
run: cli.command(async (context, heroku) => { const api = require('../lib/shared')(context, heroku) const addon = await api.getRedisAddon() if (context.flags.reset) { cli.log(`Resetting credentials for ${addon.name}`) await api.request(`/redis/v0/databases/${addon.name}/credentials_rotation`, 'POST') } else { let redis = await api.request(`/redis/v0/databases/${addon.name}`) cli.log(redis.resource_url) } })
function * run (context, heroku) { let id = context.args.addon.split(':')[0] let addon = yield heroku.get(`/addon-services/${encodeURIComponent(id)}`).catch(() => null) if (!addon) addon = (yield resolve.addon(heroku, context.app, id)).addon_service let url = `https://devcenter.heroku.com/articles/${addon.name}` if (context.flags['show-url']) { cli.log(url) } else { cli.log(`Opening ${cli.color.cyan(url)}...`) yield cli.open(url) } }
dbs.forEach(db => { if (once) cli.log() else once = true cli.styledHeader(cli.color.addon(db.name)) if (db.links.message) return cli.log(db.links.message) if (!db.links.length) return cli.log('No data sources are linked into this database') db.links.forEach(link => { cli.log(`\n * ${cli.color.cyan(link.name)}`) link.remote = `${cli.color.configVar(link.remote.attachment_name)} (${cli.color.addon(link.remote.name)})` delete link.id delete link.name cli.styledObject(link) }) })
run: cli.command(co.wrap(function * (context, heroku) { if (!context.flags.seconds) { cli.exit(1, 'Please specify a valid timeout value.') } let addon = yield api.getRedisAddon(context, heroku) let config = yield api.request(context, `/redis/v0/databases/${addon.name}/config`, 'PATCH', { timeout: parseInt(context.flags.seconds, 10) }) cli.log(`Timeout for ${addon.name} (${addon.config_vars.join(', ')}) set to ${config.timeout.value} seconds.`) if (config.timeout.value === 0) { cli.log('Connections to the Redis instance can idle indefinitely.') } else { cli.log(`Connections to the Redis instance will be stopped after idling for ${config.timeout.value} seconds.`) } }))
function createRemoteSlug(slugPath) { cli.log('creating remote slug...'); var slugInfo = app.slugs().create({ process_types: procfile }); return Promise.all([slugPath, slugInfo]) }
function * run (context, heroku) { let drain = yield heroku.request({ method: 'delete', path: `/apps/${context.app}/log-drains/${encodeURIComponent(context.args.url)}` }) cli.log(`Successfully removed drain ${cli.color.cyan(drain.url)}`) }
run: cli.command(async (context, heroku) => { const api = require('../lib/shared')(context, heroku) let addonsList = heroku.get(`/apps/${context.app}/addons`) let addon = await api.getRedisAddon(addonsList) let redisFilter = api.makeAddonsFilter('REDIS_URL') let redis = redisFilter(await addonsList) // Check if REDIS_URL is singlehandly assigned if (redis.length === 1 && redis[0].config_vars.length === 1) { let attachment = redis[0] await heroku.post('/addon-attachments', { body: { app: { name: context.app }, addon: { name: attachment.name }, confirm: context.app } }) } cli.log(`Promoting ${addon.name} to REDIS_URL on ${context.app}`) await heroku.post('/addon-attachments', { body: { app: { name: context.app }, addon: { name: addon.name }, confirm: context.app, name: 'REDIS' } }) })
function displayApps (apps, appsMetrics) { let owner = (owner) => owner.email.endsWith('@herokumanager.com') ? owner.email.split('@')[0] : owner.email for (let a of _.zip(apps, appsMetrics)) { cli.log() let app = a[0] let metrics = a[1] cli.log(cli.color.app(app.app.name)) let pipeline = app.pipeline ? ` Pipeline: ${cli.color.blue.bold(app.pipeline.pipeline.name)}` : '' cli.log(` Owner: ${cli.color.blue.bold(owner(app.app.owner))}${pipeline}`) displayFormation(app.formation) cli.log(` Last release: ${cli.color.green(time.ago(new Date(app.app.released_at)))}`) displayMetrics(metrics) displayErrors(metrics) } }
function displayMetrics (metrics) { const flatten = require('lodash.flatten') const mean = require('lodash.mean') const pad = require('lodash.pad') const round = require('lodash.round') const sum = require('lodash.sum') const values = require('lodash.values') function rpmSparkline () { if (process.platform === 'win32') return let sparkline = require('sparkline') let points = [] values(metrics.routerStatus.data).forEach((cur) => { for (let i = 0; i < cur.length; i++) { let j = Math.floor(i / 3) points[j] = (points[j] || 0) + cur[i] } }) points.pop() return dim(sparkline(points)) + ' last 24 hours rpm' } let ms = '' let rpm = '' if (metrics.routerLatency && !empty(metrics.routerLatency.data)) { ms = pad(`${round(mean(metrics.routerLatency.data.latency_p50))} ms`, 6) } if (metrics.routerStatus && !empty(metrics.routerStatus.data)) { rpm = `${round(sum(flatten(values(metrics.routerStatus.data))) / 24 / 60)} rpm ${rpmSparkline()}` } if (rpm || ms) cli.log(` ${label('Metrics:')} ${ms}${rpm}`) }
function * run (context, heroku) { let createAddon = require('../../lib/create_addon') let { app, flags, args } = context if (args.length === 0) { throw new Error('Usage: heroku addons:create SERVICE:PLAN') } let { name, as } = flags let config = parseConfig(args.slice(1)) let addon try { addon = yield createAddon(heroku, app, args[0], context.flags.confirm, context.flags.wait, { config, name, as }) if (context.flags.wait) { notify(`heroku addons:create ${addon.name}`, 'Add-on successfully provisioned') } } catch (error) { if (context.flags.wait) { notify(`heroku addons:create ${args[0]}`, 'Add-on failed to provision', false) } throw error } yield context.config.runHook('recache', { type: 'addon', app, addon }) cli.log(`Use ${cli.color.cmd('heroku addons:docs ' + addon.addon_service.name)} to view documentation`) }
function * run (context, heroku) { let auth = yield cli.action('Revoking OAuth Authorization', {success: false}, heroku.request({ method: 'DELETE', path: `/oauth/authorizations/${encodeURIComponent(context.args.id)}` })) cli.log(`done, revoked authorization from ${cli.color.cyan(auth.description)}`) }
function * run (ctx, api) { const resolve = require('../../lib/resolve') let addons if (ctx.args.addon) { addons = [yield resolve.addon(api, ctx.app, ctx.args.addon)] } else { if (ctx.app) { addons = yield api.get(`/apps/${ctx.app}/addons`) } else { addons = yield api.get('/addons') } addons = addons.filter(addon => addon.state === 'provisioning') } let interval = parseInt(ctx.flags['wait-interval']) if (!interval || interval < 0) { interval = 5 } for (let addon of addons) { addon = yield waitForAddonProvisioning(api, addon, interval) let configVars = (addon.config_vars || []) if (configVars.length > 0) { configVars = configVars.map(c => cli.color.configVar(c)).join(', ') cli.log(`Created ${cli.color.addon(addon.name)} as ${configVars}`) } } }
fastly.purgeKey(config.FASTLY_SERVICE_ID, context.args.key, function(err, obj) { if (err) { hk.error(err); } else { hk.log(obj); } });
db.links.forEach(link => { cli.log(`\n * ${cli.color.cyan(link.name)}`) link.remote = `${cli.color.configVar(link.remote.attachment_name)} (${cli.color.addon(link.remote.name)})` delete link.id delete link.name cli.styledObject(link) })
yield exec.createSocksProxy(context, heroku, configVars, function(dynoIp, dynoName, socksPort) { cli.log(`Listening on ${cli.color.white.bold(localPort)} and forwarding to ${cli.color.white.bold(`${dynoName}:${remotePort}`)}`) cli.log(`Use ${cli.color.magenta('CTRL+C')} to stop port fowarding`) net.createServer(function(connIn) { socks.connect({ host: '0.0.0.0', port: remotePort, proxyHost: '127.0.0.1', proxyPort: socksPort, auths: [ socks.auth.None() ] }, function(socket) { connIn.pipe(socket); socket.pipe(connIn); }); }).listen(localPort); });
function displayNotifications (notifications) { notifications = notifications.filter((n) => !n.read) if (notifications.length > 0) { cli.log(` You have ${cli.color.yellow(notifications.length)} unread notifications. Read them with ${cli.color.cmd('heroku notifications')}`) } }
function * run (context, heroku) { function getRelease (id) { return heroku.get(`/apps/${context.app}/releases/${id}`) } function getLatestRelease () { return heroku.request({ path: `/apps/${context.app}/releases`, partial: true, headers: { 'Range': 'version ..; max=2, order=desc' } }).then((releases) => releases[1]) } let release if (context.args.release) { let id = context.args.release.toLowerCase() id = id.startsWith('v') ? id.slice(1) : id release = yield getRelease(id) } else { release = yield getLatestRelease() } let p = heroku.post(`/apps/${context.app}/releases`, {release: release.id}) let latest = yield cli.action(`Rolling back ${cli.color.app(context.app)} to ${cli.color.green('v' + release.version)}`, p, {success: false}) cli.log(`done, ${cli.color.green('v' + latest.version)}`) cli.warn(`Rollback affects code and config vars; it doesn't add or remove addons. To undo, run: ${cli.color.cmd('heroku rollback v' + (latest.version - 1))}`) }
function displayAll (addons) { addons = sortBy(addons, 'app.name', 'plan.name', 'addon.name') if (addons.length === 0) { cli.log('No add-ons.') return } table(addons, { headerAnsi: cli.color.bold, columns: [{ key: 'app.name', label: 'Owning App', format: style('app') }, { key: 'name', label: 'Add-on', format: style('addon') }, { key: 'plan.name', label: 'Plan', format: function (plan) { if (typeof plan === 'undefined') return style('dim', '?') return plan } }, { key: 'plan.price', label: 'Price', format: function (price) { if (typeof price === 'undefined') return style('dim', '?') return formatPrice(price) } }] }) }
let release = async function (context, heroku) { if (context.flags.verbose) debug.enabled = true if (context.args.length === 0) { cli.exit(1, `Error: Requires one or more process types\n ${usage}`) return } await heroku.get(`/apps/${context.app}`) let herokuHost = process.env.HEROKU_HOST || 'heroku.com' let updateData = [] for (let process of context.args) { let image = `${context.app}/${process}` let tag = 'latest' let imageResp = await heroku.request({ host: `registry.${herokuHost}`, path: `/v2/${image}/manifests/${tag}`, headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' } }) let imageID switch (imageResp.schemaVersion) { case 1: let v1Comp = JSON.parse(imageResp.history[0].v1Compatibility) imageID = v1Comp.id break case 2: imageID = imageResp.config.digest break } updateData.push({ type: process, docker_image: imageID }) } let req = heroku.patch(`/apps/${context.app}/formation`, { body: { updates: updateData }, headers: { 'Accept': 'application/vnd.heroku+json; version=3.docker-releases' } }) await cli.action(`Releasing images ${context.args.join(',')} to ${context.app}`, req) let release = await heroku.request({ path: `/apps/${context.app}/releases`, partial: true, headers: { 'Range': 'version ..; max=2, order=desc' } }).then((releases) => releases[0]) if (release.output_stream_url && release.status === 'pending') { cli.log('Running release command...') await streamer(release.output_stream_url, process.stdout) } }