From 6f21163fa3425e399993596ef095f68afcc0cbc9 Mon Sep 17 00:00:00 2001 From: Nick den Engelsman Date: Mon, 8 Aug 2016 13:28:19 +0200 Subject: [PATCH] refactor cli and cloudformation endpoint outputs --- .../events/apiGateway/lib/deployment.js | 16 ++++ .../compile/events/apiGateway/lib/methods.js | 18 ----- .../events/apiGateway/tests/deployment.js | 25 +++++++ .../events/apiGateway/tests/methods.js | 56 -------------- lib/plugins/aws/info/README.md | 42 ++++++++++- lib/plugins/aws/info/index.js | 28 +++++-- lib/plugins/aws/info/tests/index.js | 73 ++++++++++++------- lib/plugins/info/README.md | 5 -- 8 files changed, 149 insertions(+), 114 deletions(-) diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/deployment.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/deployment.js index e44d59828..a3334b721 100644 --- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/deployment.js +++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/deployment.js @@ -22,6 +22,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(); }, }; diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js index b4905c92f..4437face8 100644 --- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js +++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js @@ -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.methodDep) { diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/deployment.js b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/deployment.js index df79aadb0..3e077770f 100644 --- a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/deployment.js +++ b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/deployment.js @@ -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(() => { @@ -41,4 +56,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 + ); + }) + ); }); diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js index 6281cd844..ea30263a9 100644 --- a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js +++ b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js @@ -183,60 +183,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); - }); - }); }); diff --git a/lib/plugins/aws/info/README.md b/lib/plugins/aws/info/README.md index 79f93cdd5..79284b1c4 100644 --- a/lib/plugins/aws/info/README.md +++ b/lib/plugins/aws/info/README.md @@ -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 +``` diff --git a/lib/plugins/aws/info/index.js b/lib/plugins/aws/info/index.js index 91a4584ef..6988d033e 100644 --- a/lib/plugins/aws/info/index.js +++ b/lib/plugins/aws/info/index.js @@ -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`); diff --git a/lib/plugins/aws/info/tests/index.js b/lib/plugins/aws/info/tests/index.js index b664cb334..bc7efa1ed 100644 --- a/lib/plugins/aws/info/tests/index.js +++ b/lib/plugins/aws/info/tests/index.js @@ -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); @@ -75,17 +96,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, @@ -124,12 +145,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', }, ]; @@ -138,15 +159,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); }); }); @@ -191,15 +208,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', }, ], }; @@ -210,9 +227,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); @@ -251,7 +270,7 @@ ${chalk.yellow('functions:')} stage: 'dev', region: 'eu-west-1', functions: [], - endpoints: [], + endpoint: undefined, }; const expectedMessage = ` diff --git a/lib/plugins/info/README.md b/lib/plugins/info/README.md index 9f0abc554..8bc858b75 100644 --- a/lib/plugins/info/README.md +++ b/lib/plugins/info/README.md @@ -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 ```