Skip to content

Commit

Permalink
feat(update): add support for automatically rolling back failed updates
Browse files Browse the repository at this point in the history
  • Loading branch information
vikaspotluri123 authored and kirrg001 committed Aug 8, 2018
1 parent 2f3de69 commit 53e4ccf
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 1 deletion.
27 changes: 26 additions & 1 deletion lib/commands/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ class UpdateCommand extends Command {
return;
}

return this.ui.listr(tasks, context);
return this.ui.listr(tasks, context).catch(error =>
this.rollbackFromFail(error, context.version, argv['auto-rollback'])
);
});
});
}
Expand Down Expand Up @@ -195,6 +197,24 @@ class UpdateCommand extends Command {
});
}

rollbackFromFail(error, newVer, force = false) {
const oldVer = this.system.getInstance().cliConfig.get('previous-version');
const question = `Unable to upgrade Ghost from v${oldVer} to v${newVer}. Would you like to revert back to v${oldVer}?`;
const promise = force ? Promise.resolve(true) : this.ui.confirm(question, true);

this.ui.error(error);

return promise.then(answer => {
if (!answer) {
return Promise.resolve();
}

return this.run({
rollback: true
});
});
}

link(context) {
const symlinkSync = require('symlink-or-copy').sync;

Expand Down Expand Up @@ -232,6 +252,11 @@ UpdateCommand.options = {
describe: 'Limit update to Ghost 1.x releases',
type: 'boolean',
default: false
},
'auto-rollback': {
description: '[--no-auto-rollback] Enable/Disable automatically rolling back Ghost if updating fails',
type: 'boolean',
default: false
}
};
UpdateCommand.checkVersion = true;
Expand Down
95 changes: 95 additions & 0 deletions test/unit/commands/update-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,44 @@ describe('Unit: Commands > Update', function () {
expect(stopStub.called).to.be.false;
});
});

it('attempts to auto-rollback on error', function () {
const UpdateCommand = require(modulePath);
const config = configStub();
const errObj = new Error('should_rollback')
config.get.withArgs('cli-version').returns('1.0.0');
config.get.withArgs('active-version').returns('1.1.0');
config.get.withArgs('previous-version').returns('1.0.0');

const ui = {
log: sinon.stub(),
listr: sinon.stub().rejects(errObj),
run: sinon.stub().callsFake(fn => fn())
};
const system = {getInstance: sinon.stub()};
class TestInstance extends Instance {
get cliConfig() { return config; }
}
const fakeInstance = sinon.stub(new TestInstance(ui, system, '/var/www/ghost'));
system.getInstance.returns(fakeInstance);
fakeInstance.running.resolves(false);

const cmdInstance = new UpdateCommand(ui, system);
const rollback = cmdInstance.rollbackFromFail = sinon.stub().rejects(new Error('rollback_successful'));
cmdInstance.runCommand = sinon.stub().resolves(true);
cmdInstance.version = sinon.stub().callsFake(context => {
context.version = '1.1.1';
return true;
});

return cmdInstance.run({}).then(() => {
expect(false, 'Promise should have rejected').to.be.true;
}).catch(error => {
expect(error.message).to.equal('rollback_successful');
expect(rollback.calledOnce).to.be.true;
expect(rollback.calledWithExactly(errObj, '1.1.1', undefined)).to.be.true;
});
});
});

describe('downloadAndUpdate task', function () {
Expand Down Expand Up @@ -796,6 +834,63 @@ describe('Unit: Commands > Update', function () {
});
});

describe('rollbackFromFail', function () {
let ui, system;

beforeEach(function () {
const cliConfig = {get: () => '1.0.0'}
ui = {
confirm: sinon.stub(),
error: () => true
};

system = {
getInstance() {
return {cliConfig};
}
};
});

it('Asks to rollback by default', function () {
const UpdateCommand = require(modulePath);
const expectedQuestion = 'Unable to upgrade Ghost from v1.0.0 to v1.1.1. Would you like to revert back to v1.0.0?'
const update = new UpdateCommand(ui, system, '/var/www/ghost');
ui.confirm.rejects(new Error('CONFIRMED'));

return update.rollbackFromFail(false, '1.1.1').then(() => {
expect(false, 'Promise should have rejected').to.be.true;
}).catch(error => {
expect(error.message).to.equal('CONFIRMED');
expect(ui.confirm.calledOnce).to.be.true;
expect(ui.confirm.calledWithExactly(expectedQuestion, true)).to.be.true;
});
});

it('Listens to the user', function () {
const UpdateCommand = require(modulePath);
const update = new UpdateCommand(ui, system, '/var/www/ghost');

ui.confirm.resolves(false);
update.run = sinon.stub().resolves();

return update.rollbackFromFail(false, '1.1.1').then(() => {
expect(ui.confirm.calledOnce).to.be.true;
expect(update.run.called).to.be.false;
});
});

it('Re-runs `run` using rollback', function () {
const UpdateCommand = require(modulePath);
const update = new UpdateCommand(ui, system, '/var/www/ghost');

update.run = sinon.stub().resolves();
return update.rollbackFromFail(false, '1.1.1', true).then(() => {
expect(update.run.calledOnce).to.be.true;
expect(update.run.calledWithExactly({rollback: true})).to.be.true;
});
});
});

describe('link', function () {
const UpdateCommand = require(modulePath);

Expand Down

0 comments on commit 53e4ccf

Please sign in to comment.