From 3236ce9cd6b7376a7cccf86c96f869bbf75f28bc Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Tue, 7 Jun 2016 15:36:45 -0700 Subject: [PATCH 1/8] check if both are null --- src/apps.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/apps.js b/src/apps.js index 79f734f98..7fa213ce5 100644 --- a/src/apps.js +++ b/src/apps.js @@ -350,6 +350,8 @@ function purchase(appStoreId, callback) { } function downloadManifest(appStoreId, manifest, callback) { + if (!appStoreId && !manifest) return callback(new AppsError(AppsError.BAD_FIELD, 'Neither manifest nor appStoreId provided')); + if (!appStoreId) return callback(null, '', manifest); var parts = appStoreId.split('@'); From 064d950f87f86b10f205f8ddfc0487e4aafcda50 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Tue, 7 Jun 2016 16:00:02 -0700 Subject: [PATCH 2/8] add new tests for field validation --- src/routes/test/apps-test.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/routes/test/apps-test.js b/src/routes/test/apps-test.js index 01f2cd0aa..028d8e4a7 100644 --- a/src/routes/test/apps-test.js +++ b/src/routes/test/apps-test.js @@ -229,6 +229,39 @@ describe('Apps', function () { }); }); + it('app install fails - null manifest', function (done) { + superagent.post(SERVER_URL + '/api/v1/apps/install') + .query({ access_token: token }) + .send({ manifest: null, password: PASSWORD }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + expect(res.body.message).to.eql('appStoreId or manifest is required'); + done(); + }); + }); + + it('app install fails - bad manifest format', function (done) { + superagent.post(SERVER_URL + '/api/v1/apps/install') + .query({ access_token: token }) + .send({ manifest: 'epic', password: PASSWORD }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + expect(res.body.message).to.eql('manifest must be an object'); + done(); + }); + }); + + it('app install fails - empty appStoreId format', function (done) { + superagent.post(SERVER_URL + '/api/v1/apps/install') + .query({ access_token: token }) + .send({ manifest: null, appStoreId: '', password: PASSWORD }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + expect(res.body.message).to.eql('appStoreId or manifest is required'); + done(); + }); + }); + it('app install fails - invalid json', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) From 06448f146d38d7c1d283b2a0ee3fb0e93e4a8c6d Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Tue, 7 Jun 2016 20:09:53 -0700 Subject: [PATCH 3/8] fix typo --- src/mailer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mailer.js b/src/mailer.js index fe76a7022..6cd2e731d 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -114,7 +114,7 @@ function getTxtRecords(callback) { function checkDns() { getTxtRecords(function (error, records) { if (error || !records) { - debug('checkDns: DNS error or no records looking up TXT records for %s %s', config.adminFqdn(), error, records); + debug('checkDns: DNS error or no records looking up TXT records for %s %s', config.fqdn(), error, records); gCheckDnsTimerId = setTimeout(checkDns, 60000); return; } From 313d98ef707ac751d8d47fe8f3e8e3cfab8751e7 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Tue, 7 Jun 2016 20:24:41 -0700 Subject: [PATCH 4/8] add a route to check for updates quickly --- src/cloudron.js | 3 +-- src/routes/cloudron.js | 16 ++++++++++++++-- src/server.js | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/cloudron.js b/src/cloudron.js index ae3d48d7b..222081f6c 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -54,8 +54,7 @@ var apps = require('./apps.js'), user = require('./user.js'), UserError = user.UserError, userdb = require('./userdb.js'), - util = require('util'), - uuid = require('node-uuid'); + util = require('util'); var REBOOT_CMD = path.join(__dirname, 'scripts/reboot.sh'), INSTALLER_UPDATE_URL = 'http://127.0.0.1:2020/api/v1/installer/update', diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index b156e973b..68f56c864 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -8,10 +8,12 @@ exports = module.exports = { getProgress: getProgress, getConfig: getConfig, update: update, - feedback: feedback + feedback: feedback, + checkForUpdates: checkForUpdates }; var assert = require('assert'), + async = require('async'), cloudron = require('../cloudron.js'), CloudronError = cloudron.CloudronError, config = require('../config.js'), @@ -20,7 +22,8 @@ var assert = require('assert'), HttpSuccess = require('connect-lastmile').HttpSuccess, progress = require('../progress.js'), mailer = require('../mailer.js'), - superagent = require('superagent'); + superagent = require('superagent'), + updateChecker = require('./updatechecker.js'); function auditSource(req) { var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null; @@ -129,6 +132,15 @@ function update(req, res, next) { }); } +function checkForUpdates(req, res, next) { + async.series([ + updateChecker.checkAppUpdates, + updateChecker.checkBoxUpdates + ], function () { + next(new HttpSuccess(200, { update: updateChecker.getUpdateInfo() })); + }); +} + function feedback(req, res, next) { assert.strictEqual(typeof req.user, 'object'); diff --git a/src/server.js b/src/server.js index 22479c49e..4e2323692 100644 --- a/src/server.js +++ b/src/server.js @@ -94,6 +94,7 @@ function initializeExpressSync() { // cloudron routes router.get ('/api/v1/cloudron/config', cloudronScope, routes.cloudron.getConfig); router.post('/api/v1/cloudron/update', cloudronScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.cloudron.update); + router.post('/api/v1/cloudron/check_for_updates', cloudronScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.cloudron.checkForUpdates); router.post('/api/v1/cloudron/reboot', cloudronScope, routes.cloudron.reboot); router.get ('/api/v1/cloudron/graphs', cloudronScope, routes.graphs.getGraphs); From 62b586e8dd4b6fc5a086fe96f2429b742c8e3f83 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Tue, 7 Jun 2016 20:57:39 -0700 Subject: [PATCH 5/8] fix require path --- src/routes/cloudron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 68f56c864..3d6ce376c 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -23,7 +23,7 @@ var assert = require('assert'), progress = require('../progress.js'), mailer = require('../mailer.js'), superagent = require('superagent'), - updateChecker = require('./updatechecker.js'); + updateChecker = require('../updatechecker.js'); function auditSource(req) { var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null; From fbb8a842c1a1faf335b1529bbc1750e0f411a3ba Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Wed, 8 Jun 2016 00:07:41 -0700 Subject: [PATCH 6/8] do not require password --- src/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.js b/src/server.js index 4e2323692..b9bbaee24 100644 --- a/src/server.js +++ b/src/server.js @@ -94,7 +94,7 @@ function initializeExpressSync() { // cloudron routes router.get ('/api/v1/cloudron/config', cloudronScope, routes.cloudron.getConfig); router.post('/api/v1/cloudron/update', cloudronScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.cloudron.update); - router.post('/api/v1/cloudron/check_for_updates', cloudronScope, routes.user.requireAdmin, routes.user.verifyPassword, routes.cloudron.checkForUpdates); + router.post('/api/v1/cloudron/check_for_updates', cloudronScope, routes.user.requireAdmin, routes.cloudron.checkForUpdates); router.post('/api/v1/cloudron/reboot', cloudronScope, routes.cloudron.reboot); router.get ('/api/v1/cloudron/graphs', cloudronScope, routes.graphs.getGraphs); From f9db24e162fd2e2968ccdacd03e7519dc06177ba Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Wed, 8 Jun 2016 08:44:58 -0700 Subject: [PATCH 7/8] Fix autoupdate detection logic We should be comparing existing manifest ports with new manifest ports. The user chosen bindings itself doesn't matter. --- src/apps.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/apps.js b/src/apps.js index 7fa213ce5..e104aa16e 100644 --- a/src/apps.js +++ b/src/apps.js @@ -843,18 +843,19 @@ function updateApps(updateInfo, auditSource, callback) { // updateInfo is { appI assert.strictEqual(typeof callback, 'function'); function canAutoupdateApp(app, newManifest) { - var tcpPorts = newManifest.tcpPorts || { }; + var newTcpPorts = newManifest.newTcpPorts || { }; + var oldTcpPorts = app.manifest.tcpPorts || { }; var portBindings = app.portBindings; // this is never null - if (Object.keys(tcpPorts).length === 0 && Object.keys(portBindings).length === 0) return null; - if (Object.keys(tcpPorts).length === 0) return new Error('tcpPorts is now empty but portBindings is not'); - if (Object.keys(portBindings).length === 0) return new Error('portBindings is now empty but tcpPorts is not'); - - for (var env in tcpPorts) { - if (!(env in portBindings)) return new Error(env + ' is required from user'); + for (var env in newTcpPorts) { + if (!(env in oldTcpPorts)) return new Error(env + ' is required from user'); } - // it's fine if one or more keys got removed + for (env in portBindings) { + if (!(env in newTcpPorts)) return new Error(env + ' was in use but new update removes it'); + } + + // it's fine if one or more (unused) keys got removed return null; } From 0c9d331f4708388cc4e2b0248d75f98e98d4e34a Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Wed, 8 Jun 2016 08:46:18 -0700 Subject: [PATCH 8/8] more changes --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index f3dee083b..2c07854d0 100644 --- a/CHANGES +++ b/CHANGES @@ -525,4 +525,5 @@ - Enable Developer mode by default for new Cloudrons - Reverse proxy fixes for apps exposing a WebDav server - Allow admins to optionall set the username and displayName on user creation +- Fix app autoupdate logic to detect if one or more in-use port bindings was removed