Merge pull request #3571 from serverless/rollback-function-support

Add rollback function support
This commit is contained in:
Eslam λ Hefnawy 2017-05-17 19:44:45 +07:00 committed by GitHub
commit 3bb686e4e3
13 changed files with 3958 additions and 226 deletions

View File

@ -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>

View File

@ -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
```

View 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`.

View File

@ -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",

View File

@ -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;

View File

@ -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);
});
});
});

View 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;

View 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);
});
});
});
});

View File

@ -67,6 +67,14 @@ class Deploy {
lifecycleEvents: [
'log',
],
commands: {
functions: {
usage: 'List all the deployed functions and their versions',
lifecycleEvents: [
'log',
],
},
},
},
},
},

View File

@ -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',
},
},
},
},
},
};
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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",