From 431e2a6ab9ccfa0eb3775186b4b4bab4f7a47eec Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Tue, 25 May 2021 21:31:48 -0700 Subject: [PATCH] clone: save app config clone also clones the tags, labels and icon. this is not done for restore or import since it's not clear if this is a good idea or not. for example, if user had some custom tags and label set and then restores to some old backup, is it expected to reset the labels and tags? --- CHANGES | 1 + src/appdb.js | 2 +- src/apps.js | 98 +++++++++++++++++++++++++++++++------------------- src/backups.js | 14 ++++---- 4 files changed, 71 insertions(+), 44 deletions(-) diff --git a/CHANGES b/CHANGES index b39b2ac47..608236b2b 100644 --- a/CHANGES +++ b/CHANGES @@ -2275,4 +2275,5 @@ * volumes: generate systemd mount configs based on type * postgresql: set max conn limit per db * ubuntu 16: add alert about EOL +* clone: save and restore app config diff --git a/src/appdb.js b/src/appdb.js index 21788649b..b73321458 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -33,7 +33,7 @@ exports = module.exports = { _clear: clear }; -var assert = require('assert'), +const assert = require('assert'), async = require('async'), BoxError = require('./boxerror.js'), database = require('./database.js'), diff --git a/src/apps.js b/src/apps.js index 0db61d0c7..25f0a54a4 100644 --- a/src/apps.js +++ b/src/apps.js @@ -70,6 +70,8 @@ exports = module.exports = { downloadFile, uploadFile, + backupConfig, + PORT_TYPE_TCP: 'tcp', PORT_TYPE_UDP: 'udp', @@ -856,7 +858,7 @@ function setAccessRestriction(app, accessRestriction, auditSource, callback) { let error = validateAccessRestriction(accessRestriction); if (error) return callback(error); - appdb.update(appId, { accessRestriction: accessRestriction }, function (error) { + appdb.update(appId, { accessRestriction }, function (error) { if (error) return callback(error); eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, accessRestriction }); @@ -875,7 +877,7 @@ function setLabel(app, label, auditSource, callback) { let error = validateLabel(label); if (error) return callback(error); - appdb.update(appId, { label: label }, function (error) { + appdb.update(appId, { label }, function (error) { if (error) return callback(error); eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, label }); @@ -894,7 +896,7 @@ function setTags(app, tags, auditSource, callback) { let error = validateTags(tags); if (error) return callback(error); - appdb.update(appId, { tags: tags }, function (error) { + appdb.update(appId, { tags }, function (error) { if (error) return callback(error); eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, tags }); @@ -1632,7 +1634,7 @@ function clone(app, data, user, auditSource, callback) { assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); - var location = data.location.toLowerCase(), + const location = data.location.toLowerCase(), domain = data.domain.toLowerCase(), portBindings = data.portBindings || null, backupId = data.backupId, @@ -1670,46 +1672,55 @@ function clone(app, data, user, auditSource, callback) { const newAppId = uuid.v4(); - const data = { - installationState: exports.ISTATE_PENDING_CLONE, - runState: exports.RSTATE_RUNNING, - memoryLimit: app.memoryLimit, - accessRestriction: app.accessRestriction, - sso: !!app.sso, - mailboxName: mailboxName, - mailboxDomain: mailboxDomain, - enableBackup: app.enableBackup, - reverseProxyConfig: app.reverseProxyConfig, - env: app.env, - alternateDomains: [], - aliasDomains: [], - servicesConfig: app.servicesConfig - }; - - appdb.add(newAppId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) { - if (error && error.reason === BoxError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error.message, locations, domainObjectMap, portBindings)); + appdb.getIcons(app.id, function (error, icons) { if (error) return callback(error); - purchaseApp({ appId: newAppId, appstoreId: app.appStoreId, manifestId: manifest.id || 'customapp' }, function (error) { + const data = { + installationState: exports.ISTATE_PENDING_CLONE, + runState: exports.RSTATE_RUNNING, + memoryLimit: app.memoryLimit, + cpuShares: app.cpuShares, + accessRestriction: app.accessRestriction, + sso: !!app.sso, + mailboxName: mailboxName, + mailboxDomain: mailboxDomain, + enableBackup: app.enableBackup, + reverseProxyConfig: app.reverseProxyConfig, + env: app.env, + alternateDomains: [], + aliasDomains: [], + servicesConfig: app.servicesConfig, + label: app.label ? `${app.label}-clone` : '', + tags: app.tags, + enableAutomaticUpdate: app.enableAutomaticUpdate, + icons: icons.icon + }; + + appdb.add(newAppId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) { + if (error && error.reason === BoxError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error.message, locations, domainObjectMap, portBindings)); if (error) return callback(error); - const restoreConfig = { backupId: backupId, backupFormat: backupInfo.format }; - const task = { - args: { restoreConfig, overwriteDns, skipDnsSetup, oldManifest: null }, - values: {}, - requiredState: exports.ISTATE_PENDING_CLONE - }; - addTask(newAppId, exports.ISTATE_PENDING_CLONE, task, function (error, result) { + purchaseApp({ appId: newAppId, appstoreId: app.appStoreId, manifestId: manifest.id || 'customapp' }, function (error) { if (error) return callback(error); - const newApp = _.extend({}, data, { appStoreId, manifest, location, domain, portBindings }); - newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]); - newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); - newApp.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + const restoreConfig = { backupId: backupId, backupFormat: backupInfo.format }; + const task = { + args: { restoreConfig, overwriteDns, skipDnsSetup, oldManifest: null }, + values: {}, + requiredState: exports.ISTATE_PENDING_CLONE + }; + addTask(newAppId, exports.ISTATE_PENDING_CLONE, task, function (error, result) { + if (error) return callback(error); - eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, oldApp: app, newApp: newApp, taskId: result.taskId }); + const newApp = _.extend({}, data, { appStoreId, manifest, location, domain, portBindings }); + newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]); + newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + newApp.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); - callback(null, { id: newAppId, taskId: result.taskId }); + eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, oldApp: app, newApp: newApp, taskId: result.taskId }); + + callback(null, { id: newAppId, taskId: result.taskId }); + }); }); }); }); @@ -2189,3 +2200,18 @@ function uploadFile(app, sourceFilePath, destFilePath, callback) { readFile.pipe(stream); }); } + +function backupConfig(app, callback) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof callback, 'function'); + + if (!safe.fs.writeFileSync(path.join(paths.APPS_DATA_DIR, app.id + '/config.json'), JSON.stringify(app))) { + return callback(new BoxError(BoxError.FS_ERROR, 'Error creating config.json: ' + safe.error.message)); + } + + appdb.getIcons(app.id, function (error, icons) { + if (!error && icons.icon) safe.fs.writeFileSync(path.join(paths.APPS_DATA_DIR, app.id + '/icon.png'), icons.icon); + + callback(null); + }); +} diff --git a/src/backups.js b/src/backups.js index 079d384c6..198da3655 100644 --- a/src/backups.js +++ b/src/backups.js @@ -1063,16 +1063,16 @@ function snapshotApp(app, progressCallback, callback) { const startTime = new Date(); progressCallback({ message: `Snapshotting app ${app.fqdn}` }); - if (!safe.fs.writeFileSync(path.join(paths.APPS_DATA_DIR, app.id + '/config.json'), JSON.stringify(app))) { - return callback(new BoxError(BoxError.FS_ERROR, 'Error creating config.json: ' + safe.error.message)); - } + apps.backupConfig(app, function (error) { + if (error) return callback(error); - services.backupAddons(app, app.manifest.addons, function (error) { - if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error.message)); + services.backupAddons(app, app.manifest.addons, function (error) { + if (error) return callback(error); - debugApp(app, `snapshotApp: took ${(new Date() - startTime)/1000} seconds`); + debugApp(app, `snapshotApp: took ${(new Date() - startTime)/1000} seconds`); - return callback(null); + return callback(null); + }); }); }