diff --git a/dashboard/src/components/AppImportDialog.vue b/dashboard/src/components/AppImportDialog.vue index 376f8f54c..0f639660d 100644 --- a/dashboard/src/components/AppImportDialog.vue +++ b/dashboard/src/components/AppImportDialog.vue @@ -29,17 +29,8 @@ async function onSubmit() { formError.value = {}; busy.value = true; - const backupConfig = { - provider: provider.value, - }; - - if (providerConfig.value.encryptionPassword) { - backupConfig.password = providerConfig.value.encryptionPassword; - backupConfig.encryptedFilenames = providerConfig.value.encryptedFilenames; - } - - const format = providerConfig.value.format; - let backupId = remotePath.value; + let backupPath = remotePath.value; + const backupConfig = {}; // only set provider specific fields, this will clear them in the db if (s3like(provider.value)) { @@ -105,19 +96,23 @@ async function onSubmit() { backupConfig.mountPoint = providerConfig.value.mountPoint; } else if (provider.value === 'filesystem') { const parts = remotePath.value.split('/'); - backupId = parts.pop() || parts.pop(); // removes any trailing slash. this is basename() - backupConfig.backupFolder = parts.join('/'); // this is dirname() + backupPath = parts.pop() || parts.pop(); // removes any trailing slash. this is basename() + backupConfig.backupDir = parts.join('/'); // this is dirname() } - if (format === 'tgz') { - if (backupId.substring(backupId.length - '.tar.gz'.length, backupId.length) === '.tar.gz') { // endsWith - backupId = backupId.replace(/.tar.gz$/, ''); - } else if (backupId.substring(backupId.length - '.tar.gz.enc'.length, backupId.length) === '.tar.gz.enc') { // endsWith - backupId = backupId.replace(/.tar.gz.enc$/, ''); - } + const data = { + format: format.value, + provider: provider.value, + config: backupConfig, + remotePath: backupPath + }; + + if (providerConfig.value.encryptionPassword) { + data.encryptionPassword = providerConfig.value.encryptionPassword; + data.encryptedFilenames = providerConfig.value.encryptedFilenames; } - const [error] = await appsModel.import(appId.value, backupId, format, backupConfig); + const [error] = await appsModel.import(appId.value, data); if (error) { busy.value = false; formError.value = {}; @@ -144,7 +139,7 @@ async function onSubmit() { formError.value.generic = error.body.message; if (provider.value === 'filesystem') { - formError.value.backupFolder = true; + formError.value.backupDir = true; } } else { formError.value.generic = 'Internal error'; @@ -171,7 +166,7 @@ function onBackupConfigChanged(event) { try { data = JSON.parse(result.target.result); // 'provider', 'config', 'limits', 'format', 'remotePath', 'encrypted', 'encryptedFilenames' if (data.provider === 'filesystem') { // this allows a user to upload a backup to server and import easily with an absolute path - data.remotePath = `${data.config.backupFolder}/${data.remotePath}`; + data.remotePath = `${data.config.backupDir}/${data.remotePath}`; } } catch (e) { console.error('Unable to parse backup config', e); diff --git a/dashboard/src/models/AppsModel.js b/dashboard/src/models/AppsModel.js index 598291830..6bb15820b 100644 --- a/dashboard/src/models/AppsModel.js +++ b/dashboard/src/models/AppsModel.js @@ -445,10 +445,10 @@ function create() { if (result.status !== 201) return [result]; return [null, result.body]; }, - async import(id, remotePath, backupFormat, backupConfig) { + async import(id, data) { let result; try { - result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/import`, { remotePath, backupFormat, backupConfig }, { access_token: accessToken }); + result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/import`, data, { access_token: accessToken }); } catch (e) { return [e]; } diff --git a/dashboard/src/views/RestoreView.vue b/dashboard/src/views/RestoreView.vue index 01497b633..06c5128a4 100644 --- a/dashboard/src/views/RestoreView.vue +++ b/dashboard/src/views/RestoreView.vue @@ -201,7 +201,7 @@ async function onSubmit() { data.backupConfig.preserveAttributes = !!providerConfig.value.preserveAttributes; } } else if (provider.value === 'filesystem') { - data.backupConfig.backupFolder = providerConfig.value.backupFolder; + data.backupConfig.backupDir = providerConfig.value.backupDir; data.backupConfig.noHardlinks = !providerConfig.value.useHardlinks; data.backupConfig.preserveAttributes = true; } else if (provider.value === 'gcs') { @@ -252,8 +252,8 @@ function onBackupConfigChanged(event) { try { config = JSON.parse(result.target.result); if (config.provider === 'filesystem') { // this allows a user to upload a backup to server and import easily with an absolute path - config.remotePath = config.backupFolder + '/' + config.remotePath; - delete config.backupFolder; + config.remotePath = config.backupDir + '/' + config.remotePath; + delete config.backupDir; } } catch (e) { console.error('Unable to parse backup config', e); diff --git a/src/apps.js b/src/apps.js index 490e90729..1135f14bf 100644 --- a/src/apps.js +++ b/src/apps.js @@ -2360,8 +2360,8 @@ async function importApp(app, data, auditSource) { provider: data.provider, config: data.config, format: data.format, - encryptionPassword: data.encryptionPassword, - encryptedFilenames: data.encryptedFilenames + encryptionPassword: data.encryptionPassword ?? null, + encryptedFilenames: data.encryptedFilenames ?? false }); restoreConfig = { remotePath: data.remotePath, backupTarget }; diff --git a/src/apptask.js b/src/apptask.js index cedccf486..d0032b687 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -265,10 +265,8 @@ async function installCommand(app, args, progressCallback) { assert.strictEqual(typeof args, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - const restoreConfig = args.restoreConfig; // has to be set when restoring - const overwriteDns = args.overwriteDns; - const skipDnsSetup = args.skipDnsSetup; - const oldManifest = args.oldManifest; + // restoreConfig is one of { backupId }, { remotePath, backupTarget } or { inPlace } + const { restoreConfig, overwriteDns, skipDnsSetup, oldManifest } = args; // this protects against the theoretical possibility of an app being marked for install/restore from // a previous version of box code @@ -326,7 +324,7 @@ async function installCommand(app, args, progressCallback) { await services.clearAddons(app, _.omit(app.manifest.addons, ['localstorage'])); await apps.loadConfig(app); await services.restoreAddons(app, app.manifest.addons); - } else if (app.installationState === apps.ISTATE_PENDING_IMPORT) { // import + } else if (app.installationState === apps.ISTATE_PENDING_IMPORT && restoreConfig.remotePath) { // import await progressCallback({ percent: 65, message: 'Downloading backup and restoring addons' }); await services.setupAddons(app, app.manifest.addons); await services.clearAddons(app, app.manifest.addons); diff --git a/src/backuptask.js b/src/backuptask.js index d3fd477aa..0fe47383f 100644 --- a/src/backuptask.js +++ b/src/backuptask.js @@ -128,10 +128,15 @@ async function downloadApp(app, restoreConfig, progressCallback) { const dataLayout = new DataLayout(appDataDir, app.storageVolumeId ? [{ localDir: await apps.getStorageDir(app), remoteDir: 'data' }] : []); const startTime = new Date(); - const backup = await backups.get(restoreConfig.backupId); - const backupTarget = await backupTargets.get(backup.targetId); + let { backupTarget, remotePath } = restoreConfig; // set when importing + if (!remotePath) { + const backup = await backups.get(restoreConfig.backupId); + if (!backup) throw new BoxError(BoxError.BAD_FIELD, 'No such backup'); + remotePath = backup.remotePath; + backupTarget = await backupTargets.get(backup.targetId); + } - await download(backupTarget, backup.remotePath, dataLayout, progressCallback); + await download(backupTarget, remotePath, dataLayout, progressCallback); debug('downloadApp: time: %s', (new Date() - startTime)/1000); } diff --git a/src/routes/apps.js b/src/routes/apps.js index fd1aa863c..dd5dde068 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -567,7 +567,7 @@ async function importApp(req, res, next) { if ('remotePath' in data) { if (typeof data.remotePath !== 'string' || !data.remotePath) return next(new HttpError(400, 'remotePath must be non-empty string')); if (typeof data.format !== 'string') return next(new HttpError(400, 'format must be string')); - if (typeof config.provider !== 'string') return next(new HttpError(400, 'provider is required')); + if (typeof data.provider !== 'string') return next(new HttpError(400, 'provider is required')); if (typeof data.config !== 'object' || !data.config) return next(new HttpError(400, 'config must be an object')); const config = req.body.config;