heroku
Version:
CLI to interact with Heroku
87 lines (86 loc) • 4.75 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const color_1 = require("@heroku-cli/color");
const command_1 = require("@heroku-cli/command");
const core_1 = require("@oclif/core");
const host_1 = require("../../lib/pg/host");
const backups_1 = require("../../lib/pg/backups");
const fetcher_1 = require("../../lib/pg/fetcher");
const util_1 = require("../../lib/pg/util");
const confirmCommand_1 = require("../../lib/confirmCommand");
const getAttachmentInfo = async function (heroku, db, app) {
if (db.match(/^postgres:\/\//)) {
const conn = (0, util_1.parsePostgresConnectionString)(db);
const host = `${conn.host}:${conn.port}`;
return {
name: conn.database ? `database ${conn.database} on ${host}` : `database on ${host}`,
url: db,
confirm: conn.database || conn.host,
};
}
const attachment = await (0, fetcher_1.getAttachment)(heroku, app, db);
if (!attachment)
throw new Error(`${db} not found on ${color_1.default.magenta(app)}`);
const { body: addon } = await heroku.get(`/addons/${attachment.addon.name}`);
const { body: config } = await heroku.get(`/apps/${attachment.app.name}/config-vars`);
const formattedConfig = Object.fromEntries(Object.entries(config).map(([k, v]) => [k.toUpperCase(), v]));
return {
name: attachment.name.replace(/^HEROKU_POSTGRESQL_/, '')
.replace(/_URL$/, ''),
url: formattedConfig[attachment.name.toUpperCase() + '_URL'],
attachment: Object.assign(Object.assign({}, attachment), { addon }),
confirm: app,
};
};
class Copy extends command_1.Command {
async run() {
var _a;
const { flags, args } = await this.parse(Copy);
const { 'wait-interval': waitInterval, verbose, confirm, app } = flags;
const pgbackups = (0, backups_1.default)(app, this.heroku);
const interval = Math.max(3, Number.parseInt(waitInterval || '0', 10)) || 3;
const [source, target] = await Promise.all([getAttachmentInfo(this.heroku, args.source, app), getAttachmentInfo(this.heroku, args.target, app)]);
if (source.url === target.url)
throw new Error('Cannot copy database onto itself');
await (0, confirmCommand_1.default)(target.confirm || args.target, confirm, `WARNING: Destructive action\nThis command will remove all data from ${color_1.default.yellow(target.name)}\nData from ${color_1.default.yellow(source.name)} will then be transferred to ${color_1.default.yellow(target.name)}`);
core_1.ux.action.start(`Starting copy of ${color_1.default.yellow(source.name)} to ${color_1.default.yellow(target.name)}`);
const attachment = target.attachment || source.attachment;
if (!attachment) {
throw new Error('Heroku PostgreSQL database must be source or target');
}
const { body: copy } = await this.heroku.post(`/client/v11/databases/${attachment.addon.id}/transfers`, {
body: {
from_name: source.name, from_url: source.url, to_name: target.name, to_url: target.url,
},
hostname: (0, host_1.default)(),
});
core_1.ux.action.stop();
if (source.attachment) {
const { body: credentials } = await this.heroku.get(`/postgres/v0/databases/${source.attachment.addon.name}/credentials`, {
hostname: (0, host_1.default)(),
headers: {
Authorization: `Basic ${Buffer.from(`:${this.heroku.auth}`).toString('base64')}`,
},
});
if (credentials.length > 1) {
core_1.ux.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.');
}
}
await pgbackups.wait('Copying', copy.uuid, interval, verbose, ((_a = attachment.addon.app) === null || _a === void 0 ? void 0 : _a.name) || app);
}
}
exports.default = Copy;
Copy.topic = 'pg';
Copy.description = 'copy all data from source db to target';
Copy.help = 'at least one of the databases must be a Heroku PostgreSQL DB';
Copy.flags = {
'wait-interval': command_1.flags.string(),
verbose: command_1.flags.boolean(),
confirm: command_1.flags.string(),
app: command_1.flags.app({ required: true }),
remote: command_1.flags.remote(),
};
Copy.args = {
source: core_1.Args.string({ required: true, description: 'config var exposed to the owning app containing the source database URL' }),
target: core_1.Args.string({ required: true, description: 'config var exposed to the owning app containing the target database URL' }),
};
;