mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
Add rollback function support
This commit is contained in:
parent
d9bc8efb3f
commit
69f945650c
@ -23,6 +23,7 @@
|
||||
"./aws/metrics/awsMetrics.js",
|
||||
"./aws/remove/index.js",
|
||||
"./aws/rollback/index.js",
|
||||
"./aws/rollbackFunction/index.js",
|
||||
"./aws/package/compile/functions/index.js",
|
||||
"./aws/package/compile/events/schedule/index.js",
|
||||
"./aws/package/compile/events/s3/index.js",
|
||||
|
||||
@ -19,10 +19,15 @@ class AwsDeployList {
|
||||
|
||||
this.hooks = {
|
||||
'before:deploy:list:log': () => BbPromise.bind(this)
|
||||
.then(this.validate),
|
||||
.then(this.validate),
|
||||
'before:deploy:list:functions:log': () => BbPromise.bind(this)
|
||||
.then(this.validate),
|
||||
|
||||
'deploy:list:log': () => BbPromise.bind(this)
|
||||
.then(this.setBucketName)
|
||||
.then(this.listDeployments),
|
||||
'deploy:list:functions:log': () => BbPromise.bind(this)
|
||||
.then(this.listFunctions),
|
||||
};
|
||||
}
|
||||
|
||||
@ -61,6 +66,77 @@ class AwsDeployList {
|
||||
return BbPromise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// list all functions and their versions
|
||||
listFunctions() {
|
||||
return BbPromise.resolve().bind(this)
|
||||
.then(this.getFunctions)
|
||||
.then(this.getFunctionVersions)
|
||||
.then(this.displayFunctions);
|
||||
}
|
||||
|
||||
getFunctions() {
|
||||
const params = {
|
||||
MaxItems: 200,
|
||||
};
|
||||
|
||||
return this.provider.request('Lambda',
|
||||
'listFunctions',
|
||||
params,
|
||||
this.options.stage,
|
||||
this.options.region)
|
||||
.then((result) => {
|
||||
const allFuncs = result.Functions;
|
||||
|
||||
const serviceName = `${this.serverless.service.service}-${this.options.stage}`;
|
||||
const regex = new RegExp(serviceName);
|
||||
const serviceFuncs = allFuncs.filter((func) => func.FunctionName.match(regex));
|
||||
|
||||
return BbPromise.resolve(serviceFuncs);
|
||||
});
|
||||
}
|
||||
|
||||
getFunctionVersions(funcs) {
|
||||
const requestPromises = [];
|
||||
|
||||
funcs.forEach((func) => {
|
||||
const params = {
|
||||
FunctionName: func.FunctionName,
|
||||
MaxItems: 5,
|
||||
};
|
||||
|
||||
const request = this.provider.request('Lambda',
|
||||
'listVersionsByFunction',
|
||||
params,
|
||||
this.options.stage,
|
||||
this.options.region);
|
||||
|
||||
requestPromises.push(request);
|
||||
});
|
||||
|
||||
return BbPromise.all(requestPromises);
|
||||
}
|
||||
|
||||
displayFunctions(funcs) {
|
||||
this.serverless.cli.log('Listing functions and their versions:');
|
||||
this.serverless.cli.log('-------------');
|
||||
|
||||
funcs.forEach((func) => {
|
||||
let message = '';
|
||||
|
||||
let name = func.Versions[0].FunctionName;
|
||||
name = name.replace(`${this.serverless.service.service}-`, '');
|
||||
name = name.replace(`${this.options.stage}-`, '');
|
||||
|
||||
message += `${name}: `;
|
||||
const versions = func.Versions.map((funcEntry) => funcEntry.Version);
|
||||
|
||||
message += versions.join(', ');
|
||||
this.serverless.cli.log(message);
|
||||
});
|
||||
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AwsDeployList;
|
||||
|
||||
@ -8,7 +8,7 @@ const Serverless = require('../../../Serverless');
|
||||
|
||||
describe('AwsDeployList', () => {
|
||||
let serverless;
|
||||
let awsDeploy;
|
||||
let awsDeployList;
|
||||
let s3Key;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -20,9 +20,9 @@ describe('AwsDeployList', () => {
|
||||
region: 'us-east-1',
|
||||
};
|
||||
s3Key = `serverless/${serverless.service.service}/${options.stage}`;
|
||||
awsDeploy = new AwsDeployList(serverless, options);
|
||||
awsDeploy.bucketName = 'deployment-bucket';
|
||||
awsDeploy.serverless.cli = {
|
||||
awsDeployList = new AwsDeployList(serverless, options);
|
||||
awsDeployList.bucketName = 'deployment-bucket';
|
||||
awsDeployList.serverless.cli = {
|
||||
log: sinon.spy(),
|
||||
};
|
||||
});
|
||||
@ -33,29 +33,29 @@ describe('AwsDeployList', () => {
|
||||
Contents: [],
|
||||
};
|
||||
const listObjectsStub = sinon
|
||||
.stub(awsDeploy.provider, 'request').resolves(s3Response);
|
||||
.stub(awsDeployList.provider, 'request').resolves(s3Response);
|
||||
|
||||
return awsDeploy.listDeployments().then(() => {
|
||||
return awsDeployList.listDeployments().then(() => {
|
||||
expect(listObjectsStub.calledOnce).to.be.equal(true);
|
||||
expect(listObjectsStub.calledWithExactly(
|
||||
'S3',
|
||||
'listObjectsV2',
|
||||
{
|
||||
Bucket: awsDeploy.bucketName,
|
||||
Bucket: awsDeployList.bucketName,
|
||||
Prefix: `${s3Key}`,
|
||||
},
|
||||
awsDeploy.options.stage,
|
||||
awsDeploy.options.region
|
||||
awsDeployList.options.stage,
|
||||
awsDeployList.options.region
|
||||
)).to.be.equal(true);
|
||||
const infoText = 'Couldn\'t find any existing deployments.';
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true);
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true);
|
||||
const verifyText = 'Please verify that stage and region are correct.';
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(verifyText)).to.be.equal(true);
|
||||
awsDeploy.provider.request.restore();
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(verifyText)).to.be.equal(true);
|
||||
awsDeployList.provider.request.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should all available deployments', () => {
|
||||
it('should display all available deployments', () => {
|
||||
const s3Response = {
|
||||
Contents: [
|
||||
{ Key: `${s3Key}/113304333331-2016-08-18T13:40:06/artifact.zip` },
|
||||
@ -66,31 +66,170 @@ describe('AwsDeployList', () => {
|
||||
};
|
||||
|
||||
const listObjectsStub = sinon
|
||||
.stub(awsDeploy.provider, 'request').resolves(s3Response);
|
||||
.stub(awsDeployList.provider, 'request').resolves(s3Response);
|
||||
|
||||
return awsDeploy.listDeployments().then(() => {
|
||||
return awsDeployList.listDeployments().then(() => {
|
||||
expect(listObjectsStub.calledOnce).to.be.equal(true);
|
||||
expect(listObjectsStub.calledWithExactly(
|
||||
'S3',
|
||||
'listObjectsV2',
|
||||
{
|
||||
Bucket: awsDeploy.bucketName,
|
||||
Bucket: awsDeployList.bucketName,
|
||||
Prefix: `${s3Key}`,
|
||||
},
|
||||
awsDeploy.options.stage,
|
||||
awsDeploy.options.region
|
||||
awsDeployList.options.stage,
|
||||
awsDeployList.options.region
|
||||
)).to.be.equal(true);
|
||||
const infoText = 'Listing deployments:';
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true);
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true);
|
||||
const timestampOne = 'Timestamp: 113304333331';
|
||||
const datetimeOne = 'Datetime: 2016-08-18T13:40:06';
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(timestampOne)).to.be.equal(true);
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(datetimeOne)).to.be.equal(true);
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(timestampOne)).to.be.equal(true);
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(datetimeOne)).to.be.equal(true);
|
||||
const timestampTow = 'Timestamp: 903940390431';
|
||||
const datetimeTwo = 'Datetime: 2016-08-18T23:42:08';
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(timestampTow)).to.be.equal(true);
|
||||
expect(awsDeploy.serverless.cli.log.calledWithExactly(datetimeTwo)).to.be.equal(true);
|
||||
awsDeploy.provider.request.restore();
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(timestampTow)).to.be.equal(true);
|
||||
expect(awsDeployList.serverless.cli.log.calledWithExactly(datetimeTwo)).to.be.equal(true);
|
||||
awsDeployList.provider.request.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#listFunctions()', () => {
|
||||
let getFunctionsStub;
|
||||
let getFunctionVersionsStub;
|
||||
let displayFunctionsStub;
|
||||
|
||||
beforeEach(() => {
|
||||
getFunctionsStub = sinon.stub(awsDeployList, 'getFunctions').resolves();
|
||||
getFunctionVersionsStub = sinon.stub(awsDeployList, 'getFunctionVersions').resolves();
|
||||
displayFunctionsStub = sinon.stub(awsDeployList, 'displayFunctions').resolves();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsDeployList.getFunctions.restore();
|
||||
awsDeployList.getFunctionVersions.restore();
|
||||
awsDeployList.displayFunctions.restore();
|
||||
});
|
||||
|
||||
it('should run promise chain in order', () => awsDeployList
|
||||
.listFunctions().then(() => {
|
||||
expect(getFunctionsStub.calledOnce).to.equal(true);
|
||||
expect(getFunctionVersionsStub.calledAfter(getFunctionsStub)).to.equal(true);
|
||||
expect(displayFunctionsStub.calledAfter(getFunctionVersionsStub)).to.equal(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('#getFunctions()', () => {
|
||||
let listFunctionsStub;
|
||||
|
||||
beforeEach(() => {
|
||||
listFunctionsStub = sinon
|
||||
.stub(awsDeployList.provider, 'request')
|
||||
.resolves({
|
||||
Functions: [
|
||||
{ FunctionName: 'listDeployments-dev-func1' },
|
||||
{ FunctionName: 'listDeployments-dev-func2' },
|
||||
{ FunctionName: 'unrelatedService-dev-func3' },
|
||||
{ FunctionName: 'unrelatedService-dev-func4' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsDeployList.provider.request.restore();
|
||||
});
|
||||
|
||||
it('should get all functions and filter out the service related ones', () => {
|
||||
const expectedResult = [
|
||||
{ FunctionName: 'listDeployments-dev-func1' },
|
||||
{ FunctionName: 'listDeployments-dev-func2' },
|
||||
];
|
||||
|
||||
return awsDeployList.getFunctions().then((result) => {
|
||||
expect(listFunctionsStub.calledOnce).to.equal(true);
|
||||
expect(listFunctionsStub.calledWithExactly(
|
||||
'Lambda',
|
||||
'listFunctions',
|
||||
{
|
||||
MaxItems: 200,
|
||||
},
|
||||
awsDeployList.options.stage,
|
||||
awsDeployList.options.region
|
||||
)).to.equal(true);
|
||||
expect(result).to.deep.equal(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getFunctionVersions()', () => {
|
||||
let listVersionsByFunctionStub;
|
||||
|
||||
beforeEach(() => {
|
||||
listVersionsByFunctionStub = sinon
|
||||
.stub(awsDeployList.provider, 'request')
|
||||
.resolves({
|
||||
Versions: [
|
||||
{ FunctionName: 'listDeployments-dev-func', Version: '$LATEST' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsDeployList.provider.request.restore();
|
||||
});
|
||||
|
||||
it('should return the versions for the provided functions', () => {
|
||||
const funcs = [
|
||||
{ FunctionName: 'listDeployments-dev-func1' },
|
||||
{ FunctionName: 'listDeployments-dev-func2' },
|
||||
];
|
||||
|
||||
return awsDeployList.getFunctionVersions(funcs).then((result) => {
|
||||
const expectedResult = [
|
||||
{
|
||||
Versions: [
|
||||
{ FunctionName: 'listDeployments-dev-func', Version: '$LATEST' },
|
||||
],
|
||||
},
|
||||
{
|
||||
Versions: [
|
||||
{ FunctionName: 'listDeployments-dev-func', Version: '$LATEST' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
expect(listVersionsByFunctionStub.calledTwice).to.equal(true);
|
||||
expect(result).to.deep.equal(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#displayFunctions()', () => {
|
||||
const funcs = [
|
||||
{
|
||||
Versions: [
|
||||
{ FunctionName: 'listDeployments-dev-func-1', Version: '$LATEST' },
|
||||
],
|
||||
},
|
||||
{
|
||||
Versions: [
|
||||
{ FunctionName: 'listDeployments-dev-func-2', Version: '$LATEST' },
|
||||
{ FunctionName: 'listDeployments-dev-func-2', Version: '4711' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
it('should display all the functions in the service together with their versions', () => {
|
||||
const log = awsDeployList.serverless.cli.log;
|
||||
|
||||
return awsDeployList.displayFunctions(funcs).then(() => {
|
||||
expect(log.calledWithExactly('Listing functions and their versions:')).to.be.equal(true);
|
||||
expect(log.calledWithExactly('-------------')).to.be.equal(true);
|
||||
|
||||
expect(log.calledWithExactly('func-1: $LATEST')).to.be.equal(true);
|
||||
expect(log.calledWithExactly('func-2: $LATEST, 4711')).to.be.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
123
lib/plugins/aws/rollbackFunction/index.js
Normal file
123
lib/plugins/aws/rollbackFunction/index.js
Normal file
@ -0,0 +1,123 @@
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const validate = require('../lib/validate');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
class AwsRollbackFunction {
|
||||
constructor(serverless, options) {
|
||||
this.serverless = serverless;
|
||||
this.options = options || {};
|
||||
this.provider = this.serverless.getProvider('aws');
|
||||
|
||||
Object.assign(this, validate);
|
||||
|
||||
this.commands = {
|
||||
rollback: {
|
||||
commands: {
|
||||
function: {
|
||||
usage: 'Rollback the function to a specific version',
|
||||
lifecycleEvents: [
|
||||
'rollback',
|
||||
],
|
||||
options: {
|
||||
function: {
|
||||
usage: 'Name of the function',
|
||||
shortcut: 'f',
|
||||
required: true,
|
||||
},
|
||||
version: {
|
||||
usage: 'Version of the function',
|
||||
shortcut: 'v',
|
||||
required: true,
|
||||
},
|
||||
stage: {
|
||||
usage: 'Stage of the function',
|
||||
shortcut: 's',
|
||||
},
|
||||
region: {
|
||||
usage: 'Region of the function',
|
||||
shortcut: 'r',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
this.hooks = {
|
||||
'rollback:function:rollback': () => BbPromise.bind(this)
|
||||
.then(this.validate)
|
||||
.then(this.getFunctionToBeRestored)
|
||||
.then(this.fetchFunctionCode)
|
||||
.then(this.restoreFunction),
|
||||
};
|
||||
}
|
||||
|
||||
getFunctionToBeRestored() {
|
||||
const funcName = this.options.function;
|
||||
let funcVersion = this.options.version;
|
||||
|
||||
// versions need to be string so that AWS understands it
|
||||
funcVersion = String(this.options.version);
|
||||
|
||||
this.serverless.cli.log(`Rolling back function "${funcName}" to version "${funcVersion}"...`);
|
||||
|
||||
const funcObj = this.serverless.service.getFunction(funcName);
|
||||
|
||||
const params = {
|
||||
FunctionName: funcObj.name,
|
||||
Qualifier: funcVersion,
|
||||
};
|
||||
|
||||
return this.provider.request(
|
||||
'Lambda',
|
||||
'getFunction',
|
||||
params,
|
||||
this.options.stage, this.options.region
|
||||
)
|
||||
.then((func) => func)
|
||||
.catch((error) => {
|
||||
if (error.message.match(/not found/)) {
|
||||
const errorMessage = [
|
||||
`Function "${funcName}" with version "${funcVersion}" not found.`,
|
||||
` Please check if you've deployed "${funcName}"`,
|
||||
` and version "${funcVersion}" is available for this function.`,
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
throw new Error(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
fetchFunctionCode(func) {
|
||||
const codeUrl = func.Code.Location;
|
||||
|
||||
return fetch(codeUrl).then((response) => response.buffer());
|
||||
}
|
||||
|
||||
restoreFunction(zipBuffer) {
|
||||
const funcName = this.options.function;
|
||||
|
||||
this.serverless.cli.log('Restoring function...');
|
||||
|
||||
const funcObj = this.serverless.service.getFunction(funcName);
|
||||
|
||||
const params = {
|
||||
FunctionName: funcObj.name,
|
||||
ZipFile: zipBuffer,
|
||||
};
|
||||
|
||||
return this.provider.request(
|
||||
'Lambda',
|
||||
'updateFunctionCode',
|
||||
params,
|
||||
this.options.stage, this.options.region
|
||||
).then(() => {
|
||||
this.serverless.cli.log(`Successfully rolled back function: "${this.options.function}"`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AwsRollbackFunction;
|
||||
244
lib/plugins/aws/rollbackFunction/index.test.js
Normal file
244
lib/plugins/aws/rollbackFunction/index.test.js
Normal file
@ -0,0 +1,244 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const sinon = require('sinon');
|
||||
const Serverless = require('../../../Serverless');
|
||||
const AwsProvider = require('../provider/awsProvider');
|
||||
const CLI = require('../../../classes/CLI');
|
||||
const proxyquire = require('proxyquire');
|
||||
|
||||
describe('AwsRollbackFunction', () => {
|
||||
let serverless;
|
||||
let awsRollbackFunction;
|
||||
let consoleLogStub;
|
||||
let fetchStub;
|
||||
let AwsRollbackFunction;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchStub = sinon.stub().resolves({ buffer: () => {} });
|
||||
AwsRollbackFunction = proxyquire('./index.js', {
|
||||
'node-fetch': fetchStub,
|
||||
});
|
||||
serverless = new Serverless();
|
||||
serverless.servicePath = true;
|
||||
serverless.service.functions = {
|
||||
hello: {
|
||||
handler: true,
|
||||
name: 'service-dev-hello',
|
||||
},
|
||||
};
|
||||
const options = {
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
function: 'hello',
|
||||
};
|
||||
serverless.setProvider('aws', new AwsProvider(serverless));
|
||||
serverless.cli = new CLI(serverless);
|
||||
awsRollbackFunction = new AwsRollbackFunction(serverless, options);
|
||||
consoleLogStub = sinon.stub(serverless.cli, 'consoleLog').returns();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
serverless.cli.consoleLog.restore();
|
||||
});
|
||||
|
||||
describe('#constructor()', () => {
|
||||
let validateStub;
|
||||
let getFunctionToBeRestoredStub;
|
||||
let fetchFunctionCodeStub;
|
||||
let restoreFunctionStub;
|
||||
|
||||
beforeEach(() => {
|
||||
validateStub = sinon
|
||||
.stub(awsRollbackFunction, 'validate').resolves();
|
||||
getFunctionToBeRestoredStub = sinon
|
||||
.stub(awsRollbackFunction, 'getFunctionToBeRestored').resolves();
|
||||
fetchFunctionCodeStub = sinon
|
||||
.stub(awsRollbackFunction, 'fetchFunctionCode').resolves();
|
||||
restoreFunctionStub = sinon
|
||||
.stub(awsRollbackFunction, 'restoreFunction').resolves();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsRollbackFunction.validate.restore();
|
||||
awsRollbackFunction.getFunctionToBeRestored.restore();
|
||||
awsRollbackFunction.fetchFunctionCode.restore();
|
||||
awsRollbackFunction.restoreFunction.restore();
|
||||
});
|
||||
|
||||
it('should have hooks', () => expect(awsRollbackFunction.hooks).to.be.not.empty);
|
||||
|
||||
it('should have commands', () => expect(awsRollbackFunction.commands).to.be.not.empty);
|
||||
|
||||
it('should set the provider variable to an instance of AwsProvider', () =>
|
||||
expect(awsRollbackFunction.provider).to.be.instanceof(AwsProvider));
|
||||
|
||||
it('should set an empty options object if no options are given', () => {
|
||||
const awsRollbackFunctionWithEmptyOptions = new AwsRollbackFunction(serverless);
|
||||
expect(awsRollbackFunctionWithEmptyOptions.options).to.deep.equal({});
|
||||
});
|
||||
|
||||
it('should run promise chain in order', () => awsRollbackFunction
|
||||
.hooks['rollback:function:rollback']().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getFunctionToBeRestoredStub.calledAfter(validateStub)).to.equal(true);
|
||||
expect(fetchFunctionCodeStub.calledAfter(getFunctionToBeRestoredStub)).to.equal(true);
|
||||
expect(restoreFunctionStub.calledAfter(fetchFunctionCodeStub)).to.equal(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('#getFunctionToBeRestored()', () => {
|
||||
describe('when function and version can be found', () => {
|
||||
let getFunctionStub;
|
||||
|
||||
beforeEach(() => {
|
||||
getFunctionStub = sinon
|
||||
.stub(awsRollbackFunction.provider, 'request')
|
||||
.resolves({ function: 'hello' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsRollbackFunction.provider.request.restore();
|
||||
});
|
||||
|
||||
it('should return the requested function', () => {
|
||||
awsRollbackFunction.options.function = 'hello';
|
||||
awsRollbackFunction.options.version = '4711';
|
||||
|
||||
return awsRollbackFunction.getFunctionToBeRestored().then((result) => {
|
||||
expect(getFunctionStub.calledOnce).to.equal(true);
|
||||
expect(getFunctionStub.calledWithExactly(
|
||||
'Lambda',
|
||||
'getFunction',
|
||||
{
|
||||
FunctionName: 'service-dev-hello',
|
||||
Qualifier: '4711',
|
||||
},
|
||||
awsRollbackFunction.options.stage,
|
||||
awsRollbackFunction.options.region
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
expect(result).to.deep.equal({ function: 'hello' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when function or version could not be found', () => {
|
||||
let getFunctionStub;
|
||||
|
||||
beforeEach(() => {
|
||||
getFunctionStub = sinon
|
||||
.stub(awsRollbackFunction.provider, 'request')
|
||||
.rejects(new Error('function hello not found'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsRollbackFunction.provider.request.restore();
|
||||
});
|
||||
|
||||
it('should translate the error message to a custom error message', () => {
|
||||
awsRollbackFunction.options.function = 'hello';
|
||||
awsRollbackFunction.options.version = '4711';
|
||||
|
||||
return awsRollbackFunction.getFunctionToBeRestored().catch((error) => {
|
||||
expect(error.message.match(/Function "hello" with version "4711" not found/));
|
||||
expect(getFunctionStub.calledOnce).to.equal(true);
|
||||
expect(getFunctionStub.calledWithExactly(
|
||||
'Lambda',
|
||||
'getFunction',
|
||||
{
|
||||
FunctionName: 'service-dev-hello',
|
||||
Qualifier: '4711',
|
||||
},
|
||||
awsRollbackFunction.options.stage,
|
||||
awsRollbackFunction.options.region
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when other error occurred', () => {
|
||||
let getFunctionStub;
|
||||
|
||||
beforeEach(() => {
|
||||
getFunctionStub = sinon
|
||||
.stub(awsRollbackFunction.provider, 'request')
|
||||
.rejects(new Error('something went wrong'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsRollbackFunction.provider.request.restore();
|
||||
});
|
||||
|
||||
it('should re-throw the error without translating it to a custom error message', () => {
|
||||
awsRollbackFunction.options.function = 'hello';
|
||||
awsRollbackFunction.options.version = '4711';
|
||||
|
||||
return awsRollbackFunction.getFunctionToBeRestored().catch((error) => {
|
||||
expect(error.message.match(/something went wrong/));
|
||||
expect(getFunctionStub.calledOnce).to.equal(true);
|
||||
expect(getFunctionStub.calledWithExactly(
|
||||
'Lambda',
|
||||
'getFunction',
|
||||
{
|
||||
FunctionName: 'service-dev-hello',
|
||||
Qualifier: '4711',
|
||||
},
|
||||
awsRollbackFunction.options.stage,
|
||||
awsRollbackFunction.options.region
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fetchFunctionCode()', () => {
|
||||
it('should fetch the zip file content of the previously requested function', () => {
|
||||
const func = {
|
||||
Code: {
|
||||
Location: 'https://foo.com/bar',
|
||||
},
|
||||
};
|
||||
|
||||
return awsRollbackFunction.fetchFunctionCode(func).then(() => {
|
||||
expect(fetchStub.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#restoreFunction()', () => {
|
||||
let updateFunctionCodeStub;
|
||||
|
||||
beforeEach(() => {
|
||||
updateFunctionCodeStub = sinon
|
||||
.stub(awsRollbackFunction.provider, 'request').resolves();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsRollbackFunction.provider.request.restore();
|
||||
});
|
||||
|
||||
it('should restore the provided function', () => {
|
||||
awsRollbackFunction.options.function = 'hello';
|
||||
const zipBuffer = new Buffer('');
|
||||
|
||||
return awsRollbackFunction.restoreFunction(zipBuffer).then(() => {
|
||||
expect(updateFunctionCodeStub.calledOnce).to.equal(true);
|
||||
expect(updateFunctionCodeStub.calledWithExactly(
|
||||
'Lambda',
|
||||
'updateFunctionCode',
|
||||
{
|
||||
FunctionName: 'service-dev-hello',
|
||||
ZipFile: zipBuffer,
|
||||
},
|
||||
awsRollbackFunction.options.stage,
|
||||
awsRollbackFunction.options.region
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -67,6 +67,14 @@ class Deploy {
|
||||
lifecycleEvents: [
|
||||
'log',
|
||||
],
|
||||
commands: {
|
||||
functions: {
|
||||
usage: 'List all the deployed functions and their versions',
|
||||
lifecycleEvents: [
|
||||
'log',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -22,6 +22,29 @@ class Rollback {
|
||||
shortcut: 'v',
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
function: {
|
||||
usage: 'Rollback the function to the previous version',
|
||||
lifecycleEvents: [
|
||||
'rollback',
|
||||
],
|
||||
options: {
|
||||
function: {
|
||||
usage: 'Name of the function',
|
||||
shortcut: 'f',
|
||||
required: true,
|
||||
},
|
||||
stage: {
|
||||
usage: 'Stage of the function',
|
||||
shortcut: 's',
|
||||
},
|
||||
region: {
|
||||
usage: 'Region of the function',
|
||||
shortcut: 'r',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -14,21 +14,41 @@ describe('Rollback', () => {
|
||||
});
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should have the command rollback', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(rollback.commands.rollback).to.not.be.undefined;
|
||||
describe('when dealing with normal rollbacks', () => {
|
||||
it('should have the command "rollback"', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(rollback.commands.rollback).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('should have a lifecycle events initialize and rollback', () => {
|
||||
expect(rollback.commands.rollback.lifecycleEvents).to.deep.equal([
|
||||
'initialize',
|
||||
'rollback',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have a required option timestamp', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(rollback.commands.rollback.options.timestamp.required).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should have a lifecycle events initialize and rollback', () => {
|
||||
expect(rollback.commands.rollback.lifecycleEvents).to.deep.equal([
|
||||
'initialize',
|
||||
'rollback',
|
||||
]);
|
||||
});
|
||||
describe('when dealing with function rollbacks', () => {
|
||||
it('should have the command "rollback function"', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(rollback.commands.rollback.commands.function).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('should have a required option timestamp', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(rollback.commands.rollback.options.timestamp.required).to.be.true;
|
||||
it('should have a lifecycle event rollback', () => {
|
||||
expect(rollback.commands.rollback.commands.function.lifecycleEvents).to.deep.equal([
|
||||
'rollback',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have a required option function', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(rollback.commands.rollback.commands.function.options.function.required).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -99,7 +99,7 @@
|
||||
"lodash": "^4.13.1",
|
||||
"minimist": "^1.2.0",
|
||||
"moment": "^2.13.0",
|
||||
"node-fetch": "^1.5.3",
|
||||
"node-fetch": "^1.6.0",
|
||||
"replaceall": "^0.1.6",
|
||||
"resolve-from": "^2.0.0",
|
||||
"semver": "^5.0.3",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user