New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resource count and warning to info display #4822

Merged
merged 8 commits into from Jan 24, 2019
Copy path View file
@@ -12,7 +12,15 @@ module.exports = {
message += `${chalk.yellow('service:')} ${info.service}\n`; message += `${chalk.yellow('service:')} ${info.service}\n`;
message += `${chalk.yellow('stage:')} ${info.stage}\n`; message += `${chalk.yellow('stage:')} ${info.stage}\n`;
message += `${chalk.yellow('region:')} ${info.region}\n`; message += `${chalk.yellow('region:')} ${info.region}\n`;
message += `${chalk.yellow('stack:')} ${info.stack}`; message += `${chalk.yellow('stack:')} ${info.stack}\n`;
message += `${chalk.yellow('resources:')} ${info.resourceCount}`;

if (_.has(info, 'resourceCount') && info.resourceCount >= 150) {
message += `\n${chalk.red('WARNING:')}\n`;
message += ` You have ${info.resourceCount} resources in your service.\n`;
message += ' CloudFormation has a hard limit of 200 resources in a service.\n';
message += ' For advice on avoiding this limit, check out this link: http://bit.ly/2IiYB38.';
}


this.serverless.cli.consoleLog(message); this.serverless.cli.consoleLog(message);
return message; return message;
@@ -32,6 +32,7 @@ describe('#display()', () => {
endpoint: null, endpoint: null,
functions: [], functions: [],
apiKeys: [], apiKeys: [],
resourceCount: 10,
}, },
}; };
consoleLogStub = sinon.stub(serverless.cli, 'consoleLog').returns(); consoleLogStub = sinon.stub(serverless.cli, 'consoleLog').returns();
@@ -48,7 +49,29 @@ describe('#display()', () => {
expectedMessage += `${chalk.yellow('service:')} my-first\n`; expectedMessage += `${chalk.yellow('service:')} my-first\n`;
expectedMessage += `${chalk.yellow('stage:')} dev\n`; expectedMessage += `${chalk.yellow('stage:')} dev\n`;
expectedMessage += `${chalk.yellow('region:')} eu-west-1\n`; expectedMessage += `${chalk.yellow('region:')} eu-west-1\n`;
expectedMessage += `${chalk.yellow('stack:')} my-first-dev`; expectedMessage += `${chalk.yellow('stack:')} my-first-dev\n`;
expectedMessage += `${chalk.yellow('resources:')} 10`;

const message = awsInfo.displayServiceInfo();
expect(consoleLogStub.calledOnce).to.equal(true);
expect(message).to.equal(expectedMessage);
});

it('should display a warning if 150+ resources', () => {
let expectedMessage = '';

expectedMessage += `${chalk.yellow.underline('Service Information')}\n`;
expectedMessage += `${chalk.yellow('service:')} my-first\n`;
expectedMessage += `${chalk.yellow('stage:')} dev\n`;
expectedMessage += `${chalk.yellow('region:')} eu-west-1\n`;
expectedMessage += `${chalk.yellow('stack:')} my-first-dev\n`;
expectedMessage += `${chalk.yellow('resources:')} 150`;
expectedMessage += `\n${chalk.red('WARNING:')}\n`;
expectedMessage += ' You have 150 resources in your service.\n';
expectedMessage += ' CloudFormation has a hard limit of 200 resources in a service.\n';
expectedMessage += ' For advice on avoiding this limit, check out this link: http://bit.ly/2IiYB38.';

awsInfo.gatheredData.info.resourceCount = 150;


const message = awsInfo.displayServiceInfo(); const message = awsInfo.displayServiceInfo();
expect(consoleLogStub.calledOnce).to.equal(true); expect(consoleLogStub.calledOnce).to.equal(true);
@@ -0,0 +1,20 @@
'use strict';

const BbPromise = require('bluebird');
const _ = require('lodash');

module.exports = {
getResourceCount() {
const stackName = this.provider.naming.getStackName();

return this.provider.request('CloudFormation',
'listStackResources',
{ StackName: stackName })
.then(result => {
if (!_.isEmpty(result)) {
this.gatheredData.info.resourceCount = result.StackResourceSummaries.length;
}
return BbPromise.resolve();
});
},
};
@@ -0,0 +1,120 @@
'use strict';

const chai = require('chai');
chai.use(require('chai-as-promised'));
const expect = require('chai').expect;
const sinon = require('sinon');
const AwsInfo = require('./index');
const AwsProvider = require('../provider/awsProvider');
const Serverless = require('../../../Serverless');

describe('#getResourceCount()', () => {
let serverless;
let awsInfo;
let listStackResourcesStub;

beforeEach(() => {
const options = {
stage: 'dev',
region: 'us-east-1',
};
serverless = new Serverless();
serverless.setProvider('aws', new AwsProvider(serverless, options));
serverless.service.service = 'my-service';
serverless.service.functions = {
hello: {},
world: {},
};
awsInfo = new AwsInfo(serverless, options);

listStackResourcesStub = sinon.stub(awsInfo.provider, 'request');
});

afterEach(() => {
awsInfo.provider.request.restore();
});

it('attach resourceCount to this.gatheredData after listStackResources call', () => {
const listStackResourcesResponse = {
ResponseMetadata: { RequestId: '81386aed-258b-11e8-b3e8-a937105b7db3' },
StackResourceSummaries:
[{ LogicalResourceId: 'ApiGatewayDeployment1520814106863',
PhysicalResourceId: 'eoa2a2',
ResourceType: 'AWS::ApiGateway::Deployment',
LastUpdatedTimestamp: '2018-03-12T00:22:40.680Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'ApiGatewayMethodHelloGet',
PhysicalResourceId: 'hello-ApiGa-11R27BUE48W38',
ResourceType: 'AWS::ApiGateway::Method',
LastUpdatedTimestamp: '2018-03-12T00:22:37.478Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'ApiGatewayResourceHello',
PhysicalResourceId: 'az5f7l',
ResourceType: 'AWS::ApiGateway::Resource',
LastUpdatedTimestamp: '2018-03-12T00:22:22.916Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'ApiGatewayRestApi',
PhysicalResourceId: 'n1uk4p7kl0',
ResourceType: 'AWS::ApiGateway::RestApi',
LastUpdatedTimestamp: '2018-03-12T00:22:19.768Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'HelloLambdaFunction',
PhysicalResourceId: 'hello-world-2-dev-hello',
ResourceType: 'AWS::Lambda::Function',
LastUpdatedTimestamp: '2018-03-12T00:22:34.095Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'HelloLambdaPermissionApiGateway',
PhysicalResourceId: 'hello-world-2-dev-HelloLambdaPermissionApiGateway-18KKZXJG1DPF5',
ResourceType: 'AWS::Lambda::Permission',
LastUpdatedTimestamp: '2018-03-12T00:22:46.950Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'HelloLambdaVersiongZDaMtQjEhvXacHdpTLnQ61zDCdI2IWVYCbuE50pj8',
PhysicalResourceId: 'arn:aws:lambda:us-east-1:111111111:function:hello-world-2-dev-hello:2',
ResourceType: 'AWS::Lambda::Version',
LastUpdatedTimestamp: '2018-03-12T00:22:37.256Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'HelloLogGroup',
PhysicalResourceId: '/aws/lambda/hello-world-2-dev-hello',
ResourceType: 'AWS::Logs::LogGroup',
LastUpdatedTimestamp: '2018-03-12T00:22:20.095Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'IamRoleLambdaExecution',
PhysicalResourceId: 'hello-world-2-dev-us-east-1-lambdaRole',
ResourceType: 'AWS::IAM::Role',
LastUpdatedTimestamp: '2018-03-12T00:22:30.995Z',
ResourceStatus: 'CREATE_COMPLETE' },
{ LogicalResourceId: 'ServerlessDeploymentBucket',
PhysicalResourceId: 'hello-world-2-dev-serverlessdeploymentbucket-1e3l68m8zaz7i',
ResourceType: 'AWS::S3::Bucket',
LastUpdatedTimestamp: '2018-03-12T00:22:11.380Z',
ResourceStatus: 'CREATE_COMPLETE' }],
};

listStackResourcesStub.resolves(listStackResourcesResponse);

awsInfo.gatheredData = {
info: {
functions: [],
endpoint: '',
service: '',
stage: '',
region: '',
stack: '',
},
outputs: [],
};

expect(awsInfo.getResourceCount()).to.be.fulfilled.then(() => {
expect(listStackResourcesStub.calledOnce).to.equal(true);
expect(listStackResourcesStub.calledWithExactly(
'CloudFormation',
'listStackResources',
{
StackName: awsInfo.provider.naming.getStackName(),
}
)).to.equal(true);

expect(awsInfo.gatheredData.info.resourceCount).to.equal(10);
});
});
});
Copy path View file
@@ -3,6 +3,7 @@
const BbPromise = require('bluebird'); const BbPromise = require('bluebird');
const validate = require('../lib/validate'); const validate = require('../lib/validate');
const getStackInfo = require('./getStackInfo'); const getStackInfo = require('./getStackInfo');
const getResourceCount = require('./getResourceCount');
const getApiKeyValues = require('./getApiKeyValues'); const getApiKeyValues = require('./getApiKeyValues');
const display = require('./display'); const display = require('./display');


@@ -15,6 +16,7 @@ class AwsInfo {
this, this,
validate, validate,
getStackInfo, getStackInfo,
getResourceCount,
getApiKeyValues, getApiKeyValues,
display display
); );
@@ -54,6 +56,7 @@ class AwsInfo {


'aws:info:gatherData': () => BbPromise.bind(this) 'aws:info:gatherData': () => BbPromise.bind(this)
.then(this.getStackInfo) .then(this.getStackInfo)
.then(this.getResourceCount)
.then(this.getApiKeyValues), .then(this.getApiKeyValues),


'aws:info:displayServiceInfo': () => BbPromise.bind(this) 'aws:info:displayServiceInfo': () => BbPromise.bind(this)
@@ -11,6 +11,7 @@ describe('AwsInfo', () => {
let awsInfo; let awsInfo;
let validateStub; let validateStub;
let getStackInfoStub; let getStackInfoStub;
let getResourceCountStub;
let getApiKeyValuesStub; let getApiKeyValuesStub;
let displayServiceInfoStub; let displayServiceInfoStub;
let displayApiKeysStub; let displayApiKeysStub;
@@ -36,6 +37,8 @@ describe('AwsInfo', () => {
.stub(awsInfo, 'validate').resolves(); .stub(awsInfo, 'validate').resolves();
getStackInfoStub = sinon getStackInfoStub = sinon
.stub(awsInfo, 'getStackInfo').resolves(); .stub(awsInfo, 'getStackInfo').resolves();
getResourceCountStub = sinon
.stub(awsInfo, 'getResourceCount').resolves();
getApiKeyValuesStub = sinon getApiKeyValuesStub = sinon
.stub(awsInfo, 'getApiKeyValues').resolves(); .stub(awsInfo, 'getApiKeyValues').resolves();
displayServiceInfoStub = sinon displayServiceInfoStub = sinon
@@ -53,6 +56,7 @@ describe('AwsInfo', () => {
afterEach(() => { afterEach(() => {
awsInfo.validate.restore(); awsInfo.validate.restore();
awsInfo.getStackInfo.restore(); awsInfo.getStackInfo.restore();
awsInfo.getResourceCount.restore();
awsInfo.getApiKeyValues.restore(); awsInfo.getApiKeyValues.restore();
awsInfo.displayServiceInfo.restore(); awsInfo.displayServiceInfo.restore();
awsInfo.displayApiKeys.restore(); awsInfo.displayApiKeys.restore();
@@ -77,6 +81,7 @@ describe('AwsInfo', () => {
awsInfo.hooks['info:info']().then(() => { awsInfo.hooks['info:info']().then(() => {
expect(validateStub.calledOnce).to.equal(true); expect(validateStub.calledOnce).to.equal(true);
expect(getStackInfoStub.calledAfter(validateStub)).to.equal(true); expect(getStackInfoStub.calledAfter(validateStub)).to.equal(true);
expect(getResourceCountStub.calledAfter(getStackInfoStub)).to.equal(true);
expect(displayServiceInfoStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayServiceInfoStub.calledAfter(getApiKeyValuesStub)).to.equal(true);
expect(displayApiKeysStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayApiKeysStub.calledAfter(getApiKeyValuesStub)).to.equal(true);
expect(displayEndpointsStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayEndpointsStub.calledAfter(getApiKeyValuesStub)).to.equal(true);
@@ -90,6 +95,7 @@ describe('AwsInfo', () => {
awsInfo.hooks['deploy:deploy']().then(() => { awsInfo.hooks['deploy:deploy']().then(() => {
expect(validateStub.calledOnce).to.equal(true); expect(validateStub.calledOnce).to.equal(true);
expect(getStackInfoStub.calledAfter(validateStub)).to.equal(true); expect(getStackInfoStub.calledAfter(validateStub)).to.equal(true);
expect(getResourceCountStub.calledAfter(getStackInfoStub)).to.equal(true);
expect(displayServiceInfoStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayServiceInfoStub.calledAfter(getApiKeyValuesStub)).to.equal(true);
expect(displayApiKeysStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayApiKeysStub.calledAfter(getApiKeyValuesStub)).to.equal(true);
expect(displayEndpointsStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayEndpointsStub.calledAfter(getApiKeyValuesStub)).to.equal(true);
ProTip! Use n and p to navigate between commits in a pull request.