diff --git a/src/apps.js b/src/apps.js index 091a9867c..0efb648a7 100644 --- a/src/apps.js +++ b/src/apps.js @@ -43,6 +43,7 @@ exports = module.exports = { setMailbox, setInbox, setTurn, + setRedis, setLocation, setStorage, repair, @@ -200,7 +201,7 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS 'apps.sso', 'apps.debugModeJson', 'apps.enableBackup', 'apps.proxyAuth', 'apps.containerIp', 'apps.crontab', 'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate', 'apps.upstreamUri', 'apps.enableMailbox', 'apps.mailboxDisplayName', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableInbox', 'apps.inboxName', 'apps.inboxDomain', - 'apps.enableTurn', 'apps.storageVolumeId', 'apps.storageVolumePrefix', 'apps.ts', 'apps.healthTime', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.appStoreIcon IS NOT NULL) AS hasAppStoreIcon' ].join(','); + 'apps.enableTurn', 'apps.enableRedis', 'apps.storageVolumeId', 'apps.storageVolumePrefix', 'apps.ts', 'apps.healthTime', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.appStoreIcon IS NOT NULL) AS hasAppStoreIcon' ].join(','); // const PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(','); const LOCATION_FIELDS = [ 'appId', 'subdomain', 'domain', 'type', 'certificateJson' ]; @@ -595,7 +596,7 @@ function removeInternalFields(app) { 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'operators', 'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags', 'label', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', - 'storageVolumeId', 'storageVolumePrefix', 'mounts', 'enableTurn', + 'storageVolumeId', 'storageVolumePrefix', 'mounts', 'enableTurn', 'enableRedis', 'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain'); removeCertificateKeys(result); @@ -843,6 +844,7 @@ async function add(id, appStoreId, manifest, subdomain, domain, portBindings, da enableMailbox = data.enableMailbox || false, upstreamUri = data.upstreamUri || '', enableTurn = 'enableTurn' in data ? data.enableTurn : true, + enableRedis = 'enableRedis' in data ? data.enableRedis : true, icon = data.icon || null; const queries = []; @@ -850,11 +852,11 @@ async function add(id, appStoreId, manifest, subdomain, domain, portBindings, da queries.push({ query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares, ' + 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, ' - + 'enableMailbox, mailboxDisplayName, upstreamUri, enableTurn) ' - + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + + 'enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis) ' + + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', args: [ id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares, sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, - enableMailbox, mailboxDisplayName, upstreamUri, enableTurn ] + enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis ] }); queries.push({ @@ -1328,6 +1330,7 @@ async function install(data, auditSource) { overwriteDns = 'overwriteDns' in data ? data.overwriteDns : false, skipDnsSetup = 'skipDnsSetup' in data ? data.skipDnsSetup : false, enableTurn = 'enableTurn' in data ? data.enableTurn : true, + enableRedis = 'enableRedis' in data ? data.enableRedis : true, appStoreId = data.appStoreId, upstreamUri = data.upstreamUri || '', manifest = data.manifest; @@ -1416,6 +1419,7 @@ async function install(data, auditSource) { enableMailbox, upstreamUri, enableTurn, + enableRedis, runState: exports.RSTATE_RUNNING, installationState: exports.ISTATE_PENDING_INSTALL }; @@ -1761,6 +1765,29 @@ async function setTurn(app, enableTurn, auditSource) { return { taskId }; } +async function setRedis(app, enableRedis, auditSource) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof enableRedis, 'boolean'); + assert.strictEqual(typeof auditSource, 'object'); + + const appId = app.id; + let error = checkAppState(app, exports.ISTATE_PENDING_SERVICES_CHANGE); + if (error) throw error; + + if (!app.manifest.addons?.redis) throw new BoxError(BoxError.BAD_FIELD, 'App does not use redis addon'); + if (!app.manifest.addons.redis.optional) throw new BoxError(BoxError.BAD_FIELD, 'redis service is not optional'); + + const task = { + args: {}, + values: { enableRedis } + }; + const taskId = await addTask(appId, exports.ISTATE_PENDING_SERVICES_CHANGE, task, auditSource); + + await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, enableRedis, taskId }); + + return { taskId }; +} + async function setAutomaticBackup(app, enable, auditSource) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof enable, 'boolean'); diff --git a/src/apptask.js b/src/apptask.js index 11904db59..407553085 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -520,6 +520,8 @@ async function changeServices(app, args, progressCallback) { if (app.manifest.addons.turn && !app.enableTurn) unusedAddons.turn = app.manifest.addons.turn; if (app.manifest.addons.sendmail && !app.enableMailbox) unusedAddons.sendmail = app.manifest.addons.sendmail; if (app.manifest.addons.recvmail && !app.enableInbox) unusedAddons.recvmail = app.manifest.addons.recvmail; + if (app.manifest.addons.redis && !app.enableRedis) unusedAddons.redis = app.manifest.addons.redis; + await progressCallback({ percent: 20, message: 'Removing unused addons' }); await services.teardownAddons(app, unusedAddons); diff --git a/src/routes/apps.js b/src/routes/apps.js index d8353c3b2..b620d96d2 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -25,6 +25,7 @@ exports = module.exports = { setTags, setIcon, setTurn, + setRedis, setMemoryLimit, setCpuShares, setAutomaticBackup, @@ -422,6 +423,18 @@ async function setTurn(req, res, next) { next(new HttpSuccess(202, { taskId: result.taskId })); } +async function setRedis(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + assert.strictEqual(typeof req.app, 'object'); + + if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean')); + + const [error, result] = await safe(apps.setRedis(req.app, req.body.enable, AuditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(202, { taskId: result.taskId })); +} + async function setLocation(req, res, next) { assert.strictEqual(typeof req.body, 'object'); assert.strictEqual(typeof req.app, 'object'); diff --git a/src/server.js b/src/server.js index e0f7088f1..88dbce0b1 100644 --- a/src/server.js +++ b/src/server.js @@ -232,6 +232,7 @@ async function initializeExpressSync() { router.post('/api/v1/apps/:id/configure/mailbox', json, token, routes.apps.load, authorizeAdmin, routes.apps.setMailbox); router.post('/api/v1/apps/:id/configure/inbox', json, token, routes.apps.load, authorizeAdmin, routes.apps.setInbox); router.post('/api/v1/apps/:id/configure/turn', json, token, routes.apps.load, authorizeAdmin, routes.apps.setTurn); + router.post('/api/v1/apps/:id/configure/redis', json, token, routes.apps.load, authorizeAdmin, routes.apps.setRedis); router.post('/api/v1/apps/:id/configure/env', json, token, routes.apps.load, authorizeOperator, routes.apps.setEnvironment); router.post('/api/v1/apps/:id/configure/storage', json, token, routes.apps.load, authorizeAdmin, routes.apps.setStorage); router.post('/api/v1/apps/:id/configure/location', json, token, routes.apps.load, authorizeAdmin, routes.apps.setLocation); diff --git a/src/services.js b/src/services.js index f6b840fb6..52eb9a0e3 100644 --- a/src/services.js +++ b/src/services.js @@ -1720,6 +1720,9 @@ async function setupRedis(app, options) { const redisName = 'redis-' + app.id; + const disabled = app.manifest.addons.redis.optional && !app.enableRedis; + if (disabled) return await addonConfigs.unset(app.id, 'redis'); + const existingPassword = await addonConfigs.getByName(app.id, 'redis', '%REDIS_PASSWORD'); const redisPassword = options.noPassword ? '' : (existingPassword || hat(4 * 48)); // see box#362 for password length