diff --git a/src/appdb.js b/src/appdb.js index ea92334bb..98f59eb91 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -103,7 +103,7 @@ function postProcess(result) { delete result.environmentVariables; for (var i = 0; i < environmentVariables.length; i++) { - result.portBindings[environmentVariables[i]] = parseInt(hostPorts[i], 10); + result.portBindings[environmentVariables[i]] = { hostPort: parseInt(hostPorts[i], 10) }; } assert(result.accessRestrictionJson === null || typeof result.accessRestrictionJson === 'string'); @@ -272,7 +272,7 @@ function add(id, appStoreId, manifest, location, domain, ownerId, portBindings, Object.keys(portBindings).forEach(function (env) { queries.push({ query: 'INSERT INTO appPortBindings (environmentVariable, hostPort, appId) VALUES (?, ?, ?)', - args: [ env, portBindings[env], id ] + args: [ env, portBindings[env].hostPort, id ] }); }); @@ -394,7 +394,7 @@ function updateWithConstraints(id, app, constraints, callback) { // replace entries by app id queries.push({ query: 'DELETE FROM appPortBindings WHERE appId = ?', args: [ id ] }); Object.keys(portBindings).forEach(function (env) { - var values = [ portBindings[env], env, id ]; + var values = [ portBindings[env].hostPort, env, id ]; queries.push({ query: 'INSERT INTO appPortBindings (hostPort, environmentVariable, appId) VALUES(?, ?, ?)', args: values }); }); } diff --git a/src/apps.js b/src/apps.js index 3c3fbd2d6..3430522f4 100644 --- a/src/apps.js +++ b/src/apps.js @@ -48,7 +48,8 @@ exports = module.exports = { // exported for testing _validateHostname: validateHostname, _validatePortBindings: validatePortBindings, - _validateAccessRestriction: validateAccessRestriction + _validateAccessRestriction: validateAccessRestriction, + _translatePortBindings: translatePortBindings }; var appdb = require('./appdb.js'), @@ -190,26 +191,44 @@ function validatePortBindings(portBindings, tcpPorts) { if (!portBindings) return null; - var env; - for (env in portBindings) { - if (!/^[a-zA-Z0-9_]+$/.test(env)) return new AppsError(AppsError.BAD_FIELD, env + ' is not valid environment variable'); + for (let portName in portBindings) { + if (!/^[a-zA-Z0-9_]+$/.test(portName)) return new AppsError(AppsError.BAD_FIELD, `${portName} is not a valid environment variable`); - if (!Number.isInteger(portBindings[env])) return new AppsError(AppsError.BAD_FIELD, portBindings[env] + ' is not an integer'); - if (RESERVED_PORTS.indexOf(portBindings[env]) !== -1) return new AppsError(AppsError.PORT_RESERVED, String(portBindings[env])); - if (portBindings[env] <= 1023 || portBindings[env] > 65535) return new AppsError(AppsError.BAD_FIELD, portBindings[env] + ' is not in permitted range'); + const hostPort = portBindings[portName]; + if (!Number.isInteger(hostPort)) return new AppsError(AppsError.BAD_FIELD, `${hostPort} is not an integer`); + if (RESERVED_PORTS.indexOf(hostPort) !== -1) return new AppsError(AppsError.PORT_RESERVED, String(hostPort)); + if (hostPort <= 1023 || hostPort > 65535) return new AppsError(AppsError.BAD_FIELD, `${hostPort} is not in permitted range`); } // it is OK if there is no 1-1 mapping between values in manifest.tcpPorts and portBindings. missing values implies // that the user wants the service disabled tcpPorts = tcpPorts || { }; - for (env in portBindings) { - if (!(env in tcpPorts)) return new AppsError(AppsError.BAD_FIELD, 'Invalid portBindings ' + env); + for (let portName in portBindings) { + if (!(portName in tcpPorts)) return new AppsError(AppsError.BAD_FIELD, `Invalid portBindings ${portName}`); } return null; } +function translatePortBindings(portBindings) { + if (!portBindings) return null; + + let result = {}; + for (let portName in portBindings) { + result[portName] = { hostPort: portBindings[portName] }; + } + return result; +} + +function postProcess(app) { + let result = {}; + for (let portName in app.portBindings) { + result[portName] = app.portBindings[portName].hostPort; + } + app.portBindings = result; +} + function validateAccessRestriction(accessRestriction) { assert.strictEqual(typeof accessRestriction, 'object'); @@ -305,8 +324,8 @@ function getDuplicateErrorDetails(location, portBindings, error) { if (match[1] === location) return new AppsError(AppsError.ALREADY_EXISTS); // check if any of the port bindings conflict - for (var env in portBindings) { - if (portBindings[env] === parseInt(match[1])) return new AppsError(AppsError.PORT_CONFLICT, match[1]); + for (let portName in portBindings) { + if (portBindings[portName] === parseInt(match[1])) return new AppsError(AppsError.PORT_CONFLICT, match[1]); } return new AppsError(AppsError.ALREADY_EXISTS); @@ -375,6 +394,8 @@ function get(appId, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app')); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + postProcess(app); + domaindb.get(app.domain, function (error, result) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); @@ -403,6 +424,8 @@ function getByIpAddress(ip, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app')); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + postProcess(app); + domaindb.get(app.domain, function (error, result) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); @@ -427,6 +450,8 @@ function getAll(callback) { appdb.getAll(function (error, apps) { if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + apps.forEach(postProcess); + async.eachSeries(apps, function (app, iteratorDone) { domaindb.get(app.domain, function (error, result) { if (error) return iteratorDone(new AppsError(AppsError.INTERNAL_ERROR, error)); @@ -583,7 +608,7 @@ function install(data, auditSource, callback) { robotsTxt: robotsTxt }; - appdb.add(appId, appStoreId, manifest, location, domain, ownerId, portBindings, data, function (error) { + appdb.add(appId, appStoreId, manifest, location, domain, ownerId, translatePortBindings(portBindings), data, function (error) { if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); @@ -649,9 +674,10 @@ function configure(appId, data, auditSource, callback) { } if ('portBindings' in data) { - portBindings = values.portBindings = data.portBindings; - error = validatePortBindings(values.portBindings, app.manifest.tcpPorts); + error = validatePortBindings(data.portBindings, app.manifest.tcpPorts); if (error) return callback(error); + values.portBindings = translatePortBindings(data.portBindings) + portBindings = data.portBindings; } else { portBindings = app.portBindings; } @@ -974,7 +1000,7 @@ function clone(appId, data, auditSource, callback) { robotsTxt: app.robotsTxt }; - appdb.add(newAppId, app.appStoreId, manifest, location, domain, ownerId, portBindings, data, function (error) { + appdb.add(newAppId, app.appStoreId, manifest, location, domain, ownerId, translatePortBindings(portBindings), data, function (error) { if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); @@ -1162,8 +1188,8 @@ function autoupdateApps(updateInfo, auditSource, callback) { // updateInfo is { var newTcpPorts = newManifest.tcpPorts || { }; var portBindings = app.portBindings; // this is never null - for (var env in portBindings) { - if (!(env in newTcpPorts)) return new Error(env + ' was in use but new update removes it'); + for (let portName in portBindings) { + if (!(portName in newTcpPorts)) return new Error(`${portName} was in use but new update removes it`); } // it's fine if one or more (unused) keys got removed diff --git a/src/docker.js b/src/docker.js index fc967daff..c237d15a5 100644 --- a/src/docker.js +++ b/src/docker.js @@ -127,12 +127,12 @@ function createSubcontainer(app, name, cmd, options, callback) { dockerPortBindings[manifest.httpPort + '/tcp'] = [ { HostIp: '127.0.0.1', HostPort: app.httpPort + '' } ]; var portEnv = []; - for (var e in app.portBindings) { - var hostPort = app.portBindings[e]; - var containerPort = manifest.tcpPorts[e].containerPort || hostPort; + for (let portName in app.portBindings) { + var hostPort = app.portBindings[portName]; + var containerPort = manifest.tcpPorts[portName].containerPort || hostPort; exposedPorts[containerPort + '/tcp'] = {}; - portEnv.push(e + '=' + hostPort); + portEnv.push(`${portName}=${hostPort}`); dockerPortBindings[containerPort + '/tcp'] = [ { HostIp: '0.0.0.0', HostPort: hostPort + '' } ]; } diff --git a/src/test/apps-test.js b/src/test/apps-test.js index 0a24b7861..f18511991 100644 --- a/src/test/apps-test.js +++ b/src/test/apps-test.js @@ -162,9 +162,9 @@ describe('Apps', function () { groupdb.add.bind(null, GROUP_0.id, GROUP_0.name), groupdb.add.bind(null, GROUP_1.id, GROUP_1.name), groups.addMember.bind(null, GROUP_0.id, USER_1.id), - appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0), - appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_1.ownerId, APP_1.portBindings, APP_1), - appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, APP_2.ownerId, APP_2.portBindings, APP_2), + appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings), APP_0), + appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_1.ownerId, apps._translatePortBindings(APP_1.portBindings), APP_1), + appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, APP_2.ownerId, apps._translatePortBindings(APP_2.portBindings), APP_2), settingsdb.set.bind(null, settings.BACKUP_CONFIG_KEY, JSON.stringify({ provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' })) ], done); }); diff --git a/src/test/database-test.js b/src/test/database-test.js index 7718c711c..6362e9878 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -221,7 +221,7 @@ describe('database', function () { manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0' }, httpPort: null, containerId: null, - portBindings: { port: 5678 }, + portBindings: { port: { hostPort: 5678 } }, health: null, accessRestriction: null, lastBackupId: null, @@ -735,7 +735,7 @@ describe('database', function () { manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0' }, httpPort: null, containerId: null, - portBindings: { port: 5678 }, + portBindings: { port: { hostPort: 5678 } }, health: null, accessRestriction: null, restoreConfig: null, diff --git a/src/test/ldap-test.js b/src/test/ldap-test.js index 1d091db50..40686e33a 100644 --- a/src/test/ldap-test.js +++ b/src/test/ldap-test.js @@ -7,6 +7,7 @@ 'use strict'; var appdb = require('../appdb.js'), + apps = require('../apps.js'), assert = require('assert'), async = require('async'), database = require('../database.js'), @@ -105,7 +106,7 @@ function setup(done) { USER_0.id = APP_0.ownerId = result.id; - appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0, callback); + appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings), APP_0, callback); }); }, appdb.update.bind(null, APP_0.id, { containerId: APP_0.containerId }), diff --git a/src/test/updatechecker-test.js b/src/test/updatechecker-test.js index 2c1b40cb2..e2cc03fae 100644 --- a/src/test/updatechecker-test.js +++ b/src/test/updatechecker-test.js @@ -6,6 +6,7 @@ 'use strict'; var appdb = require('../appdb.js'), + apps = require('../apps.js'), async = require('async'), config = require('../config.js'), constants = require('../constants.js'), @@ -300,7 +301,7 @@ describe('updatechecker - app - manual (email)', function () { if (error) return next(error); APP_0.ownerId = userObject.id; - appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0, next); + appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings), APP_0, next); }); }, settings.setAppAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER), @@ -423,7 +424,7 @@ describe('updatechecker - app - automatic (no email)', function () { if (error) return next(error); APP_0.ownerId = userObject.id; - appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0, next); + appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings), APP_0, next); }); }, settings.setAppAutoupdatePattern.bind(null, '00 00 1,3,5,23 * * *'), @@ -496,7 +497,7 @@ describe('updatechecker - app - automatic free (email)', function () { if (error) return next(error); APP_0.ownerId = userObject.id; - appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0, next); + appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings), APP_0, next); }); }, settings.setAppAutoupdatePattern.bind(null, '00 00 1,3,5,23 * * *'),