UNPKG

3.94 kBJavaScriptView Raw
1'use strict'
2
3const cli = require('heroku-cli-util')
4
5async function run(context, heroku) {
6 const url = require('url')
7 const host = require('../lib/host')
8 const pgbackups = require('../lib/pgbackups')(context, heroku)
9 const fetcher = require('../lib/fetcher')(heroku)
10 const { app, args, flags } = context
11 const interval = Math.max(3, parseInt(flags['wait-interval'])) || 3
12
13 const upperCaseConfig = function (config) {
14 const output = {}
15 for (const key in config) {
16 output[key.toUpperCase()] = config[key]
17 }
18 return output
19 }
20
21 let resolve = async function (db) {
22 if (db.match(/^postgres:\/\//)) {
23 // For the case an input is URL format
24 let uri = url.parse(db)
25 let dbname = uri.path ? uri.path.slice(1) : ''
26 let host = `${uri.hostname}:${uri.port || 5432}`
27 return {
28 name: dbname ? `database ${dbname} on ${host}` : `database on ${host}`,
29 url: db,
30 confirm: dbname || uri.host
31 }
32 } else {
33 // Other case (need to resolve attachment)
34 let attachment = await fetcher.attachment(app, db)
35 if (!attachment) throw new Error(`${db} not found on ${cli.color.app(app)}`)
36 let [addon, config] = await Promise.all([
37 heroku.get(`/addons/${attachment.addon.name}`),
38 heroku.get(`/apps/${attachment.app.name}/config-vars`)
39 ])
40 attachment.addon = addon
41 config = upperCaseConfig(config) // Upper case config var keys
42 return {
43 name: attachment.name.replace(/^HEROKU_POSTGRESQL_/, '').replace(/_URL$/, ''),
44 url: config[attachment.name.toUpperCase() + '_URL'], // Upper case attachment name
45 attachment,
46 confirm: app
47 }
48 }
49 }
50
51 // Get source and target from inputs
52 // source/target format:
53 // * url: postgres://foo/bar
54 // * config var: DATABASE_URL
55 // * just color: PINK
56 // * addon name: my-heroku-addon-name
57 // * app name + color/config: myapp::ORANGE
58 let [source, target] = await Promise.all([resolve(args.source), resolve(args.target)])
59 if (source.url === target.url) throw new Error('Cannot copy database onto itself')
60
61 await cli.confirmApp(target.confirm, flags.confirm, `WARNING: Destructive action
62This command will remove all data from ${cli.color.yellow(target.name)}
63Data from ${cli.color.yellow(source.name)} will then be transferred to ${cli.color.yellow(target.name)}`)
64
65 let copy
66 let attachment
67 await cli.action(`Starting copy of ${cli.color.yellow(source.name)} to ${cli.color.yellow(target.name)}`, async function () {
68 attachment = target.attachment || source.attachment
69 if (!attachment) throw new Error('Heroku PostgreSQL database must be source or target')
70 copy = await heroku.post(`/client/v11/databases/${attachment.addon.id}/transfers`, {
71 body: {
72 from_name: source.name,
73 from_url: source.url,
74 to_name: target.name,
75 to_url: target.url
76 },
77 host: host(attachment.addon)
78 })
79 }())
80
81 if (source.attachment) {
82 let credentials = await heroku.get(`/postgres/v0/databases/${source.attachment.addon.name}/credentials`,
83 { host: host(source.attachment.addon) })
84 if (credentials.length > 1) {
85 cli.action.warn(`pg:copy will only copy your default credential and the data it has access to. Any additional credentials and data that only they can access will not be copied.`)
86 }
87 }
88 await pgbackups.wait('Copying', copy.uuid, interval, flags.verbose, attachment.addon.app.name)
89}
90
91module.exports = {
92 topic: 'pg',
93 command: 'copy',
94 needsApp: true,
95 needsAuth: true,
96 description: 'copy all data from source db to target',
97 help: 'at least one of the databases must be a Heroku PostgreSQL DB',
98 args: [
99 { name: 'source' },
100 { name: 'target' }
101 ],
102 flags: [
103 { name: 'wait-interval', hasValue: true },
104 { name: 'verbose' },
105 { name: 'confirm', hasValue: true }
106 ],
107 run: cli.command({ preauth: true }, run)
108}