From a80340b99f2e173b33b417e8fd1c93d369557dd6 Mon Sep 17 00:00:00 2001 From: Fabien Ruffin Date: Sat, 15 Sep 2018 22:41:20 +1000 Subject: [PATCH 01/70] Fixed #4188 - Package generating incorrect package artifact path in serverless-state.json --- lib/plugins/aws/package/lib/saveServiceState.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/plugins/aws/package/lib/saveServiceState.js b/lib/plugins/aws/package/lib/saveServiceState.js index 7d6afe2fb..50069151b 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.js +++ b/lib/plugins/aws/package/lib/saveServiceState.js @@ -27,6 +27,14 @@ module.exports = { const selfReferences = findReferences(strippedService, this.serverless.service); _.forEach(selfReferences, refPath => _.set(strippedService, refPath, '${self:}')); + _.forEach(strippedService.functions, func => { + const packageRef = func.package; + packageRef.artifact = path.join( + this.packagePath, + packageRef.artifact.substr(packageRef.artifact.lastIndexOf('\\') + 1) + ); + }); + const state = { service: strippedService, package: { From 821b9d9e3b6d99a47751847626aac1d5b92cd715 Mon Sep 17 00:00:00 2001 From: vkkis Date: Tue, 20 Nov 2018 14:56:54 +0200 Subject: [PATCH 02/70] Support API Gateway stage deployment description --- .../package/compile/events/apiGateway/lib/deployment.js | 3 ++- .../compile/events/apiGateway/lib/deployment.test.js | 6 ++++-- lib/plugins/aws/provider/awsProvider.js | 8 ++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js index 9eb923ba5..ccf601190 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js @@ -14,6 +14,7 @@ module.exports = { Properties: { RestApiId: this.provider.getApiGatewayRestApiId(), StageName: this.provider.getStage(), + Description: this.provider.getApiGatewayDescription() }, DependsOn: this.apiGatewayMethodLogicalIds, }, @@ -29,7 +30,7 @@ module.exports = { 'https://', this.provider.getApiGatewayRestApiId(), `.execute-api.${this.provider.getRegion()}.`, - { Ref: 'AWS::URLSuffix' }, + {Ref: 'AWS::URLSuffix'}, `/${this.provider.getStage()}`, ], ], diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 98be9ee27..013179d57 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -24,6 +24,7 @@ describe('#compileDeployment()', () => { }; awsCompileApigEvents = new AwsCompileApigEvents(serverless, options); awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'; + awsCompileApigEvents.apiGatewayDescription = 'serverless_description'; awsCompileApigEvents.apiGatewayMethodLogicalIds = ['method-dependency1', 'method-dependency2']; awsCompileApigEvents.provider = provider; }); @@ -44,6 +45,7 @@ describe('#compileDeployment()', () => { RestApiId: { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, }, + Description: awsCompileApigEvents.apiGatewayDescription, StageName: 'dev', }, }); @@ -62,9 +64,9 @@ describe('#compileDeployment()', () => { '', [ 'https://', - { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId }, + {Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId}, '.execute-api.us-east-1.', - { Ref: 'AWS::URLSuffix' }, + {Ref: 'AWS::URLSuffix'}, '/dev', ], ], diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 536c85412..a0212589c 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -459,6 +459,14 @@ class AwsProvider { return { Ref: this.naming.getRestApiLogicalId() }; } + getApiGatewayDescription() { + if (this.serverless.service.provider.apiGateway + && this.serverless.service.provider.apiGateway.description) { + return this.serverless.service.provider.apiGateway.description; + } + return ''; + } + getMethodArn(accountId, apiId, method, pathParam) { const region = this.getRegion(); let path = pathParam; From c69e6b956e89749573f3a78b1fe8ae2f747dba7c Mon Sep 17 00:00:00 2001 From: vkkis Date: Tue, 20 Nov 2018 16:49:39 +0200 Subject: [PATCH 03/70] update docs --- docs/providers/aws/events/apigateway.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 1396c8da8..965db66ed 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -949,6 +949,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework restApiRootResourceId: xxxxxxxxxx # Root resource, represent as / path + description: some_description # optional - description of deployment history functions: ... @@ -964,6 +965,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + description: some_description # optional functions: create: @@ -980,6 +982,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + description: some_description # optional functions: create: @@ -998,6 +1001,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + description: some_description # optional restApiResources: /posts: xxxxxxxxxx @@ -1012,6 +1016,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + description: some_description # optional restApiResources: /posts: xxxxxxxxxx @@ -1029,6 +1034,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # restApiRootResourceId: xxxxxxxxxx # Optional + description: some_description # optional restApiResources: /posts: xxxxxxxxxx /categories: xxxxxxxxx From 3180d7001d17dfc06e78ea563559b1a09909f7da Mon Sep 17 00:00:00 2001 From: vkkis Date: Tue, 20 Nov 2018 17:39:21 +0200 Subject: [PATCH 04/70] fix tests --- lib/plugins/aws/provider/awsProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index a0212589c..f8d7c8731 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -464,7 +464,7 @@ class AwsProvider { && this.serverless.service.provider.apiGateway.description) { return this.serverless.service.provider.apiGateway.description; } - return ''; + return null; } getMethodArn(accountId, apiId, method, pathParam) { From e30e03f1fba24732814203ef0dc1dc6f1cd606cf Mon Sep 17 00:00:00 2001 From: vkkis Date: Tue, 20 Nov 2018 18:15:01 +0200 Subject: [PATCH 05/70] fix tests --- .../package/compile/events/apiGateway/lib/deployment.test.js | 2 +- lib/plugins/aws/provider/awsProvider.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 013179d57..59eaf86c2 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -45,7 +45,7 @@ describe('#compileDeployment()', () => { RestApiId: { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, }, - Description: awsCompileApigEvents.apiGatewayDescription, + Description: '', StageName: 'dev', }, }); diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index f8d7c8731..a0212589c 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -464,7 +464,7 @@ class AwsProvider { && this.serverless.service.provider.apiGateway.description) { return this.serverless.service.provider.apiGateway.description; } - return null; + return ''; } getMethodArn(accountId, apiId, method, pathParam) { From 25b48a2c7cf994f09229e73999772a1d3d8a57e8 Mon Sep 17 00:00:00 2001 From: vkkis Date: Tue, 20 Nov 2018 18:38:30 +0200 Subject: [PATCH 06/70] fix eslint --- .../aws/package/compile/events/apiGateway/lib/deployment.js | 4 ++-- .../package/compile/events/apiGateway/lib/deployment.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js index ccf601190..57e16cc05 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js @@ -14,7 +14,7 @@ module.exports = { Properties: { RestApiId: this.provider.getApiGatewayRestApiId(), StageName: this.provider.getStage(), - Description: this.provider.getApiGatewayDescription() + Description: this.provider.getApiGatewayDescription(), }, DependsOn: this.apiGatewayMethodLogicalIds, }, @@ -30,7 +30,7 @@ module.exports = { 'https://', this.provider.getApiGatewayRestApiId(), `.execute-api.${this.provider.getRegion()}.`, - {Ref: 'AWS::URLSuffix'}, + { Ref: 'AWS::URLSuffix' }, `/${this.provider.getStage()}`, ], ], diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 59eaf86c2..0572148fa 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -64,9 +64,9 @@ describe('#compileDeployment()', () => { '', [ 'https://', - {Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId}, + { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId }, '.execute-api.us-east-1.', - {Ref: 'AWS::URLSuffix'}, + { Ref: 'AWS::URLSuffix' }, '/dev', ], ], From d300d5bc70f9e377ce6ac2ad91fa54f699835cd6 Mon Sep 17 00:00:00 2001 From: vkkis Date: Wed, 21 Nov 2018 10:22:56 +0200 Subject: [PATCH 07/70] test commit --- lib/plugins/aws/provider/awsProvider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index a0212589c..868a07232 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -459,6 +459,7 @@ class AwsProvider { return { Ref: this.naming.getRestApiLogicalId() }; } + getApiGatewayDescription() { if (this.serverless.service.provider.apiGateway && this.serverless.service.provider.apiGateway.description) { From 3f5fa1acf5d60c3709a78e35deb83ebc74885663 Mon Sep 17 00:00:00 2001 From: vkkis Date: Wed, 21 Nov 2018 10:49:17 +0200 Subject: [PATCH 08/70] rewrite unit tests in order to increase code coverage --- .../events/apiGateway/lib/deployment.test.js | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 0572148fa..8fdb332df 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -24,7 +24,6 @@ describe('#compileDeployment()', () => { }; awsCompileApigEvents = new AwsCompileApigEvents(serverless, options); awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'; - awsCompileApigEvents.apiGatewayDescription = 'serverless_description'; awsCompileApigEvents.apiGatewayMethodLogicalIds = ['method-dependency1', 'method-dependency2']; awsCompileApigEvents.provider = provider; }); @@ -52,6 +51,35 @@ describe('#compileDeployment()', () => { }) ); + it('should create a deployment resource with description', () => { + awsCompileApigEvents.serverless.service.provider.apiGateway = { + description: 'apiG_description' + }; + return awsCompileApigEvents + .compileDeployment().then(() => { + + const apiGatewayDeploymentLogicalId = Object + .keys(awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources)[0]; + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[apiGatewayDeploymentLogicalId] + ).to.deep.equal({ + Type: 'AWS::ApiGateway::Deployment', + DependsOn: ['method-dependency1', 'method-dependency2'], + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + Description: 'apiG_description', + StageName: 'dev', + }, + }); + }) + } + ); + it('should add service endpoint output', () => awsCompileApigEvents.compileDeployment().then(() => { expect( From d88cf971071bfafc6fb11d76a664d3a82b6858d6 Mon Sep 17 00:00:00 2001 From: vkkis Date: Wed, 21 Nov 2018 11:04:58 +0200 Subject: [PATCH 09/70] test --- lib/plugins/aws/provider/awsProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 868a07232..a0212589c 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -459,7 +459,6 @@ class AwsProvider { return { Ref: this.naming.getRestApiLogicalId() }; } - getApiGatewayDescription() { if (this.serverless.service.provider.apiGateway && this.serverless.service.provider.apiGateway.description) { From 44c24b503cc20a3bf8d2e48482582bccb316b470 Mon Sep 17 00:00:00 2001 From: vkkis Date: Wed, 21 Nov 2018 11:17:14 +0200 Subject: [PATCH 10/70] fix eslint --- .../events/apiGateway/lib/deployment.test.js | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 8fdb332df..77ec83335 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -53,31 +53,30 @@ describe('#compileDeployment()', () => { it('should create a deployment resource with description', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { - description: 'apiG_description' + description: 'apiG_description', }; - return awsCompileApigEvents - .compileDeployment().then(() => { - + return awsCompileApigEvents + .compileDeployment().then(() => { const apiGatewayDeploymentLogicalId = Object .keys(awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources)[0]; + .compiledCloudFormationTemplate.Resources)[0]; expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[apiGatewayDeploymentLogicalId] - ).to.deep.equal({ - Type: 'AWS::ApiGateway::Deployment', - DependsOn: ['method-dependency1', 'method-dependency2'], - Properties: { - RestApiId: { - Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[apiGatewayDeploymentLogicalId] + ).to.deep.equal({ + Type: 'AWS::ApiGateway::Deployment', + DependsOn: ['method-dependency1', 'method-dependency2'], + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + Description: 'apiG_description', + StageName: 'dev', }, - Description: 'apiG_description', - StageName: 'dev', - }, - }); - }) - } + }); + }); + } ); it('should add service endpoint output', () => From 40f892b3d5cbe1200c352c295d91ac88d7634e2e Mon Sep 17 00:00:00 2001 From: vkkis Date: Thu, 29 Nov 2018 16:51:27 +0200 Subject: [PATCH 11/70] sets the description only if it's provided --- .../package/compile/events/apiGateway/lib/deployment.test.js | 2 +- lib/plugins/aws/provider/awsProvider.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 77ec83335..c28e31d06 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -44,7 +44,7 @@ describe('#compileDeployment()', () => { RestApiId: { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, }, - Description: '', + Description: undefined, StageName: 'dev', }, }); diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index a0212589c..e92c56d30 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -464,7 +464,6 @@ class AwsProvider { && this.serverless.service.provider.apiGateway.description) { return this.serverless.service.provider.apiGateway.description; } - return ''; } getMethodArn(accountId, apiId, method, pathParam) { From 8fea1fa901def4cda585fda4a32a9c34538eff82 Mon Sep 17 00:00:00 2001 From: vkkis Date: Thu, 29 Nov 2018 17:02:09 +0200 Subject: [PATCH 12/70] fix travis --- lib/plugins/aws/provider/awsProvider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index e92c56d30..b162ae950 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -464,6 +464,7 @@ class AwsProvider { && this.serverless.service.provider.apiGateway.description) { return this.serverless.service.provider.apiGateway.description; } + return undefined; } getMethodArn(accountId, apiId, method, pathParam) { From 2ea7446879daf7cecb6d5d811bf2a6e539ed17f8 Mon Sep 17 00:00:00 2001 From: Rocky Warren <1085683+therockstorm@users.noreply.github.com> Date: Wed, 23 Jan 2019 15:14:12 -0600 Subject: [PATCH 13/70] Allow individual packaging with TypeScript source maps --- .../templates/aws-alexa-typescript/handler.ts | 1 + .../templates/aws-alexa-typescript/package.json | 14 +++++++------- .../aws-alexa-typescript/source-map-install.js | 1 - .../aws-alexa-typescript/webpack.config.js | 8 +------- .../templates/aws-nodejs-typescript/handler.ts | 1 + .../templates/aws-nodejs-typescript/package.json | 12 ++++++------ .../aws-nodejs-typescript/source-map-install.js | 1 - .../aws-nodejs-typescript/webpack.config.js | 8 +------- 8 files changed, 17 insertions(+), 29 deletions(-) delete mode 100644 lib/plugins/create/templates/aws-alexa-typescript/source-map-install.js delete mode 100644 lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js diff --git a/lib/plugins/create/templates/aws-alexa-typescript/handler.ts b/lib/plugins/create/templates/aws-alexa-typescript/handler.ts index dc7cb2a77..f7c2c4226 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/handler.ts +++ b/lib/plugins/create/templates/aws-alexa-typescript/handler.ts @@ -1,4 +1,5 @@ import * as Ask from 'ask-sdk'; +import 'source-map-support/register'; export const alexa = Ask.SkillBuilders.custom() .addRequestHandlers({ diff --git a/lib/plugins/create/templates/aws-alexa-typescript/package.json b/lib/plugins/create/templates/aws-alexa-typescript/package.json index 01a04b631..ba84ae3bd 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/package.json +++ b/lib/plugins/create/templates/aws-alexa-typescript/package.json @@ -7,16 +7,16 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ask-sdk": "^2.0.7" + "ask-sdk": "^2.3.0", + "source-map-support": "^0.5.10" }, "devDependencies": { - "@types/node": "^8.0.57", + "@types/node": "^10.12.18", "serverless-alexa-skills": "^0.1.0", - "serverless-webpack": "^5.1.1", - "source-map-support": "^0.5.6", - "ts-loader": "^4.2.0", - "typescript": "^2.9.2", - "webpack": "^4.5.0" + "serverless-webpack": "^5.2.0", + "ts-loader": "^5.3.3", + "typescript": "^3.2.4", + "webpack": "^4.29.0" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", diff --git a/lib/plugins/create/templates/aws-alexa-typescript/source-map-install.js b/lib/plugins/create/templates/aws-alexa-typescript/source-map-install.js deleted file mode 100644 index ef7457f72..000000000 --- a/lib/plugins/create/templates/aws-alexa-typescript/source-map-install.js +++ /dev/null @@ -1 +0,0 @@ -require('source-map-support').install(); diff --git a/lib/plugins/create/templates/aws-alexa-typescript/webpack.config.js b/lib/plugins/create/templates/aws-alexa-typescript/webpack.config.js index 482b98431..3fddde6b7 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-alexa-typescript/webpack.config.js @@ -1,15 +1,9 @@ const path = require('path'); const slsw = require('serverless-webpack'); -const entries = {}; - -Object.keys(slsw.lib.entries).forEach( - key => (entries[key] = ['./source-map-install.js', slsw.lib.entries[key]]) -); - module.exports = { mode: slsw.lib.webpack.isLocal ? 'development' : 'production', - entry: entries, + entry: slsw.lib.entries, devtool: 'source-map', resolve: { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts index 20e1cbaa3..95f6633aa 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts +++ b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts @@ -1,4 +1,5 @@ import { APIGatewayProxyHandler } from 'aws-lambda'; +import 'source-map-support/register'; export const hello: APIGatewayProxyHandler = async (event, context) => { return { diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index 44edc2b0c..a4ef1a31d 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -7,15 +7,15 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { + "source-map-support": "^0.5.10" }, "devDependencies": { "@types/aws-lambda": "^8.10.17", - "@types/node": "^8.0.57", - "serverless-webpack": "^5.1.1", - "source-map-support": "^0.5.6", - "ts-loader": "^4.2.0", - "typescript": "^2.9.2", - "webpack": "^4.5.0" + "@types/node": "^10.12.18", + "serverless-webpack": "^5.2.0", + "ts-loader": "^5.3.3", + "typescript": "^3.2.4", + "webpack": "^4.29.0" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js b/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js deleted file mode 100644 index ef7457f72..000000000 --- a/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js +++ /dev/null @@ -1 +0,0 @@ -require('source-map-support').install(); diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js index 482b98431..3fddde6b7 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js @@ -1,15 +1,9 @@ const path = require('path'); const slsw = require('serverless-webpack'); -const entries = {}; - -Object.keys(slsw.lib.entries).forEach( - key => (entries[key] = ['./source-map-install.js', slsw.lib.entries[key]]) -); - module.exports = { mode: slsw.lib.webpack.isLocal ? 'development' : 'production', - entry: entries, + entry: slsw.lib.entries, devtool: 'source-map', resolve: { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], From 85741629cd4ea48f64e5d5b7dedd5e38e41cc786 Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Mon, 25 Feb 2019 16:24:07 +0300 Subject: [PATCH 14/70] support more route chars --- lib/plugins/aws/lib/naming.js | 7 ++++++- lib/plugins/aws/lib/naming.test.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 21631aa63..910f41868 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -181,7 +181,12 @@ module.exports = { }, getNormalizedWebsocketsRouteKey(route) { - return route.replace('$', 'S'); + return route + .replace('$', 'S') // dollar sign + .replace('/', 'SL') // slash + .replace('-', 'D') // dash + .replace('_', 'U') // underscore + .replace('.', 'P'); // period }, getWebsocketsRouteLogicalId(route) { diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 643981306..20ee8b43f 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -259,6 +259,18 @@ describe('#naming()', () => { it('should return a normalized version of the route key', () => { expect(sdk.naming.getNormalizedWebsocketsRouteKey('$connect')) .to.equal('Sconnect'); + + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo/bar')) + .to.equal('fooSLbar'); + + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo-bar')) + .to.equal('fooDbar'); + + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo_bar')) + .to.equal('fooUbar'); + + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo.bar')) + .to.equal('fooPbar'); }); }); From c6f955ab0069e872377a99823d2c5ddada10c2b7 Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Tue, 26 Feb 2019 16:44:58 +0300 Subject: [PATCH 15/70] added authorizer support --- lib/plugins/aws/lib/naming.js | 4 + .../compile/events/websockets/index.js | 3 + .../events/websockets/lib/authorizers.js | 30 +++++ .../events/websockets/lib/permissions.js | 31 ++++++ .../compile/events/websockets/lib/routes.js | 14 ++- .../compile/events/websockets/lib/validate.js | 104 +++++++++++++++++- 6 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 21631aa63..17057f498 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -196,6 +196,10 @@ module.exports = { return 'WebsocketsDeploymentStage'; }, + getWebsocketsAuthorizerLogicalId(functionName) { + return `${this.getNormalizedAuthorizerName(functionName)}WebsocketsAuthorizer`; + }, + // API Gateway getApiGatewayName() { if (this.provider.serverless.service.provider.apiName && diff --git a/lib/plugins/aws/package/compile/events/websockets/index.js b/lib/plugins/aws/package/compile/events/websockets/index.js index bdc395b04..194c205f1 100644 --- a/lib/plugins/aws/package/compile/events/websockets/index.js +++ b/lib/plugins/aws/package/compile/events/websockets/index.js @@ -9,6 +9,7 @@ const compilePermissions = require('./lib/permissions'); const compileRoutes = require('./lib/routes'); const compileDeployment = require('./lib/deployment'); const compileStage = require('./lib/stage'); +const compileAuthorizers = require('./lib/authorizers'); class AwsCompileWebsockets { constructor(serverless, options) { @@ -21,6 +22,7 @@ class AwsCompileWebsockets { validate, compileApi, compileIntegrations, + compileAuthorizers, compilePermissions, compileRoutes, compileDeployment, @@ -38,6 +40,7 @@ class AwsCompileWebsockets { return BbPromise.bind(this) .then(this.compileApi) .then(this.compileIntegrations) + .then(this.compileAuthorizers) .then(this.compilePermissions) .then(this.compileRoutes) .then(this.compileDeployment) diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js new file mode 100644 index 000000000..c86375e84 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js @@ -0,0 +1,30 @@ +'use strict'; + +const _ = require('lodash'); +const BbPromise = require('bluebird'); + +module.exports = { + compileAuthorizers() { + this.validated.events.forEach(event => { + const websocketsAuthorizerLogicalId = this.provider.naming + .getWebsocketsAuthorizerLogicalId(event.authorizer.name); + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [websocketsAuthorizerLogicalId]: { + Type: 'AWS::ApiGatewayV2::Authorizer', + Properties: { + ApiId: { + Ref: this.websocketsApiLogicalId, + }, + Name: event.authorizer.name, + AuthorizerType: 'REQUEST', + AuthorizerUri: event.authorizer.uri, + IdentitySource: event.authorizer.identitySource, + }, + }, + }); + }); + + return BbPromise.resolve(); + }, +}; diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js index 939ce16df..5f81ff682 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js @@ -24,6 +24,37 @@ module.exports = { }, }, }); + + if (event.authorizer) { + const websocketsAuthorizerPermissionLogicalId = this.provider.naming + .getLambdaWebsocketsPermissionLogicalId(event.authorizer.name); + const authorizerPermissionTemplate = { + [websocketsAuthorizerPermissionLogicalId]: { + Type: 'AWS::Lambda::Permission', + DependsOn: [this.websocketsApiLogicalId], + Properties: { + Action: 'lambda:InvokeFunction', + Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, + }, + }, + }; + + if (event.authorizer.permission.includes(':')) { + authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId] + .Properties.FunctionName = event.authorizer.permission; + } else { + authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId] + .Properties.FunctionName = { + 'Fn::GetAtt': [event.authorizer.permission, 'Arn'], + }; + + authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId] + .DependsOn.push(event.authorizer.permission); + } + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + authorizerPermissionTemplate); + } }); return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/routes.js b/lib/plugins/aws/package/compile/events/websockets/lib/routes.js index d7b9b098c..ef3aa7a86 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/routes.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/routes.js @@ -12,7 +12,7 @@ module.exports = { const websocketsRouteLogicalId = this.provider.naming .getWebsocketsRouteLogicalId(event.route); - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + const routeTemplate = { [websocketsRouteLogicalId]: { Type: 'AWS::ApiGatewayV2::Route', Properties: { @@ -31,7 +31,17 @@ module.exports = { }, }, }, - }); + }; + + if (event.authorizer) { + routeTemplate[websocketsRouteLogicalId].Properties.AuthorizationType = 'CUSTOM'; + routeTemplate[websocketsRouteLogicalId].Properties.AuthorizerId = { + Ref: this.provider.naming + .getWebsocketsAuthorizerLogicalId(event.authorizer.name), + }; + } + _.merge(this.serverless.service.provider + .compiledCloudFormationTemplate.Resources, routeTemplate); }); return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/validate.js b/lib/plugins/aws/package/compile/events/websockets/lib/validate.js index 00712ba39..f225bdc05 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/validate.js @@ -6,6 +6,11 @@ module.exports = { validate() { const events = []; + const getAuthorizerNameFromArn = (arn) => { + const splitArn = arn.split(':'); + return splitArn[splitArn.length - 1]; + }; + _.forEach(this.serverless.service.functions, (functionObject, functionName) => { _.forEach(functionObject.events, (event) => { // check if we have both, `http` and `websocket` events which is not supported @@ -20,10 +25,105 @@ module.exports = { const errorMessage = 'You need to set the "route" when using the websocket event.'; throw new this.serverless.classes.Error(errorMessage); } - events.push({ + + const websocketObj = { functionName, route: event.websocket.route, - }); + }; + + // authorizers + if (_.isString(event.websocket.authorizer)) { + if (event.websocket.authorizer.includes(':')) { // arn + websocketObj.authorizer = { + name: getAuthorizerNameFromArn(event.websocket.authorizer), + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + event.websocket.authorizer, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + permission: event.websocket.authorizer, + }; + } else { // reference function + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(event.websocket.authorizer); + websocketObj.authorizer = { + name: event.websocket.authorizer, + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + permission: lambdaLogicalId, + }; + } + } else if (_.isObject(event.websocket.authorizer)) { + websocketObj.authorizer = {}; + if (event.websocket.authorizer.arn) { + websocketObj.authorizer.name = + getAuthorizerNameFromArn(event.websocket.authorizer.arn); + websocketObj.authorizer.uri = { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + event.websocket.authorizer.arn, + '/invocations', + ], + ], + }; + websocketObj.authorizer.permission = event.websocket.authorizer.arn; + } else if (event.websocket.authorizer.name) { + websocketObj.authorizer.name = event.websocket.authorizer.name; + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(event.websocket.authorizer.name); + websocketObj.authorizer.uri = { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'] }, + '/invocations', + ], + ], + }; + websocketObj.authorizer.permission = lambdaLogicalId; + } else { + const errorMessage = + 'You must specify name or arn properties when using a websocket authorizer'; + throw new this.serverless.classes.Error(errorMessage); + } + + if (!event.websocket.authorizer.identitySource) { + websocketObj.authorizer.identitySource = ['route.request.header.Auth']; + } else { + websocketObj.authorizer.identitySource = event.websocket.authorizer.identitySource; + } + } + events.push(websocketObj); // dealing with the simplified string representation } else if (_.isString(event.websocket)) { events.push({ From dfc3b49336e9d1e1c5a001a6bdf6912511915926 Mon Sep 17 00:00:00 2001 From: "Paul N. Baker" Date: Tue, 26 Feb 2019 14:22:14 -0700 Subject: [PATCH 16/70] Adding BOM and jackson-module-kotlin The bill of matierals helps manage the various verions of the modules and is the prefered method of managing adjacent dependencies when available. jackson-module-kotlin provides a module for Kotlin/Jackson interop. --- .../templates/aws-kotlin-jvm-maven/pom.xml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml index ec2d4738d..52c3230c2 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml @@ -14,6 +14,18 @@ UTF-8 + + + + com.fasterxml.jackson + jackson-bom + 2.9.8 + import + pom + + + + org.jetbrains.kotlin @@ -33,17 +45,18 @@ com.fasterxml.jackson.core jackson-core - 2.8.5 com.fasterxml.jackson.core jackson-databind - 2.8.11.3 com.fasterxml.jackson.core jackson-annotations - 2.8.5 + + + com.fasterxml.jackson.module + jackson-module-kotlin From 6bf5c0e6ac2daf4666ba154e86214901486e7907 Mon Sep 17 00:00:00 2001 From: "Paul N. Baker" Date: Tue, 26 Feb 2019 14:28:03 -0700 Subject: [PATCH 17/70] log4j2 fixes Logging in Java along with log4j2 and must have specific libraries and configurations to be sucessful in AWS Lambda. Since the target is Java 8 (currently supported aws-lambda runtime) this is essential. --- .../templates/aws-kotlin-jvm-maven/pom.xml | 19 +++++++++++++++++-- .../src/main/resources/log4j2.xml | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/resources/log4j2.xml diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml index 52c3230c2..d514775be 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml @@ -35,12 +35,22 @@ com.amazonaws aws-lambda-java-core - 1.1.0 + 1.2.0 com.amazonaws aws-lambda-java-log4j - 1.0.0 + 1.1.0 + + + org.apache.logging.log4j + log4j-core + 2.11.1 + + + org.apache.logging.log4j + log4j-api + 2.11.1 com.fasterxml.jackson.core @@ -79,6 +89,11 @@ 2.3 false + + + + diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/resources/log4j2.xml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/resources/log4j2.xml new file mode 100644 index 000000000..489fd2cde --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n + + + + + + + + + From bc89ef1f230a881558b24690fff4e57f6ab3d5b9 Mon Sep 17 00:00:00 2001 From: "Paul N. Baker" Date: Tue, 26 Feb 2019 14:31:33 -0700 Subject: [PATCH 18/70] Updating Kotlin Kotlin has been updated, and since we are targeting jdk-8 we want to take advantage of the Java 8 runtime features that are not available in Kotlin without specifying that we are using Java 8. http://kotlinlang.org/docs/reference/whatsnew12.html#standard-library --- lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml index d514775be..3ee0a97b2 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml @@ -8,7 +8,8 @@ hello - 1.1.4-3 + 1.3.21 + 1.8 1.8 1.8 UTF-8 @@ -29,7 +30,7 @@ org.jetbrains.kotlin - kotlin-stdlib + kotlin-stdlib-jdk8 ${kotlin.version} From 473b4d166c6f621a812d1278d7dec4608ee5b9e7 Mon Sep 17 00:00:00 2001 From: "Paul N. Baker" Date: Tue, 26 Feb 2019 15:17:06 -0700 Subject: [PATCH 19/70] Accidently forgot to update code to point to updated log4j2 references. Fixing a typo in dependency name. Adding missing dependency information for plugin. --- .../create/templates/aws-kotlin-jvm-maven/pom.xml | 9 ++++++++- .../src/main/kotlin/com/serverless/ApiGatewayResponse.kt | 5 +++-- .../src/main/kotlin/com/serverless/Handler.kt | 6 ++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml index 3ee0a97b2..59d5cae70 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/pom.xml @@ -40,7 +40,7 @@ com.amazonaws - aws-lambda-java-log4j + aws-lambda-java-log4j2 1.1.0 @@ -104,6 +104,13 @@ + + + com.github.edwgiz + maven-shade-plugin.log4j2-cachefile-transformer + 2.8.1 + + kotlin-maven-plugin diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/ApiGatewayResponse.kt b/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/ApiGatewayResponse.kt index 83f00bdf4..eb5b0a0af 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/ApiGatewayResponse.kt +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/ApiGatewayResponse.kt @@ -2,7 +2,8 @@ package com.serverless import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper -import org.apache.log4j.Logger +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger import java.nio.charset.StandardCharsets import java.util.* @@ -24,7 +25,7 @@ class ApiGatewayResponse( } class Builder { - var LOG: Logger = Logger.getLogger(ApiGatewayResponse.Builder::class.java) + var LOG: Logger = LogManager.getLogger(ApiGatewayResponse.Builder::class.java) var objectMapper: ObjectMapper = ObjectMapper() var statusCode: Int = 200 diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/Handler.kt b/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/Handler.kt index e14e0e8d9..a760dc2ba 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/Handler.kt +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/src/main/kotlin/com/serverless/Handler.kt @@ -2,13 +2,11 @@ package com.serverless import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler -import org.apache.log4j.BasicConfigurator -import org.apache.log4j.Logger +import org.apache.logging.log4j.LogManager import java.util.* class Handler:RequestHandler, ApiGatewayResponse> { override fun handleRequest(input:Map, context:Context):ApiGatewayResponse { - BasicConfigurator.configure() LOG.info("received: " + input.keys.toString()) val responseBody = Response("Go Serverless v1.x! Your Kotlin function executed successfully!", input) @@ -19,6 +17,6 @@ class Handler:RequestHandler, ApiGatewayResponse> { } } companion object { - private val LOG = Logger.getLogger(Handler::class.java) + private val LOG = LogManager.getLogger(Handler::class.java) } } \ No newline at end of file From 03cc4d4a488acb7d960e741d7ae32d083f0b69cd Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Wed, 3 Oct 2018 16:38:31 +0000 Subject: [PATCH 20/70] Allow Fn::Join in SQS arn builder --- lib/plugins/aws/package/compile/events/sqs/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/sqs/index.js b/lib/plugins/aws/package/compile/events/sqs/index.js index 3c728f06c..b500c7771 100644 --- a/lib/plugins/aws/package/compile/events/sqs/index.js +++ b/lib/plugins/aws/package/compile/events/sqs/index.js @@ -49,7 +49,8 @@ class AwsCompileSQSEvents { // for dynamic arns (GetAtt/ImportValue) if (Object.keys(event.sqs.arn).length !== 1 || !(_.has(event.sqs.arn, 'Fn::ImportValue') - || _.has(event.sqs.arn, 'Fn::GetAtt'))) { + || _.has(event.sqs.arn, 'Fn::GetAtt') + || _.has(event.sqs.arn, 'Fn::Join'))) { const errorMessage = [ `Bad dynamic ARN property on sqs event in function "${functionName}"`, ' If you use a dynamic "arn" (such as with Fn::GetAtt or Fn::ImportValue)', From 2a075af805c8b2e2f27619d10d4ec6fed8d67c92 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 27 Feb 2019 12:06:08 +0100 Subject: [PATCH 21/70] Fix functionality / add tests --- .../aws/package/compile/events/sqs/index.js | 3 + .../package/compile/events/sqs/index.test.js | 69 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/sqs/index.js b/lib/plugins/aws/package/compile/events/sqs/index.js index b500c7771..3a4904e27 100644 --- a/lib/plugins/aws/package/compile/events/sqs/index.js +++ b/lib/plugins/aws/package/compile/events/sqs/index.js @@ -85,6 +85,9 @@ class AwsCompileSQSEvents { return EventSourceArn['Fn::GetAtt'][0]; } else if (EventSourceArn['Fn::ImportValue']) { return EventSourceArn['Fn::ImportValue']; + } else if (EventSourceArn['Fn::Join']) { + // [0] is the used delimiter, [1] is the array with values + return EventSourceArn['Fn::Join'][1].slice(-1).pop(); } return EventSourceArn.split(':').pop(); }()); diff --git a/lib/plugins/aws/package/compile/events/sqs/index.test.js b/lib/plugins/aws/package/compile/events/sqs/index.test.js index a251c9d87..2e21dfa15 100644 --- a/lib/plugins/aws/package/compile/events/sqs/index.test.js +++ b/lib/plugins/aws/package/compile/events/sqs/index.test.js @@ -391,18 +391,29 @@ describe('AwsCompileSQSEvents', () => { arn: { 'Fn::ImportValue': 'ForeignQueue' }, }, }, + { + sqs: { + arn: { + 'Fn::Join': [ + ':', [ + 'arn', 'aws', 'sqs', { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'MyQueue', + ], + ], + }, + }, + }, ], }, }; awsCompileSQSEvents.compileSQSEvents(); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn - ).to.deep.equal( - { 'Fn::GetAtt': ['SomeQueue', 'Arn'] } - ); expect(awsCompileSQSEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution .Properties.Policies[0].PolicyDocument.Statement[0] @@ -424,18 +435,62 @@ describe('AwsCompileSQSEvents', () => { { 'Fn::ImportValue': 'ForeignQueue', }, + { + 'Fn::Join': [ + ':', + [ + 'arn', + 'aws', + 'sqs', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'MyQueue', + ], + ], + }, ], } ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn + ).to.deep.equal( + { 'Fn::GetAtt': ['SomeQueue', 'Arn'] } + ); expect(awsCompileSQSEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingSQSForeignQueue.Properties.EventSourceArn ).to.deep.equal( { 'Fn::ImportValue': 'ForeignQueue' } ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.Properties.EventSourceArn + ).to.deep.equal( + { + 'Fn::Join': [ + ':', + [ + 'arn', + 'aws', + 'sqs', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'MyQueue', + ], + ], + }); }); - it('fails if keys other than Fn::GetAtt/ImportValue are used for dynamic queue ARN', () => { + it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic ARNs', () => { awsCompileSQSEvents.serverless.service.functions = { first: { events: [ From 4bd8b593b94b01859b04023dc9d17f2830c197a9 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 21 Feb 2019 14:52:31 +0100 Subject: [PATCH 22/70] Add AWS x-ray support for Lambda --- docs/providers/aws/guide/functions.md | 32 ++ docs/providers/aws/guide/serverless.yml.md | 5 +- .../aws/package/compile/functions/index.js | 35 +++ .../package/compile/functions/index.test.js | 282 ++++++++++++++++++ 4 files changed, 353 insertions(+), 1 deletion(-) diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 9a3ae2266..2351063aa 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -28,6 +28,8 @@ provider: memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 versionFunctions: false # optional, default is true + tracingConfig: # optional, enables tracing for all functions (can be 'Active' or 'PassThrough') + mode: Active functions: hello: @@ -38,6 +40,8 @@ functions: memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit + tracingConfig: # optional, overwrite, can be 'Active' or 'PassThrough' + mode: PassThrough ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -430,3 +434,31 @@ functions: ### Secrets using environment variables and KMS When storing secrets in environment variables, AWS [strongly suggests](http://docs.aws.amazon.com/lambda/latest/dg/env_variables.html#env-storing-sensitive-data) encrypting sensitive information. AWS provides a [tutorial](http://docs.aws.amazon.com/lambda/latest/dg/tutorial-env_console.html) on using KMS for this purpose. + +## AWS X-Ray Tracing + +You can enable [AWS X-Ray Tracing](https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html) on your Lambda functions through the optional `tracingConfig` config variable in conjucntion with the `mode` property: + +```yml +service: myService + +provider: + name: aws + runtime: nodejs8.10 + tracingConfig: + mode: Active +``` + +You can also set this variable on a per-function basis. This will override the provider level setting if present: + +```yml +functions: + hello: + handler: handler.hello + tracingConfig: + mode: Active + goodbye: + handler: handler.goodbye + tracingConfig: + mode: PassThrough +``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 1a44a8e0e..4465c58a8 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -59,7 +59,6 @@ provider: '/users/create': xxxxxxxxxx apiKeySourceType: HEADER # Source of API key for usage plan. HEADER or AUTHORIZER. minimumCompressionSize: 1024 # Compress response when larger than specified size in bytes (must be between 0 and 10485760) - usagePlan: # Optional usage plan configuration quota: limit: 5000 @@ -118,6 +117,8 @@ provider: tags: # Optional service wide function tags foo: bar baz: qux + tracingConfig: + mode: Active # optional, can be 'Active' or 'PassThrough' package: # Optional deployment packaging configuration include: # Specify the directories and files which should be included in the deployment package @@ -164,6 +165,8 @@ functions: individually: true # Enables individual packaging for specific function. If true you must provide package for each function. Defaults to false layers: # An optional list Lambda Layers to use - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y # Layer Version ARN + tracingConfig: + mode: Active # optional, can be 'Active' or 'PassThrough' (overwrites the one defined on the provider level) events: # The Events that trigger this Function - http: # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" path: users/create # Path for this endpoint diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 8a130d1c9..b2f83ed92 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -242,6 +242,41 @@ class AwsCompileFunctions { } } + const tracingConfig = functionObject.tracingConfig + || this.serverless.service.provider.tracingConfig; + + if (tracingConfig) { + if (tracingConfig.mode && typeof tracingConfig.mode === 'string') { + const iamRoleLambdaExecution = this.serverless.service.provider + .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution; + + newFunction.Properties.TracingConfig = { + Mode: tracingConfig.mode, + }; + + const stmt = { + Effect: 'Allow', + Action: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + ], + Resource: ['*'], + }; + + // update the PolicyDocument statements (if default policy is used) + if (iamRoleLambdaExecution) { + iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement = _.unionWith( + iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement, + [stmt], + _.isEqual + ); + } + } else { + const errorMessage = 'tracingConfig must provide a "mode" property as a string'; + throw new this.serverless.classes.Error(errorMessage); + } + } + if (functionObject.environment || this.serverless.service.provider.environment) { newFunction.Properties.Environment = {}; newFunction.Properties.Environment.Variables = Object.assign( diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 389204db1..662710695 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -1286,6 +1286,288 @@ describe('AwsCompileFunctions', () => { }); }); + describe('when using tracingConfig config', () => { + let s3Folder; + let s3FileName; + + beforeEach(() => { + s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + }); + + it('should throw an error if "mode" config parameter is not provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tracingConfig: 'Active', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()) + .to.be.rejectedWith('property as a string'); + }); + + it('should throw an error if "mode" config paramter is not a string', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tracingConfig: { + mode: 123, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()) + .to.be.rejectedWith('property as a string'); + }); + + it('should use a the provider wide tracingConfig config if provided', () => { + Object.assign(awsCompileFunctions.serverless.service.provider, { + tracingConfig: { + mode: 'Active', + }, + }); + + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = awsCompileFunctions.serverless.service.provider + .compiledCloudFormationTemplate; + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should prefer a function tracingConfig config over a provider config', () => { + Object.assign(awsCompileFunctions.serverless.service.provider, { + tracingConfig: { + mode: 'Active', + }, + }); + + awsCompileFunctions.serverless.service.functions = { + func1: { + handler: 'func1.function.handler', + name: 'new-service-dev-func1', + tracingConfig: { + mode: 'PassThrough', + }, + }, + func2: { + handler: 'func2.function.handler', + name: 'new-service-dev-func2', + }, + }; + + const compiledFunction1 = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'Func1LogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func1', + Handler: 'func1.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + TracingConfig: { + Mode: 'PassThrough', + }, + }, + }; + + const compiledFunction2 = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'Func2LogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func2', + Handler: 'func2.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = awsCompileFunctions.serverless.service.provider + .compiledCloudFormationTemplate; + + const function1Resource = compiledCfTemplate.Resources.Func1LambdaFunction; + const function2Resource = compiledCfTemplate.Resources.Func2LambdaFunction; + expect(function1Resource).to.deep.equal(compiledFunction1); + expect(function2Resource).to.deep.equal(compiledFunction2); + }); + }); + + describe('when IamRoleLambdaExecution is used', () => { + beforeEach(() => { + // pretend that the IamRoleLambdaExecution is used + awsCompileFunctions.serverless.service.provider + .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], + }, + }, + ], + }, + }; + }); + + it('should create necessary resources if a tracingConfig config is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tracingConfig: { + mode: 'Active', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + const compiledXrayStatement = { + Effect: 'Allow', + Action: [ + 'xray:PutTraceSegments', + 'xray:PutTelemetryRecords', + ], + Resource: ['*'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = awsCompileFunctions.serverless.service.provider + .compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + const xrayStatement = compiledCfTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0]; + + expect(functionResource).to.deep.equal(compiledFunction); + expect(xrayStatement).to.deep.equal(compiledXrayStatement); + }); + }); + }); + + describe('when IamRoleLambdaExecution is not used', () => { + it('should create necessary resources if a tracingConfig config is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tracingConfig: { + mode: 'Active', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = awsCompileFunctions.serverless.service.provider + .compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + }); + }); + it('should create a function resource with environment config', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact From a0b9951ac2a3fa016e57eb536ef8b07919984e02 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 22 Feb 2019 13:08:46 +0100 Subject: [PATCH 23/70] Move from object syntax to simple string syntax --- docs/providers/aws/guide/functions.md | 17 +++----- docs/providers/aws/guide/serverless.yml.md | 6 +-- .../aws/package/compile/functions/index.js | 6 +-- .../package/compile/functions/index.test.js | 41 ++++--------------- 4 files changed, 19 insertions(+), 51 deletions(-) diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 2351063aa..ab452624a 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -28,8 +28,7 @@ provider: memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 versionFunctions: false # optional, default is true - tracingConfig: # optional, enables tracing for all functions (can be 'Active' or 'PassThrough') - mode: Active + tracingConfig: Active # optional, enables tracing for all functions (can be 'Active' or 'PassThrough') functions: hello: @@ -40,8 +39,7 @@ functions: memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit - tracingConfig: # optional, overwrite, can be 'Active' or 'PassThrough' - mode: PassThrough + tracingConfig: PassThrough # optional, overwrite, can be 'Active' or 'PassThrough' ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -437,7 +435,7 @@ When storing secrets in environment variables, AWS [strongly suggests](http://do ## AWS X-Ray Tracing -You can enable [AWS X-Ray Tracing](https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html) on your Lambda functions through the optional `tracingConfig` config variable in conjucntion with the `mode` property: +You can enable [AWS X-Ray Tracing](https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html) on your Lambda functions through the optional `tracingConfig` config variable: ```yml service: myService @@ -445,8 +443,7 @@ service: myService provider: name: aws runtime: nodejs8.10 - tracingConfig: - mode: Active + tracingConfig: Active ``` You can also set this variable on a per-function basis. This will override the provider level setting if present: @@ -455,10 +452,8 @@ You can also set this variable on a per-function basis. This will override the p functions: hello: handler: handler.hello - tracingConfig: - mode: Active + tracingConfig: Active goodbye: handler: handler.goodbye - tracingConfig: - mode: PassThrough + tracingConfig: PassThrough ``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 4465c58a8..cec881588 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -117,8 +117,7 @@ provider: tags: # Optional service wide function tags foo: bar baz: qux - tracingConfig: - mode: Active # optional, can be 'Active' or 'PassThrough' + tracingConfig: Active # optional, can be 'Active' or 'PassThrough' package: # Optional deployment packaging configuration include: # Specify the directories and files which should be included in the deployment package @@ -165,8 +164,7 @@ functions: individually: true # Enables individual packaging for specific function. If true you must provide package for each function. Defaults to false layers: # An optional list Lambda Layers to use - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y # Layer Version ARN - tracingConfig: - mode: Active # optional, can be 'Active' or 'PassThrough' (overwrites the one defined on the provider level) + tracingConfig: Active # optional, can be 'Active' or 'PassThrough' (overwrites the one defined on the provider level) events: # The Events that trigger this Function - http: # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" path: users/create # Path for this endpoint diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index b2f83ed92..7d44ffdb8 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -246,12 +246,12 @@ class AwsCompileFunctions { || this.serverless.service.provider.tracingConfig; if (tracingConfig) { - if (tracingConfig.mode && typeof tracingConfig.mode === 'string') { + if (typeof tracingConfig === 'string') { const iamRoleLambdaExecution = this.serverless.service.provider .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution; newFunction.Properties.TracingConfig = { - Mode: tracingConfig.mode, + Mode: tracingConfig, }; const stmt = { @@ -272,7 +272,7 @@ class AwsCompileFunctions { ); } } else { - const errorMessage = 'tracingConfig must provide a "mode" property as a string'; + const errorMessage = 'tracingConfig requires the "mode" as a string'; throw new this.serverless.classes.Error(errorMessage); } } diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 662710695..40d638f5d 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -1296,39 +1296,22 @@ describe('AwsCompileFunctions', () => { .split(path.sep).pop(); }); - it('should throw an error if "mode" config parameter is not provided', () => { + it('should throw an error if config paramter is not a string', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', - tracingConfig: 'Active', + tracingConfig: 123, }, }; return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('property as a string'); - }); - - it('should throw an error if "mode" config paramter is not a string', () => { - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', - tracingConfig: { - mode: 123, - }, - }, - }; - - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('property as a string'); + .to.be.rejectedWith('as a string'); }); it('should use a the provider wide tracingConfig config if provided', () => { Object.assign(awsCompileFunctions.serverless.service.provider, { - tracingConfig: { - mode: 'Active', - }, + tracingConfig: 'Active', }); awsCompileFunctions.serverless.service.functions = { @@ -1371,18 +1354,14 @@ describe('AwsCompileFunctions', () => { it('should prefer a function tracingConfig config over a provider config', () => { Object.assign(awsCompileFunctions.serverless.service.provider, { - tracingConfig: { - mode: 'Active', - }, + tracingConfig: 'Active', }); awsCompileFunctions.serverless.service.functions = { func1: { handler: 'func1.function.handler', name: 'new-service-dev-func1', - tracingConfig: { - mode: 'PassThrough', - }, + tracingConfig: 'PassThrough', }, func2: { handler: 'func2.function.handler', @@ -1469,9 +1448,7 @@ describe('AwsCompileFunctions', () => { func: { handler: 'func.function.handler', name: 'new-service-dev-func', - tracingConfig: { - mode: 'Active', - }, + tracingConfig: 'Active', }, }; @@ -1527,9 +1504,7 @@ describe('AwsCompileFunctions', () => { func: { handler: 'func.function.handler', name: 'new-service-dev-func', - tracingConfig: { - mode: 'Active', - }, + tracingConfig: 'Active', }, }; From 72ce7d907ccf9a61fc134b87c63a392d4dfe45e6 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 27 Feb 2019 13:40:39 +0100 Subject: [PATCH 24/70] Simplify tracing configuration --- docs/providers/aws/guide/functions.md | 14 ++++---- docs/providers/aws/guide/serverless.yml.md | 5 +-- .../aws/package/compile/functions/index.js | 19 +++++++---- .../package/compile/functions/index.test.js | 32 +++++++++++-------- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index ab452624a..ecdfe09ed 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -28,7 +28,8 @@ provider: memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 versionFunctions: false # optional, default is true - tracingConfig: Active # optional, enables tracing for all functions (can be 'Active' or 'PassThrough') + tracing: + lambda: true # optional, enables tracing for all functions (can be true (true equals 'Active') 'Active' or 'PassThrough') functions: hello: @@ -39,7 +40,7 @@ functions: memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit - tracingConfig: PassThrough # optional, overwrite, can be 'Active' or 'PassThrough' + tracing: PassThrough # optional, overwrite, can be 'Active' or 'PassThrough' ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -435,7 +436,7 @@ When storing secrets in environment variables, AWS [strongly suggests](http://do ## AWS X-Ray Tracing -You can enable [AWS X-Ray Tracing](https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html) on your Lambda functions through the optional `tracingConfig` config variable: +You can enable [AWS X-Ray Tracing](https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html) on your Lambda functions through the optional `tracing` config variable: ```yml service: myService @@ -443,7 +444,8 @@ service: myService provider: name: aws runtime: nodejs8.10 - tracingConfig: Active + tracing: + lambda: true ``` You can also set this variable on a per-function basis. This will override the provider level setting if present: @@ -452,8 +454,8 @@ You can also set this variable on a per-function basis. This will override the p functions: hello: handler: handler.hello - tracingConfig: Active + tracing: Active goodbye: handler: handler.goodbye - tracingConfig: PassThrough + tracing: PassThrough ``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index cec881588..7425200de 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -117,7 +117,8 @@ provider: tags: # Optional service wide function tags foo: bar baz: qux - tracingConfig: Active # optional, can be 'Active' or 'PassThrough' + tracing: + lambda: true # optional, can be true (true equals 'Active'), 'Active' or 'PassThrough' package: # Optional deployment packaging configuration include: # Specify the directories and files which should be included in the deployment package @@ -164,7 +165,7 @@ functions: individually: true # Enables individual packaging for specific function. If true you must provide package for each function. Defaults to false layers: # An optional list Lambda Layers to use - arn:aws:lambda:region:XXXXXX:layer:LayerName:Y # Layer Version ARN - tracingConfig: Active # optional, can be 'Active' or 'PassThrough' (overwrites the one defined on the provider level) + tracing: Active # optional, can be 'Active' or 'PassThrough' (overwrites the one defined on the provider level) events: # The Events that trigger this Function - http: # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" path: users/create # Path for this endpoint diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 7d44ffdb8..6eee5d75e 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -242,16 +242,23 @@ class AwsCompileFunctions { } } - const tracingConfig = functionObject.tracingConfig - || this.serverless.service.provider.tracingConfig; + const tracing = functionObject.tracing + || (this.serverless.service.provider.tracing + && this.serverless.service.provider.tracing.lambda); + + if (tracing) { + if (typeof tracing === 'boolean' || typeof tracing === 'string') { + let mode = tracing; + + if (typeof tracing === 'boolean') { + mode = 'Active'; + } - if (tracingConfig) { - if (typeof tracingConfig === 'string') { const iamRoleLambdaExecution = this.serverless.service.provider .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution; newFunction.Properties.TracingConfig = { - Mode: tracingConfig, + Mode: mode, }; const stmt = { @@ -272,7 +279,7 @@ class AwsCompileFunctions { ); } } else { - const errorMessage = 'tracingConfig requires the "mode" as a string'; + const errorMessage = 'tracing requires a boolean value or the "mode" provided as a string'; throw new this.serverless.classes.Error(errorMessage); } } diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 40d638f5d..722285ba2 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -1286,7 +1286,7 @@ describe('AwsCompileFunctions', () => { }); }); - describe('when using tracingConfig config', () => { + describe('when using tracing config', () => { let s3Folder; let s3FileName; @@ -1301,7 +1301,7 @@ describe('AwsCompileFunctions', () => { func: { handler: 'func.function.handler', name: 'new-service-dev-func', - tracingConfig: 123, + tracing: 123, }, }; @@ -1309,9 +1309,11 @@ describe('AwsCompileFunctions', () => { .to.be.rejectedWith('as a string'); }); - it('should use a the provider wide tracingConfig config if provided', () => { + it('should use a the provider wide tracing config if provided', () => { Object.assign(awsCompileFunctions.serverless.service.provider, { - tracingConfig: 'Active', + tracing: { + lambda: true, + }, }); awsCompileFunctions.serverless.service.functions = { @@ -1352,16 +1354,18 @@ describe('AwsCompileFunctions', () => { }); }); - it('should prefer a function tracingConfig config over a provider config', () => { + it('should prefer a function tracing config over a provider config', () => { Object.assign(awsCompileFunctions.serverless.service.provider, { - tracingConfig: 'Active', + tracing: { + lambda: 'PassThrough', + }, }); awsCompileFunctions.serverless.service.functions = { func1: { handler: 'func1.function.handler', name: 'new-service-dev-func1', - tracingConfig: 'PassThrough', + tracing: 'Active', }, func2: { handler: 'func2.function.handler', @@ -1387,7 +1391,7 @@ describe('AwsCompileFunctions', () => { Runtime: 'nodejs4.3', Timeout: 6, TracingConfig: { - Mode: 'PassThrough', + Mode: 'Active', }, }, }; @@ -1410,7 +1414,7 @@ describe('AwsCompileFunctions', () => { Runtime: 'nodejs4.3', Timeout: 6, TracingConfig: { - Mode: 'Active', + Mode: 'PassThrough', }, }, }; @@ -1443,12 +1447,12 @@ describe('AwsCompileFunctions', () => { }; }); - it('should create necessary resources if a tracingConfig config is provided', () => { + it('should create necessary resources if a tracing config is provided', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', - tracingConfig: 'Active', + tracing: 'Active', }, }; @@ -1499,12 +1503,12 @@ describe('AwsCompileFunctions', () => { }); describe('when IamRoleLambdaExecution is not used', () => { - it('should create necessary resources if a tracingConfig config is provided', () => { + it('should create necessary resources if a tracing config is provided', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', - tracingConfig: 'Active', + tracing: 'PassThrough', }, }; @@ -1526,7 +1530,7 @@ describe('AwsCompileFunctions', () => { Runtime: 'nodejs4.3', Timeout: 6, TracingConfig: { - Mode: 'Active', + Mode: 'PassThrough', }, }, }; From e7c5acf6b82b70594d13523b509787c99a06f344 Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Wed, 27 Feb 2019 15:54:13 +0300 Subject: [PATCH 25/70] tested authorizers --- docs/providers/aws/events/websocket.md | 87 ++++++++++++++----- docs/providers/aws/guide/serverless.yml.md | 8 ++ .../events/websockets/lib/authorizers.js | 3 + .../events/websockets/lib/permissions.js | 1 + 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/docs/providers/aws/events/websocket.md b/docs/providers/aws/events/websocket.md index 849cc20ae..1a605c7b7 100644 --- a/docs/providers/aws/events/websocket.md +++ b/docs/providers/aws/events/websocket.md @@ -61,7 +61,8 @@ service: serverless-ws-test provider: name: aws runtime: nodejs8.10 - websocketApiRouteSelectionExpression: $request.body.action # custom routes are selected by the value of the action property in the body + websocketsApiName: custom-websockets-api-name + websocketsApiRouteSelectionExpression: $request.body.action # custom routes are selected by the value of the action property in the body functions: connectionHandler: @@ -82,31 +83,73 @@ functions: route: foo # will trigger if $request.body.action === "foo" ``` -## Protect your Websocket backend -To protect your websocket connection use an authorizer function on the `$connect`-route handler. It is only possible to use an authorizer function on this route, as this is the only point in time, where it is possible to prevent the ws-client to connect to our backend at all. As the client is not able to connect, the client can also not use the other websocket routes. +## Using Authorizers +You can enable an authorizer for your connect route by specifying the `authorizer` key in the websocket event definition. -It is also possible to return a "500" in the connection handler, to prevent the ws-client from connecting. +**Note:** AWS only supports authorizers for the `$connect` route. -See this example: +```yml +functions: + connectHandler: + handler: handler.connectHandler + events: + - websocket: + route: $connect + authorizer: auth # references the auth function below + auth: + handler: handler.auth +``` -```js -module.exports.connectionHandler = async (event, context) => { +Or, if your authorizer function is not managed by this service, you can provide an arn instead: - if(event.requestContext.routeKey === '$connect'){ - console.log("NEW CONNECTION INCOMMING"); - if (event.queryStringParameters.token !== 'abc') { - console.log('Connection blocked'); - return { - statusCode: 500 // currently it is not possible to respond with a 4XX - }; - } - } +```yml +functions: + connectHandler: + handler: handler.connectHandler + events: + - websocket: + route: $connect + authorizer: arn:aws:lambda:us-east-1:1234567890:function:auth +``` - console.log('Connection ok'); - return { - statusCode: 200 - }; -} +By default, the `identitySource` property is set to `route.request.header.Auth`, meaning that your request must include the auth token in the `Auth` header of the request. You can overwrite this by specifying your own `identitySource` configuration: + + +```yml +functions: + connectHandler: + handler: handler.connectHandler + events: + - websocket: + route: $connect + authorizer: + name: auth + identitySource: + - 'route.request.header.Auth' + - 'route.request.querystring.Auth' + + auth: + handler: handler.auth +``` +With the above configuration, you can now must pass the auth token in both the `Auth` query string as well as the `Auth` header. + +You can also supply an ARN instead of the name when using the object syntax for the authorizer: + +```yml +functions: + connectHandler: + handler: handler.connectHandler + events: + - websocket: + route: $connect + authorizer: + arn: arn:aws:lambda:us-east-1:1234567890:function:auth + identitySource: + - 'route.request.header.Auth' + - 'route.request.querystring.Auth' + + auth: + handler: handler.auth ``` ## Send a message to a ws-client @@ -142,4 +185,4 @@ module.exports.defaultHandler = async (event, context) => { statusCode: 200 }; } -``` \ No newline at end of file +``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 4cc755f48..40be0546f 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -30,6 +30,8 @@ provider: region: ${opt:region, 'us-east-1'} # Overwrite the default region used. Default is us-east-1 stackName: custom-stack-name # Use a custom name for the CloudFormation stack apiName: custom-api-name # Use a custom name for the API Gateway API + websocketsApiName: custom-websockets-api-name # Use a custom name for the websockets API + websocketsApiRouteSelectionExpression: $request.body.route # custom route selection expression profile: production # The default profile to use with this service memorySize: 512 # Overwrite the default memory size. Default is 1024 timeout: 10 # The default is 6 seconds. Note: API Gateway current maximum is 30 seconds @@ -175,6 +177,12 @@ functions: identityValidationExpression: someRegex - websocket: route: $connect + authorizer: + # name: auth NOTE: you can either use "name" or arn" properties + arn: arn:aws:lambda:us-east-1:1234567890:function:auth + identitySource: + - 'route.request.header.Auth' + - 'route.request.querystring.Auth' - s3: bucket: photos event: s3:ObjectCreated:* diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js index c86375e84..31062df82 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js @@ -6,6 +6,9 @@ const BbPromise = require('bluebird'); module.exports = { compileAuthorizers() { this.validated.events.forEach(event => { + if (!event.authorizer) { + return; + } const websocketsAuthorizerLogicalId = this.provider.naming .getWebsocketsAuthorizerLogicalId(event.authorizer.name); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js index 5f81ff682..fe28e2368 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js @@ -28,6 +28,7 @@ module.exports = { if (event.authorizer) { const websocketsAuthorizerPermissionLogicalId = this.provider.naming .getLambdaWebsocketsPermissionLogicalId(event.authorizer.name); + const authorizerPermissionTemplate = { [websocketsAuthorizerPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', From 544d5099faded74d31f27a29005616c6ef4be376 Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Wed, 27 Feb 2019 16:11:02 +0300 Subject: [PATCH 26/70] change route normalization names --- lib/plugins/aws/lib/naming.js | 8 ++++---- lib/plugins/aws/lib/naming.test.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 910f41868..31f1efd77 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -183,10 +183,10 @@ module.exports = { getNormalizedWebsocketsRouteKey(route) { return route .replace('$', 'S') // dollar sign - .replace('/', 'SL') // slash - .replace('-', 'D') // dash - .replace('_', 'U') // underscore - .replace('.', 'P'); // period + .replace('/', 'Slash') + .replace('-', 'Dash') + .replace('_', 'Underscore') + .replace('.', 'Period'); }, getWebsocketsRouteLogicalId(route) { diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 20ee8b43f..8395f8a17 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -261,16 +261,16 @@ describe('#naming()', () => { .to.equal('Sconnect'); expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo/bar')) - .to.equal('fooSLbar'); + .to.equal('fooSlashbar'); expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo-bar')) - .to.equal('fooDbar'); + .to.equal('fooDashbar'); expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo_bar')) - .to.equal('fooUbar'); + .to.equal('fooUnderscorebar'); expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo.bar')) - .to.equal('fooPbar'); + .to.equal('fooPeriodbar'); }); }); From 2a8fedc2f3dc6be9ffe656b91a493efedd54898d Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Wed, 27 Feb 2019 17:34:29 +0300 Subject: [PATCH 27/70] added tests --- lib/plugins/aws/lib/naming.test.js | 7 + .../compile/events/websockets/index.test.js | 7 +- .../events/websockets/lib/authorizers.test.js | 109 ++++++++++ .../events/websockets/lib/permissions.test.js | 76 +++++++ .../events/websockets/lib/routes.test.js | 46 +++++ .../events/websockets/lib/validate.test.js | 186 +++++++++++++++++- 6 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 643981306..f479e5925 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -283,6 +283,13 @@ describe('#naming()', () => { }); }); + describe('#getWebsocketsAuthorizerLogicalId()', () => { + it('should return the websockets authorizer logical id', () => { + expect(sdk.naming.getWebsocketsAuthorizerLogicalId('auth')) + .to.equal('AuthWebsocketsAuthorizer'); + }); + }); + describe('#getApiGatewayName()', () => { it('should return the composition of stage & service name if custom name not provided', () => { serverless.service.service = 'myService'; diff --git a/lib/plugins/aws/package/compile/events/websockets/index.test.js b/lib/plugins/aws/package/compile/events/websockets/index.test.js index edacacb45..ad3826e25 100644 --- a/lib/plugins/aws/package/compile/events/websockets/index.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/index.test.js @@ -35,6 +35,7 @@ describe('AwsCompileWebsocketsEvents', () => { describe('#constructor()', () => { let compileApiStub; let compileIntegrationsStub; + let compileAuthorizersStub; let compilePermissionsStub; let compileRoutesStub; let compileDeploymentStub; @@ -45,6 +46,8 @@ describe('AwsCompileWebsocketsEvents', () => { .stub(awsCompileWebsocketsEvents, 'compileApi').resolves(); compileIntegrationsStub = sinon .stub(awsCompileWebsocketsEvents, 'compileIntegrations').resolves(); + compileAuthorizersStub = sinon + .stub(awsCompileWebsocketsEvents, 'compileAuthorizers').resolves(); compilePermissionsStub = sinon .stub(awsCompileWebsocketsEvents, 'compilePermissions').resolves(); compileRoutesStub = sinon @@ -58,6 +61,7 @@ describe('AwsCompileWebsocketsEvents', () => { afterEach(() => { awsCompileWebsocketsEvents.compileApi.restore(); awsCompileWebsocketsEvents.compileIntegrations.restore(); + awsCompileWebsocketsEvents.compileAuthorizers.restore(); awsCompileWebsocketsEvents.compilePermissions.restore(); awsCompileWebsocketsEvents.compileRoutes.restore(); awsCompileWebsocketsEvents.compileDeployment.restore(); @@ -91,7 +95,8 @@ describe('AwsCompileWebsocketsEvents', () => { expect(validateStub.calledOnce).to.be.equal(true); expect(compileApiStub.calledAfter(validateStub)).to.be.equal(true); expect(compileIntegrationsStub.calledAfter(compileApiStub)).to.be.equal(true); - expect(compilePermissionsStub.calledAfter(compileIntegrationsStub)).to.be.equal(true); + expect(compileAuthorizersStub.calledAfter(compileIntegrationsStub)).to.be.equal(true); + expect(compilePermissionsStub.calledAfter(compileAuthorizersStub)).to.be.equal(true); expect(compileRoutesStub.calledAfter(compilePermissionsStub)).to.be.equal(true); expect(compileDeploymentStub.calledAfter(compileRoutesStub)).to.be.equal(true); expect(compileStageStub.calledAfter(compileDeploymentStub)).to.be.equal(true); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js new file mode 100644 index 000000000..1925ae6ce --- /dev/null +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js @@ -0,0 +1,109 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileWebsocketsEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#compileAuthorizers()', () => { + let awsCompileWebsocketsEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); + + awsCompileWebsocketsEvents.websocketsApiLogicalId + = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + }); + + it('should create an authorizer resource for routes with authorizer definition', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + }, + }, + ], + }; + + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + AuthWebsocketsAuthorizer: { + Type: 'AWS::ApiGatewayV2::Authorizer', + Properties: { + ApiId: { + Ref: 'WebsocketsApi', + }, + Name: 'auth', + AuthorizerType: 'REQUEST', + AuthorizerUri: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':apigateway:', + { + Ref: 'AWS::Region', + }, + ':lambda:path/2015-03-31/functions/', + { + 'Fn::GetAtt': [ + 'AuthLambdaFunction', + 'Arn', + ], + }, + '/invocations', + ], + ], + }, + IdentitySource: ['route.request.header.Auth'], + }, + }, + }); + }); + }); + + it('should NOT create an authorizer resource for routes with not authorizer definition', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + }, + ], + }; + + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({}); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js index 4930cde80..25e6a8663 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js @@ -94,4 +94,80 @@ describe('#compilePermissions()', () => { }); }); }); + + it('should create a permission resource for authorizer function', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + permission: 'AuthLambdaPermissionWebsockets', + }, + }, + ], + }; + + return awsCompileWebsocketsEvents.compilePermissions().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + FirstLambdaPermissionWebsockets: { + Type: 'AWS::Lambda::Permission', + DependsOn: [ + 'WebsocketsApi', + 'FirstLambdaFunction', + ], + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'FirstLambdaFunction', 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: { + 'Fn::Join': [ + '', + [ + 'apigateway.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + }, + }, + AuthLambdaPermissionWebsockets: { + Type: 'AWS::Lambda::Permission', + DependsOn: [ + 'WebsocketsApi', + 'AuthLambdaPermissionWebsockets', + ], + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'AuthLambdaPermissionWebsockets', + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: { + 'Fn::Join': [ + '', + [ + 'apigateway.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + }, + }, + }); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js index 2a1f8eea6..5a4fb203a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js @@ -82,4 +82,50 @@ describe('#compileRoutes()', () => { }); }); }); + + it('should set authorizer property for the connect route', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + }, + }, + ], + }; + + return awsCompileWebsocketsEvents.compileRoutes().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + SconnectWebsocketsRoute: { + Type: 'AWS::ApiGatewayV2::Route', + Properties: { + ApiId: { + Ref: 'WebsocketsApi', + }, + RouteKey: '$connect', + AuthorizationType: 'CUSTOM', + AuthorizerId: { + Ref: awsCompileWebsocketsEvents.provider.naming + .getWebsocketsAuthorizerLogicalId('auth'), + }, + Target: { + 'Fn::Join': [ + '/', + [ + 'integrations', { + Ref: 'FirstWebsocketsIntegration', + }, + ], + ], + }, + }, + }, + }); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js index fbb0dbcf1..7891ac518 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js @@ -5,7 +5,7 @@ const AwsCompileWebsocketsEvents = require('../index'); const Serverless = require('../../../../../../../Serverless'); const AwsProvider = require('../../../../../provider/awsProvider'); -describe('#validate()', () => { +describe.only('#validate()', () => { let serverless; let awsCompileWebsocketsEvents; @@ -59,6 +59,172 @@ describe('#validate()', () => { ]); }); + it('should add authorizer config when authorizer is specified as a string', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: 'auth', + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + permission: 'AuthLambdaFunction', + }, + }, + ]); + }); + + it('should add authorizer config when authorizer is specified as a string with arn', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: 'arn:aws:auth', + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + 'arn:aws:auth', + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + permission: 'arn:aws:auth', + }, + }, + ]); + }); + + it('should add authorizer config when authorizer is specified as an object', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: { + name: 'auth', + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + }, + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + permission: 'AuthLambdaFunction', + }, + }, + ]); + }); + + it('should add authorizer config when authorizer is specified as an object with arn', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: { + arn: 'arn:aws:auth', + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + }, + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + 'arn:aws:auth', + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + permission: 'arn:aws:auth', + }, + }, + ]); + }); + it('should ignore non-websocket events', () => { awsCompileWebsocketsEvents.serverless.service.functions = { first: { @@ -86,6 +252,24 @@ describe('#validate()', () => { expect(() => awsCompileWebsocketsEvents.validate()).to.throw(/set the "route"/); }); + it('should reject an authorizer definition without name nor arn', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: { + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + }, + }, + }, + ], + }, + }; + expect(() => awsCompileWebsocketsEvents.validate()).to.throw(/You must specify name or arn/); + }); + it('should reject a usage of both, http and websocket event types', () => { awsCompileWebsocketsEvents.serverless.service.functions = { first: { From 4fcda374f0a9b5f7328492fa479bb5b6fd247bb8 Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Wed, 27 Feb 2019 17:34:47 +0300 Subject: [PATCH 28/70] remove only --- .../aws/package/compile/events/websockets/lib/validate.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js index 7891ac518..5f03b4317 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js @@ -5,7 +5,7 @@ const AwsCompileWebsocketsEvents = require('../index'); const Serverless = require('../../../../../../../Serverless'); const AwsProvider = require('../../../../../provider/awsProvider'); -describe.only('#validate()', () => { +describe('#validate()', () => { let serverless; let awsCompileWebsocketsEvents; From cc4bafab061d2ede152c41de7c15ebf6cb5a4eee Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Thu, 28 Feb 2019 15:09:48 +0300 Subject: [PATCH 29/70] dont add websockets policies when using custom roles --- .../compile/events/websockets/lib/api.js | 31 +++++++++++-------- .../compile/events/websockets/lib/api.test.js | 13 ++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.js index 0dbd28045..f52067a6a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/api.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.js @@ -23,20 +23,25 @@ module.exports = { }, }); - // insert policy that allows functions to postToConnection - const websocketsPolicy = { - Effect: 'Allow', - Action: ['execute-api:ManageConnections'], - Resource: ['arn:aws:execute-api:*:*:*/@connections/*'], - }; + const defaultRoleResource = this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.provider.naming.getRoleLogicalId()]; - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement - .push(websocketsPolicy); + if (defaultRoleResource) { + // insert policy that allows functions to postToConnection + const websocketsPolicy = { + Effect: 'Allow', + Action: ['execute-api:ManageConnections'], + Resource: ['arn:aws:execute-api:*:*:*/@connections/*'], + }; + + this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.provider.naming.getRoleLogicalId()] + .Properties + .Policies[0] + .PolicyDocument + .Statement + .push(websocketsPolicy); + } return BbPromise.resolve(); }, diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js index bf31fbcbb..a3ed46155 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js @@ -77,4 +77,17 @@ describe('#compileApi()', () => { }, }); })); + + it('should NOT add the websockets policy if role resource does not exist', () => { + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources = {}; + + return awsCompileWebsocketsEvents + .compileApi().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[roleLogicalId]).to.deep.equal(undefined); + }); + }); }); From 24ffec19af3496aa02182c332552ed83d44aed32 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 28 Feb 2019 15:07:52 -0500 Subject: [PATCH 30/70] Print customized function names correctly in sls info output --- lib/plugins/aws/info/getStackInfo.js | 3 +-- lib/plugins/aws/info/getStackInfo.test.js | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/plugins/aws/info/getStackInfo.js b/lib/plugins/aws/info/getStackInfo.js index 1494214de..71f803fbe 100644 --- a/lib/plugins/aws/info/getStackInfo.js +++ b/lib/plugins/aws/info/getStackInfo.js @@ -40,8 +40,7 @@ module.exports = { this.serverless.service.getAllFunctions().forEach((func) => { const functionInfo = {}; functionInfo.name = func; - functionInfo.deployedName = `${ - this.serverless.service.service}-${this.provider.getStage()}-${func}`; + functionInfo.deployedName = this.serverless.service.getFunction(func).name; this.gatheredData.info.functions.push(functionInfo); }); diff --git a/lib/plugins/aws/info/getStackInfo.test.js b/lib/plugins/aws/info/getStackInfo.test.js index b980407b4..096e71ea5 100644 --- a/lib/plugins/aws/info/getStackInfo.test.js +++ b/lib/plugins/aws/info/getStackInfo.test.js @@ -20,8 +20,8 @@ describe('#getStackInfo()', () => { serverless.setProvider('aws', new AwsProvider(serverless, options)); serverless.service.service = 'my-service'; serverless.service.functions = { - hello: {}, - world: {}, + hello: { name: 'my-service-dev-hello' }, + world: { name: 'customized' }, }; serverless.service.layers = { test: {} }; awsInfo = new AwsInfo(serverless, options); @@ -85,7 +85,7 @@ describe('#getStackInfo()', () => { }, { name: 'world', - deployedName: 'my-service-dev-world', + deployedName: 'customized', }, ], layers: [ From 724515d63996292bfc04520e043c2d2ce33f8a5f Mon Sep 17 00:00:00 2001 From: Nazariy Hazdun Date: Fri, 1 Mar 2019 09:53:38 +0200 Subject: [PATCH 31/70] Add Geniusee to Consultants list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8808fb534..ffb80b3cd 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,7 @@ These consultants use the Serverless Framework and can help you build your serve * [Andrew Griffiths](https://www.andrewgriffithsonline.com/) - Independent consultant specialising in serverless technology * [Trek10](https://www.trek10.com/) * [Parallax](https://parall.ax/) – they also built the [David Guetta Campaign](https://serverlesscode.com/post/david-guetta-online-recording-with-lambda/) +* [Geniusee](https://geniusee.com) * [SC5 Online](https://sc5.io) * [Carrot Creative](https://carrot.is) * [microapps](http://microapps.com) From cf494e0b1a250d9fa99057ac42d7e07aca89f24f Mon Sep 17 00:00:00 2001 From: Boris Date: Fri, 1 Mar 2019 10:11:57 +0100 Subject: [PATCH 32/70] Fix CloudFormation template normalization Signed-off-by: Boris --- lib/plugins/aws/lib/normalizeFiles.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/lib/normalizeFiles.js b/lib/plugins/aws/lib/normalizeFiles.js index a8df6ac3b..3c799124b 100644 --- a/lib/plugins/aws/lib/normalizeFiles.js +++ b/lib/plugins/aws/lib/normalizeFiles.js @@ -7,7 +7,11 @@ module.exports = { const normalizedTemplate = _.cloneDeep(template); // reset all the S3Keys for AWS::Lambda::Function resources - _.forEach(normalizedTemplate.Resources, (value) => { + _.forEach(normalizedTemplate.Resources, (value, key) => { + if (key.startsWith('ApiGatewayDeployment')) { + delete Object.assign(normalizedTemplate.Resources, + { ApiGatewayDeployment: normalizedTemplate.Resources[key] })[key]; + } if (value.Type && value.Type === 'AWS::Lambda::Function') { const newVal = value; newVal.Properties.Code.S3Key = ''; From 3cb0a0676baaae4c454484bba6eee3f7b5439465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A8=D0=B5=D1=81=D1=82=D0=B0=D0=BA=D0=BE=D0=B2=20=D0=9F?= =?UTF-8?q?=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Fri, 1 Mar 2019 16:23:06 +0300 Subject: [PATCH 33/70] Cleanup Serverless.js file from unused code --- lib/Serverless.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/Serverless.js b/lib/Serverless.js index a547d1001..8332e96e5 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -14,7 +14,6 @@ const Service = require('./classes/Service'); const Variables = require('./classes/Variables'); const ServerlessError = require('./classes/Error').ServerlessError; const Version = require('./../package.json').version; -const _ = require('lodash'); class Serverless { constructor(config) { @@ -91,13 +90,6 @@ class Serverless { // populate variables after --help, otherwise help may fail to print // (https://github.com/serverless/serverless/issues/2041) return this.variables.populateService(this.pluginManager.cliOptions) - .then(() => { - if ((!_.includes(this.processedInput.commands, 'deploy') && - !_.includes(this.processedInput.commands, 'remove')) || !this.config.servicePath) { - return BbPromise.resolve(); - } - return BbPromise.resolve(); - }) .then(() => { // merge arrays after variables have been populated // (https://github.com/serverless/serverless/issues/3511) From cad5a689c87bba2bc87d14445589eeeb8498f712 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Sun, 3 Mar 2019 21:36:21 +0000 Subject: [PATCH 34/70] Update getting started page Made two changes: 1. Improves link hitboxes by having the `a` tag wrap the whole `docsSection` instead of just the image and title. Currently hovering over the `docsSection` makes it change colour, but not actually become clickable which I thought was weird behavior. 2. Orders `menuItem`s in same order as main page content. --- docs/getting-started.md | 156 +++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 74 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 981aef5ba..7415973f9 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,11 +6,11 @@ menuOrder: 0 menuItems: - {menuText: AWS Guide, path: /framework/docs/providers/aws/guide/quick-start} - {menuText: Azure Functions Guide, path: /framework/docs/providers/azure/guide/quick-start} - - {menuText: Fn Guide, path: /framework/docs/providers/fn/guide/quick-start} - - {menuText: OpenWhisk Guide, path: /framework/docs/providers/openwhisk/guide/quick-start} + - {menuText: Apache OpenWhisk Guide, path: /framework/docs/providers/openwhisk/guide/quick-start} - {menuText: Google Functions Guide, path: /framework/docs/providers/google/guide/quick-start} - {menuText: Kubeless Guide, path: /framework/docs/providers/kubeless/guide/quick-start} - {menuText: Spotinst Guide, path: /framework/docs/providers/spotinst/guide/quick-start} + - {menuText: Fn Guide, path: /framework/docs/providers/fn/guide/quick-start} - {menuText: Cloudflare Workers Guide, path: /framework/docs/providers/cloudflare/guide/quick-start} --> @@ -30,84 +30,92 @@ Next up, it's time to choose where you'd like your serverless service to run. ## Choose your compute provider
-
-
- - - + +
+
+ +
+
+ Amazon Web Services
Quick Start Guide
+
-
-
-
- - - + + +
+
+ +
+
+ Apache OpenWhisk
Quick Start Guide
+
-
-
-
- - - + + +
+
+ +
+
+ Kubeless
Quick Start Guide
+
-
-
- From 7d819197bb1e38a0a13720c06b36a83d09e4caec Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 4 Mar 2019 12:46:16 +0100 Subject: [PATCH 35/70] Add tests --- lib/plugins/aws/lib/normalizeFiles.js | 1 - lib/plugins/aws/lib/normalizeFiles.test.js | 58 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/lib/normalizeFiles.js b/lib/plugins/aws/lib/normalizeFiles.js index 3c799124b..e0150cc18 100644 --- a/lib/plugins/aws/lib/normalizeFiles.js +++ b/lib/plugins/aws/lib/normalizeFiles.js @@ -6,7 +6,6 @@ module.exports = { normalizeCloudFormationTemplate(template) { const normalizedTemplate = _.cloneDeep(template); - // reset all the S3Keys for AWS::Lambda::Function resources _.forEach(normalizedTemplate.Resources, (value, key) => { if (key.startsWith('ApiGatewayDeployment')) { delete Object.assign(normalizedTemplate.Resources, diff --git a/lib/plugins/aws/lib/normalizeFiles.test.js b/lib/plugins/aws/lib/normalizeFiles.test.js index de1020f09..8f1f25dd3 100644 --- a/lib/plugins/aws/lib/normalizeFiles.test.js +++ b/lib/plugins/aws/lib/normalizeFiles.test.js @@ -35,6 +35,64 @@ describe('normalizeFiles', () => { }); }); + it('should reset the S3 content keys for Lambda layer versions', () => { + const input = { + Resources: { + MyLambdaLayer: { + Type: 'AWS::Lambda::LayerVersion', + Properties: { + Content: { + S3Key: 'some-s3-key-for-the-layer', + }, + }, + }, + }, + }; + + const result = normalizeFiles.normalizeCloudFormationTemplate(input); + + expect(result).to.deep.equal({ + Resources: { + MyLambdaLayer: { + Type: 'AWS::Lambda::LayerVersion', + Properties: { + Content: { + S3Key: '', + }, + }, + }, + }, + }); + }); + + it('should remove the API Gateway Deployment random id', () => { + const input = { + Resources: { + ApiGatewayDeploymentR4ND0M: { + Type: 'AWS::ApiGateway::Deployment', + Properties: { + RestApiId: 'rest-api-id', + StageName: 'dev', + }, + }, + }, + }; + + const result = normalizeFiles.normalizeCloudFormationTemplate(input); + + expect(result).to.deep.equal({ + Resources: { + ApiGatewayDeployment: { + Type: 'AWS::ApiGateway::Deployment', + Properties: { + RestApiId: 'rest-api-id', + StageName: 'dev', + }, + }, + }, + }); + }); + it('should keep other resources untouched', () => { const input = { Resources: { From 07f1081ec584fa44427d5a90f9e73a0cfc6a92ef Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 5 Mar 2019 13:20:53 +0100 Subject: [PATCH 36/70] Update documentation --- docs/providers/aws/events/sqs.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/providers/aws/events/sqs.md b/docs/providers/aws/events/sqs.md index 8ebb23f9c..77c5041f9 100644 --- a/docs/providers/aws/events/sqs.md +++ b/docs/providers/aws/events/sqs.md @@ -34,6 +34,16 @@ functions: - sqs: arn: Fn::ImportValue: MyExportedQueueArnId + - sqs: + arn: + Fn::Join: + - ":" + - - arn + - aws + - sqs + - Ref: AWS::Region + - Ref: AWS::AccountId + - MyOtherQueue ``` ## Setting the BatchSize From c71ae6808a7008bf43ecd02d81d99437a9284352 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 21 Feb 2019 16:49:10 -0500 Subject: [PATCH 37/70] WIP --- lib/plugins/aws/invokeLocal/index.js | 56 ++++------------------------ 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index bf32955ca..e153cd827 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -131,54 +131,14 @@ class AwsInvokeLocal { || 'nodejs4.3'; const handler = this.options.functionObj.handler; - if (runtime.startsWith('nodejs')) { - const handlerPath = handler.split('.')[0]; - const handlerName = handler.split('.')[1]; - return this.invokeLocalNodeJs( - handlerPath, - handlerName, - this.options.data, - this.options.context); - } - - if (_.includes(['python2.7', 'python3.6', 'python3.7'], runtime)) { - const handlerComponents = handler.split(/\./); - const handlerPath = handlerComponents.slice(0, -1).join('.'); - const handlerName = handlerComponents.pop(); - return this.invokeLocalPython( - process.platform === 'win32' ? 'python.exe' : runtime, - handlerPath, - handlerName, - this.options.data, - this.options.context); - } - - if (runtime === 'java8') { - const className = handler.split('::')[0]; - const handlerName = handler.split('::')[1] || 'handleRequest'; - return this.invokeLocalJava( - 'java', - className, - handlerName, - this.serverless.service.package.artifact, - this.options.data, - this.options.context); - } - - if (runtime === 'ruby2.5') { - const handlerComponents = handler.split(/\./); - const handlerPath = handlerComponents[0]; - const handlerName = handlerComponents.slice(1).join('.'); - return this.invokeLocalRuby( - process.platform === 'win32' ? 'ruby.exe' : 'ruby', - handlerPath, - handlerName, - this.options.data, - this.options.context); - } - - throw new this.serverless.classes - .Error('You can only invoke Node.js, Python, Java & Ruby functions locally.'); + return new BbPromise(resolve => { + const docker = spawn('docker', + ['run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, + `lambci/lambda:${runtime}`, handler, JSON.stringify(this.options.data)]); + docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + docker.on('close', () => resolve()); + }); } invokeLocalPython(runtime, handlerPath, handlerName, event, context) { From 0dc1a5f38f7962d232a6195b599738c02452e3b5 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 22 Feb 2019 16:11:11 -0500 Subject: [PATCH 38/70] support for layers!! --- lib/plugins/aws/invokeLocal/index.js | 68 +++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index e153cd827..025b58167 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -10,6 +10,9 @@ const chalk = require('chalk'); const stdin = require('get-stdin'); const spawn = require('child_process').spawn; const inspect = require('util').inspect; +const download = require('download'); +const jsZip = require('jszip'); +const mkdirp = require('mkdirp'); class AwsInvokeLocal { constructor(serverless, options) { @@ -125,20 +128,71 @@ class AwsInvokeLocal { return BbPromise.resolve(); } - invokeLocal() { + buildDockerImage() { const runtime = this.options.functionObj.runtime || this.serverless.service.provider.runtime || 'nodejs4.3'; + + const layers = _.mapKeys( + this.serverless.service.layers, + (value, key) => this.provider.naming.getLambdaLayerLogicalId(key) + ); + const layerPathsPromise = Promise.all((this.options.functionObj.layers || []).map(layer => { + if (layer.Ref) { + return layers[layer.Ref].path; + } + const arnParts = layer.split(':'); + const layerArn = arnParts.slice(0, -1).join(':'); + const layerVersion = Number(arnParts.slice(-1)[0]); + this.serverless.cli.consoleLog(`Downloading layer ${layer}...`); + return this.provider.request( + 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) + .then(layerInfo => download(layerInfo.Content.Location)) + .then(jsZip.loadAsync) + .then(zip => Promise.all( + Object.keys(zip.files) + .map(filename => zip.files[filename].async('string').then(fileData => { + if (filename.endsWith('/')) { + return; + } + mkdirp.sync( + path.dirname(`.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`)); + fs.writeFileAsync( + `.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`, fileData); + })))) + .then(() => `.serverless/invokeLocal/layers/${arnParts[6]}`); + })); + + const imageName = 'sls-docker'; + + return layerPathsPromise.then(layerPaths => new BbPromise((resolve, reject) => { + let dockerfile = `FROM lambci/lambda:${runtime}`; + for (const layerPath of layerPaths) { + dockerfile += `\nADD --chown=sbx_user1051:495 ${layerPath} /opt`; + // fs.copySync(layerPath, `.serverless/invokeLocal/${layerPath}`); + } + mkdirp.sync(path.dirname('.serverless/invokeLocal')); + fs.writeFileSync('.serverless/invokeLocal/Dockerfile', dockerfile); + this.serverless.cli.consoleLog('Building docker image...'); + const docker = spawn('docker', ['build', '-t', imageName, + `${this.serverless.config.servicePath}`, '-f', '.serverless/invokeLocal/Dockerfile']); + docker.on('close', error => (error ? reject(error) : resolve(imageName))); + })); + } + + invokeLocal() { const handler = this.options.functionObj.handler; - return new BbPromise(resolve => { - const docker = spawn('docker', - ['run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, - `lambci/lambda:${runtime}`, handler, JSON.stringify(this.options.data)]); + return this.buildDockerImage().then(imageName => new BbPromise((resolve, reject) => { + const dockerArgs = [ + 'run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, imageName, + handler, JSON.stringify(this.options.data), + ]; + const docker = spawn('docker', dockerArgs); docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - docker.on('close', () => resolve()); - }); + docker.on('close', error => (error ? reject(error) : resolve(imageName))); + })); } invokeLocalPython(runtime, handlerPath, handlerName, event, context) { From 6046d9463aee471f56a829cfe127f96d33f26538 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 22 Feb 2019 17:00:25 -0500 Subject: [PATCH 39/70] fix executable & binary layer contents --- lib/plugins/aws/invokeLocal/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 025b58167..872b4218a 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -151,14 +151,16 @@ class AwsInvokeLocal { .then(jsZip.loadAsync) .then(zip => Promise.all( Object.keys(zip.files) - .map(filename => zip.files[filename].async('string').then(fileData => { + .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { if (filename.endsWith('/')) { return; } mkdirp.sync( path.dirname(`.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`)); - fs.writeFileAsync( - `.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`, fileData); + return fs.writeFileAsync( + `.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`, fileData, { + mode: zip.files[filename].unixPermissions, + }); })))) .then(() => `.serverless/invokeLocal/layers/${arnParts[6]}`); })); From ebf9eef91726698bc4b38580c4af7a81e94427a6 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 22 Feb 2019 17:06:07 -0500 Subject: [PATCH 40/70] support for service wide layer declaration. closes #5582 --- lib/plugins/aws/invokeLocal/index.js | 56 ++++++++++--------- .../aws/package/compile/functions/index.js | 7 +-- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 872b4218a..6a75407df 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -137,33 +137,35 @@ class AwsInvokeLocal { this.serverless.service.layers, (value, key) => this.provider.naming.getLambdaLayerLogicalId(key) ); - const layerPathsPromise = Promise.all((this.options.functionObj.layers || []).map(layer => { - if (layer.Ref) { - return layers[layer.Ref].path; - } - const arnParts = layer.split(':'); - const layerArn = arnParts.slice(0, -1).join(':'); - const layerVersion = Number(arnParts.slice(-1)[0]); - this.serverless.cli.consoleLog(`Downloading layer ${layer}...`); - return this.provider.request( - 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) - .then(layerInfo => download(layerInfo.Content.Location)) - .then(jsZip.loadAsync) - .then(zip => Promise.all( - Object.keys(zip.files) - .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { - if (filename.endsWith('/')) { - return; - } - mkdirp.sync( - path.dirname(`.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`)); - return fs.writeFileAsync( - `.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`, fileData, { - mode: zip.files[filename].unixPermissions, - }); - })))) - .then(() => `.serverless/invokeLocal/layers/${arnParts[6]}`); - })); + const layerPathsPromise = Promise.all( + (this.options.functionObj.layers || this.serverless.service.provider.layers || []) + .map(layer => { + if (layer.Ref) { + return layers[layer.Ref].path; + } + const arnParts = layer.split(':'); + const layerArn = arnParts.slice(0, -1).join(':'); + const layerVersion = Number(arnParts.slice(-1)[0]); + this.serverless.cli.consoleLog(`Downloading layer ${layer}...`); + return this.provider.request( + 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) + .then(layerInfo => download(layerInfo.Content.Location)) + .then(jsZip.loadAsync) + .then(zip => Promise.all( + Object.keys(zip.files) + .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { + if (filename.endsWith('/')) { + return Promise.resolve(); + } + mkdirp.sync( + path.dirname(`.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`)); + return fs.writeFileAsync( + `.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`, fileData, { + mode: zip.files[filename].unixPermissions, + }); + })))) + .then(() => `.serverless/invokeLocal/layers/${arnParts[6]}`); + })); const imageName = 'sls-docker'; diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 6eee5d75e..410480c74 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -361,10 +361,9 @@ class AwsCompileFunctions { if (functionObject.layers && _.isArray(functionObject.layers)) { newFunction.Properties.Layers = functionObject.layers; - /* TODO - is a DependsOn needed? - newLayer.DependsOn = [NEW LAYER??] - .concat(newLayer.DependsOn || []); - */ + } else if (this.serverless.service.provider.layers && _.isArray( + this.serverless.service.provider.layers)) { + newFunction.Properties.Layers = this.serverless.service.provider.layers; } const functionLogicalId = this.provider.naming From 9e5512e66e21d9a6477d067a0e96aa79f0d186ca Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 09:21:51 -0500 Subject: [PATCH 41/70] use path.join, right log func, and pre-download base image if needed --- lib/plugins/aws/invokeLocal/index.js | 69 +++++++++++++++++++--------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 6a75407df..3ac4014d1 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -128,6 +128,28 @@ class AwsInvokeLocal { return BbPromise.resolve(); } + checkDockerImage() { + const runtime = this.options.functionObj.runtime + || this.serverless.service.provider.runtime + || 'nodejs4.3'; + + return new BbPromise((resolve, reject) => { + const docker = spawn('docker', ['pull', `lambci/lambda:${runtime}`]); + let stdout; + docker.stdout.on('data', (buf) => { stdout = buf.toString()}); + docker.on('close', error => (error ? reject(error) : resolve(stdout))); + }).then(stdout => Boolean(stdout.trim())); + } + + pullDockerImage() { + this.serverless.cli.log('Downloading base docker image...'); + + return new BbPromise((resolve, reject) => { + const docker = spawn('docker', ['pull', `lambci/lambda:${runtime}`]); + docker.on('close', error => (error ? reject(error) : resolve())); + }); + + } buildDockerImage() { const runtime = this.options.functionObj.runtime || this.serverless.service.provider.runtime @@ -146,7 +168,7 @@ class AwsInvokeLocal { const arnParts = layer.split(':'); const layerArn = arnParts.slice(0, -1).join(':'); const layerVersion = Number(arnParts.slice(-1)[0]); - this.serverless.cli.consoleLog(`Downloading layer ${layer}...`); + this.serverless.cli.log(`Downloading layer ${layer}...`); return this.provider.request( 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) .then(layerInfo => download(layerInfo.Content.Location)) @@ -154,17 +176,17 @@ class AwsInvokeLocal { .then(zip => Promise.all( Object.keys(zip.files) .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { - if (filename.endsWith('/')) { + if (filename.endsWith(path.sep)) { return Promise.resolve(); } - mkdirp.sync( - path.dirname(`.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`)); - return fs.writeFileAsync( - `.serverless/invokeLocal/layers/${arnParts[6]}/${filename}`, fileData, { + mkdirp.sync(path.join( + '.serverless', 'invokeLocal', 'layers', 'arnParts[6]', 'filename')); + return fs.writeFileAsync(path.join( + '.serverless', 'invokeLocal', 'layers', arnParts[6], filename), fileData, { mode: zip.files[filename].unixPermissions, }); })))) - .then(() => `.serverless/invokeLocal/layers/${arnParts[6]}`); + .then(() => path.join('.serverless', 'invokeLocal', 'layers', arnParts[6])); })); const imageName = 'sls-docker'; @@ -173,13 +195,13 @@ class AwsInvokeLocal { let dockerfile = `FROM lambci/lambda:${runtime}`; for (const layerPath of layerPaths) { dockerfile += `\nADD --chown=sbx_user1051:495 ${layerPath} /opt`; - // fs.copySync(layerPath, `.serverless/invokeLocal/${layerPath}`); } - mkdirp.sync(path.dirname('.serverless/invokeLocal')); - fs.writeFileSync('.serverless/invokeLocal/Dockerfile', dockerfile); - this.serverless.cli.consoleLog('Building docker image...'); + mkdirp.sync(path.join('.serverless', 'invokeLocal')); + const dockerfilePath = path.join('.serverless', 'invokeLocal', 'Dockerfile'); + fs.writeFileSync(dockerfilePath, dockerfile); + this.serverless.cli.log('Building docker image...'); const docker = spawn('docker', ['build', '-t', imageName, - `${this.serverless.config.servicePath}`, '-f', '.serverless/invokeLocal/Dockerfile']); + `${this.serverless.config.servicePath}`, '-f', dockerfilePath]); docker.on('close', error => (error ? reject(error) : resolve(imageName))); })); } @@ -187,16 +209,19 @@ class AwsInvokeLocal { invokeLocal() { const handler = this.options.functionObj.handler; - return this.buildDockerImage().then(imageName => new BbPromise((resolve, reject) => { - const dockerArgs = [ - 'run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, imageName, - handler, JSON.stringify(this.options.data), - ]; - const docker = spawn('docker', dockerArgs); - docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - docker.on('close', error => (error ? reject(error) : resolve(imageName))); - })); + return this.checkDockerImage() + .then(exists => (exists ? Promise.resolve(): this.pullDockerImage())) + .then(() => this.buildDockerImage()) + .then(imageName => new BbPromise((resolve, reject) => { + const dockerArgs = [ + 'run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, imageName, + handler, JSON.stringify(this.options.data), + ]; + const docker = spawn('docker', dockerArgs); + docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + docker.on('close', error => (error ? reject(error) : resolve(imageName))); + })); } invokeLocalPython(runtime, handlerPath, handlerName, event, context) { From 47d08005de5fe8cb4832ded77c20dab4456a8f4a Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 09:54:10 -0500 Subject: [PATCH 42/70] use 's builtin unzipping --- lib/plugins/aws/invokeLocal/index.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 3ac4014d1..3318a4295 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -11,7 +11,6 @@ const stdin = require('get-stdin'); const spawn = require('child_process').spawn; const inspect = require('util').inspect; const download = require('download'); -const jsZip = require('jszip'); const mkdirp = require('mkdirp'); class AwsInvokeLocal { @@ -171,21 +170,10 @@ class AwsInvokeLocal { this.serverless.cli.log(`Downloading layer ${layer}...`); return this.provider.request( 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) - .then(layerInfo => download(layerInfo.Content.Location)) - .then(jsZip.loadAsync) - .then(zip => Promise.all( - Object.keys(zip.files) - .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { - if (filename.endsWith(path.sep)) { - return Promise.resolve(); - } - mkdirp.sync(path.join( - '.serverless', 'invokeLocal', 'layers', 'arnParts[6]', 'filename')); - return fs.writeFileAsync(path.join( - '.serverless', 'invokeLocal', 'layers', arnParts[6], filename), fileData, { - mode: zip.files[filename].unixPermissions, - }); - })))) + .then(layerInfo => download( + layerInfo.Content.Location, + path.join('.serverless', 'invokeLocal', 'layers', arnParts[6]), + { extract: true })) .then(() => path.join('.serverless', 'invokeLocal', 'layers', arnParts[6])); })); From dbd6b0a19ca01649b9d22dfbfc62962ca1a44361 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 11:51:31 -0500 Subject: [PATCH 43/70] rework a bit --- lib/plugins/aws/invokeLocal/index.js | 48 ++++++++++++++++++---------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 3318a4295..7e1335552 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -133,32 +133,33 @@ class AwsInvokeLocal { || 'nodejs4.3'; return new BbPromise((resolve, reject) => { - const docker = spawn('docker', ['pull', `lambci/lambda:${runtime}`]); - let stdout; - docker.stdout.on('data', (buf) => { stdout = buf.toString()}); - docker.on('close', error => (error ? reject(error) : resolve(stdout))); - }).then(stdout => Boolean(stdout.trim())); + const docker = spawn('docker', ['images', '-q', `lambci/lambda:${runtime}`]); + let stdout = ''; + docker.stdout.on('data', (buf) => { stdout += buf.toString(); }); + docker.on('close', error => (error ? reject(error) : resolve(Boolean(stdout.trim())))); + }); } pullDockerImage() { + const runtime = this.options.functionObj.runtime + || this.serverless.service.provider.runtime + || 'nodejs4.3'; + this.serverless.cli.log('Downloading base docker image...'); return new BbPromise((resolve, reject) => { const docker = spawn('docker', ['pull', `lambci/lambda:${runtime}`]); docker.on('close', error => (error ? reject(error) : resolve())); }); - } - buildDockerImage() { - const runtime = this.options.functionObj.runtime - || this.serverless.service.provider.runtime - || 'nodejs4.3'; + getLayerPaths() { const layers = _.mapKeys( this.serverless.service.layers, (value, key) => this.provider.naming.getLambdaLayerLogicalId(key) ); - const layerPathsPromise = Promise.all( + + return Promise.all( (this.options.functionObj.layers || this.serverless.service.provider.layers || []) .map(layer => { if (layer.Ref) { @@ -167,19 +168,31 @@ class AwsInvokeLocal { const arnParts = layer.split(':'); const layerArn = arnParts.slice(0, -1).join(':'); const layerVersion = Number(arnParts.slice(-1)[0]); + const layerContentsPath = path.join( + '.serverless', 'invokeLocal', 'layers', arnParts[6], arnParts[7]); + if (fs.existsSync(layerContentsPath)) { + return layerContentsPath; + } this.serverless.cli.log(`Downloading layer ${layer}...`); return this.provider.request( 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) .then(layerInfo => download( layerInfo.Content.Location, - path.join('.serverless', 'invokeLocal', 'layers', arnParts[6]), + layerContentsPath, { extract: true })) - .then(() => path.join('.serverless', 'invokeLocal', 'layers', arnParts[6])); + .then(() => layerContentsPath); })); + } + + buildDockerImage(layerPaths) { + const runtime = this.options.functionObj.runtime + || this.serverless.service.provider.runtime + || 'nodejs4.3'; + const imageName = 'sls-docker'; - return layerPathsPromise.then(layerPaths => new BbPromise((resolve, reject) => { + return new BbPromise((resolve, reject) => { let dockerfile = `FROM lambci/lambda:${runtime}`; for (const layerPath of layerPaths) { dockerfile += `\nADD --chown=sbx_user1051:495 ${layerPath} /opt`; @@ -191,15 +204,16 @@ class AwsInvokeLocal { const docker = spawn('docker', ['build', '-t', imageName, `${this.serverless.config.servicePath}`, '-f', dockerfilePath]); docker.on('close', error => (error ? reject(error) : resolve(imageName))); - })); + }); } invokeLocal() { const handler = this.options.functionObj.handler; return this.checkDockerImage() - .then(exists => (exists ? Promise.resolve(): this.pullDockerImage())) - .then(() => this.buildDockerImage()) + .then(exists => (exists ? Promise.resolve() : this.pullDockerImage())) + .then(() => this.getLayerPaths()) + .then((layerPaths) => this.buildDockerImage(layerPaths)) .then(imageName => new BbPromise((resolve, reject) => { const dockerArgs = [ 'run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, imageName, From 6ceef7071857cb8618dcaea407a4316240fd0b54 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 14:15:48 -0500 Subject: [PATCH 44/70] move docker to own func to retain old functionality --- lib/plugins/aws/invokeLocal/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 7e1335552..9f81be73b 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -208,6 +208,10 @@ class AwsInvokeLocal { } invokeLocal() { + return this.invokeLocalDocker(); + } + + invokeLocalDocker() { const handler = this.options.functionObj.handler; return this.checkDockerImage() From df1860dc463160f27e40691c08b2b38fcb1bb264 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 14:52:15 -0500 Subject: [PATCH 45/70] restore original functionality! docker as fallback/flag --- lib/plugins/aws/invokeLocal/index.js | 64 ++++++++++++++++++++++++++-- lib/plugins/invoke/invoke.js | 2 + 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 9f81be73b..361019b7c 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -127,6 +127,66 @@ class AwsInvokeLocal { return BbPromise.resolve(); } + invokeLocal() { + const runtime = this.options.functionObj.runtime + || this.serverless.service.provider.runtime + || 'nodejs4.3'; + const handler = this.options.functionObj.handler; + + if (this.options.docker) { + return this.invokeLocalDocker(); + } + + if (runtime.startsWith('nodejs')) { + const handlerPath = handler.split('.')[0]; + const handlerName = handler.split('.')[1]; + return this.invokeLocalNodeJs( + handlerPath, + handlerName, + this.options.data, + this.options.context); + } + + if (_.includes(['python2.7', 'python3.6', 'python3.7'], runtime)) { + const handlerComponents = handler.split(/\./); + const handlerPath = handlerComponents.slice(0, -1).join('.'); + const handlerName = handlerComponents.pop(); + return this.invokeLocalPython( + process.platform === 'win32' ? 'python.exe' : runtime, + handlerPath, + handlerName, + this.options.data, + this.options.context); + } + + if (runtime === 'java8') { + const className = handler.split('::')[0]; + const handlerName = handler.split('::')[1] || 'handleRequest'; + return this.invokeLocalJava( + 'java', + className, + handlerName, + this.serverless.service.package.artifact, + this.options.data, + this.options.context); + } + + if (runtime === 'ruby2.5') { + const handlerComponents = handler.split(/\./); + const handlerPath = handlerComponents[0]; + const handlerName = handlerComponents.slice(1).join('.'); + return this.invokeLocalRuby( + process.platform === 'win32' ? 'ruby.exe' : 'ruby', + handlerPath, + handlerName, + this.options.data, + this.options.context); + } + + return this.invokeLocalDocker(); + } + + checkDockerImage() { const runtime = this.options.functionObj.runtime || this.serverless.service.provider.runtime @@ -207,10 +267,6 @@ class AwsInvokeLocal { }); } - invokeLocal() { - return this.invokeLocalDocker(); - } - invokeLocalDocker() { const handler = this.options.functionObj.handler; diff --git a/lib/plugins/invoke/invoke.js b/lib/plugins/invoke/invoke.js index b01212b82..d07db0216 100644 --- a/lib/plugins/invoke/invoke.js +++ b/lib/plugins/invoke/invoke.js @@ -87,7 +87,9 @@ class Invoke { usage: 'Override environment variables. e.g. --env VAR1=val1 --env VAR2=val2', shortcut: 'e', }, + docker: { usage: 'Flag to turn on docker use for node/python/ruby/java' }, }, + }, }, }, From 50d2c737fa31b8da07d655c221555ba24e9875f4 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 15:42:42 -0500 Subject: [PATCH 46/70] comment out failing test for non node/java/python/ruby runtimes --- lib/plugins/aws/invokeLocal/index.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index c8bba83f4..fda6fdfb8 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -468,11 +468,13 @@ describe('AwsInvokeLocal', () => { }); }); + /* it('throw error when using runtime other than Node.js, Python, Java or Ruby', () => { awsInvokeLocal.options.functionObj.runtime = 'invalid-runtime'; expect(() => awsInvokeLocal.invokeLocal()).to.throw(Error); delete awsInvokeLocal.options.functionObj.runtime; }); + */ }); describe('#invokeLocalNodeJs', () => { From 1e0d7509a101520cca6b0ce6ae51a240bba78c0c Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 15:53:40 -0500 Subject: [PATCH 47/70] add mkdirp as an explicit deplendency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 8113de4c4..098a6d884 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "jwt-decode": "^2.2.0", "lodash": "^4.13.1", "minimist": "^1.2.0", + "mkdirp": "^0.5.1", "moment": "^2.13.0", "nanomatch": "^1.2.13", "node-fetch": "^1.6.0", From 60c4151baf78a3f909814addd7f225f5a5a71901 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 16:43:42 -0500 Subject: [PATCH 48/70] global layer cache --- lib/plugins/aws/invokeLocal/index.js | 29 ++++++++++++++++++++-------- package-lock.json | 5 +++++ package.json | 1 + 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 361019b7c..0e57fb5dc 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -4,6 +4,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); const os = require('os'); const fs = BbPromise.promisifyAll(require('fs')); +const fse = require('fs-extra'); const path = require('path'); const validate = require('../lib/validate'); const chalk = require('chalk'); @@ -12,6 +13,10 @@ const spawn = require('child_process').spawn; const inspect = require('util').inspect; const download = require('download'); const mkdirp = require('mkdirp'); +const cachedir = require('cachedir'); + +const cachePath = path.join(cachedir('serverless'), 'invokeLocal'); + class AwsInvokeLocal { constructor(serverless, options) { @@ -229,17 +234,25 @@ class AwsInvokeLocal { const layerArn = arnParts.slice(0, -1).join(':'); const layerVersion = Number(arnParts.slice(-1)[0]); const layerContentsPath = path.join( - '.serverless', 'invokeLocal', 'layers', arnParts[6], arnParts[7]); + '.serverless', 'layers', arnParts[6], arnParts[7]); + const layerContentsCachePath = path.join( + cachePath, 'layers', arnParts[6], arnParts[7]); if (fs.existsSync(layerContentsPath)) { return layerContentsPath; } - this.serverless.cli.log(`Downloading layer ${layer}...`); - return this.provider.request( - 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) - .then(layerInfo => download( - layerInfo.Content.Location, - layerContentsPath, - { extract: true })) + let downloadPromise = Promise.resolve() + if (!fs.existsSync(layerContentsCachePath)) { + this.serverless.cli.log(`Downloading layer ${layer}...`); + mkdirp.sync(path.join(layerContentsCachePath)); + downloadPromise = this.provider.request( + 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) + .then(layerInfo => download( + layerInfo.Content.Location, + layerContentsPath, + { extract: true })) + } + return downloadPromise + .then(() => fse.copySync(layerContentsCachePath, layerContentsPath)) .then(() => layerContentsPath); })); } diff --git a/package-lock.json b/package-lock.json index 0c506ee20..c769a3879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1166,6 +1166,11 @@ "unset-value": "^1.0.0" } }, + "cachedir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.1.0.tgz", + "integrity": "sha512-xGBpPqoBvn3unBW7oxgb8aJn42K0m9m1/wyjmazah10Fq7bROGG3kRAE6OIyr3U3PIJUqGuebhCEdMk9OKJG0A==" + }, "caller-id": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-id/-/caller-id-0.1.0.tgz", diff --git a/package.json b/package.json index 098a6d884..f66bf30ef 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "async": "^1.5.2", "aws-sdk": "^2.373.0", "bluebird": "^3.5.0", + "cachedir": "^2.1.0", "chalk": "^2.0.0", "ci-info": "^1.1.1", "download": "^5.0.2", From ee3fef217d6c00c1d714ef1d1da692746d82726b Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 25 Feb 2019 17:29:03 -0500 Subject: [PATCH 49/70] support for package artifact based services! --- lib/plugins/aws/invokeLocal/index.js | 47 +++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 0e57fb5dc..aa88f9399 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -14,6 +14,7 @@ const inspect = require('util').inspect; const download = require('download'); const mkdirp = require('mkdirp'); const cachedir = require('cachedir'); +const jszip = require('jszip'); const cachePath = path.join(cachedir('serverless'), 'invokeLocal'); @@ -240,7 +241,7 @@ class AwsInvokeLocal { if (fs.existsSync(layerContentsPath)) { return layerContentsPath; } - let downloadPromise = Promise.resolve() + let downloadPromise = Promise.resolve(); if (!fs.existsSync(layerContentsCachePath)) { this.serverless.cli.log(`Downloading layer ${layer}...`); mkdirp.sync(path.join(layerContentsCachePath)); @@ -249,7 +250,7 @@ class AwsInvokeLocal { .then(layerInfo => download( layerInfo.Content.Location, layerContentsPath, - { extract: true })) + { extract: true })); } return downloadPromise .then(() => fse.copySync(layerContentsCachePath, layerContentsPath)) @@ -280,16 +281,46 @@ class AwsInvokeLocal { }); } + extractArtifact() { + const artifact = _.get(this.options.functionObj, 'package.artifact', _.get( + this.serverless.service, 'package.artifact' + )); + if (!artifact) { + return this.serverless.config.servicePath; + } + return fs.readFileAsync(artifact) + .then(jszip.loadAsync) + .then(zip => Promise.all( + Object.keys(zip.files) + .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { + if (filename.endsWith(path.sep)) { + return Promise.resolve(); + } + mkdirp.sync(path.join( + '.serverless', 'invokeLocal', 'artifact')); + return fs.writeFileAsync(path.join( + '.serverless', 'invokeLocal', 'artifact', filename), fileData, { + mode: zip.files[filename].unixPermissions, + }); + })))) + .then(() => path.join( + this.serverless.config.servicePath, '.serverless', 'invokeLocal', 'artifact')); + } + + invokeLocalDocker() { const handler = this.options.functionObj.handler; - return this.checkDockerImage() - .then(exists => (exists ? Promise.resolve() : this.pullDockerImage())) - .then(() => this.getLayerPaths()) - .then((layerPaths) => this.buildDockerImage(layerPaths)) - .then(imageName => new BbPromise((resolve, reject) => { + return Promise.all([ + this.checkDockerImage().then(exists => (exists ? {} : this.pullDockerImage())), + this.getLayerPaths().then(layerPaths => this.buildDockerImage(layerPaths)), + this.extractArtifact(), + ]) + .then((results) => new BbPromise((resolve, reject) => { + const imageName = results[1]; + const artifactPath = results[2]; const dockerArgs = [ - 'run', '--rm', '-v', `${this.serverless.config.servicePath}:/var/task`, imageName, + 'run', '--rm', '-v', `${artifactPath}:/var/task`, imageName, handler, JSON.stringify(this.options.data), ]; const docker = spawn('docker', dockerArgs); From 34b028c3fb2b02d7fae060c9affbab5a48997039 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 5 Mar 2019 15:44:06 -0500 Subject: [PATCH 50/70] [SLS-6891] fix regerssion with golang check on windows --- lib/plugins/package/lib/packageService.js | 4 ++-- .../package/lib/packageService.test.js | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index d8e9990aa..983ae2c42 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -97,7 +97,7 @@ module.exports = { */ const filesToChmodPlusX = process.platform !== 'win32' ? [] : Object.values(this.serverless.service.functions) - .map(f => Object.assign({ runtime: this.serverless.service.provider.runtime }, f)) + .map(f => Object.assign({ runtime: this.serverless.service.provider.runtime || 'node8.10' }, f)) .filter(f => f.runtime && f.runtime.startsWith('go')) .map(f => f.handler); @@ -137,7 +137,7 @@ module.exports = { const zipFileName = `${functionName}.zip`; const filesToChmodPlusX = []; if (process.platform === 'win32') { - const runtime = functionName.runtime || this.serverless.service.provider.runtime; + const runtime = functionName.runtime || this.serverless.service.provider.runtime || 'node8.10'; if (runtime.startsWith('go')) { filesToChmodPlusX.push(functionObject.handler); } diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index 9747566c7..cda987239 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -347,6 +347,28 @@ describe('#packageService()', () => { serverless.config.servicePath = servicePath; serverless.service.provider.runtime = 'go1.x'; + return expect(packagePlugin.packageService()).to.be.fulfilled + .then(() => BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly( + files, + zipFileName, + undefined, + ['foo'] + ), + ])); + }); + + (process.platfrom === 'win32' ? it : it.skip)( + 'should call zipService with settings & no binaries to chmod for non-go on win32', () => { + const servicePath = 'test'; + const zipFileName = `${serverless.service.service}.zip`; + + serverless.config.servicePath = servicePath; + return expect(packagePlugin.packageService()).to.be.fulfilled .then(() => BbPromise.all([ expect(getExcludesStub).to.be.calledOnce, From f97f0b1ea915d069b15276afc901cdd2e3a4a0bc Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 5 Mar 2019 15:52:22 -0500 Subject: [PATCH 51/70] lint --- lib/plugins/package/lib/packageService.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 983ae2c42..2ba7159e2 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -97,7 +97,9 @@ module.exports = { */ const filesToChmodPlusX = process.platform !== 'win32' ? [] : Object.values(this.serverless.service.functions) - .map(f => Object.assign({ runtime: this.serverless.service.provider.runtime || 'node8.10' }, f)) + .map(f => Object.assign({ + runtime: this.serverless.service.provider.runtime || 'node8.10', + }, f)) .filter(f => f.runtime && f.runtime.startsWith('go')) .map(f => f.handler); @@ -137,7 +139,8 @@ module.exports = { const zipFileName = `${functionName}.zip`; const filesToChmodPlusX = []; if (process.platform === 'win32') { - const runtime = functionName.runtime || this.serverless.service.provider.runtime || 'node8.10'; + const runtime = functionName.runtime || this.serverless.service.provider.runtime + || 'node8.10'; if (runtime.startsWith('go')) { filesToChmodPlusX.push(functionObject.handler); } From accd9316fe41fb5b1d0e513a6f9768c64c17b582 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 5 Mar 2019 16:46:59 -0500 Subject: [PATCH 52/70] test covvg! --- lib/plugins/aws/invokeLocal/index.test.js | 108 ++++++++++++++++++++-- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index fda6fdfb8..c390ad113 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -32,6 +32,7 @@ describe('AwsInvokeLocal', () => { function: 'first', }; serverless = new Serverless(); + serverless.config.servicePath = 'servicePath'; serverless.cli = new CLI(serverless); provider = new AwsProvider(serverless, options); serverless.setProvider('aws', provider); @@ -334,6 +335,7 @@ describe('AwsInvokeLocal', () => { let invokeLocalPythonStub; let invokeLocalJavaStub; let invokeLocalRubyStub; + let invokeLocalDockerStub; beforeEach(() => { invokeLocalNodeJsStub = @@ -344,6 +346,8 @@ describe('AwsInvokeLocal', () => { sinon.stub(awsInvokeLocal, 'invokeLocalJava').resolves(); invokeLocalRubyStub = sinon.stub(awsInvokeLocal, 'invokeLocalRuby').resolves(); + invokeLocalDockerStub = + sinon.stub(awsInvokeLocal, 'invokeLocalDocker').resolves(); awsInvokeLocal.serverless.service.service = 'new-service'; awsInvokeLocal.provider.options.stage = 'dev'; @@ -468,13 +472,24 @@ describe('AwsInvokeLocal', () => { }); }); - /* - it('throw error when using runtime other than Node.js, Python, Java or Ruby', () => { - awsInvokeLocal.options.functionObj.runtime = 'invalid-runtime'; - expect(() => awsInvokeLocal.invokeLocal()).to.throw(Error); - delete awsInvokeLocal.options.functionObj.runtime; + it('should call invokeLocalDocker if using runtime provided', () => { + awsInvokeLocal.options.functionObj.runtime = 'provided'; + awsInvokeLocal.options.functionObj.handler = 'handler.foobar'; + return awsInvokeLocal.invokeLocal().then(() => { + expect(invokeLocalDockerStub.calledOnce).to.be.equal(true); + expect(invokeLocalDockerStub.calledWithExactly()).to.be.equal(true); + }); + }); + + it('should call invokeLocalDocker if using --docker option with nodejs8.10', () => { + awsInvokeLocal.options.functionObj.runtime = 'nodejs8.10'; + awsInvokeLocal.options.functionObj.handler = 'handler.foobar'; + awsInvokeLocal.options.docker = true; + return awsInvokeLocal.invokeLocal().then(() => { + expect(invokeLocalDockerStub.calledOnce).to.be.equal(true); + expect(invokeLocalDockerStub.calledWithExactly()).to.be.equal(true); + }); }); - */ }); describe('#invokeLocalNodeJs', () => { @@ -1099,4 +1114,85 @@ describe('AwsInvokeLocal', () => { }); }); }); + + describe('#invokeLocalDocker()', () => { + let awsInvokeLocalMocked; + let spawnStub; + beforeEach(() => { + awsInvokeLocal.provider.options.stage = 'dev'; + awsInvokeLocal.options = { + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + + spawnStub = sinon.stub().returns({ + stderr: new EventEmitter().on('data', () => {}), + stdout: new EventEmitter().on('data', () => {}), + stdin: { + write: () => {}, + end: () => {}, + }, + on: (key, callback) => callback(), + }); + mockRequire('child_process', { spawn: spawnStub }); + + // Remove Node.js internal "require cache" contents and re-require ./index.js + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + + const AwsInvokeLocalMocked = require('./index'); // eslint-disable-line global-require + + serverless.setProvider('aws', new AwsProvider(serverless, options)); + awsInvokeLocalMocked = new AwsInvokeLocalMocked(serverless, options); + + awsInvokeLocalMocked.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + runtime: 'nodejs8.10', + }, + data: {}, + }; + }); + + + afterEach(() => { + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + }); + + it('watwat', () => + awsInvokeLocalMocked.invokeLocalDocker().then(() => { + expect(spawnStub.getCall(0).args).to.deep.equal(['docker', + ['images', '-q', 'lambci/lambda:nodejs8.10']]); + expect(spawnStub.getCall(1).args).to.deep.equal(['docker', [ + 'build', + '-t', + 'sls-docker', + 'servicePath', + '-f', + '.serverless/invokeLocal/Dockerfile', + ]]); + expect(spawnStub.getCall(2).args).to.deep.equal(['docker', + ['pull', 'lambci/lambda:nodejs8.10']]); + expect(spawnStub.getCall(3).args).to.deep.equal(['docker', [ + 'run', + '--rm', + '-v', + 'servicePath:/var/task', + 'sls-docker', + 'handler.hello', + '{}', + ]]); + }) + ); + }); }); From af92c09335688c6560e9b89e148b89a76f75eb85 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 6 Mar 2019 09:27:42 -0500 Subject: [PATCH 53/70] review feedback --- lib/plugins/aws/invokeLocal/index.js | 18 +++++++++--------- lib/plugins/aws/invokeLocal/index.test.js | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index aa88f9399..4177f638f 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -211,7 +211,7 @@ class AwsInvokeLocal { || this.serverless.service.provider.runtime || 'nodejs4.3'; - this.serverless.cli.log('Downloading base docker image...'); + this.serverless.cli.log('Downloading base Docker image...'); return new BbPromise((resolve, reject) => { const docker = spawn('docker', ['pull', `lambci/lambda:${runtime}`]); @@ -225,7 +225,7 @@ class AwsInvokeLocal { (value, key) => this.provider.naming.getLambdaLayerLogicalId(key) ); - return Promise.all( + return BbPromise.all( (this.options.functionObj.layers || this.serverless.service.provider.layers || []) .map(layer => { if (layer.Ref) { @@ -241,7 +241,7 @@ class AwsInvokeLocal { if (fs.existsSync(layerContentsPath)) { return layerContentsPath; } - let downloadPromise = Promise.resolve(); + let downloadPromise = BbPromise.resolve(); if (!fs.existsSync(layerContentsCachePath)) { this.serverless.cli.log(`Downloading layer ${layer}...`); mkdirp.sync(path.join(layerContentsCachePath)); @@ -274,7 +274,7 @@ class AwsInvokeLocal { mkdirp.sync(path.join('.serverless', 'invokeLocal')); const dockerfilePath = path.join('.serverless', 'invokeLocal', 'Dockerfile'); fs.writeFileSync(dockerfilePath, dockerfile); - this.serverless.cli.log('Building docker image...'); + this.serverless.cli.log('Building Docker image...'); const docker = spawn('docker', ['build', '-t', imageName, `${this.serverless.config.servicePath}`, '-f', dockerfilePath]); docker.on('close', error => (error ? reject(error) : resolve(imageName))); @@ -290,11 +290,11 @@ class AwsInvokeLocal { } return fs.readFileAsync(artifact) .then(jszip.loadAsync) - .then(zip => Promise.all( + .then(zip => BbPromise.all( Object.keys(zip.files) .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { if (filename.endsWith(path.sep)) { - return Promise.resolve(); + return BbPromise.resolve(); } mkdirp.sync(path.join( '.serverless', 'invokeLocal', 'artifact')); @@ -311,7 +311,7 @@ class AwsInvokeLocal { invokeLocalDocker() { const handler = this.options.functionObj.handler; - return Promise.all([ + return BbPromise.all([ this.checkDockerImage().then(exists => (exists ? {} : this.pullDockerImage())), this.getLayerPaths().then(layerPaths => this.buildDockerImage(layerPaths)), this.extractArtifact(), @@ -509,7 +509,7 @@ class AwsInvokeLocal { this.serverless.cli.consoleLog(JSON.stringify(result, null, 4)); } - return new Promise((resolve) => { + return new BbPromise((resolve) => { const callback = (err, result) => { if (!hasResponded) { hasResponded = true; @@ -557,7 +557,7 @@ class AwsInvokeLocal { const maybeThennable = lambda(event, context, callback); if (!_.isUndefined(maybeThennable)) { - return Promise.resolve(maybeThennable) + return BbPromise.resolve(maybeThennable) .then( callback.bind(this, null), callback.bind(this) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index c390ad113..6165549c9 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -1169,11 +1169,13 @@ describe('AwsInvokeLocal', () => { delete require.cache[require.resolve('child_process')]; }); - it('watwat', () => + it('calls docker', () => awsInvokeLocalMocked.invokeLocalDocker().then(() => { expect(spawnStub.getCall(0).args).to.deep.equal(['docker', ['images', '-q', 'lambci/lambda:nodejs8.10']]); - expect(spawnStub.getCall(1).args).to.deep.equal(['docker', [ + expect(spawnStub.getCall(1).args).to.deep.equal(['docker', + ['pull', 'lambci/lambda:nodejs8.10']]); + expect(spawnStub.getCall(2).args).to.deep.equal(['docker', [ 'build', '-t', 'sls-docker', @@ -1181,8 +1183,6 @@ describe('AwsInvokeLocal', () => { '-f', '.serverless/invokeLocal/Dockerfile', ]]); - expect(spawnStub.getCall(2).args).to.deep.equal(['docker', - ['pull', 'lambci/lambda:nodejs8.10']]); expect(spawnStub.getCall(3).args).to.deep.equal(['docker', [ 'run', '--rm', From 615c79362a36f3c30d6b8f5d6fcf2613c9ff2d31 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 7 Mar 2019 13:30:55 +0100 Subject: [PATCH 54/70] Update docs --- docs/providers/aws/events/apigateway.md | 12 ++++++------ docs/providers/aws/guide/serverless.yml.md | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 4180196cd..62f2f50e0 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -981,7 +981,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework restApiRootResourceId: xxxxxxxxxx # Root resource, represent as / path - description: some_description # optional - description of deployment history + description: Some Description # optional - description of deployment history functions: ... @@ -997,7 +997,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx - description: some_description # optional + description: Some Description functions: create: @@ -1014,7 +1014,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx - description: some_description # optional + description: Some Description functions: create: @@ -1033,7 +1033,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx - description: some_description # optional + description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1048,7 +1048,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx - description: some_description # optional + description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1066,7 +1066,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # restApiRootResourceId: xxxxxxxxxx # Optional - description: some_description # optional + description: Some Description restApiResources: /posts: xxxxxxxxxx /categories: xxxxxxxxx diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index a735bfb5e..6fc7f54bc 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -61,6 +61,7 @@ provider: '/users/create': xxxxxxxxxx apiKeySourceType: HEADER # Source of API key for usage plan. HEADER or AUTHORIZER. minimumCompressionSize: 1024 # Compress response when larger than specified size in bytes (must be between 0 and 10485760) + description: Some Description # optional description for the API Gateway stage deployment usagePlan: # Optional usage plan configuration quota: limit: 5000 From fc2600b2d2eb9f0785091dad030915a789c1319a Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 7 Mar 2019 13:36:05 +0100 Subject: [PATCH 55/70] Update Description in tests --- .../package/compile/events/apiGateway/lib/deployment.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index c28e31d06..a3354e05d 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -53,8 +53,9 @@ describe('#compileDeployment()', () => { it('should create a deployment resource with description', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { - description: 'apiG_description', + description: 'Some Description', }; + return awsCompileApigEvents .compileDeployment().then(() => { const apiGatewayDeploymentLogicalId = Object @@ -71,7 +72,7 @@ describe('#compileDeployment()', () => { RestApiId: { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, }, - Description: 'apiG_description', + Description: 'Some Description', StageName: 'dev', }, }); From 2a846cabb86304d2bd5770f21a62dcfed498fe57 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 7 Mar 2019 14:44:13 +0100 Subject: [PATCH 56/70] Add check for packageReference / Add tests --- .../aws/package/lib/saveServiceState.js | 10 ++-- .../aws/package/lib/saveServiceState.test.js | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/lib/saveServiceState.js b/lib/plugins/aws/package/lib/saveServiceState.js index 50069151b..69056668c 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.js +++ b/lib/plugins/aws/package/lib/saveServiceState.js @@ -29,10 +29,12 @@ module.exports = { _.forEach(strippedService.functions, func => { const packageRef = func.package; - packageRef.artifact = path.join( - this.packagePath, - packageRef.artifact.substr(packageRef.artifact.lastIndexOf('\\') + 1) - ); + if (!_.isEmpty(packageRef)) { + packageRef.artifact = path.join( + this.packagePath, + packageRef.artifact.substr(packageRef.artifact.lastIndexOf('\\') + 1) + ); + } }); const state = { diff --git a/lib/plugins/aws/package/lib/saveServiceState.test.js b/lib/plugins/aws/package/lib/saveServiceState.test.js index 064fb861c..a94e625fd 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.test.js +++ b/lib/plugins/aws/package/lib/saveServiceState.test.js @@ -101,4 +101,57 @@ describe('#saveServiceState()', () => { .to.equal(true); }); }); + + it('should resolve package artifact paths on function levels', () => { + const filePath = path.join( + awsPackage.serverless.config.servicePath, + '.serverless', + 'service-state.json' + ); + + const distPath = path.join('..', 'dist'); + const firstFuncArtifactPath = path.join('..', 'artifacts', 'first.zip'); + + awsPackage.packagePath = distPath; + serverless.service.package.path = distPath; + serverless.service.functions = { + first: { + package: { + artifact: firstFuncArtifactPath, + }, + }, + second: { + package: {}, + }, + }; + + return awsPackage.saveServiceState().then(() => { + const expectedStateFileContent = { + service: { + provider: { + compiledCloudFormationTemplate: 'compiled content', + }, + functions: { + first: { + package: { + artifact: firstFuncArtifactPath, + }, + }, + second: { + package: {}, + }, + }, + }, + package: { + individually: false, + artifactDirectoryName: 'artifact-directory', + artifact: 'service.zip', + }, + }; + + expect(getServiceStateFileNameStub.calledOnce).to.equal(true); + expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) + .to.equal(true); + }); + }); }); From ecdd28f5096869f920fa7c26ab767ed076d300c1 Mon Sep 17 00:00:00 2001 From: Matheus Silva Santos de Oliveira Date: Sun, 10 Mar 2019 10:27:42 -0300 Subject: [PATCH 57/70] Add links to the respective core concepts I think it would be a good idea to have a link to the core concepts when mentioning them. --- docs/providers/aws/guide/resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/guide/resources.md b/docs/providers/aws/guide/resources.md index 9841a71fd..4384818fd 100644 --- a/docs/providers/aws/guide/resources.md +++ b/docs/providers/aws/guide/resources.md @@ -12,7 +12,7 @@ layout: Doc # AWS - Resources -If you are using AWS as a provider for your Service, all *Resources* are other AWS infrastructure resources which the AWS Lambda functions in your *Service* depend on, like AWS DynamoDB or AWS S3. +If you are using AWS as a provider for your Service, all [*Resources*](./intro.md#resources) are other AWS infrastructure resources which the AWS Lambda functions in your [*Service*](./intro.md#services) depend on, like AWS DynamoDB or AWS S3. Using the Serverless Framework, you can define the infrastructure resources you need in `serverless.yml`, and easily deploy them. From 8c0d2825fd598e9d1f019cf7de10ee09786bd050 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 11 Mar 2019 14:48:07 -0400 Subject: [PATCH 58/70] document docker support! --- docs/providers/aws/cli-reference/invoke-local.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/providers/aws/cli-reference/invoke-local.md b/docs/providers/aws/cli-reference/invoke-local.md index 120aa51a4..6597cdf24 100644 --- a/docs/providers/aws/cli-reference/invoke-local.md +++ b/docs/providers/aws/cli-reference/invoke-local.md @@ -29,6 +29,8 @@ serverless invoke local --function functionName - `--contextPath` or `-x`, The path to a json file holding input context to be passed to the invoked function. This path is relative to the root directory of the service. - `--context` or `-c`, String data to be passed as a context to your function. Same like with `--data`, context included in `--contextPath` will overwrite the context you passed with `--context` flag. * `--env` or `-e` String representing an environment variable to set when invoking your function, in the form `=`. Can be repeated for more than one environment variable. +* `--docker` Enable docker support for NodeJS/Python/Ruby/Java. Enabled by default for other + runtimes. ## Environment @@ -107,7 +109,11 @@ serverless invoke local -f functionName -e VAR1=value1 -e VAR2=value2 ### Limitations -Currently, `invoke local` only supports the NodeJs, Python, Java, & Ruby runtimes. +Use of the `--docker` flag and runtimes other than NodeJs, Python, Java, & Ruby depend on having +[Docker](https://www.docker.com/) installed. On MacOS & Windows, install +[Docker Desktop](https://www.docker.com/products/docker-desktop); On Linux install +[Docker engine](https://www.docker.com/products/docker-engine) and ensure your user is in the +`docker` group so that you can invoke docker without `sudo`. **Note:** In order to get correct output when using Java runtime, your Response class must implement `toString()` method. From e7df2df6d12f3f1ad79296f8ecb66a65218edde8 Mon Sep 17 00:00:00 2001 From: Dale Fenton Date: Mon, 11 Mar 2019 16:56:17 -0400 Subject: [PATCH 59/70] add type configuration to aws serverless.yml guide for authorizer --- docs/providers/aws/guide/serverless.yml.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 6fc7f54bc..069fc3288 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -181,6 +181,7 @@ functions: resultTtlInSeconds: 0 identitySource: method.request.header.Authorization identityValidationExpression: someRegex + type: token # token or request. Determines input to the authorier function, called with the auth token or the entire request event. Defaults to token - websocket: route: $connect authorizer: From 756dd14dc94a1bb14b1c6d353bcc5e178226da08 Mon Sep 17 00:00:00 2001 From: Jacob Griswold-Moran Date: Tue, 12 Mar 2019 10:57:46 -0400 Subject: [PATCH 60/70] adds inputTransformer option to aws schedule events This change allows the InputTransformer cloudformation option to be used in aws `schedule` event types. --- docs/providers/aws/events/schedule.md | 7 ++ docs/providers/aws/guide/serverless.yml.md | 5 ++ .../package/compile/events/schedule/index.js | 31 ++++++- .../compile/events/schedule/index.test.js | 80 +++++++++++++++++++ 4 files changed, 120 insertions(+), 3 deletions(-) diff --git a/docs/providers/aws/events/schedule.md b/docs/providers/aws/events/schedule.md index a74c5a3d3..162c276a9 100644 --- a/docs/providers/aws/events/schedule.md +++ b/docs/providers/aws/events/schedule.md @@ -47,6 +47,13 @@ functions: rate: cron(0 12 * * ? *) enabled: false inputPath: '$.stageVariables' + - schedule: + rate: rate(2 hours) + enabled: true + inputTransformer: + inputPathsMap: + eventTime: '$.time' + inputTemplate: '{"time": , "key1": "value1"}' ``` ## Specify Name and Description diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 6fc7f54bc..50a7c4855 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -200,12 +200,17 @@ functions: description: a description of my scheduled event's purpose rate: rate(10 minutes) enabled: false + # Note, you can use only one of input, inputPath, or inputTransformer input: key1: value1 key2: value2 stageParams: stage: dev inputPath: '$.stageVariables' + inputTransformer: + inputPathsMap: + eventTime: '$.time' + inputTemplate: '{"time": , "key1": "value1"}' - sns: topicName: aggregate displayName: Data aggregation pipeline diff --git a/lib/plugins/aws/package/compile/events/schedule/index.js b/lib/plugins/aws/package/compile/events/schedule/index.js index 2639509ae..e44e53506 100644 --- a/lib/plugins/aws/package/compile/events/schedule/index.js +++ b/lib/plugins/aws/package/compile/events/schedule/index.js @@ -40,6 +40,7 @@ class AwsCompileScheduledEvents { let State; let Input; let InputPath; + let InputTransformer; let Name; let Description; @@ -56,13 +57,15 @@ class AwsCompileScheduledEvents { } Input = event.schedule.input; InputPath = event.schedule.inputPath; + InputTransformer = event.schedule.inputTransformer; Name = event.schedule.name; Description = event.schedule.description; - if (Input && InputPath) { + const inputOptions = [Input, InputPath, InputTransformer].filter(i => i); + if (inputOptions.length > 1) { const errorMessage = [ - 'You can\'t set both input & inputPath properties at the', - 'same time for schedule events.', + 'You can only set one of input, inputPath, or inputTransformer ', + 'properties at the same time for schedule events. ', 'Please check the AWS docs for more info', ].join(''); throw new this.serverless.classes @@ -90,6 +93,9 @@ class AwsCompileScheduledEvents { // escape quotes to favor JSON.parse Input = Input.replace(/\"/g, '\\"'); // eslint-disable-line } + if (InputTransformer) { + InputTransformer = this.formatInputTransformer(InputTransformer); + } } else if (this.validateScheduleSyntax(event.schedule)) { ScheduleExpression = event.schedule; State = 'ENABLED'; @@ -118,6 +124,7 @@ class AwsCompileScheduledEvents { "Targets": [{ ${Input ? `"Input": "${Input}",` : ''} ${InputPath ? `"InputPath": "${InputPath}",` : ''} + ${InputTransformer ? `"InputTransformer": ${InputTransformer},` : ''} "Arn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Id": "${scheduleId}" }] @@ -158,6 +165,24 @@ class AwsCompileScheduledEvents { return typeof input === 'string' && (rateSyntaxPattern.test(input) || cronSyntaxPattern.test(input)); } + + formatInputTransformer(inputTransformer) { + if (!inputTransformer.inputTemplate) { + throw new this.serverless.classes.Error( + 'The inputTemplate key is required when specifying an ' + + 'inputTransformer for a schedule event' + ); + } + const cfmOutput = { + // InputTemplate is required + InputTemplate: inputTransformer.inputTemplate, + }; + // InputPathsMap is optional + if (inputTransformer.inputPathsMap) { + cfmOutput.InputPathsMap = inputTransformer.inputPathsMap; + } + return JSON.stringify(cfmOutput); + } } module.exports = AwsCompileScheduledEvents; diff --git a/lib/plugins/aws/package/compile/events/schedule/index.test.js b/lib/plugins/aws/package/compile/events/schedule/index.test.js index 09745ef20..1be50220e 100644 --- a/lib/plugins/aws/package/compile/events/schedule/index.test.js +++ b/lib/plugins/aws/package/compile/events/schedule/index.test.js @@ -405,6 +405,38 @@ describe('AwsCompileScheduledEvents', () => { ).to.equal('{"key":"value"}'); }); + it('should respect inputTransformer variable', () => { + awsCompileScheduledEvents.serverless.service.functions = { + first: { + events: [ + { + schedule: { + rate: 'rate(10 minutes)', + enabled: false, + inputTransformer: { + inputPathsMap: { + eventTime: '$.time', + }, + inputTemplate: '{"time": , "key1": "value1"}', + }, + }, + }, + ], + }, + }; + + awsCompileScheduledEvents.compileScheduledEvents(); + + expect(awsCompileScheduledEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 + .Properties.Targets[0].InputTransformer + ).to.eql({ + InputTemplate: '{"time": , "key1": "value1"}', + InputPathsMap: { eventTime: '$.time' }, + }); + }); + + it('should throw an error when both Input and InputPath are set', () => { awsCompileScheduledEvents.serverless.service.functions = { first: { @@ -426,6 +458,32 @@ describe('AwsCompileScheduledEvents', () => { expect(() => awsCompileScheduledEvents.compileScheduledEvents()).to.throw(Error); }); + it('should throw an error when both Input and InputTransformer are set', () => { + awsCompileScheduledEvents.serverless.service.functions = { + first: { + events: [ + { + schedule: { + rate: 'rate(10 minutes)', + enabled: false, + input: { + key: 'value', + }, + inputTransformer: { + inputPathsMap: { + eventTime: '$.time', + }, + inputTemplate: '{"time": , "key1": "value1"}', + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileScheduledEvents.compileScheduledEvents()).to.throw(Error); + }); + it('should not throw an error when Input body is a valid JSON string', () => { awsCompileScheduledEvents.serverless.service.functions = { first: { @@ -466,6 +524,28 @@ describe('AwsCompileScheduledEvents', () => { expect(() => awsCompileScheduledEvents.compileScheduledEvents()).to.throw(Error); }); + it('should throw an error when inputTransformer does not have inputTemplate', () => { + awsCompileScheduledEvents.serverless.service.functions = { + first: { + events: [ + { + schedule: { + rate: 'rate(10 minutes)', + enabled: false, + inputTransformer: { + inputPathsMap: { + eventTime: '$.time', + }, + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileScheduledEvents.compileScheduledEvents()).to.throw(Error); + }); + it('should not create corresponding resources when scheduled events are not given', () => { awsCompileScheduledEvents.serverless.service.functions = { first: { From 0d0cba8334d4fe11ecc48661d31b17e27b20724c Mon Sep 17 00:00:00 2001 From: Jacob Griswold-Moran Date: Tue, 12 Mar 2019 11:20:33 -0400 Subject: [PATCH 61/70] adds inputTransformer option to aws cloudwatchEvent events This change allows the InputTransformer cloudformation option to be used in aws `cloudwatchEvent` event types. --- docs/providers/aws/events/cloudwatch-event.md | 13 +++ docs/providers/aws/guide/serverless.yml.md | 6 +- .../compile/events/cloudWatchEvent/index.js | 31 ++++++- .../events/cloudWatchEvent/index.test.js | 92 +++++++++++++++++++ 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/docs/providers/aws/events/cloudwatch-event.md b/docs/providers/aws/events/cloudwatch-event.md index 5ffbd64bd..fc9d32dfb 100644 --- a/docs/providers/aws/events/cloudwatch-event.md +++ b/docs/providers/aws/events/cloudwatch-event.md @@ -89,6 +89,19 @@ functions: state: - pending inputPath: '$.stageVariables' + - cloudwatchEvent: + event: + source: + - "aws.ec2" + detail-type: + - "EC2 Instance State-change Notification" + detail: + state: + - pending + inputTransformer: + inputPathsMap: + eventTime: '$.time' + inputTemplate: '{"time": , "key1": "value1"}' ``` ## Specifying a Description diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 50a7c4855..b317d0334 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -243,13 +243,17 @@ functions: detail: state: - pending - # Note: you can either use "input" or "inputPath" + # Note, you can use only one of input, inputPath, or inputTransformer input: key1: value1 key2: value2 stageParams: stage: dev inputPath: '$.stageVariables' + inputTransformer: + inputPathsMap: + eventTime: '$.time' + inputTemplate: '{"time": , "key1": "value1"}' - cloudwatchLog: logGroup: '/aws/lambda/hello' filter: '{$.userIdentity.type = Root}' diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js index 6f7fd8acb..9f8fff08d 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js @@ -25,6 +25,7 @@ class AwsCompileCloudWatchEventEvents { let State; let Input; let InputPath; + let InputTransformer; let Description; let Name; @@ -45,13 +46,15 @@ class AwsCompileCloudWatchEventEvents { } Input = event.cloudwatchEvent.input; InputPath = event.cloudwatchEvent.inputPath; + InputTransformer = event.cloudwatchEvent.inputTransformer; Description = event.cloudwatchEvent.description; Name = event.cloudwatchEvent.name; - if (Input && InputPath) { + const inputOptions = [Input, InputPath, InputTransformer].filter(i => i); + if (inputOptions.length > 1) { const errorMessage = [ - 'You can\'t set both input & inputPath properties at the', - 'same time for cloudwatch events.', + 'You can only set one of input, inputPath, or inputTransformer ', + 'properties at the same time for cloudwatch events. ', 'Please check the AWS docs for more info', ].join(''); throw new this.serverless.classes.Error(errorMessage); @@ -64,6 +67,9 @@ class AwsCompileCloudWatchEventEvents { // escape quotes to favor JSON.parse Input = Input.replace(/\"/g, '\\"'); // eslint-disable-line } + if (InputTransformer) { + InputTransformer = this.formatInputTransformer(InputTransformer); + } } else { const errorMessage = [ `CloudWatch event of function "${functionName}" is not an object`, @@ -93,6 +99,7 @@ class AwsCompileCloudWatchEventEvents { "Targets": [{ ${Input ? `"Input": "${Input.replace(/\\n|\\r/g, '')}",` : ''} ${InputPath ? `"InputPath": "${InputPath.replace(/\r?\n/g, '')}",` : ''} + ${InputTransformer ? `"InputTransformer": ${InputTransformer},` : ''} "Arn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Id": "${cloudWatchId}" }] @@ -128,6 +135,24 @@ class AwsCompileCloudWatchEventEvents { } }); } + + formatInputTransformer(inputTransformer) { + if (!inputTransformer.inputTemplate) { + throw new this.serverless.classes.Error( + 'The inputTemplate key is required when specifying an ' + + 'inputTransformer for a cloudwatchEvent event' + ); + } + const cfmOutput = { + // InputTemplate is required + InputTemplate: inputTransformer.inputTemplate, + }; + // InputPathsMap is optional + if (inputTransformer.inputPathsMap) { + cfmOutput.InputPathsMap = inputTransformer.inputPathsMap; + } + return JSON.stringify(cfmOutput); + } } module.exports = AwsCompileCloudWatchEventEvents; diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js index 0dd5123a2..e7decdb2e 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js @@ -218,6 +218,42 @@ describe('awsCompileCloudWatchEventEvents', () => { ).to.equal('{"key":"value"}'); }); + it('should respect inputTransformer variable', () => { + awsCompileCloudWatchEventEvents.serverless.service.functions = { + first: { + events: [ + { + cloudwatchEvent: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + inputTransformer: { + inputPathsMap: { + eventTime: '$.time', + }, + inputTemplate: '{"time": , "key1": "value1"}', + }, + }, + }, + ], + }, + }; + + awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); + + expect(awsCompileCloudWatchEventEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 + .Properties.Targets[0].InputTransformer + ).to.eql({ + InputTemplate: '{"time": , "key1": "value1"}', + InputPathsMap: { eventTime: '$.time' }, + }); + }); + + it('should respect description variable', () => { awsCompileCloudWatchEventEvents.serverless.service.functions = { first: { @@ -328,6 +364,62 @@ describe('awsCompileCloudWatchEventEvents', () => { expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error); }); + it('should throw an error when both Input and InputTransformer are set', () => { + awsCompileCloudWatchEventEvents.serverless.service.functions = { + first: { + events: [ + { + cloudwatchEvent: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + input: { + key: 'value', + }, + inputTransformer: { + inputPathsMap: { + eventTime: '$.time', + }, + inputTemplate: '{"time": , "key1": "value1"}', + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error); + }); + + it('should throw an error when inputTransformer does not have inputTemplate', () => { + awsCompileCloudWatchEventEvents.serverless.service.functions = { + first: { + events: [ + { + cloudwatchEvent: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + inputTransformer: { + inputPathsMap: { + eventTime: '$.time', + }, + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error); + }); + it('should respect variables if multi-line variables is given', () => { awsCompileCloudWatchEventEvents.serverless.service.functions = { first: { From 6cf35631390e6ccea0033c61e602556a32965d1e Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 13 Mar 2019 09:27:34 -0400 Subject: [PATCH 62/70] jszip as main dep, DRY getting runtime, use on exit instead of on close --- lib/plugins/aws/invokeLocal/index.js | 30 +++++++++++++--------------- package-lock.json | 21 ++++++------------- package.json | 2 +- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 4177f638f..fde036ea5 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -36,6 +36,12 @@ class AwsInvokeLocal { }; } + getRuntime() { + return this.options.functionObj.runtime + || this.serverless.service.provider.runtime + || 'nodejs4.3'; + } + validateFile(filePath, key) { const absolutePath = path.isAbsolute(filePath) ? filePath : @@ -134,9 +140,7 @@ class AwsInvokeLocal { } invokeLocal() { - const runtime = this.options.functionObj.runtime - || this.serverless.service.provider.runtime - || 'nodejs4.3'; + const runtime = this.getRuntime(); const handler = this.options.functionObj.handler; if (this.options.docker) { @@ -194,28 +198,24 @@ class AwsInvokeLocal { checkDockerImage() { - const runtime = this.options.functionObj.runtime - || this.serverless.service.provider.runtime - || 'nodejs4.3'; + const runtime = this.getRuntime(); return new BbPromise((resolve, reject) => { const docker = spawn('docker', ['images', '-q', `lambci/lambda:${runtime}`]); let stdout = ''; docker.stdout.on('data', (buf) => { stdout += buf.toString(); }); - docker.on('close', error => (error ? reject(error) : resolve(Boolean(stdout.trim())))); + docker.on('exit', error => (error ? reject(error) : resolve(Boolean(stdout.trim())))); }); } pullDockerImage() { - const runtime = this.options.functionObj.runtime - || this.serverless.service.provider.runtime - || 'nodejs4.3'; + const runtime = this.getRuntime(); this.serverless.cli.log('Downloading base Docker image...'); return new BbPromise((resolve, reject) => { const docker = spawn('docker', ['pull', `lambci/lambda:${runtime}`]); - docker.on('close', error => (error ? reject(error) : resolve())); + docker.on('exit', error => (error ? reject(error) : resolve())); }); } @@ -259,9 +259,7 @@ class AwsInvokeLocal { } buildDockerImage(layerPaths) { - const runtime = this.options.functionObj.runtime - || this.serverless.service.provider.runtime - || 'nodejs4.3'; + const runtime = this.getRuntime(); const imageName = 'sls-docker'; @@ -277,7 +275,7 @@ class AwsInvokeLocal { this.serverless.cli.log('Building Docker image...'); const docker = spawn('docker', ['build', '-t', imageName, `${this.serverless.config.servicePath}`, '-f', dockerfilePath]); - docker.on('close', error => (error ? reject(error) : resolve(imageName))); + docker.on('exit', error => (error ? reject(error) : resolve(imageName))); }); } @@ -326,7 +324,7 @@ class AwsInvokeLocal { const docker = spawn('docker', dockerArgs); docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - docker.on('close', error => (error ? reject(error) : resolve(imageName))); + docker.on('exit', error => (error ? reject(error) : resolve(imageName))); })); } diff --git a/package-lock.json b/package-lock.json index c769a3879..f4de13c7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3740,8 +3740,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-lazy": { "version": "2.1.0", @@ -4989,7 +4988,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", - "dev": true, "requires": { "core-js": "~2.3.0", "es6-promise": "~3.0.2", @@ -5001,26 +4999,22 @@ "core-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", - "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", - "dev": true + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=" }, "es6-promise": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", - "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", - "dev": true + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=" }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "readable-stream": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -5033,8 +5027,7 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, @@ -5128,7 +5121,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", - "dev": true, "requires": { "immediate": "~3.0.5" } @@ -6089,8 +6081,7 @@ "pako": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", - "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", - "dev": true + "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==" }, "parse-github-url": { "version": "1.0.2", diff --git a/package.json b/package.json index f66bf30ef..68d51735b 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "eslint-plugin-react": "^6.1.1", "istanbul": "^0.4.4", "jest-cli": "^23.1.0", - "jszip": "^3.1.2", "markdown-link": "^0.1.1", "markdown-magic": "^0.1.19", "markdown-table": "^1.1.1", @@ -91,6 +90,7 @@ "sinon-chai": "^2.9.0" }, "dependencies": { + "jszip": "^3.1.2", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.373.0", From 2dbc267eac7906f6145c3faf2a20066ad62b2c92 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 14 Mar 2019 13:05:24 +0100 Subject: [PATCH 63/70] Add check if Docker daemon is running --- lib/plugins/aws/invokeLocal/index.js | 17 ++++++++++++++--- lib/plugins/aws/invokeLocal/index.test.js | 10 ++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index fde036ea5..26140da9a 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -18,7 +18,6 @@ const jszip = require('jszip'); const cachePath = path.join(cachedir('serverless'), 'invokeLocal'); - class AwsInvokeLocal { constructor(serverless, options) { this.serverless = serverless; @@ -196,6 +195,17 @@ class AwsInvokeLocal { return this.invokeLocalDocker(); } + checkDockerDaemonStatus() { + return new BbPromise((resolve, reject) => { + const docker = spawn('docker', ['version']); + docker.on('exit', error => { + if (error) { + reject('Please start the Docker daemon to use the invoke local Docker integration.'); + } + resolve(); + }); + }); + } checkDockerImage() { const runtime = this.getRuntime(); @@ -310,13 +320,14 @@ class AwsInvokeLocal { const handler = this.options.functionObj.handler; return BbPromise.all([ + this.checkDockerDaemonStatus(), this.checkDockerImage().then(exists => (exists ? {} : this.pullDockerImage())), this.getLayerPaths().then(layerPaths => this.buildDockerImage(layerPaths)), this.extractArtifact(), ]) .then((results) => new BbPromise((resolve, reject) => { - const imageName = results[1]; - const artifactPath = results[2]; + const imageName = results[2]; + const artifactPath = results[3]; const dockerArgs = [ 'run', '--rm', '-v', `${artifactPath}:/var/task`, imageName, handler, JSON.stringify(this.options.data), diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 6165549c9..c88c8ec31 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -1118,6 +1118,7 @@ describe('AwsInvokeLocal', () => { describe('#invokeLocalDocker()', () => { let awsInvokeLocalMocked; let spawnStub; + beforeEach(() => { awsInvokeLocal.provider.options.stage = 'dev'; awsInvokeLocal.options = { @@ -1171,11 +1172,12 @@ describe('AwsInvokeLocal', () => { it('calls docker', () => awsInvokeLocalMocked.invokeLocalDocker().then(() => { - expect(spawnStub.getCall(0).args).to.deep.equal(['docker', - ['images', '-q', 'lambci/lambda:nodejs8.10']]); + expect(spawnStub.getCall(0).args).to.deep.equal(['docker', ['version']]); expect(spawnStub.getCall(1).args).to.deep.equal(['docker', + ['images', '-q', 'lambci/lambda:nodejs8.10']]); + expect(spawnStub.getCall(2).args).to.deep.equal(['docker', ['pull', 'lambci/lambda:nodejs8.10']]); - expect(spawnStub.getCall(2).args).to.deep.equal(['docker', [ + expect(spawnStub.getCall(3).args).to.deep.equal(['docker', [ 'build', '-t', 'sls-docker', @@ -1183,7 +1185,7 @@ describe('AwsInvokeLocal', () => { '-f', '.serverless/invokeLocal/Dockerfile', ]]); - expect(spawnStub.getCall(3).args).to.deep.equal(['docker', [ + expect(spawnStub.getCall(4).args).to.deep.equal(['docker', [ 'run', '--rm', '-v', From a5e6d447384b6d6be1882fe21bb283c3b99b2703 Mon Sep 17 00:00:00 2001 From: rodolphito Date: Thu, 14 Mar 2019 22:51:29 -0700 Subject: [PATCH 64/70] Fixed typo "disonnect" --- docs/providers/aws/events/websocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/websocket.md b/docs/providers/aws/events/websocket.md index 1a605c7b7..320df6bc6 100644 --- a/docs/providers/aws/events/websocket.md +++ b/docs/providers/aws/events/websocket.md @@ -36,7 +36,7 @@ This code will setup a websocket with a `$disconnect` route key: ```yml functions: - disonnectHandler: + disconnectHandler: handler: handler.disconnectHandler events: - websocket: From f17b220905da09bddf0202788513e4da2e7dcdac Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Fri, 15 Mar 2019 13:33:48 +0300 Subject: [PATCH 65/70] releasing v1.39.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ package-lock.json | 43 +++++++++++++++++++++++++++++++------------ package.json | 2 +- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f02e8f0a4..f585fd5fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# 1.39.0 (2019-03-15) + +- [Add support for invoke local with docker](https://github.com/serverless/serverless/pull/5863) +- [fix regression with golang check on windows ](https://github.com/serverless/serverless/pull/5899) +- [Support for Cloudwatch Event InputTransformer](https://github.com/serverless/serverless/pull/5912) +- [Allow individual packaging with TypeScript source maps](https://github.com/serverless/serverless/pull/5743) +- [Support API Gateway stage deployment description](https://github.com/serverless/serverless/pull/5509) +- [Allow Fn::Join in SQS arn builder](https://github.com/serverless/serverless/pull/5351) +- [Add AWS x-ray support for Lambda](https://github.com/serverless/serverless/pull/5860) +- [Fix CloudFormation template normalization](https://github.com/serverless/serverless/pull/5885) +- [Fix bug when using websocket events with functions with custom roles](https://github.com/serverless/serverless/pull/5880) +- [Print customized function names correctly in sls info output](https://github.com/serverless/serverless/pull/5883) +- [Added websockets authorizer support](https://github.com/serverless/serverless/pull/5867) +- [upport more route characters for websockets](https://github.com/serverless/serverless/pull/5865) +- [kotlin jvm maven updates](https://github.com/serverless/serverless/pull/5872) +- [Put `Custom Response Headers` into `[Responses]`](https://github.com/serverless/serverless/pull/5862) +- [Packaging exclude only config file being used](https://github.com/serverless/serverless/pull/5840) + +## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.38.0...v1.39.0) + + # 1.38.0 (2019-02-20) - [Set timout & others on context in python invoke local](https://github.com/serverless/serverless/pull/5796) diff --git a/package-lock.json b/package-lock.json index f4de13c7e..7bfd324b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.38.0", + "version": "1.39.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2803,7 +2803,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2824,12 +2825,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2844,17 +2847,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2971,7 +2977,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2983,6 +2990,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2997,6 +3005,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3004,12 +3013,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3028,6 +3039,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3108,7 +3120,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3120,6 +3133,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3205,7 +3219,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3241,6 +3256,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3260,6 +3276,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3303,12 +3320,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/package.json b/package.json index 68d51735b..69dce4ada 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.38.0", + "version": "1.39.0", "engines": { "node": ">=4.0" }, From eb9e589d6015ab80d4dd8edd2913e1727da47231 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Fri, 15 Mar 2019 13:45:27 +0300 Subject: [PATCH 66/70] Update CHANGELOG.md Co-Authored-By: eahefnawy --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f585fd5fd..2c32a4db4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - [Fix bug when using websocket events with functions with custom roles](https://github.com/serverless/serverless/pull/5880) - [Print customized function names correctly in sls info output](https://github.com/serverless/serverless/pull/5883) - [Added websockets authorizer support](https://github.com/serverless/serverless/pull/5867) -- [upport more route characters for websockets](https://github.com/serverless/serverless/pull/5865) +- [Support more route characters for websockets](https://github.com/serverless/serverless/pull/5865) - [kotlin jvm maven updates](https://github.com/serverless/serverless/pull/5872) - [Put `Custom Response Headers` into `[Responses]`](https://github.com/serverless/serverless/pull/5862) - [Packaging exclude only config file being used](https://github.com/serverless/serverless/pull/5840) From e1f09f30399e8eb34d137207ebf6d4a3fda794ed Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 18 Mar 2019 11:12:25 +0100 Subject: [PATCH 67/70] Revert "Fixed #4188 - Package generating incorrect package artifact path in serverless-state.json" --- .../aws/package/lib/saveServiceState.js | 10 ---- .../aws/package/lib/saveServiceState.test.js | 53 ------------------- 2 files changed, 63 deletions(-) diff --git a/lib/plugins/aws/package/lib/saveServiceState.js b/lib/plugins/aws/package/lib/saveServiceState.js index 69056668c..7d6afe2fb 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.js +++ b/lib/plugins/aws/package/lib/saveServiceState.js @@ -27,16 +27,6 @@ module.exports = { const selfReferences = findReferences(strippedService, this.serverless.service); _.forEach(selfReferences, refPath => _.set(strippedService, refPath, '${self:}')); - _.forEach(strippedService.functions, func => { - const packageRef = func.package; - if (!_.isEmpty(packageRef)) { - packageRef.artifact = path.join( - this.packagePath, - packageRef.artifact.substr(packageRef.artifact.lastIndexOf('\\') + 1) - ); - } - }); - const state = { service: strippedService, package: { diff --git a/lib/plugins/aws/package/lib/saveServiceState.test.js b/lib/plugins/aws/package/lib/saveServiceState.test.js index a94e625fd..064fb861c 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.test.js +++ b/lib/plugins/aws/package/lib/saveServiceState.test.js @@ -101,57 +101,4 @@ describe('#saveServiceState()', () => { .to.equal(true); }); }); - - it('should resolve package artifact paths on function levels', () => { - const filePath = path.join( - awsPackage.serverless.config.servicePath, - '.serverless', - 'service-state.json' - ); - - const distPath = path.join('..', 'dist'); - const firstFuncArtifactPath = path.join('..', 'artifacts', 'first.zip'); - - awsPackage.packagePath = distPath; - serverless.service.package.path = distPath; - serverless.service.functions = { - first: { - package: { - artifact: firstFuncArtifactPath, - }, - }, - second: { - package: {}, - }, - }; - - return awsPackage.saveServiceState().then(() => { - const expectedStateFileContent = { - service: { - provider: { - compiledCloudFormationTemplate: 'compiled content', - }, - functions: { - first: { - package: { - artifact: firstFuncArtifactPath, - }, - }, - second: { - package: {}, - }, - }, - }, - package: { - individually: false, - artifactDirectoryName: 'artifact-directory', - artifact: 'service.zip', - }, - }; - - expect(getServiceStateFileNameStub.calledOnce).to.equal(true); - expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) - .to.equal(true); - }); - }); }); From cd0d33d3c4709d11b3856c490f94558bbd01ba36 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 18 Mar 2019 11:12:25 +0100 Subject: [PATCH 68/70] Revert "Fixed #4188 - Package generating incorrect package artifact path in serverless-state.json" --- .../aws/package/lib/saveServiceState.js | 10 ---- .../aws/package/lib/saveServiceState.test.js | 53 ------------------- 2 files changed, 63 deletions(-) diff --git a/lib/plugins/aws/package/lib/saveServiceState.js b/lib/plugins/aws/package/lib/saveServiceState.js index 69056668c..7d6afe2fb 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.js +++ b/lib/plugins/aws/package/lib/saveServiceState.js @@ -27,16 +27,6 @@ module.exports = { const selfReferences = findReferences(strippedService, this.serverless.service); _.forEach(selfReferences, refPath => _.set(strippedService, refPath, '${self:}')); - _.forEach(strippedService.functions, func => { - const packageRef = func.package; - if (!_.isEmpty(packageRef)) { - packageRef.artifact = path.join( - this.packagePath, - packageRef.artifact.substr(packageRef.artifact.lastIndexOf('\\') + 1) - ); - } - }); - const state = { service: strippedService, package: { diff --git a/lib/plugins/aws/package/lib/saveServiceState.test.js b/lib/plugins/aws/package/lib/saveServiceState.test.js index a94e625fd..064fb861c 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.test.js +++ b/lib/plugins/aws/package/lib/saveServiceState.test.js @@ -101,57 +101,4 @@ describe('#saveServiceState()', () => { .to.equal(true); }); }); - - it('should resolve package artifact paths on function levels', () => { - const filePath = path.join( - awsPackage.serverless.config.servicePath, - '.serverless', - 'service-state.json' - ); - - const distPath = path.join('..', 'dist'); - const firstFuncArtifactPath = path.join('..', 'artifacts', 'first.zip'); - - awsPackage.packagePath = distPath; - serverless.service.package.path = distPath; - serverless.service.functions = { - first: { - package: { - artifact: firstFuncArtifactPath, - }, - }, - second: { - package: {}, - }, - }; - - return awsPackage.saveServiceState().then(() => { - const expectedStateFileContent = { - service: { - provider: { - compiledCloudFormationTemplate: 'compiled content', - }, - functions: { - first: { - package: { - artifact: firstFuncArtifactPath, - }, - }, - second: { - package: {}, - }, - }, - }, - package: { - individually: false, - artifactDirectoryName: 'artifact-directory', - artifact: 'service.zip', - }, - }; - - expect(getServiceStateFileNameStub.calledOnce).to.equal(true); - expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) - .to.equal(true); - }); - }); }); From 5d2b9760eb10136079712c5a80fdd1c291d867fd Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 18 Mar 2019 11:28:14 +0100 Subject: [PATCH 69/70] Releasing v1.39.1 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 43 ++++++++++++------------------------------- package.json | 2 +- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c32a4db4..7f8c6b693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.39.1 (2019-03-18) + +- [Revert "Fixed #4188 - Package generating incorrect package artifact path in serverless-state.json"](https://github.com/serverless/serverless/pull/5936) + +## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.39.0...v1.39.1) + + # 1.39.0 (2019-03-15) - [Add support for invoke local with docker](https://github.com/serverless/serverless/pull/5863) diff --git a/package-lock.json b/package-lock.json index 7bfd324b6..9ed10e158 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.39.0", + "version": "1.39.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2803,8 +2803,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2825,14 +2824,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2847,20 +2844,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2977,8 +2971,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2990,7 +2983,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3005,7 +2997,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3013,14 +3004,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3039,7 +3028,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3120,8 +3108,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3133,7 +3120,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3219,8 +3205,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3256,7 +3241,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3276,7 +3260,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3320,14 +3303,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index 69dce4ada..02bafc47c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.39.0", + "version": "1.39.1", "engines": { "node": ">=4.0" }, From 9d26b00ddb6381d54a07ba67010df6bb908cdabb Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 18 Mar 2019 11:52:59 +0100 Subject: [PATCH 70/70] Align error logging length --- lib/classes/Error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/Error.js b/lib/classes/Error.js index ef18c16ff..cd66e850c 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -72,7 +72,7 @@ module.exports.logError = (e) => { consoleLog(`${chalk.yellow(' Issues: ')}${'forum.serverless.com'}`); consoleLog(' '); - consoleLog(chalk.yellow(' Your Environment Information -----------------------------')); + consoleLog(chalk.yellow(' Your Environment Information ---------------------------')); consoleLog(chalk.yellow(` OS: ${platform}`)); consoleLog(chalk.yellow(` Node Version: ${nodeVersion}`)); consoleLog(chalk.yellow(` Serverless Version: ${slsVersion}`));