1 | 'use strict'
|
2 |
|
3 | const co = require('co')
|
4 | const cli = require('heroku-cli-util')
|
5 |
|
6 | function * run (context, heroku) {
|
7 | const host = require('../../lib/host')
|
8 | const fetcher = require('../../lib/fetcher')(heroku)
|
9 | const util = require('../../lib/util')
|
10 | const {app, args, flags} = context
|
11 | let db = yield fetcher.addon(app, args.database)
|
12 | let all = flags.all
|
13 |
|
14 | if (all && 'name' in flags) {
|
15 | cli.exit(1, 'cannot pass both --all and --name')
|
16 | }
|
17 | let cred = flags.name || 'default'
|
18 | if (cred === 'default' && 'force' in flags && !all) {
|
19 | cli.exit(1, 'Cannot force rotate the default credential.')
|
20 | }
|
21 | if (util.starterPlan(db) && cred !== 'default') {
|
22 | throw new Error(`Only one default credential is supported for Hobby tier databases.`)
|
23 | }
|
24 | let attachments = yield heroku.get(`/addons/${db.name}/addon-attachments`)
|
25 | if (flags.name) {
|
26 | attachments = attachments.filter(a => a.namespace === `credential:${cred}`)
|
27 | }
|
28 |
|
29 | let warnings = []
|
30 | if (!flags.all) {
|
31 | warnings.push(`The password for the ${cred} credential will rotate.`)
|
32 | }
|
33 | if (flags.all || flags.force || cred === 'default') {
|
34 | warnings.push(`Connections will be reset and applications will be restarted.`)
|
35 | } else {
|
36 | warnings.push(`Connections older than 30 minutes will be reset, and a temporary rotation username will be used during the process.`)
|
37 | }
|
38 | if (attachments.length > 0) {
|
39 | warnings.push(`This command will affect the app${(attachments.length > 1) ? 's' : ''} ${[...new Set(attachments.map(c => cli.color.app(c.app.name)))].sort().join(', ')}.`)
|
40 | }
|
41 |
|
42 | yield cli.confirmApp(app, flags.confirm, `WARNING: Destructive Action
|
43 | ${warnings.join('\n')}`)
|
44 |
|
45 | let body = flags.force ? {host: host(db), force: true} : {host: host(db)}
|
46 |
|
47 | if (all) {
|
48 | yield cli.action(`Rotating all credentials on ${cli.color.addon(db.name)}`, co(function * () {
|
49 | yield heroku.post(`/postgres/v0/databases/${db.name}/credentials_rotation`,
|
50 | body)
|
51 | }))
|
52 | } else {
|
53 | yield cli.action(`Rotating ${cred} on ${cli.color.addon(db.name)}`, co(function * () {
|
54 | yield heroku.post(`/postgres/v0/databases/${db.name}/credentials/${encodeURIComponent(cred)}/credentials_rotation`,
|
55 | body)
|
56 | }))
|
57 | }
|
58 | }
|
59 |
|
60 | module.exports = {
|
61 | topic: 'pg',
|
62 | command: 'credentials:rotate',
|
63 | description: 'rotate the database credentials',
|
64 | needsApp: true,
|
65 | needsAuth: true,
|
66 | flags: [
|
67 | {name: 'name', char: 'n', description: 'which credentials to rotate (default credentials if not specified)', hasValue: true},
|
68 | {name: 'all', description: 'rotate all credentials', hasValue: false},
|
69 | {name: 'confirm', char: 'c', hasValue: true},
|
70 | {name: 'force', description: 'forces rotating the targeted credentials', hasValue: false}
|
71 | ],
|
72 | args: [{name: 'database', optional: true}],
|
73 | run: cli.command({preauth: true}, co.wrap(run))
|
74 | }
|