diff --git a/CHANGES b/CHANGES index b62e0ae46..d1b07e3dc 100644 --- a/CHANGES +++ b/CHANGES @@ -1472,4 +1472,5 @@ * Backup and update tasks are now cancelable * Move graphite away from port 3000 (reserved by ESXi) * Flexible mailbox management +* Automatic updates can be toggled per app diff --git a/migrations/20181207165827-apps-add-enableAutomaticUpdate.js b/migrations/20181207165827-apps-add-enableAutomaticUpdate.js new file mode 100644 index 000000000..ec9504596 --- /dev/null +++ b/migrations/20181207165827-apps-add-enableAutomaticUpdate.js @@ -0,0 +1,16 @@ +'use strict'; + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE apps ADD COLUMN enableAutomaticUpdate BOOLEAN DEFAULT 1', function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE apps DROP COLUMN enableAutomaticUpdate', function (error) { + if (error) console.error(error); + callback(error); + }); +}; + diff --git a/migrations/schema.sql b/migrations/schema.sql index d27ff3d11..df84eb282 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -80,6 +80,7 @@ CREATE TABLE IF NOT EXISTS apps( debugModeJson TEXT, // options for development mode robotsTxt TEXT, enableBackup BOOLEAN DEFAULT 1, // misnomer: controls automatic daily backups + enableAutomaticUpdate BOOLEAN DEFAULT 1, mailboxName VARCHAR(128), // mailbox of this app // the following fields do not belong here, they can be removed when we use a queue for apptask diff --git a/src/appdb.js b/src/appdb.js index c626fd283..20b6ad77e 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -69,7 +69,7 @@ var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationSta 'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.httpPort', 'subdomains.subdomain AS location', 'subdomains.domain', 'apps.accessRestrictionJson', 'apps.restoreConfigJson', 'apps.oldConfigJson', 'apps.updateConfigJson', 'apps.memoryLimit', 'apps.xFrameOptions', 'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup', - 'apps.creationTime', 'apps.updateTime', 'apps.ownerId', 'apps.mailboxName', 'apps.ts' ].join(','); + 'apps.creationTime', 'apps.updateTime', 'apps.ownerId', 'apps.mailboxName', 'apps.enableAutomaticUpdate', 'apps.ts' ].join(','); var PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(','); @@ -120,6 +120,7 @@ function postProcess(result) { result.sso = !!result.sso; // make it bool result.enableBackup = !!result.enableBackup; // make it bool + result.enableAutomaticUpdate = !!result.enableAutomaticUpdate; // make it bool assert(result.debugModeJson === null || typeof result.debugModeJson === 'string'); result.debugMode = safe.JSON.parse(result.debugModeJson); diff --git a/src/apps.js b/src/apps.js index 825bc871e..65704f149 100644 --- a/src/apps.js +++ b/src/apps.js @@ -346,7 +346,7 @@ function removeInternalFields(app) { 'location', 'domain', 'fqdn', 'mailboxName', 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'xFrameOptions', 'sso', 'debugMode', 'robotsTxt', 'enableBackup', 'creationTime', 'updateTime', 'ts', - 'alternateDomains', 'ownerId', 'env'); + 'alternateDomains', 'ownerId', 'env', 'enableAutomaticUpdate'); } function removeRestrictedFields(app) { @@ -525,6 +525,7 @@ function install(data, user, auditSource, callback) { debugMode = data.debugMode || null, robotsTxt = data.robotsTxt || null, enableBackup = 'enableBackup' in data ? data.enableBackup : true, + enableAutomaticUpdate = 'enableAutomaticUpdate' in data ? data.enableAutomaticUpdate : true, backupId = data.backupId || null, backupFormat = data.backupFormat || 'tgz', ownerId = data.ownerId, @@ -606,6 +607,7 @@ function install(data, user, auditSource, callback) { mailboxName: mailboxNameForLocation(location, manifest), restoreConfig: backupId ? { backupId: backupId, backupFormat: backupFormat } : null, enableBackup: enableBackup, + enableAutomaticUpdate: enableAutomaticUpdate, robotsTxt: robotsTxt, alternateDomains: alternateDomains, env: env @@ -755,6 +757,7 @@ function configure(appId, data, user, auditSource, callback) { } if ('enableBackup' in data) values.enableBackup = data.enableBackup; + if ('enableAutomaticUpdate' in data) values.enableAutomaticUpdate = data.enableAutomaticUpdate; values.oldConfig = getAppConfig(app); @@ -1193,6 +1196,7 @@ function autoupdateApps(updateInfo, auditSource, callback) { // updateInfo is { assert.strictEqual(typeof callback, 'function'); function canAutoupdateApp(app, newManifest) { + if (!app.enableAutomaticUpdate) return new Error('Automatic update disabled'); if ((semver.major(app.manifest.version) !== 0) && (semver.major(app.manifest.version) !== semver.major(newManifest.version))) return new Error('Major version change'); // major changes are blocking const newTcpPorts = newManifest.tcpPorts || { }; diff --git a/src/routes/apps.js b/src/routes/apps.js index 07864ab2a..681106f55 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -134,6 +134,7 @@ function installApp(req, res, next) { if ('sso' in data && typeof data.sso !== 'boolean') return next(new HttpError(400, 'sso must be a boolean')); if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean')); + if ('enableAutomaticUpdate' in data && typeof data.enableAutomaticUpdate !== 'boolean') return next(new HttpError(400, 'enableAutomaticUpdate must be a boolean')); if (('debugMode' in data) && typeof data.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object')); @@ -187,6 +188,7 @@ function configureApp(req, res, next) { if (data.xFrameOptions && typeof data.xFrameOptions !== 'string') return next(new HttpError(400, 'xFrameOptions must be a string')); if ('enableBackup' in data && typeof data.enableBackup !== 'boolean') return next(new HttpError(400, 'enableBackup must be a boolean')); + if ('enableAutomaticUpdate' in data && typeof data.enableAutomaticUpdate !== 'boolean') return next(new HttpError(400, 'enableAutomaticUpdate must be a boolean')); if (('debugMode' in data) && typeof data.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object')); diff --git a/src/test/database-test.js b/src/test/database-test.js index 7949734c0..8628a77ab 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -233,7 +233,8 @@ describe('database', function () { enableBackup: true, ownerId: USER_0.id, env: {}, - mailboxName: 'talktome' + mailboxName: 'talktome', + enableAutomaticUpdate: true }; it('cannot delete referenced domain', function (done) { @@ -755,7 +756,8 @@ describe('database', function () { env: { 'CUSTOM_KEY': 'CUSTOM_VALUE' }, - mailboxName: 'talktome' + mailboxName: 'talktome', + enableAutomaticUpdate: true }; var APP_1 = { @@ -784,7 +786,8 @@ describe('database', function () { ownerId: USER_0.id, alternateDomains: [], env: {}, - mailboxName: 'callme' + mailboxName: 'callme', + enableAutomaticUpdate: true }; before(function (done) {