Merge pull request #1794 from nicka/feature/refactor-aws-cloudformation-endpoint-outputs

Refactor for the CLI endpoints outputs
This commit is contained in:
Florian Motlik 2016-08-11 16:12:25 +02:00 committed by GitHub
commit 2a97b5bdfd
8 changed files with 149 additions and 114 deletions

View File

@ -25,6 +25,22 @@ module.exports = {
_.merge(this.serverless.service.resources.Resources, newDeploymentObject);
// create CLF Output for endpoint
const outputServiceEndpointTemplate = `
{
"Description": "URL of the service endpoint",
"Value": { "Fn::Join" : [ "", [ "https://", { "Ref": "RestApiApigEvent" },
".execute-api.${this.options.region}.amazonaws.com/${this.options.stage}"] ] }
}`;
const newOutputEndpointObject = {
ServiceEndpoint: JSON.parse(outputServiceEndpointTemplate),
};
this.serverless.service.resources.Outputs =
this.serverless.service.resources.Outputs || {};
_.merge(this.serverless.service.resources.Outputs, newOutputEndpointObject);
return BbPromise.resolve();
},
};

View File

@ -5,8 +5,6 @@ const _ = require('lodash');
module.exports = {
compileMethods() {
let endpointCounter = 1;
_.forEach(this.serverless.service.functions, (functionObject, functionName) => {
functionObject.events.forEach(event => {
if (event.http) {
@ -164,22 +162,6 @@ module.exports = {
_.merge(this.serverless.service.resources.Resources,
methodObject);
// create CLF Output for endpoint
const outputEndpointTemplate = `
{
"Description": "Endpoint info",
"Value": { "Fn::Join" : [ "", [ "${method.toUpperCase()} - https://", { "Ref": "RestApiApigEvent" },
".execute-api.${this.options.region}.amazonaws.com/${this.options.stage}/${path}"] ] }
}`;
const newOutputEndpointObject = {
[`Endpoint${endpointCounter++}`]: JSON.parse(outputEndpointTemplate),
};
this.serverless.service.resources.Outputs =
this.serverless.service.resources.Outputs || {};
_.merge(this.serverless.service.resources.Outputs, newOutputEndpointObject);
// store a method logical id in memory to be used
// by Deployment resources "DependsOn" property
if (this.methodDependencies) {

View File

@ -19,6 +19,21 @@ describe('#compileDeployment()', () => {
},
},
},
Outputs: {
ServiceEndpoint: {
Description: 'URL of the service endpoint',
Value: {
'Fn::Join': [
'',
[
'https://',
{ Ref: 'RestApiApigEvent' },
'.execute-api.us-east-1.amazonaws.com/dev',
],
],
},
},
},
};
beforeEach(() => {
@ -43,4 +58,14 @@ describe('#compileDeployment()', () => {
);
})
);
it('should add service endpoint output', () => awsCompileApigEvents
.compileDeployment().then(() => {
expect(
awsCompileApigEvents.serverless.service.resources.Outputs.ServiceEndpoint
).to.deep.equal(
serviceResourcesAwsResourcesObjectMock.Outputs.ServiceEndpoint
);
})
);
});

View File

@ -202,60 +202,4 @@ describe('#compileMethods()', () => {
)).to.equal(JSON.stringify(lambdaUriObject));
});
});
it("should initiate the Outputs section if it's not available", () => {
awsCompileApigEvents.serverless.service.resources.Outputs = false;
awsCompileApigEvents.compileMethods().then(() => {
expect(awsCompileApigEvents.serverless.service.resources.Outputs)
.to.have.all.keys('Endpoint1', 'Endpoint2');
});
});
it('should create corresponding endpoint output objects', () => {
const expectedOutputs = {
Endpoint1: {
Description: 'Endpoint info',
Value: { 'Fn::Join': ['', ['POST - https://', { Ref: 'RestApiApigEvent' },
'.execute-api.us-east-1.amazonaws.com/dev/users/create']] },
},
Endpoint2: {
Description: 'Endpoint info',
Value: { 'Fn::Join': ['', ['GET - https://', { Ref: 'RestApiApigEvent' },
'.execute-api.us-east-1.amazonaws.com/dev/users/list']] },
},
};
awsCompileApigEvents.compileMethods().then(() => {
expect(awsCompileApigEvents.serverless.service.resources.Outputs)
.to.deep.equal(expectedOutputs);
});
});
it("should initiate the Outputs section if it's not available", () => {
awsCompileApigEvents.serverless.service.resources.Outputs = false;
awsCompileApigEvents.compileMethods().then(() => {
expect(awsCompileApigEvents.serverless.service.resources.Outputs)
.to.have.all.keys('Endpoint1', 'Endpoint2');
});
});
it('should create corresponding endpoint output objects', () => {
const expectedOutputs = {
Endpoint1: {
Description: 'Endpoint info',
Value: { 'Fn::Join': ['', ['POST - https://', { Ref: 'RestApiApigEvent' },
'.execute-api.us-east-1.amazonaws.com/dev/users/create']] },
},
Endpoint2: {
Description: 'Endpoint info',
Value: { 'Fn::Join': ['', ['GET - https://', { Ref: 'RestApiApigEvent' },
'.execute-api.us-east-1.amazonaws.com/dev/users/list']] },
},
};
awsCompileApigEvents.compileMethods().then(() => {
expect(awsCompileApigEvents.serverless.service.resources.Outputs)
.to.deep.equal(expectedOutputs);
});
});
});

View File

@ -5,4 +5,44 @@ This plugin displays information about the service.
## How it works
`Info` hooks into the [`info:info`](/lib/plugins/info) lifecycle. It will get the general information about the service and will query
CloudFormation for the Outputs of the stack. Outputs will include Lambda functions ARNs, endpoints and other resources.
CloudFormation for the `Outputs` of the stack. Outputs will include Lambda function ARN's, a `ServiceEndpoint` for the API Gateway endpoint and user provided custom Outputs.
### Lambda function ARN's
It uses the `Function[0-9]` CloudFormation Outputs.
**Example:**
```
$ serverless info
service: my-service
stage: dev
region: us-east-1
accountId: 12345678
endpoints:
None
functions:
my-service-dev-hello: arn:aws:lambda:us-east-1:12345678:function:my-service-dev-hello
```
### API Gateway Endpoints
It uses the `ServiceEndpoint` CloudFormation Output together with the functions http events.
**Example:**
```
$ serverless info
service: my-service
stage: dev
region: us-east-1
accountId: 12345678
endpoints:
GET - https://..../dev/users
GET - https://..../dev/likes
functions:
my-service-dev-users: arn:aws:lambda:us-east-1:12345678:function:my-service-dev-users
my-service-dev-likes: arn:aws:lambda:us-east-1:12345678:function:my-service-dev-likes
```

View File

@ -4,6 +4,7 @@ const BbPromise = require('bluebird');
const validate = require('../lib/validate');
const SDK = require('../');
const chalk = require('chalk');
const _ = require('lodash');
class AwsInfo {
constructor(serverless, options) {
@ -59,11 +60,9 @@ class AwsInfo {
});
// Endpoints
info.endpoints = [];
outputs.filter(x => x.OutputKey.match(/^Endpoint\d+$/))
outputs.filter(x => x.OutputKey.match(/^ServiceEndpoint/))
.forEach(x => {
const endpointInfo = { endpoint: x.OutputValue };
info.endpoints.push(endpointInfo);
info.endpoint = x.OutputValue;
});
// Resources
@ -100,9 +99,24 @@ ${chalk.yellow('region:')} ${info.region}`;
let endpointsMessage = `\n${chalk.yellow('endpoints:')}`;
if (info.endpoints && info.endpoints.length > 0) {
info.endpoints.forEach((e) => {
endpointsMessage = endpointsMessage.concat(`\n ${e.endpoint}`);
if (info.endpoint) {
_.forEach(this.serverless.service.functions, (functionObject) => {
functionObject.events.forEach(event => {
if (event.http) {
let method;
let path;
if (typeof event.http === 'object') {
method = event.http.method.toUpperCase();
path = event.http.path;
} else if (typeof event.http === 'string') {
method = event.http.split(' ')[0].toUpperCase();
path = event.http.split(' ')[1];
}
endpointsMessage = endpointsMessage.concat(`\n ${method} - ${info.endpoint}/${path}`);
}
});
});
} else {
endpointsMessage = endpointsMessage.concat(`\n None`);

View File

@ -10,10 +10,31 @@ const chalk = require('chalk');
describe('AwsInfo', () => {
const serverless = new Serverless();
serverless.service.functions = {
function1: {
events: [
{
http: {
path: 'function1',
method: 'GET',
},
},
],
},
function2: {
events: [
{
http: {
path: 'function2',
method: 'POST',
},
},
],
},
};
const options = {
stage: 'dev',
region: 'us-east-1',
function: 'first',
};
const awsInfo = new AwsInfo(serverless, options);
@ -81,17 +102,17 @@ describe('AwsInfo', () => {
{
Description: 'Lambda function info',
OutputKey: 'Function1Arn',
OutputValue: 'arn:aws:iam::12345678:function:my-first-function',
OutputValue: 'arn:aws:iam::12345678:function:function1',
},
{
Description: 'Lambda function info',
OutputKey: 'Function2Arn',
OutputValue: 'arn:aws:iam::12345678:function:my-second-function',
OutputValue: 'arn:aws:iam::12345678:function:function2',
},
{
Description: 'Endpoint info',
OutputKey: 'Endpoint1',
OutputValue: 'GET - https://ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev/hello',
Description: 'URL of the service endpoint',
OutputKey: 'ServiceEndpoint',
OutputValue: 'ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev',
},
],
StackStatusReason: null,
@ -130,12 +151,12 @@ describe('AwsInfo', () => {
it('should get function name and Arn', () => {
const expectedFunctions = [
{
name: 'my-first-function',
arn: 'arn:aws:iam::12345678:function:my-first-function',
name: 'function1',
arn: 'arn:aws:iam::12345678:function:function1',
},
{
name: 'my-second-function',
arn: 'arn:aws:iam::12345678:function:my-second-function',
name: 'function2',
arn: 'arn:aws:iam::12345678:function:function2',
},
];
@ -144,15 +165,11 @@ describe('AwsInfo', () => {
});
});
it('should get endpoints', () => {
const expectedEndpoints = [
{
endpoint: 'GET - https://ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev/hello',
},
];
it('should get endpoint', () => {
const expectedEndpoint = 'ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev';
return awsInfo.gather().then((info) => {
expect(info.endpoints).to.deep.equal(expectedEndpoints);
expect(info.endpoint).to.deep.equal(expectedEndpoint);
});
});
@ -197,15 +214,15 @@ describe('AwsInfo', () => {
service: 'my-first',
stage: 'dev',
region: 'eu-west-1',
endpoints: [
{
endpoint: 'GET - https://ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev/hello',
},
],
endpoint: 'ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev',
functions: [
{
name: 'my-first-dev-hello',
arn: 'arn:aws:lambda:eu-west-1:12345678:function:my-first-dev-hello',
name: 'function1',
arn: 'arn:aws:iam::12345678:function:function1',
},
{
name: 'function2',
arn: 'arn:aws:iam::12345678:function:function2',
},
],
};
@ -216,9 +233,11 @@ ${chalk.yellow('service:')} my-first
${chalk.yellow('stage:')} dev
${chalk.yellow('region:')} eu-west-1
${chalk.yellow('endpoints:')}
GET - https://ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev/hello
GET - ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev/function1
POST - ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev/function2
${chalk.yellow('functions:')}
my-first-dev-hello: arn:aws:lambda:eu-west-1:12345678:function:my-first-dev-hello
function1: arn:aws:iam::12345678:function:function1
function2: arn:aws:iam::12345678:function:function2
`;
expect(awsInfo.display(info)).to.equal(expectedMessage);
@ -257,7 +276,7 @@ ${chalk.yellow('functions:')}
stage: 'dev',
region: 'eu-west-1',
functions: [],
endpoints: [],
endpoint: undefined,
};
const expectedMessage = `

View File

@ -22,10 +22,5 @@ $ serverless info
service: my-service
stage: dev
region: us-east-1
accountId: 12345678
endpoints:
GET - https://..../dev/hello
functions:
my-service-dev-hello: arn:aws:lambda:us-east-1:12345678:function:my-service-dev-hello
```