Fix installation states
App operations can only be done in 'installed' or 'error' state. If some other operation is in progress, you have to cancel it first. This guarantees that the old app command got killed.
This commit is contained in:
+5
-28
@@ -475,7 +475,7 @@ function setHealth(appId, health, healthTime, callback) {
|
||||
|
||||
var values = { health, healthTime };
|
||||
|
||||
var constraints = 'AND runState NOT LIKE "pending_%" AND installationState = "installed"';
|
||||
var constraints = `AND runState NOT LIKE "pending_%" AND installationState = "${exports.ISTATE_INSTALLED}"`;
|
||||
|
||||
updateWithConstraints(appId, values, constraints, callback);
|
||||
}
|
||||
@@ -483,36 +483,13 @@ function setHealth(appId, health, healthTime, callback) {
|
||||
function setInstallationCommand(appId, installationState, values, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof installationState, 'string');
|
||||
|
||||
if (typeof values === 'function') {
|
||||
callback = values;
|
||||
values = { };
|
||||
} else {
|
||||
assert.strictEqual(typeof values, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
}
|
||||
assert.strictEqual(typeof values, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
values.installationState = installationState;
|
||||
values.errorMessage = '';
|
||||
|
||||
// Rules are:
|
||||
// uninstall is allowed in any state
|
||||
// force update is allowed in any state including pending_uninstall! (for better or worse)
|
||||
// restore is allowed from installed or error state or currently restoring
|
||||
// configure is allowed in installed state or currently configuring or in error state
|
||||
// update and backup are allowed only in installed state
|
||||
|
||||
if (installationState === exports.ISTATE_PENDING_UNINSTALL || installationState === exports.ISTATE_PENDING_FORCE_UPDATE) {
|
||||
updateWithConstraints(appId, values, '', callback);
|
||||
} else if (installationState === exports.ISTATE_PENDING_RESTORE) {
|
||||
updateWithConstraints(appId, values, 'AND (installationState = "installed" OR installationState = "error" OR installationState = "pending_restore")', callback);
|
||||
} else if (installationState === exports.ISTATE_PENDING_UPDATE || installationState === exports.ISTATE_PENDING_BACKUP) {
|
||||
updateWithConstraints(appId, values, 'AND installationState = "installed"', callback);
|
||||
} else if (installationState === exports.ISTATE_PENDING_CONFIGURE) {
|
||||
updateWithConstraints(appId, values, 'AND (installationState = "installed" OR installationState = "pending_configure" OR installationState = "error")', callback);
|
||||
} else {
|
||||
callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, 'invalid installationState'));
|
||||
}
|
||||
updateWithConstraints(appId, values, `AND (installationState = "${exports.ISTATE_INSTALLED}" OR installationState = "${exports.ISTATE_ERROR}")`, callback);
|
||||
}
|
||||
|
||||
function setRunCommand(appId, runState, callback) {
|
||||
@@ -521,7 +498,7 @@ function setRunCommand(appId, runState, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var values = { runState: runState };
|
||||
updateWithConstraints(appId, values, 'AND runState NOT LIKE "pending_%" AND installationState = "installed"', callback);
|
||||
updateWithConstraints(appId, values, `AND installationState = "${exports.ISTATE_INSTALLED}"`, callback);
|
||||
}
|
||||
|
||||
function getAppStoreIds(callback) {
|
||||
|
||||
+24
-8
@@ -750,6 +750,7 @@ function configure(appId, data, user, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
|
||||
let domain, location, portBindings, values = { };
|
||||
if ('location' in data && 'domain' in data) {
|
||||
@@ -896,6 +897,7 @@ function update(appId, data, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
|
||||
downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
|
||||
if (error) return callback(error);
|
||||
@@ -1029,6 +1031,7 @@ function restore(appId, data, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
|
||||
// for empty or null backupId, use existing manifest to mimic a reinstall
|
||||
var func = data.backupId ? backups.get.bind(null, data.backupId) : function (next) { return next(null, { manifest: app.manifest }); };
|
||||
@@ -1186,6 +1189,7 @@ function uninstall(appId, auditSource, callback) {
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED && app.installationState !== appdb.ISTATE_ERROR) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
|
||||
appstore.unpurchaseApp(appId, { appstoreId: app.appStoreId, manifestId: app.manifest.id }, function (error) {
|
||||
if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND));
|
||||
@@ -1216,11 +1220,17 @@ function start(appId, callback) {
|
||||
|
||||
debug('Will start app with id:%s', appId);
|
||||
|
||||
appdb.setRunCommand(appId, appdb.RSTATE_PENDING_START, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.runState !== appdb.RSTATE_STOPPED) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
|
||||
scheduleTask(appId, {}, callback);
|
||||
appdb.setRunCommand(appId, appdb.RSTATE_PENDING_START, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
scheduleTask(appId, {}, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1230,11 +1240,17 @@ function stop(appId, callback) {
|
||||
|
||||
debug('Will stop app with id:%s', appId);
|
||||
|
||||
appdb.setRunCommand(appId, appdb.RSTATE_PENDING_STOP, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
if (app.installationState !== appdb.ISTATE_INSTALLED) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
if (app.runState !== appdb.RSTATE_RUNNING) return callback(new AppsError(AppsError.BAD_STATE, `Not allowed in this app state : ${app.installationState}`));
|
||||
|
||||
scheduleTask(appId, {}, callback);
|
||||
appdb.setRunCommand(appId, appdb.RSTATE_PENDING_STOP, function (error) {
|
||||
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.BAD_STATE)); // might be a bad guess
|
||||
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
|
||||
|
||||
scheduleTask(appId, {}, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +276,7 @@ function uninstallApp(req, res, next) {
|
||||
|
||||
apps.uninstall(req.params.id, auditSource.fromRequest(req), function (error, result) {
|
||||
if (error && error.reason === AppsError.EXTERNAL_ERROR) return next(new HttpError(424, error));
|
||||
if (error && error.reason === AppsError.BAD_STATE) return next(new HttpError(409, error.message));
|
||||
if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app'));
|
||||
if (error) return next(new HttpError(500, error));
|
||||
|
||||
|
||||
@@ -228,6 +228,8 @@ function stopBox(done) {
|
||||
}
|
||||
|
||||
describe('App API', function () {
|
||||
let taskId = '';
|
||||
|
||||
before(startBox);
|
||||
after(stopBox);
|
||||
|
||||
@@ -424,6 +426,7 @@ describe('App API', function () {
|
||||
APP_ID = res.body.id;
|
||||
expect(fake2.isDone()).to.be.ok();
|
||||
expect(fake3.isDone()).to.be.ok();
|
||||
taskId = res.body.taskId;
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -500,6 +503,15 @@ describe('App API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('can stop the task', function (done) {
|
||||
superagent.post(SERVER_URL + '/api/v1/tasks/' + taskId + '/stop')
|
||||
.query({ access_token: token })
|
||||
.end(function (err, res) {
|
||||
expect(res.statusCode).to.equal(204);
|
||||
setTimeout(done, 4000); // wait for it to really die
|
||||
});
|
||||
});
|
||||
|
||||
it('can uninstall app', function (done) {
|
||||
var fake1 = nock(settings.apiServerOrigin()).get(function (uri) { return uri.indexOf('/api/v1/cloudronapps/') >= 0; }).reply(200, { });
|
||||
var fake2 = nock(settings.apiServerOrigin()).delete(function (uri) { return uri.indexOf('/api/v1/cloudronapps/') >= 0; }).reply(204, { });
|
||||
|
||||
Reference in New Issue
Block a user