heroku
Version:
CLI to interact with Heroku
136 lines (134 loc) • 6.12 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 tsheredoc_1 = require("tsheredoc");
const confirmCommand_1 = require("../../../lib/confirmCommand");
const backups_1 = require("../../../lib/pg/backups");
const fetcher_1 = require("../../../lib/pg/fetcher");
const host_1 = require("../../../lib/pg/host");
const nls_1 = require("../../../nls");
function dropboxURL(url) {
if (url.match(/^https?:\/\/www\.dropbox\.com/) && !url.endsWith('dl=1')) {
if (url.endsWith('dl=0'))
url = url.replace('dl=0', 'dl=1');
else if (url.includes('?'))
url += '&dl=1';
else
url += '?dl=1';
}
return url;
}
class Restore extends command_1.Command {
async run() {
const { flags, args } = await this.parse(Restore);
const { app, 'wait-interval': waitInterval, extensions, confirm, verbose } = flags;
const interval = Math.max(3, waitInterval);
const { addon: db } = await (0, fetcher_1.getAttachment)(this.heroku, app, args.database);
const { name, wait } = (0, backups_1.default)(app, this.heroku);
let backupURL;
let backupName = args.backup;
if (backupName && backupName.match(/^https?:\/\//)) {
backupURL = dropboxURL(backupName);
}
else {
let backupApp;
if (backupName && backupName.match(/::/)) {
[backupApp, backupName] = backupName.split('::');
}
else {
backupApp = app;
}
const { body: transfers } = await this.heroku.get(`/client/v11/apps/${backupApp}/transfers`, { hostname: (0, host_1.default)() });
const backups = transfers.filter(t => t.from_type === 'pg_dump' && t.to_type === 'gof3r');
let backup;
if (backupName) {
backup = backups.find(b => name(b) === backupName);
if (!backup)
throw new Error(`Backup ${color_1.default.cyan(backupName)} not found for ${color_1.default.app(backupApp)}`);
if (!backup.succeeded)
throw new Error(`Backup ${color_1.default.cyan(backupName)} for ${color_1.default.app(backupApp)} did not complete successfully`);
}
else {
backup = backups.filter(b => b.succeeded).sort((a, b) => {
if (a.finished_at < b.finished_at) {
return -1;
}
if (a.finished_at > b.finished_at) {
return 1;
}
return 0;
}).pop();
if (!backup) {
throw new Error(`No backups for ${color_1.default.app(backupApp)}. Capture one with ${color_1.default.cyan.bold('heroku pg:backups:capture')}`);
}
backupName = name(backup);
}
backupURL = backup.to_url;
}
await (0, confirmCommand_1.default)(app, confirm);
core_1.ux.action.start(`Starting restore of ${color_1.default.cyan(backupName)} to ${color_1.default.yellow(db.name)}`);
core_1.ux.log((0, tsheredoc_1.default)(`
Use Ctrl-C at any time to stop monitoring progress; the backup will continue restoring.
Use ${color_1.default.cyan.bold('heroku pg:backups')} to check progress.
Stop a running restore with ${color_1.default.cyan.bold('heroku pg:backups:cancel')}.
`));
const { body: restore } = await this.heroku.post(`/client/v11/databases/${db.id}/restores`, {
body: { backup_url: backupURL, extensions: this.getSortedExtensions(extensions) }, hostname: (0, host_1.default)(),
});
core_1.ux.action.stop();
await wait('Restoring', restore.uuid, interval, verbose, db.app.id);
}
getSortedExtensions(extensions) {
return extensions === null || extensions === void 0 ? void 0 : extensions.split(',').map(ext => ext.trim().toLowerCase()).sort();
}
}
exports.default = Restore;
Restore.topic = 'pg';
Restore.description = 'restore a backup (default latest) to a database';
Restore.flags = {
'wait-interval': command_1.flags.integer({ default: 3 }),
extensions: command_1.flags.string({
char: 'e',
description: (0, tsheredoc_1.default)(`
comma-separated list of extensions to pre-install in the default
public schema or an optional custom schema
(for example: hstore or myschema.hstore)
`),
}),
verbose: command_1.flags.boolean({ char: 'v' }),
confirm: command_1.flags.string({ char: 'c' }),
app: command_1.flags.app({ required: true }),
remote: command_1.flags.remote(),
};
Restore.args = {
backup: core_1.Args.string({ description: 'URL or backup ID from another app' }),
database: core_1.Args.string({ description: `${(0, nls_1.nls)('pg:database:arg:description')} ${(0, nls_1.nls)('pg:database:arg:description:default:suffix')}` }),
};
Restore.examples = [
(0, tsheredoc_1.default)(`
# Basic Restore from Backup ID
$ heroku pg:backups:restore b101 DATABASE_URL --app my-heroku-app
`),
(0, tsheredoc_1.default)(`
# Restore from Another App
$ heroku pg:backups:restore example-app::b101 DATABASE_URL --app my-heroku-app
`),
(0, tsheredoc_1.default)(`
# Restore from a Public URL
$ heroku pg:backups:restore 'https://s3.amazonaws.com/my-bucket/mydb.dump' DATABASE_URL --app my-heroku-app
`),
(0, tsheredoc_1.default)(`
# Verbose Output
$ heroku pg:backups:restore b101 DATABASE_URL --app my-heroku-app --verbose
`),
(0, tsheredoc_1.default)(`
# Restore with Confirmation Prompt
$ heroku pg:backups:restore b101 DATABASE_URL --app my-heroku-app --confirm my-heroku-app
`),
(0, tsheredoc_1.default)(`
# Restore with a Specific Database Name
$ heroku pg:backups:restore b101 HEROKU_POSTGRESQL_PINK --app my-heroku-app
`),
];
;