mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
Merge pull request #3571 from serverless/rollback-function-support
Add rollback function support
This commit is contained in:
commit
3bb686e4e3
@ -65,6 +65,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
|
||||
<li><a href="./cli-reference/metrics.md">Metrics</a></li>
|
||||
<li><a href="./cli-reference/info.md">Info</a></li>
|
||||
<li><a href="./cli-reference/rollback.md">Rollback</a></li>
|
||||
<li><a href="./cli-reference/rollback-function.md">Rollback Function</a></li>
|
||||
<li><a href="./cli-reference/remove.md">Remove</a></li>
|
||||
<li><a href="./cli-reference/slstats.md">Serverless Stats</a></li>
|
||||
</ul>
|
||||
|
||||
@ -12,21 +12,27 @@ layout: Doc
|
||||
|
||||
# AWS - Deploy List
|
||||
|
||||
The `sls deploy list` command will list your recent deployments available in your S3 deployment bucket. It will use stage and region from the provider config and show the timestamp of each deployment so you can roll back if necessary using `sls rollback`.
|
||||
The `sls deploy list [functions]` command will list information about your deployments.
|
||||
|
||||
You can either see all available deployments in your S3 deployment bucket by running `serverless deploy list` or you can see the deployed functions by running `serverless deploy list functions`.
|
||||
|
||||
The displayed information is useful when rolling back a deployment or function via `serverless rollback`.
|
||||
|
||||
## Options
|
||||
|
||||
- `--stage` or `-s` The stage in your service that you want to deploy to.
|
||||
- `--region` or `-r` The region in that stage that you want to deploy to.
|
||||
|
||||
## Artifacts
|
||||
|
||||
After the `serverless deploy` command runs all created deployment artifacts are placed in the `.serverless` folder of the service.
|
||||
|
||||
## Examples
|
||||
|
||||
### List existing deploys
|
||||
|
||||
```bash
|
||||
serverless deploy list --stage dev --region us-east-1
|
||||
serverless deploy list
|
||||
```
|
||||
|
||||
### List deployed functions and their versions
|
||||
|
||||
```bash
|
||||
serverless deploy list functions
|
||||
```
|
||||
|
||||
36
docs/providers/aws/cli-reference/rollback-function.md
Normal file
36
docs/providers/aws/cli-reference/rollback-function.md
Normal file
@ -0,0 +1,36 @@
|
||||
<!--
|
||||
title: Serverless Rollback Function CLI Command
|
||||
menuText: rollback function
|
||||
menuOrder: 16
|
||||
description: Rollback a function to a specific version
|
||||
layout: Doc
|
||||
-->
|
||||
|
||||
<!-- DOCS-SITE-LINK:START automatically generated -->
|
||||
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/rollback-function)
|
||||
<!-- DOCS-SITE-LINK:END -->
|
||||
|
||||
|
||||
# AWS - Rollback Function
|
||||
|
||||
Rollback a function service to a specific version.
|
||||
|
||||
```bash
|
||||
serverless rollback function --name <name> --version <version>
|
||||
```
|
||||
|
||||
**Note:** You can only rollback a function which was previously deployed through `serverless deploy`. Functions are not versioned when running `serverless deploy function`.
|
||||
|
||||
## Options
|
||||
|
||||
- `--name` or `-n` The name of the function which should be rolled-back
|
||||
- `--version` or `-v` The version to which the function should be rolled back
|
||||
|
||||
## Examples
|
||||
|
||||
### AWS
|
||||
|
||||
At first you want to run `serverless deploy list functions` to see all the deployed functions of your service and their corresponding versions.
|
||||
After picking a function and the version you can run the `serverless rollback function` command to rollback the function.
|
||||
|
||||
E.g. `serverless rollback function -f my-function -v 23` rolls back the function `my-function` to the version `23`.
|
||||
@ -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 last 5 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).reverse();
|
||||
|
||||
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,171 @@ 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 last 5 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: 4711, $LATEST')).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,34 @@ 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,
|
||||
},
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
3413
npm-shrinkwrap.json
generated
3413
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@ -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