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); + }); }); }