heroku/heroku-pg

View on GitHub
commands/promote.js

Summary

Maintainability
B
5 hrs
Test Coverage
'use strict'
 
const cli = require('heroku-cli-util')
const co = require('co')
 
Function `run` has 88 lines of code (exceeds 25 allowed). Consider refactoring.
function * run (context, heroku) {
const fetcher = require('../lib/fetcher')(heroku)
const {app, args} = context
const attachment = yield fetcher.attachment(app, args.database)
let current
 
yield cli.action(`Ensuring an alternate alias for existing ${cli.color.configVar('DATABASE_URL')}`, co(function * () {
// Finds or creates a non-DATABASE attachment for the DB currently
// attached as DATABASE.
//
// If current DATABASE is attached by other names, return one of them.
// If current DATABASE is only attachment, create a new one and return it.
// If no current DATABASE, return nil.
let attachments = yield heroku.get(`/apps/${app}/addon-attachments`)
current = attachments.find(a => a.name === 'DATABASE')
if (!current) return
 
if (current.addon.name === attachment.addon.name && current.namespace === attachment.namespace) {
if (attachment.namespace) {
throw new Error(`${cli.color.attachment(attachment.name)} is already promoted on ${cli.color.app(app)}`)
} else {
throw new Error(`${cli.color.addon(attachment.addon.name)} is already promoted on ${cli.color.app(app)}`)
}
}
let existing = attachments.filter(a => a.addon.id === current.addon.id && a.namespace === current.namespace).find(a => a.name !== 'DATABASE')
if (existing) return cli.action.done(cli.color.configVar(existing.name + '_URL'))
 
// The current add-on occupying the DATABASE attachment has no
// other attachments. In order to promote this database without
// error, we can create a secondary attachment, just-in-time.
 
let backup = yield heroku.post('/addon-attachments', {
body: {
app: {name: app},
addon: {name: current.addon.name},
namespace: current.namespace,
confirm: app
}
})
cli.action.done(cli.color.configVar(backup.name + '_URL'))
}))
 
let promotionMessage
if (attachment.namespace) {
promotionMessage = `Promoting ${cli.color.attachment(attachment.name)} to ${cli.color.configVar('DATABASE_URL')} on ${cli.color.app(app)}`
} else {
promotionMessage = `Promoting ${cli.color.addon(attachment.addon.name)} to ${cli.color.configVar('DATABASE_URL')} on ${cli.color.app(app)}`
}
 
yield cli.action(promotionMessage, co(function * () {
yield heroku.post('/addon-attachments', {
body: {
name: 'DATABASE',
app: {name: app},
addon: {name: attachment.addon.name},
namespace: attachment.namespace,
confirm: app
}
})
}))
 
let releasePhase = (yield heroku.get(`/apps/${app}/formation`))
.find((formation) => formation.type === 'release')
 
if (releasePhase) {
yield cli.action('Checking release phase', co(function * () {
let releases = yield heroku.request({
path: `/apps/${app}/releases`,
partial: true,
headers: {
'Range': `version ..; max=5, order=desc`
}
})
let attach = releases.find((release) => release.description.includes('Attach DATABASE'))
let detach = releases.find((release) => release.description.includes('Detach DATABASE'))
 
if (!attach || !detach) {
throw new Error('Unable to check release phase. Check your Attach DATABASE release for failures.')
}
 
let endTime = Date.now() + 900000 // 15 minutes from now
let [attachId, detachId] = [attach.id, detach.id]
while (true) {
let attach = yield fetcher.release(app, attachId)
if (attach && attach.status === 'succeeded') {
let msg = 'pg:promote succeeded.'
let detach = yield fetcher.release(app, detachId)
if (detach && detach.status === 'failed') {
msg += ` It is safe to ignore the failed ${detach.description} release.`
}
return cli.action.done(msg)
} else if (attach && attach.status === 'failed') {
let msg = `pg:promote failed because ${attach.description} release was unsuccessful. Your application is currently running `
let detach = yield fetcher.release(app, detachId)
Avoid deeply nested control flow statements.
if (detach && detach.status === 'succeeded') {
msg += 'without an attached DATABASE_URL.'
} else {
msg += `with ${current.addon.name} attached as DATABASE_URL.`
}
msg += ' Check your release phase logs for failure causes.'
return cli.action.done(msg)
Avoid deeply nested control flow statements.
} else if (Date.now() > endTime) {
return cli.action.done('timeout. Check your Attach DATABASE release for failures.')
}
 
yield new Promise((resolve) => setTimeout(resolve, 5000))
}
}))
}
}
 
module.exports = {
topic: 'pg',
command: 'promote',
description: 'sets DATABASE as your DATABASE_URL',
needsApp: true,
needsAuth: true,
args: [{name: 'database'}],
run: cli.command({preauth: true}, co.wrap(run))
}