From 3e6a98686133829e77140e55cb4a14832d942176 Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Thu, 15 Jun 2017 00:26:41 +0100 Subject: [PATCH 001/125] Fix broken merge in CUP compile event. Fixes #3773. --- .../compile/events/cognitoUserPool/index.js | 7 +++ .../events/cognitoUserPool/index.test.js | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index d94063701c0..5a866c4c372 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -103,6 +103,7 @@ class AwsCompileCognitoUserPoolEvents { const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + // Attach `DependsOn` for any relevant Lambdas const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this .provider.naming.getLambdaLogicalId(value.functionName)); @@ -115,6 +116,12 @@ class AwsCompileCognitoUserPoolEvents { DependsOn, }; + // If overrides exist in `Resources`, merge them in + if (_.has(this.serverless.service.resources, userPoolLogicalId)) { + const existingResource = this.serverless.service.resources[userPoolLogicalId]; + _.merge(userPoolTemplate.Properties, existingResource.Properties); + } + const userPoolCFResource = { [userPoolLogicalId]: userPoolTemplate, }; diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js index 6ec9a6c2805..0c022120433 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js @@ -264,6 +264,53 @@ describe('AwsCompileCognitoUserPoolEvents', () => { ).to.equal('AWS::Lambda::Permission'); }); + it('should be overrideable from Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = { + CognitoUserPoolMyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'ProdMyUserPool', + MfaConfiguration: 'OFF', + EmailVerificationSubject: 'Your verification code', + EmailVerificationMessage: 'Your verification code is {####}.', + SmsVerificationMessage: 'Your verification code is {####}.', + }, + }, + }; + + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties).length + ).to.equal(6); + expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig).length + ).to.equal(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + it('should not create resources when CUP events are not given', () => { awsCompileCognitoUserPoolEvents.serverless.service.functions = { first: { From 93d552a62bec2ac293d1c9a989e50440a10e7782 Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Sat, 17 Jun 2017 00:38:53 +0100 Subject: [PATCH 002/125] Add hook for `mergeCustomProviderResources` Why: So we can merge any custom resources ourselves. Moved code around in `events/cognitoUserPool`, also updated tests by adding checks for the `DependsOn` clause. --- .../compile/events/cognitoUserPool/index.js | 112 ++++++++----- .../events/cognitoUserPool/index.test.js | 149 ++++++++++++++++-- 2 files changed, 208 insertions(+), 53 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index 5a866c4c372..a875305c3ba 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -20,19 +20,21 @@ class AwsCompileCognitoUserPoolEvents { this.hooks = { 'package:compileEvents': this.compileCognitoUserPoolEvents.bind(this), + 'aws:package:finalize:mergeCustomProviderResources': this + .mergeWithCustomResources.bind(this), }; } - compileCognitoUserPoolEvents() { + findUserPoolsAndFunctions() { const userPools = []; const cognitoUserPoolTriggerFunctions = []; // Iterate through all functions declared in `serverless.yml` - this.serverless.service.getAllFunctions().forEach((functionName) => { + _.forEach(this.serverless.service.getAllFunctions(), (functionName) => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { - functionObj.events.forEach(event => { + _.forEach(functionObj.events, (event) => { if (event.cognitoUserPool) { // Check event definition for `cognitoUserPool` object if (typeof event.cognitoUserPool === 'object') { @@ -80,58 +82,59 @@ class AwsCompileCognitoUserPoolEvents { } }); - // Generate CloudFormation templates for Cognito User Pool changes - _.forEach(userPools, (poolName) => { - // Create a `LambdaConfig` object for the CloudFormation template - const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { - poolName, - }); - - const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { - const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + return { cognitoUserPoolTriggerFunctions, userPools }; + } - // Return a new object to avoid lint errors - return Object.assign({}, result, { - [value.triggerSource]: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], - }, - }); - }, {}); + generateTemplateForPool(poolName, currentPoolTriggerFunctions) { + const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + + // Return a new object to avoid lint errors + return Object.assign({}, result, { + [value.triggerSource]: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + }); + }, {}); - const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); - // Attach `DependsOn` for any relevant Lambdas - const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this - .provider.naming.getLambdaLogicalId(value.functionName)); + // Attach `DependsOn` for any relevant Lambdas + const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this + .provider.naming.getLambdaLogicalId(value.functionName)); - const userPoolTemplate = { + return { + [userPoolLogicalId]: { Type: 'AWS::Cognito::UserPool', Properties: { UserPoolName: poolName, LambdaConfig: lambdaConfig, }, DependsOn, - }; + }, + }; + } - // If overrides exist in `Resources`, merge them in - if (_.has(this.serverless.service.resources, userPoolLogicalId)) { - const existingResource = this.serverless.service.resources[userPoolLogicalId]; - _.merge(userPoolTemplate.Properties, existingResource.Properties); - } + compileCognitoUserPoolEvents() { + const { cognitoUserPoolTriggerFunctions, userPools } = this.findUserPoolsAndFunctions(); - const userPoolCFResource = { - [userPoolLogicalId]: userPoolTemplate, - }; + // Generate CloudFormation templates for Cognito User Pool changes + _.forEach(userPools, (poolName) => { + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); + const userPoolCFResource = this.generateTemplateForPool( + poolName, + currentPoolTriggerFunctions + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, userPoolCFResource); }); // Generate CloudFormation templates for IAM permissions to allow Cognito to trigger Lambda - cognitoUserPoolTriggerFunctions.forEach((cognitoUserPoolTriggerFunction) => { + _.forEach(cognitoUserPoolTriggerFunctions, (cognitoUserPoolTriggerFunction) => { const userPoolLogicalId = this.provider.naming .getCognitoUserPoolLogicalId(cognitoUserPoolTriggerFunction.poolName); const lambdaLogicalId = this.provider.naming @@ -166,6 +169,41 @@ class AwsCompileCognitoUserPoolEvents { permissionCFResource); }); } + + mergeWithCustomResources() { + const { cognitoUserPoolTriggerFunctions, userPools } = this.findUserPoolsAndFunctions(); + + _.forEach(userPools, (poolName) => { + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + + // If overrides exist in `Resources`, merge them in + if (_.has(this.serverless.service.resources, userPoolLogicalId)) { + const customUserPool = this.serverless.service.resources[userPoolLogicalId]; + const generatedUserPool = this.generateTemplateForPool( + poolName, + currentPoolTriggerFunctions + )[userPoolLogicalId]; + + // Merge `DependsOn` clauses + const customUserPoolDependsOn = _.get(customUserPool, 'DependsOn', []); + const DependsOn = generatedUserPool.DependsOn.concat(customUserPoolDependsOn); + + // Merge default and custom resources, and `DependsOn` clause + const mergedTemplate = Object.assign( + {}, + _.merge(generatedUserPool, customUserPool), + { DependsOn } + ); + + // Merge resource back into `Resources` + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + { [userPoolLogicalId]: mergedTemplate } + ); + } + }); + } } module.exports = AwsCompileCognitoUserPoolEvents; diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js index 0c022120433..a67e80bb712 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileCognitoUserPoolEvents = require('./index'); @@ -115,9 +116,15 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type @@ -153,9 +160,15 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type @@ -195,6 +208,9 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1 .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] @@ -204,6 +220,9 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2 .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] @@ -250,10 +269,14 @@ describe('AwsCompileCognitoUserPoolEvents', () => { .compiledCloudFormationTemplate.Resources .CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig).length - ).to.equal(2); + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(2); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(2); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type @@ -264,7 +287,60 @@ describe('AwsCompileCognitoUserPoolEvents', () => { ).to.equal('AWS::Lambda::Permission'); }); - it('should be overrideable from Resources', () => { + it('should not create resources when CUP events are not given', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [], + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + ).to.deep.equal({}); + }); + }); + + describe('#mergeWithCustomResources()', () => { + it('does not merge if no custom resource is found in Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = {}; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(2); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should merge custom resources found in Resources', () => { awsCompileCognitoUserPoolEvents.serverless.service.functions = { first: { events: [ @@ -290,40 +366,81 @@ describe('AwsCompileCognitoUserPoolEvents', () => { }, }; - awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(6); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties).length - ).to.equal(6); - expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(1); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig).length - ).to.equal(1); + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); }); - it('should not create resources when CUP events are not given', () => { + it('should merge `DependsOn` clauses correctly if being overridden from Resources', () => { awsCompileCognitoUserPoolEvents.serverless.service.functions = { first: { - events: [], + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = { + CognitoUserPoolMyUserPool: { + DependsOn: ['Something', 'SomethingElse', ['Nothing', 'NothingAtAll']], + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'ProdMyUserPool', + MfaConfiguration: 'OFF', + EmailVerificationSubject: 'Your verification code', + EmailVerificationMessage: 'Your verification code is {####}.', + SmsVerificationMessage: 'Your verification code is {####}.', + }, }, }; awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); - expect( - awsCompileCognitoUserPoolEvents.serverless.service.provider + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources - ).to.deep.equal({}); + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(4); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(6); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); }); }); }); From 78010db86aebdc19904acff7d8c79383762ef96e Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Sun, 25 Jun 2017 02:44:26 +0100 Subject: [PATCH 003/125] Fixes as per review --- .../package/compile/events/cognitoUserPool/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index a875305c3ba..76f0a4c054c 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -20,8 +20,7 @@ class AwsCompileCognitoUserPoolEvents { this.hooks = { 'package:compileEvents': this.compileCognitoUserPoolEvents.bind(this), - 'aws:package:finalize:mergeCustomProviderResources': this - .mergeWithCustomResources.bind(this), + 'after:package:finalize': this.mergeWithCustomResources.bind(this), }; } @@ -119,7 +118,9 @@ class AwsCompileCognitoUserPoolEvents { } compileCognitoUserPoolEvents() { - const { cognitoUserPoolTriggerFunctions, userPools } = this.findUserPoolsAndFunctions(); + const result = this.findUserPoolsAndFunctions(); + const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; + const userPools = result.userPools; // Generate CloudFormation templates for Cognito User Pool changes _.forEach(userPools, (poolName) => { @@ -171,7 +172,9 @@ class AwsCompileCognitoUserPoolEvents { } mergeWithCustomResources() { - const { cognitoUserPoolTriggerFunctions, userPools } = this.findUserPoolsAndFunctions(); + const result = this.findUserPoolsAndFunctions(); + const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; + const userPools = result.userPools; _.forEach(userPools, (poolName) => { const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); From 9d3a5d0d41d8050b976bebad90abfe7810f10c6a Mon Sep 17 00:00:00 2001 From: "Rolando (Max) Espinoza" Date: Wed, 26 Jul 2017 16:39:34 -0400 Subject: [PATCH 004/125] Add notificationArns AWS provider property to enable Cloudformation notifications. --- lib/plugins/aws/deploy/lib/createStack.js | 4 ++++ lib/plugins/aws/lib/updateStack.js | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/plugins/aws/deploy/lib/createStack.js b/lib/plugins/aws/deploy/lib/createStack.js index fc3bcf4c285..54ac58242ba 100644 --- a/lib/plugins/aws/deploy/lib/createStack.js +++ b/lib/plugins/aws/deploy/lib/createStack.js @@ -32,6 +32,10 @@ module.exports = { params.RoleARN = this.serverless.service.provider.cfnRole; } + if (this.serverless.service.provider.notificationArns) { + params.NotificationARNs = this.serverless.service.provider.notificationArns; + } + return this.provider.request( 'CloudFormation', 'createStack', diff --git a/lib/plugins/aws/lib/updateStack.js b/lib/plugins/aws/lib/updateStack.js index 3df012a1f19..bb675be79c7 100644 --- a/lib/plugins/aws/lib/updateStack.js +++ b/lib/plugins/aws/lib/updateStack.js @@ -36,6 +36,10 @@ module.exports = { params.RoleARN = this.serverless.service.provider.cfnRole; } + if (this.serverless.service.provider.notificationArns) { + params.NotificationARNs = this.serverless.service.provider.notificationArns; + } + return this.provider.request('CloudFormation', 'createStack', params, @@ -72,6 +76,10 @@ module.exports = { params.RoleARN = this.serverless.service.provider.cfnRole; } + if (this.serverless.service.provider.notificationArns) { + params.NotificationARNs = this.serverless.service.provider.notificationArns; + } + // Policy must have at least one statement, otherwise no updates would be possible at all if (this.serverless.service.provider.stackPolicy && this.serverless.service.provider.stackPolicy.length) { From 50bffc1aabf50495fe3e7b7c428e105063f9081e Mon Sep 17 00:00:00 2001 From: "Rolando (Max) Espinoza" Date: Sun, 17 Sep 2017 01:16:26 -0300 Subject: [PATCH 005/125] Add notificationArns property provider reference. --- docs/providers/aws/guide/serverless.yml.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 6a3f0154018..fc21170223c 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -83,6 +83,8 @@ provider: subnetIds: - subnetId1 - subnetId2 + notificationArns: # List of existing Amazon SNS topics in the same region where notifications about stack events are sent. + - 'arn:aws:sns:us-east-1:XXXXXX:mytopic' package: # Optional deployment packaging configuration include: # Specify the directories and files which should be included in the deployment package From 1b6b7a5871fba4b939637f9c34590df246904e31 Mon Sep 17 00:00:00 2001 From: "Rolando (Max) Espinoza" Date: Sun, 17 Sep 2017 01:15:49 -0300 Subject: [PATCH 006/125] Add notificationArns tests. --- .../aws/deploy/lib/createStack.test.js | 16 ++++++++++++ lib/plugins/aws/lib/updateStack.test.js | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index 898b8061856..28ba748c923 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -71,6 +71,22 @@ describe('createStack', () => { awsDeploy.monitorStack.restore(); }); }); + + it('should use use notificationArns if it is specified', () => { + const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; + awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; + + const createStackStub = sinon + .stub(awsDeploy.provider, 'request').resolves(); + sinon.stub(awsDeploy, 'monitorStack').resolves(); + + return awsDeploy.create().then(() => { + expect(createStackStub.args[0][2].NotificationARNs) + .to.deep.equal([mytopicArn]); + awsDeploy.provider.request.restore(); + awsDeploy.monitorStack.restore(); + }); + }); }); describe('#createStack()', () => { diff --git a/lib/plugins/aws/lib/updateStack.test.js b/lib/plugins/aws/lib/updateStack.test.js index dd0f0225e2a..367efba9c21 100644 --- a/lib/plugins/aws/lib/updateStack.test.js +++ b/lib/plugins/aws/lib/updateStack.test.js @@ -94,6 +94,21 @@ describe('updateStack', () => { awsDeploy.monitorStack.restore(); }); }); + + it('should use use notificationArns if it is specified', () => { + const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; + awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; + + const createStackStub = sinon.stub(awsDeploy.provider, 'request').resolves(); + sinon.stub(awsDeploy, 'monitorStack').resolves(); + + return awsDeploy.createFallback().then(() => { + expect(createStackStub.args[0][2].NotificationARNs) + .to.deep.equal([mytopicArn]); + awsDeploy.provider.request.restore(); + awsDeploy.monitorStack.restore(); + }); + }); }); describe('#update()', () => { @@ -172,6 +187,16 @@ describe('updateStack', () => { .to.equal('arn:aws:iam::123456789012:role/myrole'); }); }); + + it('should use use notificationArns if it is specified', () => { + const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; + awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; + + return awsDeploy.update().then(() => { + expect(updateStackStub.args[0][2].NotificationARNs) + .to.deep.equal([mytopicArn]); + }); + }); }); describe('#updateStack()', () => { From 1d7bd3cd783211868f349a7471d4eba15ac48079 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Tue, 24 Oct 2017 13:56:42 +0000 Subject: [PATCH 007/125] Add validation to remove command --- lib/plugins/remove/remove.js | 7 +++++++ lib/plugins/remove/remove.test.js | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/plugins/remove/remove.js b/lib/plugins/remove/remove.js index 1f098b38ac2..eb330ddd061 100644 --- a/lib/plugins/remove/remove.js +++ b/lib/plugins/remove/remove.js @@ -2,11 +2,17 @@ const BbPromise = require('bluebird'); const userStats = require('../../utils/userStats'); +const validate = require('../lib/validate'); class Remove { constructor(serverless) { this.serverless = serverless; + Object.assign( + this, + validate + ); + this.commands = { remove: { usage: 'Remove Serverless service and all resources', @@ -31,6 +37,7 @@ class Remove { }; this.hooks = { + 'before:remove:remove': () => BbPromise.bind(this).then(this.validate), 'after:remove:remove': () => BbPromise.bind(this).then(this.track), }; } diff --git a/lib/plugins/remove/remove.test.js b/lib/plugins/remove/remove.test.js index 598ad30b363..08a9d05e938 100644 --- a/lib/plugins/remove/remove.test.js +++ b/lib/plugins/remove/remove.test.js @@ -3,6 +3,7 @@ const expect = require('chai').expect; const Remove = require('./remove'); const Serverless = require('../../Serverless'); +const sinon = require('sinon'); describe('Remove', () => { let remove; @@ -20,4 +21,20 @@ describe('Remove', () => { it('should have commands', () => expect(remove.commands).to.be.not.empty); }); + + describe('"before:remove:remove" hook', () => { + let validateStub; + + beforeEach(() => { + validateStub = sinon.stub(remove, 'validate').resolves(); + }); + + afterEach(() => { + remove.validate.restore(); + }); + + it('should run the validation', () => expect(remove.hooks['before:remove:remove']()) + .to.be.fulfilled.then(() => expect(validateStub).to.be.called) + ); + }); }); From b4d19dcd1a4f025199d6a1304006b012dde274f1 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Fri, 15 Dec 2017 04:11:51 -0500 Subject: [PATCH 008/125] Prevent duplicated completion results --- lib/utils/autocomplete.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/utils/autocomplete.js b/lib/utils/autocomplete.js index c2b4c530135..408135a9def 100644 --- a/lib/utils/autocomplete.js +++ b/lib/utils/autocomplete.js @@ -1,25 +1,19 @@ 'use strict'; +const path = require('path'); + const Serverless = require('../Serverless'); const crypto = require('crypto'); const getCacheFile = require('./getCacheFile'); const getServerlessConfigFile = require('./getServerlessConfigFile'); -const tab = require('tabtab')({ - name: 'serverless', -}); +const name = path.basename(process.argv[0]); -const tabSls = require('tabtab')({ - name: 'sls', -}); +const tab = require('tabtab')({ name }); const getSugestions = (commands) => { - tab.on('serverless', (data, done) => { - done(null, Object.keys(commands)); - }); - - tabSls.on('sls', (data, done) => { + tab.on(name, (data, done) => { done(null, Object.keys(commands)); }); @@ -27,13 +21,9 @@ const getSugestions = (commands) => { tab.on(command, (data, done) => { done(null, commands[command]); }); - tabSls.on(command, (data, done) => { - done(null, commands[command]); - }); }); tab.start(); - tabSls.start(); }; const cacheFileValid = (serverlessConfigFile, validationHash) => { From 72de3d21b60e4f4cda2f215253a555cf46de92f2 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Fri, 15 Dec 2017 05:39:12 -0500 Subject: [PATCH 009/125] Remove instances of incorrect command completion --- lib/utils/autocomplete.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/utils/autocomplete.js b/lib/utils/autocomplete.js index 408135a9def..67ef89e38f3 100644 --- a/lib/utils/autocomplete.js +++ b/lib/utils/autocomplete.js @@ -14,7 +14,11 @@ const tab = require('tabtab')({ name }); const getSugestions = (commands) => { tab.on(name, (data, done) => { - done(null, Object.keys(commands)); + if (data.words === 1) { + done(null, Object.keys(commands)); + } else { + done(null, []); + } }); Object.keys(commands).forEach(command => { From 228adea94b674255aa661223e0787de68bc3a7e8 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Fri, 15 Dec 2017 06:15:06 -0500 Subject: [PATCH 010/125] Fix typo --- lib/utils/autocomplete.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/autocomplete.js b/lib/utils/autocomplete.js index 67ef89e38f3..6b744aa8fe7 100644 --- a/lib/utils/autocomplete.js +++ b/lib/utils/autocomplete.js @@ -12,7 +12,7 @@ const name = path.basename(process.argv[0]); const tab = require('tabtab')({ name }); -const getSugestions = (commands) => { +const getSuggestions = (commands) => { tab.on(name, (data, done) => { if (data.words === 1) { done(null, Object.keys(commands)); @@ -58,7 +58,7 @@ const autocomplete = () => { if (!cacheFile || !cacheFileValid(serverlessConfigFile, cacheFile.validationHash)) { return; } - return getSugestions(cacheFile.commands); // eslint-disable-line consistent-return + return getSuggestions(cacheFile.commands); // eslint-disable-line consistent-return }); }); }; From 11cf69bd80e93dd2ec158bbd0e99205d73181827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20A=2E=20A=CC=81lvarez?= Date: Tue, 5 Dec 2017 16:34:13 +0100 Subject: [PATCH 011/125] fall back to POJO if handler doesn't take Map --- .../java/com/serverless/InvokeBridge.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java index ab962942669..e7f8ef7705e 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -57,8 +59,23 @@ private Object getInstance() throws Exception { private Object invoke(HashMap event, Context context) throws Exception { Method[] methods = this.clazz.getDeclaredMethods(); - - return methods[1].invoke(this.instance, event, context); + Method method = methods[1]; + Class requestClass = method.getParameterTypes()[0]; + + if (requestClass.isAssignableFrom(event.getClass())) { + return method.invoke(this.instance, event, context); + } else { + Object request = requestClass.newInstance(); + PropertyDescriptor[] properties = Introspector.getBeanInfo(requestClass).getPropertyDescriptors(); + for(int i=0; i < properties.length; i++) { + if (properties[i].getWriteMethod() == null) continue; + String propertyName = properties[i].getName(); + if (event.containsKey(propertyName)) { + properties[i].getWriteMethod().invoke(request, event.get(propertyName)); + } + } + return method.invoke(this.instance, request, context); + } } private HashMap parseInput(String input) throws IOException { From d9c986e97f28e33b4e83beb4bf93028e274386cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20A=2E=20A=CC=81lvarez?= Date: Wed, 6 Dec 2017 13:47:39 +0100 Subject: [PATCH 012/125] pass handler name to java --- lib/plugins/aws/invokeLocal/index.js | 21 ++++++++++++++------- lib/plugins/aws/invokeLocal/index.test.js | 6 ++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index f73c5cb10fe..49cd79b349f 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -118,10 +118,10 @@ class AwsInvokeLocal { || this.serverless.service.provider.runtime || 'nodejs4.3'; const handler = this.options.functionObj.handler; - const handlerPath = handler.split('.')[0]; - const handlerName = handler.split('.')[1]; if (runtime.startsWith('nodejs')) { + const handlerPath = handler.split('.')[0]; + const handlerName = handler.split('.')[1]; return this.invokeLocalNodeJs( handlerPath, handlerName, @@ -130,6 +130,8 @@ class AwsInvokeLocal { } if (runtime === 'python2.7' || runtime === 'python3.6') { + const handlerPath = handler.split('.')[0]; + const handlerName = handler.split('.')[1]; return this.invokeLocalPython( process.platform === 'win32' ? 'python.exe' : runtime, handlerPath, @@ -139,9 +141,12 @@ class AwsInvokeLocal { } if (runtime === 'java8') { + const className = handler.split('::')[0]; + const handlerName = handler.split('::')[1] || 'handleRequest'; return this.invokeLocalJava( 'java', - handler, + className, + handlerName, this.serverless.service.package.artifact, this.options.data, this.options.context); @@ -177,11 +182,12 @@ class AwsInvokeLocal { }); } - callJavaBridge(artifactPath, className, input) { + callJavaBridge(artifactPath, className, handlerName, input) { return new BbPromise((resolve) => fs.statAsync(artifactPath).then(() => { const java = spawn('java', [ `-DartifactPath=${artifactPath}`, `-DclassName=${className}`, + `-DhandlerName=${handlerName}`, '-jar', path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), ]); @@ -201,7 +207,7 @@ class AwsInvokeLocal { })); } - invokeLocalJava(runtime, className, artifactPath, event, customContext) { + invokeLocalJava(runtime, className, handlerName, artifactPath, event, customContext) { const timeout = Number(this.options.functionObj.timeout) || Number(this.serverless.service.provider.timeout) || 6; @@ -220,7 +226,7 @@ class AwsInvokeLocal { const executablePath = path.join(javaBridgePath, 'target'); return new BbPromise(resolve => fs.statAsync(executablePath) - .then(() => this.callJavaBridge(artifactPath, className, input)) + .then(() => this.callJavaBridge(artifactPath, className, handlerName, input)) .then(resolve) .catch(() => { const mvn = spawn('mvn', [ @@ -235,7 +241,8 @@ class AwsInvokeLocal { mvn.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); mvn.stdin.end(); - mvn.on('close', () => this.callJavaBridge(artifactPath, className, input).then(resolve)); + mvn.on('close', () => this.callJavaBridge(artifactPath, className, handlerName, input) + .then(resolve)); })); } diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index d2542acaeb6..c2e83743507 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -379,6 +379,7 @@ describe('AwsInvokeLocal', () => { expect(invokeLocalJavaStub.calledWithExactly( 'java', 'handler.hello', + 'handleRequest', undefined, {}, undefined @@ -588,6 +589,7 @@ describe('AwsInvokeLocal', () => { awsInvokeLocalMocked.callJavaBridge( __dirname, 'com.serverless.Handler', + 'handleRequest', '{}' ).then(() => { expect(writeChildStub.calledOnce).to.be.equal(true); @@ -625,6 +627,7 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.invokeLocalJava( 'java', 'com.serverless.Handler', + 'handleRequest', __dirname, {} ).then(() => { @@ -632,6 +635,7 @@ describe('AwsInvokeLocal', () => { expect(callJavaBridgeStub.calledWithExactly( __dirname, 'com.serverless.Handler', + 'handleRequest', JSON.stringify({ event: {}, context: { @@ -694,6 +698,7 @@ describe('AwsInvokeLocal', () => { awsInvokeLocalMocked.invokeLocalJava( 'java', 'com.serverless.Handler', + 'handleRequest', __dirname, {} ).then(() => { @@ -701,6 +706,7 @@ describe('AwsInvokeLocal', () => { expect(callJavaBridgeMockedStub.calledWithExactly( __dirname, 'com.serverless.Handler', + 'handleRequest', JSON.stringify({ event: {}, context: { From db08b03be41605865e761ccc49f20deceeda2a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20A=2E=20A=CC=81lvarez?= Date: Wed, 6 Dec 2017 14:11:07 +0100 Subject: [PATCH 013/125] resolve handler method like AWS lambda --- .../java/com/serverless/InvokeBridge.java | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java index e7f8ef7705e..879c52f2fc5 100644 --- a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java @@ -18,12 +18,14 @@ public class InvokeBridge { private File artifact; private String className; + private String handlerName; private Object instance; private Class clazz; private InvokeBridge() { this.artifact = new File(new File("."), System.getProperty("artifactPath")); this.className = System.getProperty("className"); + this.handlerName = System.getProperty("handlerName"); try { HashMap parsedInput = parseInput(getInput()); @@ -58,14 +60,12 @@ private Object getInstance() throws Exception { } private Object invoke(HashMap event, Context context) throws Exception { - Method[] methods = this.clazz.getDeclaredMethods(); - Method method = methods[1]; + Method method = findHandlerMethod(this.clazz, this.handlerName); Class requestClass = method.getParameterTypes()[0]; - if (requestClass.isAssignableFrom(event.getClass())) { - return method.invoke(this.instance, event, context); - } else { - Object request = requestClass.newInstance(); + Object request = event; + if (!requestClass.isAssignableFrom(event.getClass())) { + request = requestClass.newInstance(); PropertyDescriptor[] properties = Introspector.getBeanInfo(requestClass).getPropertyDescriptors(); for(int i=0; i < properties.length; i++) { if (properties[i].getWriteMethod() == null) continue; @@ -74,8 +74,38 @@ private Object invoke(HashMap event, Context context) throws Exc properties[i].getWriteMethod().invoke(request, event.get(propertyName)); } } + } + + if (method.getParameterCount() == 1) { + return method.invoke(this.instance, request); + } else if (method.getParameterCount() == 2) { return method.invoke(this.instance, request, context); + } else { + throw new NoSuchMethodException("Handler should take 1 or 2 arguments: " + method); + } + } + + private Method findHandlerMethod(Class clazz, String handlerName) throws Exception { + Method candidateMethod = null; + for(Method method: clazz.getDeclaredMethods()) { + if (method.getName().equals(handlerName) && !method.isBridge()) { + // Select the method with the largest number of parameters + // If two or more methods have the same number of parameters, AWS Lambda selects the method that has + // the Context as the last parameter. + // If none or all of these methods have the Context parameter, then the behavior is undefined. + int paramCount = method.getParameterCount(); + boolean lastParamIsContext = paramCount >= 1 && method.getParameterTypes()[paramCount-1].getName().equals("com.amazonaws.services.lambda.runtime.Context"); + if (candidateMethod == null || paramCount > candidateMethod.getParameterCount() || (paramCount == candidateMethod.getParameterCount() && lastParamIsContext)) { + candidateMethod = method; + } + } } + + if (candidateMethod == null) { + throw new NoSuchMethodException("Could not find handler for " + handlerName + " in " + clazz.getName()); + } + + return candidateMethod; } private HashMap parseInput(String input) throws IOException { From b444155a279992efc5dd402179d8ae1450745a4d Mon Sep 17 00:00:00 2001 From: Will Yang Date: Fri, 22 Dec 2017 11:15:31 +1100 Subject: [PATCH 014/125] improve aws cloudwatchlog filter to avoid massive quotes in serverless.yml --- .../compile/events/cloudWatchLog/index.js | 2 +- .../events/cloudWatchLog/index.test.js | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js index 400a6fae0a4..304bf19ba7a 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js @@ -84,7 +84,7 @@ class AwsCompileCloudWatchLogEvents { "DependsOn": "${lambdaPermissionLogicalId}", "Properties": { "LogGroupName": "${LogGroupName}", - "FilterPattern": "${FilterPattern}", + "FilterPattern": ${JSON.stringify(FilterPattern)}, "DestinationArn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] } } } diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js index 7777188d239..04ebc33cb78 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js @@ -138,6 +138,28 @@ describe('AwsCompileCloudWatchLogEvents', () => { ).to.equal('{$.userIdentity.type = Root}'); }); + it('should respect "filter" variable of plain text', () => { + awsCompileCloudWatchLogEvents.serverless.service.functions = { + first: { + events: [ + { + cloudwatchLog: { + logGroup: '/aws/lambda/hello1', + filter: '"userIdentity.type=Root" - "logLevel=Debug"', + }, + }, + ], + }, + }; + + awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); + + expect(awsCompileCloudWatchLogEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 + .Properties.FilterPattern + ).to.equal('"userIdentity.type=Root" - "logLevel=Debug"'); + }); + it('should set an empty string for FilterPattern statement when "filter" variable is not given' , () => { awsCompileCloudWatchLogEvents.serverless.service.functions = { From ced088c4faa626e1bf8cf53ce2ea50b0d9e6af77 Mon Sep 17 00:00:00 2001 From: Will Yang Date: Wed, 3 Jan 2018 14:15:54 +1100 Subject: [PATCH 015/125] unescape quotes when necessary to compatible with old filter patterns --- .../aws/package/compile/events/cloudWatchLog/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js index 304bf19ba7a..697d9736d09 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js @@ -78,6 +78,13 @@ class AwsCompileCloudWatchLogEvents { .getLambdaCloudWatchLogPermissionLogicalId(functionName, cloudWatchLogNumberInFunction); + // unescape quotes once when the first quote is detected escaped + const idxFirstSlash = FilterPattern.indexOf('\\'); + const idxFirstQuote = FilterPattern.indexOf('"'); + if (idxFirstSlash >= 0 && idxFirstQuote >= 0 && idxFirstQuote > idxFirstSlash) { + FilterPattern = FilterPattern.replace(/\\("|\\|')/g, (match, g) => g); + } + const cloudWatchLogRuleTemplate = ` { "Type": "AWS::Logs::SubscriptionFilter", From 624234603462b2a194375e7ed56154e8b35910cb Mon Sep 17 00:00:00 2001 From: Will Yang Date: Thu, 4 Jan 2018 12:32:46 +1100 Subject: [PATCH 016/125] add test case for escaped quotes --- .../events/cloudWatchLog/index.test.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js index 04ebc33cb78..f323bf61a27 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js @@ -160,6 +160,28 @@ describe('AwsCompileCloudWatchLogEvents', () => { ).to.equal('"userIdentity.type=Root" - "logLevel=Debug"'); }); + it('should respect escaped "filter" variable of plain text', () => { + awsCompileCloudWatchLogEvents.serverless.service.functions = { + first: { + events: [ + { + cloudwatchLog: { + logGroup: '/aws/lambda/hello1', + filter: '\\"userIdentity.type=Root\\" - \\"logLevel=Debug\\"', + }, + }, + ], + }, + }; + + awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); + + expect(awsCompileCloudWatchLogEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 + .Properties.FilterPattern + ).to.equal('"userIdentity.type=Root" - "logLevel=Debug"'); + }); + it('should set an empty string for FilterPattern statement when "filter" variable is not given' , () => { awsCompileCloudWatchLogEvents.serverless.service.functions = { From 5a0a31f8a0d51381a3de2e55a8e0884594a10cf8 Mon Sep 17 00:00:00 2001 From: Will Yang Date: Fri, 5 Jan 2018 09:37:19 +1100 Subject: [PATCH 017/125] use double quotes string to be better to represent the test case --- .../package/compile/events/cloudWatchLog/index.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js index f323bf61a27..bfc3263cd35 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js @@ -145,7 +145,7 @@ describe('AwsCompileCloudWatchLogEvents', () => { { cloudwatchLog: { logGroup: '/aws/lambda/hello1', - filter: '"userIdentity.type=Root" - "logLevel=Debug"', + filter: '"Total amount" -"level=Debug"', }, }, ], @@ -157,7 +157,7 @@ describe('AwsCompileCloudWatchLogEvents', () => { expect(awsCompileCloudWatchLogEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 .Properties.FilterPattern - ).to.equal('"userIdentity.type=Root" - "logLevel=Debug"'); + ).to.equal('"Total amount" -"level=Debug"'); }); it('should respect escaped "filter" variable of plain text', () => { @@ -167,7 +167,7 @@ describe('AwsCompileCloudWatchLogEvents', () => { { cloudwatchLog: { logGroup: '/aws/lambda/hello1', - filter: '\\"userIdentity.type=Root\\" - \\"logLevel=Debug\\"', + filter: "\\\"Total amount\\\" -\\\"level=Debug\\\"", // eslint-disable-line quotes }, }, ], @@ -179,7 +179,7 @@ describe('AwsCompileCloudWatchLogEvents', () => { expect(awsCompileCloudWatchLogEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 .Properties.FilterPattern - ).to.equal('"userIdentity.type=Root" - "logLevel=Debug"'); + ).to.equal('"Total amount" -"level=Debug"'); }); it('should set an empty string for FilterPattern statement when "filter" variable is not given' From 1ae47641b171d575bc7190797bbfd3d8b8d2ce06 Mon Sep 17 00:00:00 2001 From: thomasmichaelwallace Date: Fri, 12 Jan 2018 13:57:21 +0000 Subject: [PATCH 018/125] allow authorizer.name to override implied authorizer.arn name. --- .../compile/events/apiGateway/lib/validate.js | 6 ++++- .../events/apiGateway/lib/validate.test.js | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index 03dcbcfb259..bbafbb75163 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -220,7 +220,11 @@ module.exports = { type = 'AWS_IAM'; } else if (authorizer.arn) { arn = authorizer.arn; - name = this.provider.naming.extractAuthorizerNameFromArn(arn); + if (_.isString(authorizer.name)) { + name = authorizer.name; + } else { + name = this.provider.naming.extractAuthorizerNameFromArn(arn); + } } else if (authorizer.name) { name = authorizer.name; arn = this.getLambdaArn(name); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index 6338005f271..a2132f048e0 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -934,6 +934,30 @@ describe('#validate()', () => { expect(validated.events[0].http.authorizer.arn).to.equal('xxx:dev-authorizer'); }); + it('should handle an authorizer.arn with an explicit authorizer.name object', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + path: 'foo/bar', + method: 'GET', + authorizer: { + arn: 'xxx:dev-authorizer', + name: 'custom-name', + }, + }, + }, + ], + }, + }; + + const validated = awsCompileApigEvents.validate(); + expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events[0].http.authorizer.name).to.equal('custom-name'); + expect(validated.events[0].http.authorizer.arn).to.equal('xxx:dev-authorizer'); + }); + it('should throw an error if the provided config is not an object', () => { awsCompileApigEvents.serverless.service.functions = { first: { From 70d1bcb8d5692a3d17dfd018edb6848eec904f2a Mon Sep 17 00:00:00 2001 From: David Przybilla Date: Sun, 14 Jan 2018 19:35:45 +0900 Subject: [PATCH 019/125] Adding service : cloudwatch alerts on slack --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c3e8d36d28e..74d93dbb5a4 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ The following are services you can instantly install and use by running `serverl * [Ruby](https://github.com/stewartlord/serverless-ruby) - Call a Ruby function from your lambda * [Slack App](https://github.com/johnagan/serverless-slack-app) - Slack App Boilerplate with OAuth and Bot actions * [Swift](https://github.com/choefele/swift-lambda-app) - Full-featured project template to develop Lambda functions in Swift +* [Cloudwatch Alerts on Slack](github.com/dav009/serverless-aws-alarms-notifier) - Get AWS Cloudwatch alerts notifications on Slack **Note**: the `serverless install` command will only work on V1.0 or later. From fd12262a55aa9c19906479f54a46bff9e1c41f91 Mon Sep 17 00:00:00 2001 From: David Przybilla Date: Mon, 15 Jan 2018 09:46:25 +0900 Subject: [PATCH 020/125] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74d93dbb5a4..e6ff0f7fa4e 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ The following are services you can instantly install and use by running `serverl * [Ruby](https://github.com/stewartlord/serverless-ruby) - Call a Ruby function from your lambda * [Slack App](https://github.com/johnagan/serverless-slack-app) - Slack App Boilerplate with OAuth and Bot actions * [Swift](https://github.com/choefele/swift-lambda-app) - Full-featured project template to develop Lambda functions in Swift -* [Cloudwatch Alerts on Slack](github.com/dav009/serverless-aws-alarms-notifier) - Get AWS Cloudwatch alerts notifications on Slack +* [Cloudwatch Alerts on Slack](https://github.com/dav009/serverless-aws-alarms-notifier) - Get AWS Cloudwatch alerts notifications on Slack **Note**: the `serverless install` command will only work on V1.0 or later. From f88415eedde8201afc85d6a3e7880310b0cd71c8 Mon Sep 17 00:00:00 2001 From: drexler Date: Tue, 16 Jan 2018 08:07:55 -0500 Subject: [PATCH 021/125] enable source map generation for nodejs-typescript example --- .../create/templates/aws-nodejs-typescript/webpack.config.js | 1 + 1 file changed, 1 insertion(+) 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 b15fcd6d494..41c1876cd1f 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js @@ -3,6 +3,7 @@ const slsw = require('serverless-webpack'); module.exports = { entry: slsw.lib.entries, + devtool: 'source-map', resolve: { extensions: [ '.js', From 298154abf13a02d50c8643106c67b5cc523ef51c Mon Sep 17 00:00:00 2001 From: Andres Date: Thu, 18 Jan 2018 17:08:14 +0100 Subject: [PATCH 022/125] Update documentation and templates for serverless-kubeless 0.3 --- docs/providers/kubeless/README.md | 1 + docs/providers/kubeless/guide/functions.md | 136 ++++++++++++++++- docs/providers/kubeless/guide/packaging.md | 144 ++++++++++++++++++ .../templates/kubeless-nodejs/package.json | 2 +- .../templates/kubeless-python/package.json | 2 +- 5 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 docs/providers/kubeless/guide/packaging.md diff --git a/docs/providers/kubeless/README.md b/docs/providers/kubeless/README.md index b9bba665a2d..aaf72767710 100644 --- a/docs/providers/kubeless/README.md +++ b/docs/providers/kubeless/README.md @@ -30,6 +30,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Functions
  • Events
  • Deploying
  • +
  • Packaging
  • Debugging
  • Workflow
  • diff --git a/docs/providers/kubeless/guide/functions.md b/docs/providers/kubeless/guide/functions.md index b85619a2694..294de477914 100644 --- a/docs/providers/kubeless/guide/functions.md +++ b/docs/providers/kubeless/guide/functions.md @@ -25,6 +25,9 @@ service: my-service provider: name: kubeless runtime: python2.7 + memorySize: 512M # optional, maximum memory + timeout: 10 # optional, in seconds, default is 180 + namespace: funcions # optional, deployment namespace if not specified it uses "default" plugins: - serverless-kubeless @@ -34,7 +37,12 @@ functions: # and the K8s service object to get a request to call the function hello: # The function to call as a response to the HTTP event - handler: handler.hello + handler: handler.hello # required, handler set + description: Description of what the function does # optional, to set the description as an annotation + memorySize: 512M # optional, maximum memory + timeout: 10 # optional, in seconds, default is 180 + namespace: funcions # optional, deployment namespace, if not specified "default" will be used + port: 8081 # optional, deploy http-based function with a custom port, default is 8080 ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -88,3 +96,129 @@ The Kubeless provider plugin supports the following runtimes. Please see the following repository for sample projects using those runtimes: [https://github.com/serverless/serverless-kubeless/tree/master/examples](https://github.com/serverless/serverless-kubeless/tree/master/examples) + +## Environment Variables + +You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key/value collection of strings: + +```yml +# serverless.yml +service: service-name +provider: kubeless +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + environment: + TABLE_NAME: tableName +``` + +Or if you want to apply environment variable configuration to all functions in your service, you can add the configuration to the higher level `provider` object. Environment variables configured at the function level are merged with those at the provider level, so your function with specific environment variables will also have access to the environment variables defined at the provider level. If an environment variable with the same key is defined at both the function and provider levels, the function-specific value overrides the provider-level default value. For example: + +```yml +# serverless.yml +service: service-name +provider: + name: kubeless + environment: + SYSTEM_NAME: mySystem + TABLE_NAME: tableName1 + +plugins: + - serverless-kubeless + +functions: + hello: + # this function will have SYSTEM_NAME=mySystem and TABLE_NAME=tableName1 from the provider-level environment config above + handler: handler.hello + users: + # this function will have SYSTEM_NAME=mySystem from the provider-level environment config above + # but TABLE_NAME will be tableName2 because this more specific config will override the default above + handler: handler.users + environment: + TABLE_NAME: tableName2 +``` + +## Labels + +Using the `labels` configuration makes it possible to add `key` / `value` labels to your functions. + +Those labels will appear in deployments, services and pods and will make it easier to group functions by label or find functions with a common label. + +```yml +provider: + name: kubeless + +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + labels: + foo: bar +``` + +## Custom hostname and path + +It is possible to define a custom hostname and path that will be used to serve a function in a specific endpoint. For doing this, it is necessary to have an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) available in the cluster. + +```yml +provider: + name: kubeless + hostname: myhostname.io +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + events: + - http: + path: /hello +``` + +In the example above, once the Ingress Rule has been processed by the Ingress controller, you can call the function using as endpoing `myhostname.io/hello`. + +If no hostname is given but a function specifies a `path`, the plugin will use the IP of the cluster followed by a DNS mapping service. By default [nip.io](http://nip.io) will be used but this can be configured with the property `defaultDNSResolution`. + +```yml +provider: + name: kubeless + defaultDNSResolution: 'xip.io' +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + events: + - http: + path: /hello +``` + +The above will result in an endpoint like `1.2.3.4.xip.io/hello` where `1.2.3.4` is the IP of the cluster server. + +The final URL in which the function will be listening can be retrieved executing `serverless info`. + +## Custom images (alpha feature) + +It is possible to skip the Kubeless build system and specify a prebuilt image to run a function. This feature is useful for using Kubeless with languages that are still not supported or if the function package [is over 1MB](./packaging.md#package-maximum-size). To get more information about how to use custom images visit the [upstream documentation](https://github.com/kubeless/kubeless/blob/master/docs/runtimes.md#custom-runtime-alpha). + +```yml +service: hello + +provider: + name: kubeless + runtime: python2.7 + +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + image: tuna/kubeless-python:0.0.6 +``` diff --git a/docs/providers/kubeless/guide/packaging.md b/docs/providers/kubeless/guide/packaging.md new file mode 100644 index 00000000000..2b788756c1b --- /dev/null +++ b/docs/providers/kubeless/guide/packaging.md @@ -0,0 +1,144 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/packaging) + + +# Kubeless - Packaging + +## Package CLI Command + +Using the Serverless CLI tool, you can package your project without deploying with Kubeless. This is best used with CI / CD workflows to ensure consistent deployable artifacts. + +Running the following command will build and save all of the deployment artifacts in the service's .serverless directory: + +```bash +serverless package +``` + +However, you can also use the --package option to add a destination path and Serverless will store your deployment artifacts there (./my-artifacts in the following case): + +```bash +serverless package --package my-artifacts +``` + +## Package Maximum Size + +Kubeless uses [Kubernetes ConfigMaps](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) to store functions configuration and code. These ConfigMaps have a limitation in size of one MB since they are stored as a single entry in a `etcd` database. Due to this limitation, the maximum possible size for a Kubeless function package is no more than one MB. Note that only code and configuration files should be included in this package, dependencies will be installed during the build process. If your function package size is over one MB please [exclude some directories](#exclude-include) or create a [custom image](./functions#custom-images-alpha-feature) with your function. By default, files under the `node_modules` folder will be excluded. + +## Package Configuration + +Sometimes you might like to have more control over your function artifacts and how they are packaged. + +You can use the `package` and `exclude` configuration for more control over the packaging process. + +### Exclude / include + +Exclude and include allows you to define globs that will be excluded / included from the resulting artifact. If you wish to +include files you can use a glob pattern prefixed with `!` such as `!re-include-me/**` in `exclude` or the dedicated `include` config. +Serverless will run the glob patterns in order. + +At first it will apply the globs defined in `exclude`. After that it'll add all the globs from `include`. This way you can always re-include +previously excluded files and directories. + +### Examples + +Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively + +``` yml +package: + exclude: + - node_modules/** + - '!node_modules/node-fetch/**' +``` + +Exclude all files but `handler.js` using `exclude` and `include` + +``` yml +package: + exclude: + - src/** + include: + - src/function/handler.js +``` + +**Note:** Don't forget to use the correct glob syntax if you want to exclude directories + +``` +exclude: + - tmp/** + - .git/** +``` + +### Artifact + +For complete control over the packaging process you can specify your own artifact zip file. +Serverless won't zip your service if this is configured and therefore `exclude` and `include` will be ignored. Either you use artifact or include / exclude. + +The artifact option is especially useful in case your development environment allows you to generate a deployable artifact like Maven does for Java. + +### Example + +```yml +service: my-service +package: + artifact: path/to/my-artifact.zip +``` + +### Packaging functions separately + +If you want even more controls over your functions for deployment you can configure them to be packaged independently. This allows you more control for optimizing your deployment. To enable individual packaging set `individually` to true in the service or function wide packaging settings. + +Then for every function you can use the same `exclude`, `include` or `artifact` config options as you can service wide. The `exclude` and `include` option will be merged with the service wide options to create one `exclude` and `include` config per function during packaging. + +```yml +service: my-service +package: + individually: true + exclude: + - excluded-by-default.json +functions: + hello: + handler: handler.hello + package: + # We're including this file so it will be in the final package of this function only + include: + - excluded-by-default.json + world: + handler: handler.hello + package: + exclude: + - some-file.js +``` + +You can also select which functions to be packaged separately, and have the rest use the service package by setting the `individually` flag at the function level: + +```yml +service: my-service +functions: + hello: + handler: handler.hello + world: + handler: handler.hello + package: + individually: true +``` + +### Development dependencies + +Serverless will auto-detect and exclude development dependencies based on the runtime your service is using. + +This ensures that only the production relevant packages and modules are included in your zip file. Doing this drastically reduces the overall size of the deployment package which will be uploaded to the cloud provider. + +You can opt-out of automatic dev dependency exclusion by setting the `excludeDevDependencies` package config to `false`: + +```yml +package: + excludeDevDependencies: false +``` diff --git a/lib/plugins/create/templates/kubeless-nodejs/package.json b/lib/plugins/create/templates/kubeless-nodejs/package.json index f3231c9d56a..039292997e2 100644 --- a/lib/plugins/create/templates/kubeless-nodejs/package.json +++ b/lib/plugins/create/templates/kubeless-nodejs/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Example function for serverless kubeless", "dependencies": { - "serverless-kubeless": "^0.2.4", + "serverless-kubeless": "^0.3.1", "lodash": "^4.1.0" }, "devDependencies": {}, diff --git a/lib/plugins/create/templates/kubeless-python/package.json b/lib/plugins/create/templates/kubeless-python/package.json index 9809da791b0..2eacb76727a 100644 --- a/lib/plugins/create/templates/kubeless-python/package.json +++ b/lib/plugins/create/templates/kubeless-python/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Sample Kubeless Python serverless framework service.", "dependencies": { - "serverless-kubeless": "^0.2.4" + "serverless-kubeless": "^0.3.1" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" From 4bf41186f9127ae241aea802d66188a1820a6cb8 Mon Sep 17 00:00:00 2001 From: Andres Date: Thu, 18 Jan 2018 17:26:49 +0100 Subject: [PATCH 023/125] Document how to install dependencies --- docs/providers/kubeless/guide/functions.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/providers/kubeless/guide/functions.md b/docs/providers/kubeless/guide/functions.md index 294de477914..8881110c865 100644 --- a/docs/providers/kubeless/guide/functions.md +++ b/docs/providers/kubeless/guide/functions.md @@ -97,6 +97,16 @@ Please see the following repository for sample projects using those runtimes: [https://github.com/serverless/serverless-kubeless/tree/master/examples](https://github.com/serverless/serverless-kubeless/tree/master/examples) +## Installing dependencies + +For installing dependencies the standard dependency file should be placed in the function folder: + + - For Python functions, it will use the file `requirements.txt` + - For Nodejs functions, `dependencies` in the `package.json` file will be installed + - For Ruby functions, it will use the file `Gemfile.rb` + +If one of the above files is found, the depencies will be installed using a [`Init Container`](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). + ## Environment Variables You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key/value collection of strings: From 6b6a4941583db369cf360e0366623cb92391a04d Mon Sep 17 00:00:00 2001 From: Daniel Schildt Date: Fri, 19 Jan 2018 05:06:51 +0200 Subject: [PATCH 024/125] docs(google) update hello-world example's invoke If a new user creates a new service with the `google-nodejs` template, the pre-configured function is named as `first` instead of `helloWorld`. More details from the configuration file of the template: - https://github.com/serverless/boilerplate-googlecloudfunctions-nodejs/blob/master/serverless.yml#L22 --- docs/providers/google/examples/hello-world/node/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/google/examples/hello-world/node/README.md b/docs/providers/google/examples/hello-world/node/README.md index 4f6f3b10f05..51f5af2044b 100644 --- a/docs/providers/google/examples/hello-world/node/README.md +++ b/docs/providers/google/examples/hello-world/node/README.md @@ -31,7 +31,7 @@ Update the `credentials` and your `project` property in the `serverless.yml` fil ## 5. Invoke deployed function -`serverless invoke --function helloWorld` +`serverless invoke --function first` In your terminal window you should see a response from the Google Cloud From cfc52f91f5ca883e5ca0edaf29a47fc00c5b3d7d Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 19 Jan 2018 14:52:18 -0800 Subject: [PATCH 025/125] Update README.md --- docs/providers/spotinst/examples/node/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/spotinst/examples/node/README.md b/docs/providers/spotinst/examples/node/README.md index a8345f43017..a4b8f43d78e 100644 --- a/docs/providers/spotinst/examples/node/README.md +++ b/docs/providers/spotinst/examples/node/README.md @@ -14,7 +14,7 @@ layout: Doc Make sure `serverless` is installed. ## 1. Create a service -`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory there the JavaScript template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. +`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory where the JavaScript template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy ```bash @@ -42,4 +42,4 @@ Congrats you have just deployed and ran your Hello World function! `-t` is short hand for `--template` -`-p` is short hang for `--path` \ No newline at end of file +`-p` is short hang for `--path` From 4353c8105e46117dbe65402e56e45c9f2af0d4be Mon Sep 17 00:00:00 2001 From: Roberto Novelo Date: Mon, 22 Jan 2018 17:26:43 -0600 Subject: [PATCH 026/125] Typo Fix --- docs/providers/aws/events/cloudwatch-log.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/cloudwatch-log.md b/docs/providers/aws/events/cloudwatch-log.md index bddf59117da..2072598ab27 100644 --- a/docs/providers/aws/events/cloudwatch-log.md +++ b/docs/providers/aws/events/cloudwatch-log.md @@ -14,7 +14,7 @@ layout: Doc ## Simple event definition -This will enable your Lambda function to be called by an Log Stream. +This will enable your Lambda function to be called by a Log Stream. ```yml functions: From 0283af7a3ede148d4dfd701db808a4cca213967e Mon Sep 17 00:00:00 2001 From: CuongLe Date: Tue, 23 Jan 2018 12:38:05 +0700 Subject: [PATCH 027/125] remove aws credentials.signatureVersion in `enableS3TransferAcceleration` Fix error: The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256. See more: https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region --- lib/plugins/aws/provider/awsProvider.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index bcc78e58372..6eae516f637 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -328,8 +328,6 @@ class AwsProvider { enableS3TransferAcceleration(credentials) { this.serverless.cli.log('Using S3 Transfer Acceleration Endpoint...'); credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign - credentials.signatureVersion = 'v2'; // eslint-disable-line no-param-reassign - // see https://github.com/aws/aws-sdk-js/issues/281 } getRegion() { From df85e2d9bc16a7bb8d5fcfe5a94b5c30aa2ca1ce Mon Sep 17 00:00:00 2001 From: Yun Zhi Lin Date: Tue, 23 Jan 2018 23:52:51 +1100 Subject: [PATCH 028/125] add support for AWS go: new create templates aws-go and aws-go-dep, docs, updated integration tests --- README.md | 2 +- docker-compose.yml | 10 ++ .../aws/examples/hello-world/README.md | 4 +- .../aws/examples/hello-world/go/README.md | 72 ++++++++++++ docs/providers/aws/guide/services.md | 1 + lib/plugins/create/create.js | 2 + lib/plugins/create/create.test.js | 34 ++++++ .../create/templates/aws-go-dep/Gopkg.lock | 19 ++++ .../create/templates/aws-go-dep/Gopkg.toml | 25 +++++ .../create/templates/aws-go-dep/Makefile | 4 + .../create/templates/aws-go-dep/gitignore | 8 ++ .../create/templates/aws-go-dep/hello/main.go | 19 ++++ .../templates/aws-go-dep/serverless.yml | 104 ++++++++++++++++++ .../create/templates/aws-go-dep/world/main.go | 19 ++++ lib/plugins/create/templates/aws-go/Makefile | 4 + lib/plugins/create/templates/aws-go/gitignore | 5 + .../create/templates/aws-go/hello/main.go | 19 ++++ .../create/templates/aws-go/serverless.yml | 104 ++++++++++++++++++ .../create/templates/aws-go/world/main.go | 19 ++++ tests/templates/integration-test-template | 5 + tests/templates/test_all_templates | 2 + 21 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 docs/providers/aws/examples/hello-world/go/README.md create mode 100644 lib/plugins/create/templates/aws-go-dep/Gopkg.lock create mode 100644 lib/plugins/create/templates/aws-go-dep/Gopkg.toml create mode 100644 lib/plugins/create/templates/aws-go-dep/Makefile create mode 100644 lib/plugins/create/templates/aws-go-dep/gitignore create mode 100644 lib/plugins/create/templates/aws-go-dep/hello/main.go create mode 100644 lib/plugins/create/templates/aws-go-dep/serverless.yml create mode 100644 lib/plugins/create/templates/aws-go-dep/world/main.go create mode 100644 lib/plugins/create/templates/aws-go/Makefile create mode 100644 lib/plugins/create/templates/aws-go/gitignore create mode 100644 lib/plugins/create/templates/aws-go/hello/main.go create mode 100644 lib/plugins/create/templates/aws-go/serverless.yml create mode 100644 lib/plugins/create/templates/aws-go/world/main.go diff --git a/README.md b/README.md index e6ff0f7fa4e..044fb4b6c81 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ The following are services you can instantly install and use by running `serverl ## Features -* Supports Node.js, Python, Java, Scala, C#, F#, Groovy, Kotlin, PHP & Swift. +* Supports Node.js, Python, Java, Scala, C#, F#, Go, Groovy, Kotlin, PHP & Swift. * Manages the lifecycle of your serverless architecture (build, deploy, update, delete). * Safely deploy functions, events and their required resources together via provider resource managers (e.g., AWS CloudFormation). * Functions can be grouped ("serverless services") for easy management of code, resources & processes, across large projects & teams. diff --git a/docker-compose.yml b/docker-compose.yml index f9ae71b3ed3..98c398c8f73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,6 +63,16 @@ services: image: microsoft/dotnet:1.0.4-sdk volumes: - ./tmp/serverless-integration-test-aws-fsharp:/app + aws-go: + image: golang:1.9 + volumes: + - ./tmp/serverless-integration-test-aws-go:/app + - ./tmp/serverless-integration-test-aws-go:/go/src/app + aws-go-dep: + image: yunspace/golang:1.9 + volumes: + - ./tmp/serverless-integration-test-aws-go-dep:/app + - ./tmp/serverless-integration-test-aws-go-dep:/go/src/app aws-nodejs-typescript: image: node:6.10.3 volumes: diff --git a/docs/providers/aws/examples/hello-world/README.md b/docs/providers/aws/examples/hello-world/README.md index d1f1d070a4c..378b0567756 100644 --- a/docs/providers/aws/examples/hello-world/README.md +++ b/docs/providers/aws/examples/hello-world/README.md @@ -17,6 +17,8 @@ Pick your language of choice: * [JavaScript](./node) * [Python](./python) -* [csharp](./csharp) +* [C#](./csharp) +* [F#](./fsharp) +* [Go](./go) [View all examples](https://www.serverless.com/framework/docs/providers/aws/examples/) diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md new file mode 100644 index 00000000000..8c522ccaecd --- /dev/null +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -0,0 +1,72 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/go/) + + +# Hello World Go Example + +Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). + +You should also have [go](https://golang.org/doc/install) and [make](https://www.gnu.org/software/make/) + +It is always good practice to organise your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximise the benefits of go tooling. + +## 1. Create a service +There are two templates for `go`: + +1. [aws-go](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go) - `serverless create --template aws-go --path myService` +2. [aws-go-dep](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go-dep) - `serverless create --template aws-go-dep --path myService` + +where: +- 'aws-go' fetches dependencies using standard `go get`. +- 'aws-go-dep' uses [go dep](https://github.com/golang/dep) and requires your project to be in `$GOPATH/src` +- 'myService' is a new folder to be created with template service files. + +Change directories into 'myService' folder and you can see this project has 2 handler functions: `hello` and `world` split into 2 separate go packages (folders): + +``` +. +├── hello/ +│ └── main.go +├── world/ +│ └── main.go +``` + +This because a `main()` function is required as entry point for each handler executable. + +## 2. Build using go build to create static binaries + +Run `make build` to build both functions. Successful build should generate the following binaries: + +``` +. +├── bin/ +│ |── hello +│ └── world +``` + +## 3. Deploy +`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +## 4. Invoke deployed function +Invoking the both functions should return a successful results: + +```bash +serverless invoke -f hello +{ + "message": "Go Serverless v1.0! Your function executed successfully!" +} + +serverless invoke --f world +{ + "message": "Okay so your other function also executed successfully!" +} +``` + +Congrats you have just deployed and run your Hello World function! diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index 20a27a0270d..24d33dab159 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -63,6 +63,7 @@ Here are the available runtimes for AWS Lambda: * aws-scala-sbt * aws-csharp * aws-fsharp +* aws-go Check out the [create command docs](../cli-reference/create) for all the details and options. diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 8027ba9fb97..84368002a70 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -28,6 +28,8 @@ const validTemplates = [ 'aws-scala-sbt', 'aws-csharp', 'aws-fsharp', + 'aws-go', + 'aws-go-dep', 'azure-nodejs', 'google-nodejs', 'kubeless-python', diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 3bba193faa0..c0bf9d726da 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -726,5 +726,39 @@ describe('Create', () => { expect((/service: my-awesome-service/).test(serverlessYmlfileContent)).to.equal(true); }); }); + + it('should generate scaffolding for "aws-go" template', () => { + process.chdir(tmpDir); + create.options.template = 'aws-go'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include(path.join('hello', 'main.go')); + expect(dirContent).to.include(path.join('world', 'main.go')); + expect(dirContent).to.include('Makefile'); + expect(dirContent).to.include('.gitignore'); + }); + }); + + it('should generate scaffolding for "aws-go-dep" template', () => { + process.chdir(tmpDir); + create.options.template = 'aws-go-dep'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include(path.join('hello', 'main.go')); + expect(dirContent).to.include(path.join('world', 'main.go')); + expect(dirContent).to.include('Gopkg.toml'); + expect(dirContent).to.include('Gopkg.lock'); + expect(dirContent).to.include('Makefile'); + expect(dirContent).to.include('.gitignore'); + }); + }); }); }); diff --git a/lib/plugins/create/templates/aws-go-dep/Gopkg.lock b/lib/plugins/create/templates/aws-go-dep/Gopkg.lock new file mode 100644 index 00000000000..f93855f76bc --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/Gopkg.lock @@ -0,0 +1,19 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/aws/aws-lambda-go" + packages = [ + "lambda", + "lambda/messages", + "lambdacontext" + ] + revision = "6e2e37798efbb1dfd8e9c6681702e683a6046517" + version = "v1.0.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "85fa166cc59d0fa113a1517ffbb5dee0f1fa4a6795239936afb18c64364af759" + solver-name = "gps-cdcl" + solver-version = 1 \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/Gopkg.toml b/lib/plugins/create/templates/aws-go-dep/Gopkg.toml new file mode 100644 index 00000000000..8fce8929ead --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/Gopkg.toml @@ -0,0 +1,25 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/aws/aws-lambda-go" + version = "^1.0.1" diff --git a/lib/plugins/create/templates/aws-go-dep/Makefile b/lib/plugins/create/templates/aws-go-dep/Makefile new file mode 100644 index 00000000000..46a24c449e1 --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/Makefile @@ -0,0 +1,4 @@ +build: + dep ensure + env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go + env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/gitignore b/lib/plugins/create/templates/aws-go-dep/gitignore new file mode 100644 index 00000000000..99a966a94de --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/gitignore @@ -0,0 +1,8 @@ +# Serverless directories +.serverless + +# golang output binary directory +bin + +# golang vendor (dependencies) directory +vendor \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/hello/main.go b/lib/plugins/create/templates/aws-go-dep/hello/main.go new file mode 100644 index 00000000000..e75c5af0407 --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/hello/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Go Serverless v1.0! Your function executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml new file mode 100644 index 00000000000..67f92c77f2e --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -0,0 +1,104 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: aws-go-dep # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: aws + runtime: go1.x + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can add statements to the Lambda function's IAM Role here +# iamRoleStatements: +# - Effect: "Allow" +# Action: +# - "s3:ListBucket" +# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } +# - Effect: "Allow" +# Action: +# - "s3:PutObject" +# Resource: +# Fn::Join: +# - "" +# - - "arn:aws:s3:::" +# - "Ref" : "ServerlessDeploymentBucket" +# - "/*" + +# you can define service wide environment variables here +# environment: +# variable1: value1 + +package: + exclude: + - ./** + include: + - ./bin/** + +functions: + hello: + handler: bin/hello + world: + handler: bin/world + +# The following are a few example events you can configure +# NOTE: Please make sure to change your handler code to work with those events +# Check the event documentation for details +# events: +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env:BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic +# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 +# - alexaSkill +# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx +# - iot: +# sql: "SELECT * FROM 'some_topic'" +# - cloudwatchEvent: +# event: +# source: +# - "aws.ec2" +# detail-type: +# - "EC2 Instance State-change Notification" +# detail: +# state: +# - pending +# - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp + +# Define function environment variables here +# environment: +# variable2: value2 + +# you can add CloudFormation resource templates here +#resources: +# Resources: +# NewResource: +# Type: AWS::S3::Bucket +# Properties: +# BucketName: my-new-bucket +# Outputs: +# NewOutput: +# Description: "Description for the output" +# Value: "Some output value" diff --git a/lib/plugins/create/templates/aws-go-dep/world/main.go b/lib/plugins/create/templates/aws-go-dep/world/main.go new file mode 100644 index 00000000000..bf46aa6fbcb --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/world/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Okay so your other function also executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-go/Makefile b/lib/plugins/create/templates/aws-go/Makefile new file mode 100644 index 00000000000..3b697426e39 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/Makefile @@ -0,0 +1,4 @@ +build: + go get github.com/aws/aws-lambda-go/lambda + env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go + env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go/gitignore b/lib/plugins/create/templates/aws-go/gitignore new file mode 100644 index 00000000000..f5b4c36adc3 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/gitignore @@ -0,0 +1,5 @@ +# Serverless directories +.serverless + +# golang output binary directory +bin \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go/hello/main.go b/lib/plugins/create/templates/aws-go/hello/main.go new file mode 100644 index 00000000000..e75c5af0407 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/hello/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Go Serverless v1.0! Your function executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml new file mode 100644 index 00000000000..c3a1e2c95d4 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -0,0 +1,104 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: aws-go # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: aws + runtime: go1.x + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can add statements to the Lambda function's IAM Role here +# iamRoleStatements: +# - Effect: "Allow" +# Action: +# - "s3:ListBucket" +# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } +# - Effect: "Allow" +# Action: +# - "s3:PutObject" +# Resource: +# Fn::Join: +# - "" +# - - "arn:aws:s3:::" +# - "Ref" : "ServerlessDeploymentBucket" +# - "/*" + +# you can define service wide environment variables here +# environment: +# variable1: value1 + +package: + exclude: + - ./** + include: + - ./bin/** + +functions: + hello: + handler: bin/hello + world: + handler: bin/world + +# The following are a few example events you can configure +# NOTE: Please make sure to change your handler code to work with those events +# Check the event documentation for details +# events: +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env:BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic +# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 +# - alexaSkill +# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx +# - iot: +# sql: "SELECT * FROM 'some_topic'" +# - cloudwatchEvent: +# event: +# source: +# - "aws.ec2" +# detail-type: +# - "EC2 Instance State-change Notification" +# detail: +# state: +# - pending +# - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp + +# Define function environment variables here +# environment: +# variable2: value2 + +# you can add CloudFormation resource templates here +#resources: +# Resources: +# NewResource: +# Type: AWS::S3::Bucket +# Properties: +# BucketName: my-new-bucket +# Outputs: +# NewOutput: +# Description: "Description for the output" +# Value: "Some output value" diff --git a/lib/plugins/create/templates/aws-go/world/main.go b/lib/plugins/create/templates/aws-go/world/main.go new file mode 100644 index 00000000000..bf46aa6fbcb --- /dev/null +++ b/lib/plugins/create/templates/aws-go/world/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Okay so your other function also executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/tests/templates/integration-test-template b/tests/templates/integration-test-template index 468532a571b..4bbd065d859 100755 --- a/tests/templates/integration-test-template +++ b/tests/templates/integration-test-template @@ -37,5 +37,10 @@ serverless deploy -v echo "Invoking Service" serverless invoke --function hello +if [ $template == "aws-go" ] || [ $template == "aws-go-dep" ] +then + serverless invoke --function world +fi + echo "Removing Service" serverless remove -v diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates index 79bbebd5530..fd88105abf8 100755 --- a/tests/templates/test_all_templates +++ b/tests/templates/test_all_templates @@ -10,6 +10,8 @@ function integration-test { integration-test aws-csharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' integration-test aws-fsharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' +integration-test aws-go 'cd /go/src/app && make build' +integration-test aws-go-dep 'cd /go/src/app && make build' integration-test aws-groovy-gradle ./gradlew build integration-test aws-java-gradle ./gradlew build integration-test aws-java-maven mvn package From 92f0392f9d46d51e5d890d2292e75db7d22ca879 Mon Sep 17 00:00:00 2001 From: Adnan Asani Date: Thu, 25 Jan 2018 19:07:13 +0100 Subject: [PATCH 029/125] docs(google/cli) add invoke-local.md --- .../google/cli-reference/invoke-local.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/providers/google/cli-reference/invoke-local.md diff --git a/docs/providers/google/cli-reference/invoke-local.md b/docs/providers/google/cli-reference/invoke-local.md new file mode 100644 index 00000000000..54ab1170190 --- /dev/null +++ b/docs/providers/google/cli-reference/invoke-local.md @@ -0,0 +1,56 @@ + + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/invoke-local) + + + +# Google - Invoke Local + +Invokes deployed function locally. It allows to send event data to the function, read logs and display other important information of the function invocation. + +```bash +serverless invoke -f functionName +``` + +## Options + +* `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. + \_ `--data` or `-d` Data you want to pass into the function +* `--path` or `-p` Path to JSON or YAML file holding input data. This path is relative to the root directory of the service. +* `--raw` Pass data as a raw string even if it is JSON. If not set, JSON data are parsed and passed as an object. +* `--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. + +> Keep in mind that if you pass both `--path` and `--data`, the data included in the `--path` file will overwrite the data you passed with the `--data` flag. + +## Examples + +### Local function invocation + +```bash +serverless invoke local -f functionName +``` + +### Local function invocation with data + +```bash +serverless invoke local -f functionName -d '{ "data": "hello world" }' +``` + +### Local function invocation with data passing + +```bash +serverless invoke local -f functionName -p path/to/file.json + +# OR + +serverless invoke local -f functionName -p path/to/file.yaml +``` From d5dc0cbf4b93ef804444da8c20a42477d6605ba4 Mon Sep 17 00:00:00 2001 From: Alexey Kotlyarov Date: Mon, 29 Jan 2018 19:25:21 +1100 Subject: [PATCH 030/125] Do not explicitly color messages in white On white background terminals, white text is unreadable. Use the default terminal color instead. Fixes #4673 --- lib/classes/Error.js | 19 +++++++++---------- lib/plugins/aws/invoke/index.js | 4 ++-- lib/plugins/aws/invoke/index.test.js | 3 +-- lib/plugins/aws/utils/formatLambdaLogEvent.js | 2 +- .../aws/utils/formatLambdaLogEvent.test.js | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/classes/Error.js b/lib/classes/Error.js index 47e21897c94..53a97e7c0a7 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -19,7 +19,7 @@ const writeMessage = (messageType, message) => { consoleLog(' '); if (message) { - consoleLog(chalk.white(` ${message}`)); + consoleLog(` ${message}`); } consoleLog(' '); @@ -61,17 +61,16 @@ module.exports.logError = (e) => { consoleLog(' '); } - const platform = chalk.white(process.platform); - const nodeVersion = chalk.white(process.version.replace(/^[v|V]/, '')); - const slsVersion = chalk.white(version); + const platform = process.platform; + const nodeVersion = process.version.replace(/^[v|V]/, ''); + const slsVersion = version; consoleLog(chalk.yellow(' Get Support --------------------------------------------')); - consoleLog(`${chalk.yellow(' Docs: ')}${chalk.white('docs.serverless.com')}`); - consoleLog(`${chalk.yellow(' Bugs: ')}${chalk - .white('github.com/serverless/serverless/issues')}`); - consoleLog(`${chalk.yellow(' Forums: ')}${chalk.white('forum.serverless.com')}`); - consoleLog(`${chalk.yellow(' Chat: ')}${chalk - .white('gitter.im/serverless/serverless')}`); + consoleLog(`${chalk.yellow(' Docs: ')}${'docs.serverless.com'}`); + consoleLog(`${chalk.yellow(' Bugs: ')}${ + 'github.com/serverless/serverless/issues'}`); + consoleLog(`${chalk.yellow(' Forums: ')}${'forum.serverless.com'}`); + consoleLog(`${chalk.yellow(' Chat: ')}${'gitter.im/serverless/serverless'}`); consoleLog(' '); consoleLog(chalk.yellow(' Your Environment Information -----------------------------')); diff --git a/lib/plugins/aws/invoke/index.js b/lib/plugins/aws/invoke/index.js index 58cd837d902..2354a1d282d 100644 --- a/lib/plugins/aws/invoke/index.js +++ b/lib/plugins/aws/invoke/index.js @@ -83,12 +83,12 @@ class AwsInvoke { } log(invocationReply) { - const color = !invocationReply.FunctionError ? 'white' : 'red'; + const color = !invocationReply.FunctionError ? (x => x) : chalk.red; if (invocationReply.Payload) { const response = JSON.parse(invocationReply.Payload); - this.consoleLog(chalk[color](JSON.stringify(response, null, 4))); + this.consoleLog(color(JSON.stringify(response, null, 4))); } if (invocationReply.LogResult) { diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 8f3a7bcda88..91591169587 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -6,7 +6,6 @@ const path = require('path'); const AwsInvoke = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); -const chalk = require('chalk'); const testUtils = require('../../../../tests/utils'); describe('AwsInvoke', () => { @@ -271,7 +270,7 @@ describe('AwsInvoke', () => { }; return awsInvoke.log(invocationReplyMock).then(() => { - const expectedPayloadMessage = `${chalk.white('{\n "testProp": "testValue"\n}')}`; + const expectedPayloadMessage = '{\n "testProp": "testValue"\n}'; expect(consoleLogStub.calledWith(expectedPayloadMessage)).to.equal(true); }); diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.js b/lib/plugins/aws/utils/formatLambdaLogEvent.js index 2c2286e9b98..62efe8f644c 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.js @@ -34,7 +34,7 @@ module.exports = (msgParam) => { } else if (!isNaN((new Date(splitted[1])).getTime())) { date = splitted[1]; reqId = splitted[2]; - level = `${chalk.white(splitted[0])}\t`; + level = `${splitted[0]}\t`; } else { return msg; } diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js index b1ae167f2fa..a5d4639dfc4 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js @@ -37,7 +37,7 @@ describe('#formatLambdaLogEvent()', () => { const momentDate = moment('2016-01-01T12:00:00Z').format('YYYY-MM-DD HH:mm:ss.SSS (Z)'); expectedLogMessage += `${chalk.green(momentDate)}\t`; expectedLogMessage += `${chalk.yellow('99c30000-b01a-11e5-93f7-b8e85631a00e')}\t`; - expectedLogMessage += `${chalk.white('[INFO]')}\t`; + expectedLogMessage += `${'[INFO]'}\t`; expectedLogMessage += 'test'; expect(formatLambdaLogEvent(pythonLoggerLine)).to.equal(expectedLogMessage); From 2e47cab9ffa635f09bf2c0b698dad1dcbdeaa311 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 29 Jan 2018 08:31:07 -0800 Subject: [PATCH 031/125] Releasing v1.26.0 --- CHANGELOG.md | 13 +++ package-lock.json | 250 ++++++++++++++++++++++++---------------------- package.json | 2 +- 3 files changed, 144 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b64b53eb4e..98352edea00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 1.26.0 (29.01.2018) +- [AWS Go support](https://github.com/serverless/serverless/pull/4669) +- [Support for using an existing ApiGateway and Resources](https://github.com/serverless/serverless/pull/4247) +- [Add logRetentionInDays config](https://github.com/serverless/serverless/pull/4591) +- [Add support of `serverless.js` configuration file](https://github.com/serverless/serverless/pull/4590) +- [Add "did you mean..." CLI suggestions](https://github.com/serverless/serverless/pull/4586) +- [Add `--template-path` option to `serverless create`](https://github.com/serverless/serverless/pull/4576) +- [Add support POJO input support for Java invoke local](https://github.com/serverless/serverless/pull/4596) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.25.0...v1.26.0) + + # 1.25.0 (20.12.2017) - [Improve Stage and Region Usage](https://github.com/serverless/serverless/pull/4560) - [Add API Gateway endpoint configuration](https://github.com/serverless/serverless/pull/4531) diff --git a/package-lock.json b/package-lock.json index 9c6add94cf9..a238c631184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.25.0", + "version": "1.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -33,9 +33,9 @@ "dev": true }, "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", "dev": true }, "acorn-globals": { @@ -213,7 +213,7 @@ "graphql-anywhere": "3.1.0", "graphql-tag": "2.6.1", "redux": "3.7.2", - "symbol-observable": "1.1.0", + "symbol-observable": "1.2.0", "whatwg-fetch": "2.0.3" } }, @@ -367,9 +367,9 @@ "dev": true }, "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "async": { @@ -389,12 +389,11 @@ "dev": true }, "aws-sdk": { - "version": "2.172.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.172.0.tgz", - "integrity": "sha1-R9+3mQeXbrvVOFYupaJYNbWz810=", + "version": "2.188.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.188.0.tgz", + "integrity": "sha1-kGKrx9umOTRZ+i80I89dKU8ARhE=", "requires": { "buffer": "4.9.1", - "crypto-browserify": "1.0.9", "events": "1.1.1", "jmespath": "0.15.0", "querystring": "0.2.0", @@ -887,7 +886,7 @@ "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "dev": true, "requires": { - "assertion-error": "1.0.2", + "assertion-error": "1.1.0", "deep-eql": "0.1.3", "type-detect": "1.0.0" } @@ -951,13 +950,13 @@ } }, "cli-usage": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.4.tgz", - "integrity": "sha1-fAHg3HBsI0s5yTODjI4gshdXduI=", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.7.tgz", + "integrity": "sha512-x/Q52iLSZsRrRb2ePmTsVYXrGcrPQ8G4yRAY7QpMlumxAfPVrnDOH2X6Z5s8qsAX7AA7YuIi8AXFrvH0wWEesA==", "dev": true, "requires": { - "marked": "0.3.7", - "marked-terminal": "1.7.0" + "marked": "0.3.12", + "marked-terminal": "2.0.0" } }, "cli-width": { @@ -1202,11 +1201,6 @@ "boom": "2.10.1" } }, - "crypto-browserify": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", - "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" - }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -1233,7 +1227,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "damerau-levenshtein": { @@ -1481,9 +1475,9 @@ "dev": true }, "doctrine": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.2.tgz", - "integrity": "sha512-y0tm5Pq6ywp3qSTZ1vPgVdAnbDEoeoc5wlOHXoY1c4Wug/a7JvqHIl7BTvwodaHmejWkK/9dSb3sCYfyo/om8A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "2.0.2" @@ -1535,9 +1529,9 @@ } }, "end-of-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", - "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "1.4.0" } @@ -1585,9 +1579,9 @@ } }, "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "version": "0.10.38", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", "dev": true, "requires": { "es6-iterator": "2.0.3", @@ -1601,7 +1595,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-symbol": "3.1.1" } }, @@ -1612,7 +1606,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-set": "0.1.5", "es6-symbol": "3.1.1", @@ -1632,7 +1626,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", "event-emitter": "0.3.5" @@ -1645,7 +1639,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "es6-weak-map": { @@ -1655,7 +1649,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } @@ -1714,7 +1708,7 @@ "chalk": "1.1.3", "concat-stream": "1.6.0", "debug": "2.6.9", - "doctrine": "2.0.2", + "doctrine": "2.1.0", "escope": "3.6.0", "espree": "3.5.2", "esquery": "1.0.0", @@ -1727,7 +1721,7 @@ "imurmurhash": "0.1.4", "inquirer": "0.12.0", "is-my-json-valid": "2.17.1", - "is-resolvable": "1.0.1", + "is-resolvable": "1.1.0", "js-yaml": "3.10.0", "json-stable-stringify": "1.0.1", "levn": "0.3.0", @@ -1887,7 +1881,7 @@ "doctrine": "1.5.0", "has": "1.0.1", "jsx-ast-utils": "1.4.1", - "object.assign": "4.0.4" + "object.assign": "4.1.0" }, "dependencies": { "doctrine": { @@ -1908,7 +1902,7 @@ "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", "dev": true, "requires": { - "acorn": "5.2.1", + "acorn": "5.3.0", "acorn-jsx": "3.0.1" } }, @@ -1955,7 +1949,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "events": { @@ -2404,9 +2398,9 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, "graphlib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.1.tgz", - "integrity": "sha1-QjUsUrovTQNctWbrkfc5X3bryVE=", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.5.tgz", + "integrity": "sha512-XvtbqCcw+EM5SqQrIetIKKD+uZVNQtDPD1goIg7K73RuRZtVI5rYMdcCVSHm/AS1sCBZ7vt0p5WgXouucHQaOA==", "requires": { "lodash": "4.17.4" } @@ -2416,7 +2410,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.5.tgz", "integrity": "sha512-Q7cx22DiLhwHsEfUnUip1Ww/Vfx7FS0w6+iHItNuN61+XpegHSa3k5U0+6M5BcpavQImBwFiy0z3uYwY7cXMLQ==", "requires": { - "iterall": "1.1.3" + "iterall": "1.1.4" } }, "graphql-anywhere": { @@ -2484,7 +2478,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "commander": "2.12.2", + "commander": "2.13.0", "is-my-json-valid": "2.17.1", "pinkie-promise": "2.0.1" }, @@ -2509,9 +2503,9 @@ } }, "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, "supports-color": { @@ -2549,6 +2543,12 @@ "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz", "integrity": "sha512-JkaetveU7hFbqnAC1EV1sF4rlojU2D4Usc5CmS69l6NfmPDnpnFUegzFg33eDkkpNCxZ0mQp65HwUDrNFS/8MA==" }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", @@ -2794,9 +2794,9 @@ "dev": true }, "is-ci": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", - "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", + "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", "dev": true, "requires": { "ci-info": "1.1.2" @@ -3000,9 +3000,9 @@ } }, "is-resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz", - "integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -3193,7 +3193,7 @@ "babel-types": "6.26.0", "babylon": "6.18.0", "istanbul-lib-coverage": "1.1.1", - "semver": "5.4.1" + "semver": "5.5.0" } }, "istanbul-lib-report": { @@ -3274,9 +3274,9 @@ } }, "iterall": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.3.tgz", - "integrity": "sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.4.tgz", + "integrity": "sha512-eaDsM/PY8D/X5mYQhecVc5/9xvSHED7yPON+ffQroBeTuqUVm7dfphMkK8NksXuImqZlVRoKtrNfMIVCYIqaUQ==" }, "jest-changed-files": { "version": "17.0.2", @@ -3294,7 +3294,7 @@ "callsites": "2.0.0", "chalk": "1.1.3", "graceful-fs": "4.1.11", - "is-ci": "1.0.10", + "is-ci": "1.1.0", "istanbul-api": "1.2.1", "istanbul-lib-coverage": "1.1.1", "istanbul-lib-instrument": "1.9.1", @@ -3823,8 +3823,8 @@ "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.7.tgz", "integrity": "sha1-uesB/in16j6Sh48VrqEK04taz4k=", "requires": { - "commander": "2.12.2", - "graphlib": "2.1.1", + "commander": "2.13.0", + "graphlib": "2.1.5", "js-yaml": "3.10.0", "native-promise-only": "0.8.1", "path-loader": "1.0.4", @@ -3833,9 +3833,9 @@ }, "dependencies": { "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==" + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" } } }, @@ -4354,12 +4354,12 @@ "dev": true }, "markdown-magic": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.19.tgz", - "integrity": "sha1-IKnWWdqgx7DOt64DCVxuLK41KgM=", + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.20.tgz", + "integrity": "sha1-Xw73k0L6G0O7pCr+Y9MCMobj7sM=", "dev": true, "requires": { - "commander": "2.12.2", + "commander": "2.13.0", "deepmerge": "1.5.2", "find-up": "2.1.0", "fs-extra": "1.0.0", @@ -4370,9 +4370,9 @@ }, "dependencies": { "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, "find-up": { @@ -4435,15 +4435,15 @@ } }, "marked": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.7.tgz", - "integrity": "sha512-zBEP4qO1YQp5aXHt8S5wTiOv9i2X74V/LQL0zhUNvVaklt6Ywa6lChxIvS+ibYlCGgADwKwZFhjC3+XfpsvQvQ==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", + "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==", "dev": true }, "marked-terminal": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.7.0.tgz", - "integrity": "sha1-yMRgiBx3LHYEtkNnAH7l938SWQQ=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-2.0.0.tgz", + "integrity": "sha1-Xq9Wi+ZvaGVBr6UqVYKAMQox3i0=", "dev": true, "requires": { "cardinal": "1.0.0", @@ -4742,11 +4742,11 @@ "integrity": "sha1-BW0UJE89zBzq3+aK+c/wxUc6M/M=", "dev": true, "requires": { - "cli-usage": "0.1.4", + "cli-usage": "0.1.7", "growly": "1.3.0", "lodash.clonedeep": "3.0.2", "minimist": "1.2.0", - "semver": "5.4.1", + "semver": "5.5.0", "shellwords": "0.1.1", "which": "1.3.0" } @@ -4768,7 +4768,7 @@ "requires": { "hosted-git-info": "2.5.0", "is-builtin-module": "1.0.0", - "semver": "5.4.1", + "semver": "5.5.0", "validate-npm-package-license": "3.0.1" } }, @@ -4848,13 +4848,14 @@ "dev": true }, "object.assign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", - "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { "define-properties": "1.1.2", "function-bind": "1.1.1", + "has-symbols": "1.0.0", "object-keys": "1.0.11" } }, @@ -4899,9 +4900,9 @@ "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" }, "opn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", - "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", + "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", "requires": { "is-wsl": "1.1.0" } @@ -4975,10 +4976,13 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } }, "p-locate": { "version": "2.0.0", @@ -4986,18 +4990,24 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.1.0" + "p-limit": "1.2.0" } }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "6.7.1", - "registry-auth-token": "3.3.1", + "registry-auth-token": "3.3.2", "registry-url": "3.1.0", - "semver": "5.4.1" + "semver": "5.5.0" } }, "pako": { @@ -5197,9 +5207,9 @@ } }, "promise-queue": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.3.tgz", - "integrity": "sha1-hTTXa/RnPDuqOoK7oBvSlcww8U8=" + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", + "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, "proto-list": { "version": "1.2.4", @@ -5322,9 +5332,9 @@ } }, "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.4.tgz", + "integrity": "sha1-oPYGyq4qO4YrvQ74VILAElsxX6M=", "requires": { "deep-extend": "0.4.2", "ini": "1.3.5", @@ -5420,7 +5430,7 @@ "lodash": "4.17.4", "lodash-es": "4.17.4", "loose-envify": "1.3.1", - "symbol-observable": "1.1.0" + "symbol-observable": "1.2.0" } }, "regenerator-runtime": { @@ -5439,11 +5449,11 @@ } }, "registry-auth-token": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", - "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", "requires": { - "rc": "1.2.2", + "rc": "1.2.4", "safe-buffer": "5.1.1" } }, @@ -5452,7 +5462,7 @@ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { - "rc": "1.2.2" + "rc": "1.2.4" } }, "remarkable": { @@ -5533,7 +5543,7 @@ "stringstream": "0.0.5", "tough-cookie": "2.3.3", "tunnel-agent": "0.4.3", - "uuid": "3.1.0" + "uuid": "3.2.1" }, "dependencies": { "form-data": { @@ -5560,9 +5570,9 @@ "dev": true }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", "dev": true } } @@ -5694,16 +5704,16 @@ } }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { - "semver": "5.4.1" + "semver": "5.5.0" } }, "semver-regex": { @@ -6002,9 +6012,9 @@ } }, "symbol-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", - "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "symbol-tree": { "version": "3.2.2", @@ -6118,7 +6128,7 @@ "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", "requires": { "bl": "1.2.1", - "end-of-stream": "1.4.0", + "end-of-stream": "1.4.1", "readable-stream": "2.3.3", "xtend": "4.0.1" } diff --git a/package.json b/package.json index 6c45371f694..ec049036b40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.25.0", + "version": "1.26.0", "engines": { "node": ">=4.0" }, From 02044203244e7812e8882d5b2af7d1b419ced67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zalewski?= Date: Tue, 30 Jan 2018 09:22:00 +0100 Subject: [PATCH 032/125] Added support for concurrency option in AWS Lambda AWS Lambda has support for concurrency limit and it should be supported in serverless.yml --- docs/providers/aws/guide/functions.md | 1 + .../aws/package/compile/functions/index.js | 13 +++ .../package/compile/functions/index.test.js | 107 ++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 2d6a970ed68..66758212046 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -37,6 +37,7 @@ functions: runtime: python2.7 # optional overwrite, default is provider runtime 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, it is not set and AWS use Account concurrency limit ``` The `handler` property points to the file and module containing the code you want to run in your function. diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 971bf1e2bd6..fe758e9fa14 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -279,6 +279,19 @@ class AwsCompileFunctions { delete newFunction.Properties.VpcConfig; } + if (functionObject.reservedConcurrency) { + if (_.isInteger(functionObject.reservedConcurrency)) { + newFunction.Properties.ReservedConcurrentExecutions = functionObject.reservedConcurrency; + } else { + const warningMessage = [ + 'WARNING! You should use integer as reservedConcurrency value on function: ', + `${newFunction.Properties.FunctionName}`, + ].join(''); + + this.serverless.cli.log(warningMessage); + } + } + newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)] .concat(newFunction.DependsOn || []); diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index b5e18c68424..dee3c214b32 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -3,6 +3,7 @@ const _ = require('lodash'); const path = require('path'); const chai = require('chai'); +const sinon = require('sinon'); const AwsProvider = require('../../../provider/awsProvider'); const AwsCompileFunctions = require('./index'); const testUtils = require('../../../../../../tests/utils'); @@ -15,6 +16,7 @@ const expect = chai.expect; describe('AwsCompileFunctions', () => { let serverless; let awsCompileFunctions; + let logStub; const functionName = 'test'; const compiledFunctionName = 'TestLambdaFunction'; @@ -25,6 +27,8 @@ describe('AwsCompileFunctions', () => { }; serverless = new Serverless(options); serverless.setProvider('aws', new AwsProvider(serverless, options)); + serverless.cli = new serverless.classes.CLI(); + logStub = sinon.stub(serverless.cli, 'log'); awsCompileFunctions = new AwsCompileFunctions(serverless, options); awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, @@ -1730,6 +1734,109 @@ describe('AwsCompileFunctions', () => { ); }); }); + + it('should set function declared reserved concurrency limit', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: 5, + }, + }; + 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, + ReservedConcurrentExecutions: 5, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should ignore non-integer reserved concurrency limit set on function', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: '1', + }, + }; + 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, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should show warning if non-integer reserved concurrency limit set on function', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: '1', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + const warningMessage = [ + 'WARNING! You should use integer as reservedConcurrency value on function: ', + 'new-service-dev-func', + ].join(''); + + expect( + logStub.calledWithExactly(warningMessage) + ).to.be.equal(true); + }); + }); }); describe('#compileRole()', () => { From 87af47a75e2c2e93ddd66cf15dded78b24c10c8a Mon Sep 17 00:00:00 2001 From: Jason Butz Date: Tue, 30 Jan 2018 17:51:08 -0500 Subject: [PATCH 033/125] Add Google Cloud Functions label documentation --- docs/providers/google/guide/functions.md | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/providers/google/guide/functions.md b/docs/providers/google/guide/functions.md index 587bb7f4942..9e1d6f9da74 100644 --- a/docs/providers/google/guide/functions.md +++ b/docs/providers/google/guide/functions.md @@ -96,3 +96,43 @@ exports.event = (event, callback) => { callback(); }; ``` + +## Labels + +Google Cloud Platform supports [labels to assist in organizing resources](https://cloud.google.com/resource-manager/docs/creating-managing-labels). +Serverless allows you to define labels to be applied to functions upon deploy. +Labels are defined as key-value pairs. + +Labels can be applied globally, to all functions in your configuration file, and to specific functions. + +```yml +# serverless.yml + +provider: + name: google + labels: + application: Severless Example + +functions: + first: + handler: httpFirst + events: + - http: path + labels: + team: GCF Team + second: + handler: httpSecond + events: + - http: path + labels: + application: Serverless Example - Documentation +``` + +With the above configuration the `httpFirst` function would have two labels applied, `application` and `team`. +The value of the `application` label would be `Severless Example`, the value of the `team` label would be `GCF Team`. + +The `httpSecond` function would have only one label applied, `application`, and it would have a value of `Serverless Example - Documentation`. + +Labels defined under the `provider` object are applied to all functions in the file. +Labels defined under specific functions only apply to that function. +The labels defined under a function's object will override global labels for that function. From 883389348158637ae69db4cd46603d02d9450a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zalewski?= Date: Wed, 31 Jan 2018 08:50:54 +0100 Subject: [PATCH 034/125] Changed warning to error message if reservedConcurrency is non-integer --- docs/providers/aws/guide/functions.md | 2 +- .../aws/package/compile/functions/index.js | 6 +- .../package/compile/functions/index.test.js | 61 +++---------------- 3 files changed, 11 insertions(+), 58 deletions(-) diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 66758212046..468701dce7f 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -37,7 +37,7 @@ functions: runtime: python2.7 # optional overwrite, default is provider runtime 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, it is not set and AWS use Account concurrency limit + reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit ``` The `handler` property points to the file and module containing the code you want to run in your function. diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index fe758e9fa14..9b00faf1378 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -283,12 +283,12 @@ class AwsCompileFunctions { if (_.isInteger(functionObject.reservedConcurrency)) { newFunction.Properties.ReservedConcurrentExecutions = functionObject.reservedConcurrency; } else { - const warningMessage = [ - 'WARNING! You should use integer as reservedConcurrency value on function: ', + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', `${newFunction.Properties.FunctionName}`, ].join(''); - this.serverless.cli.log(warningMessage); + 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 dee3c214b32..d170bdeb217 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -3,7 +3,6 @@ const _ = require('lodash'); const path = require('path'); const chai = require('chai'); -const sinon = require('sinon'); const AwsProvider = require('../../../provider/awsProvider'); const AwsCompileFunctions = require('./index'); const testUtils = require('../../../../../../tests/utils'); @@ -16,7 +15,6 @@ const expect = chai.expect; describe('AwsCompileFunctions', () => { let serverless; let awsCompileFunctions; - let logStub; const functionName = 'test'; const compiledFunctionName = 'TestLambdaFunction'; @@ -28,7 +26,6 @@ describe('AwsCompileFunctions', () => { serverless = new Serverless(options); serverless.setProvider('aws', new AwsProvider(serverless, options)); serverless.cli = new serverless.classes.CLI(); - logStub = sinon.stub(serverless.cli, 'log'); awsCompileFunctions = new AwsCompileFunctions(serverless, options); awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, @@ -1776,10 +1773,8 @@ describe('AwsCompileFunctions', () => { }); }); - it('should ignore non-integer reserved concurrency limit set on function', () => { - const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + it('should throw an informative error message if non-integer reserved concurrency limit set ' + + 'on function', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -1787,55 +1782,13 @@ describe('AwsCompileFunctions', () => { reservedConcurrency: '1', }, }; - 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, - }, - }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction - ).to.deep.equal(compiledFunction); - }); - }); + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', + 'new-service-dev-func', + ].join(''); - it('should show warning if non-integer reserved concurrency limit set on function', () => { - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', - reservedConcurrency: '1', - }, - }; - - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const warningMessage = [ - 'WARNING! You should use integer as reservedConcurrency value on function: ', - 'new-service-dev-func', - ].join(''); - - expect( - logStub.calledWithExactly(warningMessage) - ).to.be.equal(true); - }); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(errorMessage); }); }); From 022e99f77c514fbf0df1a1d30bdb399f8513f53a Mon Sep 17 00:00:00 2001 From: Frank Schmid Date: Wed, 31 Jan 2018 11:28:45 +0100 Subject: [PATCH 035/125] Added rejection instead of throw for consistency --- lib/plugins/aws/package/compile/functions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 9b00faf1378..d880d0f1115 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -288,7 +288,7 @@ class AwsCompileFunctions { `${newFunction.Properties.FunctionName}`, ].join(''); - throw new this.serverless.classes.Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } } From f286b26378ac441faeeb44ae44b9538a6ae7940c Mon Sep 17 00:00:00 2001 From: ma-bo-do-fu Date: Thu, 1 Feb 2018 13:33:21 +0900 Subject: [PATCH 036/125] Fix typo Fix typo in apigateway.md chilren -> children --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 7da3daf1b42..2d04400b4ee 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -872,7 +872,7 @@ functions: ``` -In case the application has many chilren and grandchildren paths, you also want to break them out into smaller services. +In case the application has many children and grandchildren paths, you also want to break them out into smaller services. ```yml service: service-a From fc24fb4637c92c00253fee220db45b5d7802f88e Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 11:59:46 -0500 Subject: [PATCH 037/125] Update README.md On first pass this doc was a bit confusing for me. Hopefully these edits make it easier for any Serverless newcomer to get started with a better understanding of the commands and available options. --- .../aws/examples/hello-world/node/README.md | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/node/README.md b/docs/providers/aws/examples/hello-world/node/README.md index 792bd4e8d51..c45f28fe3b2 100644 --- a/docs/providers/aws/examples/hello-world/node/README.md +++ b/docs/providers/aws/examples/hello-world/node/README.md @@ -13,18 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-nodejs --path myService` or `sls create --template aws-nodejs --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-nodejs --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-nodejs with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { From 386f3cdc6740f899ddf0615749c250c3d9c94388 Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 20:29:57 -0500 Subject: [PATCH 038/125] updated csharp Hello World --- .../aws/examples/hello-world/csharp/README.md | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index 9f0d170a4dc..508d4e09814 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -13,8 +13,27 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-csharp --path myService` or `sls create --template aws-csharp --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-csharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-csharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Build using .NET CLI tools and create zip package @@ -29,14 +48,23 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins ``` ## 3. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function AWS Lambda based on the settings in `serverless.yml`. + ## 4. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { From 81f18587c3992bf9bf8ccd01f0f47529c90b3ab3 Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 20:47:50 -0500 Subject: [PATCH 039/125] fixed typo --- docs/providers/aws/examples/hello-world/node/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/examples/hello-world/node/README.md b/docs/providers/aws/examples/hello-world/node/README.md index c45f28fe3b2..6b4c62e8f55 100644 --- a/docs/providers/aws/examples/hello-world/node/README.md +++ b/docs/providers/aws/examples/hello-world/node/README.md @@ -41,7 +41,7 @@ The `--path` or shorthand `-p` is the location to be created with the template s sls deploy ``` -This will deploy your function AWS Lambda based on the settings in `serverless.yml`. +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function From efa5bda488ce5a09bd2bbd3769ef0a2a572e5e78 Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 20:48:03 -0500 Subject: [PATCH 040/125] updated csharp demo --- docs/providers/aws/examples/hello-world/csharp/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index 508d4e09814..910ec6b0631 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -38,7 +38,7 @@ The `--path` or shorthand `-p` is the location to be created with the template s ## 2. Build using .NET CLI tools and create zip package ``` -# Linux or OSX +# Linux or Mac OS ./build.sh ``` @@ -53,7 +53,7 @@ The `--path` or shorthand `-p` is the location to be created with the template s sls deploy ``` -This will deploy your function AWS Lambda based on the settings in `serverless.yml`. +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 4. Invoke deployed function From 7a0488b762c44817df12a7eaaa07455a187d3987 Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 20:48:25 -0500 Subject: [PATCH 041/125] updated fsharp hello world --- .../aws/examples/hello-world/fsharp/README.md | 72 ++++++++++++++----- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index 7c7de6acb48..b38c7fd7be1 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -7,31 +7,69 @@ layout: Doc # Hello World F# Example -## Prerequisites +Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). -* Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). -* [.Net Core 1.0.1 SDK](https://www.microsoft.com/net/download/core) - * 1.1 isn't currently supported by AWS Lambda -* [NodeJS v4 or higher](https://nodejs.org/en/) -* An AWS Account +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. -## Build and Package +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + +## 1. Create a service + +``` +sls create --template aws-fsharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. + +## 2. Build using .NET CLI tools and create zip package + +``` +# Linux or Mac OS +./build.sh +``` -From the root of this directory, run `build.cmd`, or `build.sh` if on Linux / Mac. +``` +# Windows PowerShell +./build.cmd +``` -This will produce your deployment package at `bin/release/netcoreapp1.0/deploy-package.zip`. +## 3. Deploy -## Deployment and Invocation +``` +sls deploy +``` -Once packaged, you can follow [these instructions](https://github.com/serverless/serverless#quick-start) to deploy and remotely invoke the function on AWS Lambda. +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. -In short the commands you will need to run are (from the root of this directory): +## 4. Invoke deployed function ``` -serverless config credentials --provider aws --key {YourAwsAccessKey} --secret {YourAwsSecret} -serverless deploy -v -serverless invoke -f hello -l -serverless remove +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. + +```bash +{ + "Message": "Go Serverless v1.0! Your function executed successfully!", + "Request": { + "Key1": null, + "Key2": null, + "Key3": null + } +} ``` -By default this template deploys to us-east-1, you can change that in "serverless.yml" under the `region: us-east-1` key. \ No newline at end of file +Congrats you have just deployed and run your Hello World function! From 97d4171b662f5b5ef5065a9c7feb4cbce831c61c Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 1 Feb 2018 18:07:09 -0800 Subject: [PATCH 042/125] added active versions, corrected broken links --- .../spotinst/guide/active-versions.md | 127 ++++++++++++++++++ docs/providers/spotinst/guide/create-token.md | 2 +- docs/providers/spotinst/guide/quick-start.md | 4 + .../spotinst/guide/serverless.yml.md | 40 ++++-- docs/providers/spotinst/guide/variables.md | 2 +- 5 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 docs/providers/spotinst/guide/active-versions.md diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md new file mode 100644 index 00000000000..01f05794c7e --- /dev/null +++ b/docs/providers/spotinst/guide/active-versions.md @@ -0,0 +1,127 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/active-versions) + + +# Spotinst Functions - Active Versions + +In the following documentation, you can learn how to use the versioning capability within Spotinst Functions. You can also find information on how to distribute incoming traffic across multiple versions using predefined weights. + +Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and is incremented by one each update. Each function version is immutable and cannot be changed. + +//TODO - what is missing? + +## Latest Version +The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. This tag indicates that all traffic should be routed to the most recently updated version of your function without specifically citing the version ID. + +*Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* + +Default configuration for activeVersions when a new function is created: +``` +"activeVersions": [ + { + "version": "$LATEST", + "percentage": 100.0 + } +] +``` + + +## Active Version +'Active' version represents the current active version(s) of your function. By default, the active version is pointing to the 'Latest', meaning every update to your function will also affect your 'Active' version. However, you can override the default configuration by specifying which version(s) you would like associated with the 'Active' version. + +The 'Active' version can point to more than one version of your function, including 'Latest'. This allows you to distribute your incoming traffic between multiple versions and dictate what percentage is sent to each version. + +For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. + +### Examples +``` +"activeVersions": [ + { + "version": "$LATEST", + "percentage": 100.0 + } +] +``` + +100% of traffic will go to the most recently published update. + +``` +"activeVersions": [ + { + "version": "$LATEST", + "percentage": 80.0 + }, + { + "version": "2", + "percentage": 20.0 + } +] +``` +80% of traffic goes to the most recently published update, and 20% goes to version 2. + +``` +"activeVersions": [ + { + "version": "5", + "percentage": 50.0 + }, + { + "version": "3", + "percentage": 25.0 + }, + { + "version": "1", + "percentage": 25.0 + } +] +``` +Traffic is split between versions 1. 3, and 5. Changes made to your latest version will not affect production traffic. + +### Configure Active Version +You can configure your 'Active' version by using any of the following methods: +- Configure 'Active' version at the time you update a function + - You can use the 'update' API request to change your 'Active' version configuration by adding the 'activeVersions' object. _Note: $LATEST refers to the new version created following the update request. + ``` + "activeVersions": [ + { + "version": "$LATEST", + "percentage": 20 + }, + { + "version": "2", + "percentage": 80 + } + ] + ``` +- Explicitly configure 'Active' version + - In the event you would like to change your 'Active' version configuration without updating your function, you can use the 'Configure Active Version' action from the console and the API + - Console: + 1. Navigate to your function + 2. Click 'Actions' + 3. Select 'Configure Active Version' + - API: (update function) + ``` + "activeVersions": [ + { + "version": "$LATEST", + "percentage": 30 + }, + { + "version": "2", + "percentage": 70 + } + ], + ``` + +### Requirements +- The sum of all percentages must be 100% +- You can set up to two decimal digits in the percentage +- Changes made to the ratio using the Spotinst Console will be overwritten by the contents of activeVersions in your serverless.yml file. \ No newline at end of file diff --git a/docs/providers/spotinst/guide/create-token.md b/docs/providers/spotinst/guide/create-token.md index 9fbaf1d9b87..46e8bb87cee 100644 --- a/docs/providers/spotinst/guide/create-token.md +++ b/docs/providers/spotinst/guide/create-token.md @@ -7,7 +7,7 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/create-token) # Spotinst Functions - Create Token diff --git a/docs/providers/spotinst/guide/quick-start.md b/docs/providers/spotinst/guide/quick-start.md index 077ab90ad14..2292eb30369 100644 --- a/docs/providers/spotinst/guide/quick-start.md +++ b/docs/providers/spotinst/guide/quick-start.md @@ -6,6 +6,10 @@ description: Getting started with the Serverless Framework on AWS Lambda layout: Doc --> + +### [Read this on the main serverless docs site](https://serverless.com/framework/docs/providers/spotinst/guide/quick-start/) + + # Spotinst - Quick Start Here is a quick guide on how to create a new serverless project using the Spotinst NodeJS template. For more detailed instruction please check out the other reference material provided. diff --git a/docs/providers/spotinst/guide/serverless.yml.md b/docs/providers/spotinst/guide/serverless.yml.md index a53db4f0af0..1e0f43e0cda 100644 --- a/docs/providers/spotinst/guide/serverless.yml.md +++ b/docs/providers/spotinst/guide/serverless.yml.md @@ -5,23 +5,34 @@ menuOrder: 5 description: Serverless.yml reference layout: Doc --> + + +### [Read this on the main serverless docs site](https://serverless.com/framework/docs/providers/spotinst/guide/serverless.yml/) + + # Serverless.yml Reference This is an outline of a `serverless.yml` file with descriptions of the properties for reference ```yml -# serverless.yml - -# The service can be whatever you choose. You can have multiple functions -# under one service +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! -service: your-service - -# The provider is Spotinst and the Environment ID can be found on the -# Spotinst Console under Functions +service: four provider: name: spotinst + #stage: #Optional setting. By default it is set to 'dev' spotinst: environment: #{Your Environment ID} @@ -38,11 +49,17 @@ provider: functions: function-name: - runtime: nodejs4.8 + runtime: nodejs8.3 handler: handler.main memory: 128 timeout: 30 -# access: public + access: private +# activeVersions: [ +# { +# "version": "$LATEST", +# "percentage": 100.0 +# } +# ] # cron: # active: false # value: '*/1 * * * *' @@ -50,7 +67,8 @@ functions: # Key: "Value", # } - +# extend the framework using plugins listed here: +# https://github.com/serverless/plugins plugins: - serverless-spotinst-functions ``` diff --git a/docs/providers/spotinst/guide/variables.md b/docs/providers/spotinst/guide/variables.md index a120acc0901..69411d98a4e 100644 --- a/docs/providers/spotinst/guide/variables.md +++ b/docs/providers/spotinst/guide/variables.md @@ -7,7 +7,7 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/variables) # Spotinst Functions - Variables From 2c7c8c71e2719aff22a4e8e6c1f1e3271072de57 Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 21:14:17 -0500 Subject: [PATCH 043/125] updated go hello-world --- .../aws/examples/hello-world/go/README.md | 64 ++++++++++++++++--- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md index 8c522ccaecd..f5a71321faa 100644 --- a/docs/providers/aws/examples/hello-world/go/README.md +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -13,20 +13,46 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + You should also have [go](https://golang.org/doc/install) and [make](https://www.gnu.org/software/make/) -It is always good practice to organise your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximise the benefits of go tooling. +It is always good practice to organize your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximize the benefits of go tooling. ## 1. Create a service There are two templates for `go`: -1. [aws-go](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go) - `serverless create --template aws-go --path myService` -2. [aws-go-dep](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go-dep) - `serverless create --template aws-go-dep --path myService` +The Serverless Framework includes starter templates for various languages and providers. There are two templates for `go`. + +#### [aws-go](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go) + +`aws-go` fetches dependencies using standard `go get`. + +``` +sls create --template aws-go --path myService +``` + +#### [aws-go-dep](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go-dep) + +`aws-go-dep` uses [go dep](https://github.com/golang/dep) and requires your project to be in `$GOPATH/src` + +``` +sls create --template aws-go-dep --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-nodejs with the `--template` or shorthand `-t` flag. -where: -- 'aws-go' fetches dependencies using standard `go get`. -- 'aws-go-dep' uses [go dep](https://github.com/golang/dep) and requires your project to be in `$GOPATH/src` -- 'myService' is a new folder to be created with template service files. +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into 'myService' folder and you can see this project has 2 handler functions: `hello` and `world` split into 2 separate go packages (folders): @@ -52,18 +78,36 @@ Run `make build` to build both functions. Successful build should generate the f ``` ## 3. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 4. Invoke deployed function -Invoking the both functions should return a successful results: + +``` +sls invoke -f hello +``` + +``` +sls invoke -f world +``` + +Invoke either deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. ```bash serverless invoke -f hello + { "message": "Go Serverless v1.0! Your function executed successfully!" } -serverless invoke --f world +serverless invoke -f world + { "message": "Okay so your other function also executed successfully!" } From b147126c3e504001ad62829e8809ff3456fb68fc Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Thu, 1 Feb 2018 21:17:01 -0500 Subject: [PATCH 044/125] updated Python hello-world --- .../aws/examples/hello-world/python/README.md | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/python/README.md b/docs/providers/aws/examples/hello-world/python/README.md index d1e432b603a..d48a6bd1b05 100644 --- a/docs/providers/aws/examples/hello-world/python/README.md +++ b/docs/providers/aws/examples/hello-world/python/README.md @@ -13,18 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-python --path myService` or `sls create --template aws-python --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-python --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-python with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -33,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have just deployed and run your Hello World function! From d7eea2005e40b261c2b0592bfc00878e04f72c82 Mon Sep 17 00:00:00 2001 From: Kobi Meirson Date: Fri, 2 Feb 2018 16:18:00 +0200 Subject: [PATCH 045/125] Restricting alexaSkill functions to specific Alexa skills (#4701) * - Adding support for restricting the executing alexa skill by id (serverless/serverless#4700) - Adding support for multiple `alexaSkill` events on a single function (allows multiple Alexa Skills on a single lambda) * Adding a comment on the `serverless.cli` addition when testing. * Updating templates to have the right `alexaSkill` syntax * Cleaning up Travis-CI's occasional errors with sinon stubs going wild (hopefully) (https://travis-ci.org/serverless/serverless/jobs/335361582 / https://travis-ci.org/serverless/serverless/jobs/335706593 / https://travis-ci.org/serverless/serverless/jobs/335682396) * one last test case to make sure the alexaSkill file will be fully covered. * PR notes * naming - Add a default suffix for alexaSkillLogicalId if undefined * Revert changes to createStack.test.js (#c967c8d956b3d96afbaefa7fbe3e6eb498ecdd7c) * createStack.test - reject with an Error, not with a promise that resolves to an error. --- docs/providers/aws/events/alexa-skill.md | 31 +++- .../hello-world/fsharp/serverless.yml | 2 +- docs/providers/aws/guide/serverless.yml.md | 4 +- .../aws/deploy/lib/createStack.test.js | 4 +- .../aws/deploy/lib/extendedValidate.test.js | 10 +- .../aws/deploy/lib/uploadArtifacts.test.js | 27 ++-- lib/plugins/aws/lib/naming.js | 5 +- lib/plugins/aws/lib/naming.test.js | 8 +- .../compile/events/alexaSkill/index.js | 85 ++++++---- .../compile/events/alexaSkill/index.test.js | 148 +++++++++++++++++- .../templates/aws-csharp/serverless.yml | 2 +- .../templates/aws-fsharp/serverless.yml | 2 +- .../templates/aws-go-dep/serverless.yml | 2 +- .../create/templates/aws-go/serverless.yml | 2 +- .../aws-groovy-gradle/serverless.yml | 2 +- .../templates/aws-java-gradle/serverless.yml | 2 +- .../templates/aws-java-maven/serverless.yml | 2 +- .../aws-kotlin-jvm-gradle/serverless.yml | 2 +- .../aws-kotlin-jvm-maven/serverless.yml | 2 +- .../aws-kotlin-nodejs-gradle/serverless.yml | 2 +- .../templates/aws-nodejs/serverless.yml | 2 +- .../templates/aws-python/serverless.yml | 2 +- .../templates/aws-python3/serverless.yml | 2 +- .../templates/aws-scala-sbt/serverless.yml | 2 +- 24 files changed, 278 insertions(+), 74 deletions(-) diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md index 915464bcf1b..3ade42de377 100644 --- a/docs/providers/aws/events/alexa-skill.md +++ b/docs/providers/aws/events/alexa-skill.md @@ -14,14 +14,41 @@ layout: Doc ## Event definition -This will enable your Lambda function to be called by an Alexa skill kit. +This will enable your Lambda function to be called by an Alexa Skill kit. +`amzn1.ask.skill.xx-xx-xx-xx-xx` is a skill ID for Alexa Skills kit. You receive a skill ID once you register and create a skill in [Amazon Developer Console](https://developer.amazon.com/). +After deploying, add your deployed Lambda function ARN to which this event is attached to the Service Endpoint under Configuration on Amazon Developer Console. ```yml functions: mySkill: handler: mySkill.handler events: - - alexaSkill + - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx-xx ``` You can find detailed guides on how to create an Alexa Skill with Serverless using NodeJS [here](https://github.com/serverless/examples/tree/master/aws-node-alexa-skill) as well as in combination with Python [here](https://github.com/serverless/examples/tree/master/aws-python-alexa-skill). + +## Enabling / Disabling + +**Note:** `alexaSkill` events are enabled by default. + +This will create and attach a alexaSkill event for the `mySkill` function which is disabled. If enabled it will call +the `mySkill` function by an Alexa Skill. + +```yaml +functions: + mySkill: + handler: mySkill.handler + events: + - alexaSkill: + appId: amzn1.ask.skill.xx-xx-xx-xx + enabled: false +``` + +## Backwards compatability + +Previous syntax of this event didn't require a skill ID as parameter, but according to [Amazon's documentation](https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html#configuring-the-alexa-skills-kit-trigger) you should restrict your lambda function to be executed only by your skill. + +Omitting the skill id will make your Lambda function available for the public, allowing any other skill developer to invoke it. + +(This is important, as [opposing to custom HTTPS endpoints](https://developer.amazon.com/docs/custom-skills/handle-requests-sent-by-alexa.html#request-verify), there's no way to validate the request was sent by your skill.) \ No newline at end of file diff --git a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml index c90e1089a71..6ef081debe8 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml +++ b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml @@ -66,7 +66,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index c9f1c35a771..16608c5b70a 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -159,7 +159,9 @@ functions: batchSize: 100 startingPosition: LATEST enabled: false - - alexaSkill + - alexaSkill: + appId: amzn1.ask.skill.xx-xx-xx-xx + enabled: true - alexaSmartHome: appId: amzn1.ask.skill.xx-xx-xx-xx enabled: true diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index 28d5820e99b..370422d73d3 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -6,7 +6,6 @@ const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const BbPromise = require('bluebird'); const testUtils = require('../../../../../tests/utils'); describe('createStack', () => { @@ -98,8 +97,7 @@ describe('createStack', () => { it('should set the createLater flag and resolve if deployment bucket is provided', () => { awsDeploy.serverless.service.provider.deploymentBucket = 'serverless'; - sandbox.stub(awsDeploy.provider, 'request') - .returns(BbPromise.reject({ message: 'does not exist' })); + sandbox.stub(awsDeploy.provider, 'request').rejects(new Error('does not exist')); return awsDeploy.createStack().then(() => { expect(awsDeploy.createLater).to.equal(true); diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/lib/plugins/aws/deploy/lib/extendedValidate.test.js index 7b1d3d9adcf..7cbabdc5fb9 100644 --- a/lib/plugins/aws/deploy/lib/extendedValidate.test.js +++ b/lib/plugins/aws/deploy/lib/extendedValidate.test.js @@ -1,6 +1,6 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); @@ -8,6 +8,10 @@ const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); const testUtils = require('../../../../../tests/utils'); +chai.use(require('sinon-chai')); + +const expect = chai.expect; + describe('extendedValidate', () => { let awsDeploy; const tmpDirPath = testUtils.getTmpDirPath(); @@ -60,8 +64,8 @@ describe('extendedValidate', () => { }); afterEach(() => { - awsDeploy.serverless.utils.fileExistsSync.restore(); - awsDeploy.serverless.utils.readFileSync.restore(); + fileExistsSyncStub.restore(); + readFileSyncStub.restore(); }); it('should throw error if state file does not exist', () => { diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index df96e23ff68..7c907b34587 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -90,8 +90,8 @@ describe('uploadArtifacts', () => { }); afterEach(() => { - normalizeFiles.normalizeCloudFormationTemplate.restore(); - awsDeploy.provider.request.restore(); + normalizeCloudFormationTemplateStub.restore(); + uploadStub.restore(); }); it('should upload the CloudFormation file to the S3 bucket', () => { @@ -159,8 +159,8 @@ describe('uploadArtifacts', () => { }); afterEach(() => { - fs.readFileSync.restore(); - awsDeploy.provider.request.restore(); + readFileSyncStub.restore(); + uploadStub.restore(); }); it('should throw for null artifact paths', () => { @@ -265,7 +265,7 @@ describe('uploadArtifacts', () => { .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.functions.second.package.artifact); - awsDeploy.uploadZipFile.restore(); + uploadZipFileStub.restore(); }); }); @@ -286,7 +286,7 @@ describe('uploadArtifacts', () => { const uploadZipFileStub = sinon .stub(awsDeploy, 'uploadZipFile').resolves(); - sinon.stub(fs, 'statSync').returns({ size: 1024 }); + const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); @@ -294,8 +294,9 @@ describe('uploadArtifacts', () => { .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.package.artifact); - awsDeploy.uploadZipFile.restore(); - fs.statSync.restore(); + }).finally(() => { + uploadZipFileStub.restore(); + statSyncStub.restore(); }); }); @@ -303,16 +304,16 @@ describe('uploadArtifacts', () => { awsDeploy.serverless.config.servicePath = 'some/path'; awsDeploy.serverless.service.service = 'new-service'; - sinon.stub(fs, 'statSync').returns({ size: 1024 }); - sinon.stub(awsDeploy, 'uploadZipFile').resolves(); + const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); + const uploadZipFileStub = sinon.stub(awsDeploy, 'uploadZipFile').resolves(); sinon.spy(awsDeploy.serverless.cli, 'log'); return awsDeploy.uploadFunctions().then(() => { const expected = 'Uploading service .zip file to S3 (1 KB)...'; expect(awsDeploy.serverless.cli.log.calledWithExactly(expected)).to.be.equal(true); - - fs.statSync.restore(); - awsDeploy.uploadZipFile.restore(); + }).finally(() => { + statSyncStub.restore(); + uploadZipFileStub.restore(); }); }); }); diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index f2c81c776e4..efb408408ce 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -282,8 +282,9 @@ module.exports = { getLambdaApiGatewayPermissionLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionApiGateway`; }, - getLambdaAlexaSkillPermissionLogicalId(functionName) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill`; + getLambdaAlexaSkillPermissionLogicalId(functionName, alexaSkillIndex) { + return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill${ + alexaSkillIndex || '0'}`; }, getLambdaAlexaSmartHomePermissionLogicalId(functionName, alexaSmartHomeIndex) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSmartHome${ diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 7becb143864..530d0621e56 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -464,9 +464,15 @@ describe('#naming()', () => { describe('#getLambdaAlexaSkillPermissionLogicalId()', () => { it('should normalize the function name and append the standard suffix', + () => { + expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName', 2)) + .to.equal('FunctionNameLambdaPermissionAlexaSkill2'); + }); + + it('should normalize the function name and append a default suffix if not defined', () => { expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionAlexaSkill'); + .to.equal('FunctionNameLambdaPermissionAlexaSkill0'); }); }); diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.js index ecc32cb4451..5baa07ca2d7 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.js @@ -15,37 +15,35 @@ class AwsCompileAlexaSkillEvents { compileAlexaSkillEvents() { this.serverless.service.getAllFunctions().forEach((functionName) => { const functionObj = this.serverless.service.getFunction(functionName); + let alexaSkillNumberInFunction = 0; if (functionObj.events) { functionObj.events.forEach(event => { + let enabled = true; + let appId; if (event === 'alexaSkill') { - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - - const permissionTemplate = { - Type: 'AWS::Lambda::Permission', - Properties: { - FunctionName: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], - }, - Action: 'lambda:InvokeFunction', - Principal: 'alexa-appkit.amazon.com', - }, - }; - - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlexaSkillPermissionLogicalId(functionName); - - const permissionCloudForamtionResource = { - [lambdaPermissionLogicalId]: permissionTemplate, - }; - - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - permissionCloudForamtionResource); - } else if (event.alexaSkill) { + const warningMessage = [ + 'Warning! You are using an old syntax for alexaSkill which doesn\'t', + ' restrict the invocation solely to your skill.', + ' Please refer to the documentation for additional information.', + ].join(''); + this.serverless.cli.log(warningMessage); + } else if (_.isString(event.alexaSkill)) { + appId = event.alexaSkill; + } else if (_.isPlainObject(event.alexaSkill)) { + if (!_.isString(event.alexaSkill.appId)) { + const errorMessage = [ + `Missing "appId" property for alexaSkill event in function ${functionName}`, + ' The correct syntax is: appId: amzn1.ask.skill.xx-xx-xx-xx-xx', + ' OR an object with "appId" property.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + appId = event.alexaSkill.appId; + // Parameter `enabled` is optional, hence the explicit non-equal check for false. + enabled = event.alexaSkill.enabled !== false; + } else { const errorMessage = [ `Alexa Skill event of function "${functionName}" is not an object or string.`, ' The correct syntax is: alexaSkill.', @@ -53,6 +51,39 @@ class AwsCompileAlexaSkillEvents { ].join(''); throw new this.serverless.classes.Error(errorMessage); } + alexaSkillNumberInFunction++; + + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(functionName); + + const permissionTemplate = { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction', + Principal: 'alexa-appkit.amazon.com', + }, + }; + + if (appId) { + permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, ''); + } + + const lambdaPermissionLogicalId = this.provider.naming + .getLambdaAlexaSkillPermissionLogicalId(functionName, + alexaSkillNumberInFunction); + + const permissionCloudForamtionResource = { + [lambdaPermissionLogicalId]: permissionTemplate, + }; + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCloudForamtionResource); }); } }); diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js index 338a9fd0838..6b94fd4a7f5 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js @@ -1,5 +1,7 @@ 'use strict'; +/* eslint-disable no-unused-expressions */ + const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileAlexaSkillEvents = require('./index'); @@ -8,11 +10,19 @@ const Serverless = require('../../../../../../Serverless'); describe('AwsCompileAlexaSkillEvents', () => { let serverless; let awsCompileAlexaSkillEvents; + let consolePrinted; beforeEach(() => { serverless = new Serverless(); serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; serverless.setProvider('aws', new AwsProvider(serverless)); + consolePrinted = ''; + serverless.cli = { + // serverless.cli isn't available in tests, so we will mimic it. + log: txt => { + consolePrinted += `${txt}\r\n`; + }, + }; awsCompileAlexaSkillEvents = new AwsCompileAlexaSkillEvents(serverless); }); @@ -25,7 +35,42 @@ describe('AwsCompileAlexaSkillEvents', () => { }); describe('#compileAlexaSkillEvents()', () => { - it('should throw an error if alexaSkill event is not an string', () => { + it('should show a warning if alexaSkill appId is not specified', () => { + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + 'alexaSkill', + ], + }, + }; + + awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); + + expect(consolePrinted).to.contain.string('old syntax for alexaSkill'); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:InvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.be.undefined; + }); + + it('should throw an error if alexaSkill event is not a string or an object', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { events: [ @@ -39,11 +84,36 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error); }); - it('should create corresponding resources when a alexaSkill event is provided', () => { + it('should throw an error if alexaSkill event appId is not a string', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { events: [ - 'alexaSkill', + { + alexaSkill: { + appId: 42, + }, + }, + ], + }, + }; + + expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error); + }); + + it('should create corresponding resources when multiple alexaSkill events are provided', () => { + const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx'; + const skillId2 = 'amzn1.ask.skill.yy-yy-yy-yy'; + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + alexaSkill: skillId1, + }, + { + alexaSkill: { + appId: skillId2, + }, + }, ], }, }; @@ -52,20 +122,84 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Type + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:InvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.equal(skillId1); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Type ).to.equal('AWS::Lambda::Permission'); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.FunctionName + .FirstLambdaPermissionAlexaSkill2.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.Action + .FirstLambdaPermissionAlexaSkill2.Properties.Action ).to.equal('lambda:InvokeFunction'); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.Principal + .FirstLambdaPermissionAlexaSkill2.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.EventSourceToken + ).to.equal(skillId2); + }); + + it('should create corresponding resources when a disabled alexaSkill event is provided', () => { + const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx'; + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + alexaSkill: { + appId: skillId1, + enabled: false, + }, + }, + ], + }, + }; + + awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:DisableInvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.equal(skillId1); }); it('should not create corresponding resources when alexaSkill event is not given', () => { diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index fb55c7aacee..12e2196fe43 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -67,7 +67,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index 21dd15dd37d..0f8ca52b410 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -67,7 +67,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml index 67f92c77f2e..76819414366 100644 --- a/lib/plugins/create/templates/aws-go-dep/serverless.yml +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml index c3a1e2c95d4..47fd7378729 100644 --- a/lib/plugins/create/templates/aws-go/serverless.yml +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index a1adaaf4d92..e7956282923 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index e8811732e9e..c1079296fda 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index 106bab8aaca..7f94d0131c5 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index fff1b55109b..434eb3d8155 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index cb892140f01..1387dfc8a84 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index 5fd69aab122..bbe5cbed097 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -60,7 +60,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 1cce386a131..471855a41df 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index f651608e047..2486ba648b2 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index 1d69fcb9da4..3d77f19acd3 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 7eb89a39da7..ee0e94d284d 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -66,7 +66,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" From e5033aeb0cc730e0ec0704f474d3b04bb2cb3c47 Mon Sep 17 00:00:00 2001 From: Daniel Olson Date: Fri, 2 Feb 2018 11:17:40 -0500 Subject: [PATCH 046/125] updated a few grammatical errors --- .../aws/examples/hello-world/csharp/README.md | 2 +- .../aws/examples/hello-world/fsharp/README.md | 2 +- docs/providers/aws/examples/hello-world/go/README.md | 2 +- .../aws/examples/hello-world/node/README.md | 2 +- .../aws/examples/hello-world/python/README.md | 2 +- .../azure/examples/hello-world/node/README.md | 2 +- .../google/examples/hello-world/node/README.md | 2 +- .../openwhisk/examples/hello-world/node/README.md | 2 +- .../openwhisk/examples/hello-world/php/README.md | 2 +- .../openwhisk/examples/hello-world/python/README.md | 2 +- .../openwhisk/examples/hello-world/swift/README.md | 2 +- docs/providers/spotinst/examples/java8/README.md | 2 +- docs/providers/spotinst/examples/node/README.md | 2 +- docs/providers/spotinst/examples/python/README.md | 2 +- docs/providers/spotinst/examples/ruby/README.md | 12 ++++++------ 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index 910ec6b0631..f230f7f88fc 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -77,4 +77,4 @@ In your terminal window you should see the response from AWS Lambda. } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index b38c7fd7be1..562c1e498c8 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -72,4 +72,4 @@ In your terminal window you should see the response from AWS Lambda. } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md index f5a71321faa..1720c009b0d 100644 --- a/docs/providers/aws/examples/hello-world/go/README.md +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -113,4 +113,4 @@ serverless invoke -f world } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/node/README.md b/docs/providers/aws/examples/hello-world/node/README.md index 6b4c62e8f55..fb927951f61 100644 --- a/docs/providers/aws/examples/hello-world/node/README.md +++ b/docs/providers/aws/examples/hello-world/node/README.md @@ -60,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda. } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/python/README.md b/docs/providers/aws/examples/hello-world/python/README.md index d48a6bd1b05..c6380de149b 100644 --- a/docs/providers/aws/examples/hello-world/python/README.md +++ b/docs/providers/aws/examples/hello-world/python/README.md @@ -60,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda. } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/azure/examples/hello-world/node/README.md b/docs/providers/azure/examples/hello-world/node/README.md index 22fc16bb826..9c18f181873 100644 --- a/docs/providers/azure/examples/hello-world/node/README.md +++ b/docs/providers/azure/examples/hello-world/node/README.md @@ -39,4 +39,4 @@ In your terminal window you should see the response from azure } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/google/examples/hello-world/node/README.md b/docs/providers/google/examples/hello-world/node/README.md index 51f5af2044b..5802b97794d 100644 --- a/docs/providers/google/examples/hello-world/node/README.md +++ b/docs/providers/google/examples/hello-world/node/README.md @@ -35,4 +35,4 @@ Update the `credentials` and your `project` property in the `serverless.yml` fil In your terminal window you should see a response from the Google Cloud -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/node/README.md b/docs/providers/openwhisk/examples/hello-world/node/README.md index f9a6d399900..4eadbc6006a 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/README.md +++ b/docs/providers/openwhisk/examples/hello-world/node/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/php/README.md b/docs/providers/openwhisk/examples/hello-world/php/README.md index c254887bf06..4ace5e28fac 100644 --- a/docs/providers/openwhisk/examples/hello-world/php/README.md +++ b/docs/providers/openwhisk/examples/hello-world/php/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/python/README.md b/docs/providers/openwhisk/examples/hello-world/python/README.md index 1056a80600d..35faa3edf9a 100644 --- a/docs/providers/openwhisk/examples/hello-world/python/README.md +++ b/docs/providers/openwhisk/examples/hello-world/python/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/swift/README.md b/docs/providers/openwhisk/examples/hello-world/swift/README.md index 93dec3d3087..6dc26cfda13 100644 --- a/docs/providers/openwhisk/examples/hello-world/swift/README.md +++ b/docs/providers/openwhisk/examples/hello-world/swift/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/spotinst/examples/java8/README.md b/docs/providers/spotinst/examples/java8/README.md index 34e9f2b30d1..8de2ab42bef 100644 --- a/docs/providers/spotinst/examples/java8/README.md +++ b/docs/providers/spotinst/examples/java8/README.md @@ -32,7 +32,7 @@ In your terminal window you should see the response {"hello":"null"} ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/node/README.md b/docs/providers/spotinst/examples/node/README.md index a8345f43017..03e0d98959e 100644 --- a/docs/providers/spotinst/examples/node/README.md +++ b/docs/providers/spotinst/examples/node/README.md @@ -32,7 +32,7 @@ In your terminal window you should see the response {"hello":"from NodeJS8.3 function"} ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/python/README.md b/docs/providers/spotinst/examples/python/README.md index c4f58a6284c..a6ae892364d 100644 --- a/docs/providers/spotinst/examples/python/README.md +++ b/docs/providers/spotinst/examples/python/README.md @@ -34,7 +34,7 @@ In your terminal window you should see the response '{"hello":"from Python2.7 function"}' ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/ruby/README.md b/docs/providers/spotinst/examples/ruby/README.md index e0d7d2e2dd3..a7325a394c6 100644 --- a/docs/providers/spotinst/examples/ruby/README.md +++ b/docs/providers/spotinst/examples/ruby/README.md @@ -11,21 +11,21 @@ layout: Doc # Hello World Ruby Example -Make sure `serverless` is installed. +Make sure `serverless` is installed. ## 1. Create a service `serverless create --template spotinst-ruby --path serviceName` `serviceName` is going to be a new directory there the Ruby template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy -```bash +```bash serverless deploy ``` ## 3. Invoke deployed function ```bash serverless invoke --function hello -``` +``` In your terminal window you should see the response @@ -33,14 +33,14 @@ In your terminal window you should see the response '{"hello":"from Ruby2.4.1 function"}' ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide -`sls` is short hand for serverless cli commands +`sls` is short hand for serverless cli commands `-f` is short hand for `--function` `-t` is short hand for `--template` -`-p` is short hang for `--path` \ No newline at end of file +`-p` is short hang for `--path` From 0f117c49e8df9ed722372c7618ae7c8405a82669 Mon Sep 17 00:00:00 2001 From: Kobi Meirson Date: Fri, 2 Feb 2018 18:48:45 +0200 Subject: [PATCH 047/125] Stop throwing errors if alexaSkill is not in the events list (#4708) * The new alexaSkill skill id restriction (#4701) was throwing an error if an event which is not `alexaSkill` was presented. * `.to.not.throw()` is a function, not a getter. --- .../compile/events/alexaSkill/index.js | 108 +++++++++--------- .../compile/events/alexaSkill/index.test.js | 17 +++ 2 files changed, 72 insertions(+), 53 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.js index 5baa07ca2d7..89c863bb4b2 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.js @@ -19,71 +19,73 @@ class AwsCompileAlexaSkillEvents { if (functionObj.events) { functionObj.events.forEach(event => { - let enabled = true; - let appId; - if (event === 'alexaSkill') { - const warningMessage = [ - 'Warning! You are using an old syntax for alexaSkill which doesn\'t', - ' restrict the invocation solely to your skill.', - ' Please refer to the documentation for additional information.', - ].join(''); - this.serverless.cli.log(warningMessage); - } else if (_.isString(event.alexaSkill)) { - appId = event.alexaSkill; - } else if (_.isPlainObject(event.alexaSkill)) { - if (!_.isString(event.alexaSkill.appId)) { + if (event === 'alexaSkill' || event.alexaSkill) { + let enabled = true; + let appId; + if (event === 'alexaSkill') { + const warningMessage = [ + 'Warning! You are using an old syntax for alexaSkill which doesn\'t', + ' restrict the invocation solely to your skill.', + ' Please refer to the documentation for additional information.', + ].join(''); + this.serverless.cli.log(warningMessage); + } else if (_.isString(event.alexaSkill)) { + appId = event.alexaSkill; + } else if (_.isPlainObject(event.alexaSkill)) { + if (!_.isString(event.alexaSkill.appId)) { + const errorMessage = [ + `Missing "appId" property for alexaSkill event in function ${functionName}`, + ' The correct syntax is: appId: amzn1.ask.skill.xx-xx-xx-xx-xx', + ' OR an object with "appId" property.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + appId = event.alexaSkill.appId; + // Parameter `enabled` is optional, hence the explicit non-equal check for false. + enabled = event.alexaSkill.enabled !== false; + } else { const errorMessage = [ - `Missing "appId" property for alexaSkill event in function ${functionName}`, - ' The correct syntax is: appId: amzn1.ask.skill.xx-xx-xx-xx-xx', - ' OR an object with "appId" property.', + `Alexa Skill event of function "${functionName}" is not an object or string.`, + ' The correct syntax is: alexaSkill.', ' Please check the docs for more info.', ].join(''); throw new this.serverless.classes.Error(errorMessage); } - appId = event.alexaSkill.appId; - // Parameter `enabled` is optional, hence the explicit non-equal check for false. - enabled = event.alexaSkill.enabled !== false; - } else { - const errorMessage = [ - `Alexa Skill event of function "${functionName}" is not an object or string.`, - ' The correct syntax is: alexaSkill.', - ' Please check the docs for more info.', - ].join(''); - throw new this.serverless.classes.Error(errorMessage); - } - alexaSkillNumberInFunction++; + alexaSkillNumberInFunction++; - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(functionName); - const permissionTemplate = { - Type: 'AWS::Lambda::Permission', - Properties: { - FunctionName: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + const permissionTemplate = { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction', + Principal: 'alexa-appkit.amazon.com', }, - Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction', - Principal: 'alexa-appkit.amazon.com', - }, - }; + }; - if (appId) { - permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, ''); - } + if (appId) { + permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, ''); + } - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlexaSkillPermissionLogicalId(functionName, - alexaSkillNumberInFunction); + const lambdaPermissionLogicalId = this.provider.naming + .getLambdaAlexaSkillPermissionLogicalId(functionName, + alexaSkillNumberInFunction); - const permissionCloudForamtionResource = { - [lambdaPermissionLogicalId]: permissionTemplate, - }; + const permissionCloudForamtionResource = { + [lambdaPermissionLogicalId]: permissionTemplate, + }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - permissionCloudForamtionResource); + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCloudForamtionResource); + } }); } }); diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js index 6b94fd4a7f5..cb9090ae6cc 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js @@ -216,5 +216,22 @@ describe('AwsCompileAlexaSkillEvents', () => { .compiledCloudFormationTemplate.Resources ).to.deep.equal({}); }); + + it('should not not throw error when other events are present', () => { + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'get', + path: '/', + }, + }, + ], + }, + }; + + expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.not.throw(); + }); }); }); From 4115c664b35cb6d3782fecff987b997efb3f8d8d Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 10:33:37 -0800 Subject: [PATCH 048/125] Update README.md --- docs/providers/spotinst/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/spotinst/README.md b/docs/providers/spotinst/README.md index 5fbe8fba63a..cafa3140887 100755 --- a/docs/providers/spotinst/README.md +++ b/docs/providers/spotinst/README.md @@ -33,6 +33,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Document Store SDK
  • Endpoint Set Up
  • Endpoint API Documentation
  • +
  • Active Versions Documentation
  • From e27c0d274c6a2afd511f1d3e7b894aedb5d19123 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 10:38:28 -0800 Subject: [PATCH 049/125] refined and reduced --- .../spotinst/guide/active-versions.md | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 01f05794c7e..12c4fe7033c 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -12,14 +12,10 @@ layout: Doc # Spotinst Functions - Active Versions -In the following documentation, you can learn how to use the versioning capability within Spotinst Functions. You can also find information on how to distribute incoming traffic across multiple versions using predefined weights. - Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and is incremented by one each update. Each function version is immutable and cannot be changed. -//TODO - what is missing? - ## Latest Version -The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. This tag indicates that all traffic should be routed to the most recently updated version of your function without specifically citing the version ID. +The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. *Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* @@ -33,10 +29,7 @@ Default configuration for activeVersions when a new function is created: ] ``` - ## Active Version -'Active' version represents the current active version(s) of your function. By default, the active version is pointing to the 'Latest', meaning every update to your function will also affect your 'Active' version. However, you can override the default configuration by specifying which version(s) you would like associated with the 'Active' version. - The 'Active' version can point to more than one version of your function, including 'Latest'. This allows you to distribute your incoming traffic between multiple versions and dictate what percentage is sent to each version. For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. @@ -86,23 +79,7 @@ For example, say you wanted to test a new version of your function to determine Traffic is split between versions 1. 3, and 5. Changes made to your latest version will not affect production traffic. ### Configure Active Version -You can configure your 'Active' version by using any of the following methods: -- Configure 'Active' version at the time you update a function - - You can use the 'update' API request to change your 'Active' version configuration by adding the 'activeVersions' object. _Note: $LATEST refers to the new version created following the update request. - ``` - "activeVersions": [ - { - "version": "$LATEST", - "percentage": 20 - }, - { - "version": "2", - "percentage": 80 - } - ] - ``` -- Explicitly configure 'Active' version - - In the event you would like to change your 'Active' version configuration without updating your function, you can use the 'Configure Active Version' action from the console and the API +You can configure active versions in the serverless.yml file, but you can also use the Spotinst Console to configure the versions without deploying a new update. In the event you would like to change your 'Active' version configuration without updating your function, you can use the 'Configure Active Version' action from the console and the API - Console: 1. Navigate to your function 2. Click 'Actions' @@ -124,4 +101,4 @@ You can configure your 'Active' version by using any of the following methods: ### Requirements - The sum of all percentages must be 100% - You can set up to two decimal digits in the percentage -- Changes made to the ratio using the Spotinst Console will be overwritten by the contents of activeVersions in your serverless.yml file. \ No newline at end of file +- Changes made to the ratio using the Spotinst Console will be overwritten by the contents of activeVersions in your serverless.yml file. From 86963cdb17d3e750abbf9a50b139b87f7bd855cc Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 10:45:05 -0800 Subject: [PATCH 050/125] Update active-versions.md --- docs/providers/spotinst/guide/active-versions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 12c4fe7033c..23318679b02 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -85,7 +85,7 @@ You can configure active versions in the serverless.yml file, but you can also u 2. Click 'Actions' 3. Select 'Configure Active Version' - API: (update function) - ``` + ```yml "activeVersions": [ { "version": "$LATEST", From eea117660b051aa1b3c5675156e276665d4141f0 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 10:48:07 -0800 Subject: [PATCH 051/125] Update active-versions.md --- .../spotinst/guide/active-versions.md | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 23318679b02..341f403ff5c 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -20,7 +20,7 @@ The 'Latest' version refers to the most recent version created by the last updat *Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* Default configuration for activeVersions when a new function is created: -``` +```yml "activeVersions": [ { "version": "$LATEST", @@ -35,7 +35,7 @@ The 'Active' version can point to more than one version of your function, includ For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. ### Examples -``` +```yml "activeVersions": [ { "version": "$LATEST", @@ -46,7 +46,7 @@ For example, say you wanted to test a new version of your function to determine 100% of traffic will go to the most recently published update. -``` +```yml "activeVersions": [ { "version": "$LATEST", @@ -60,7 +60,7 @@ For example, say you wanted to test a new version of your function to determine ``` 80% of traffic goes to the most recently published update, and 20% goes to version 2. -``` +```yml "activeVersions": [ { "version": "5", @@ -80,23 +80,24 @@ Traffic is split between versions 1. 3, and 5. Changes made to your latest versi ### Configure Active Version You can configure active versions in the serverless.yml file, but you can also use the Spotinst Console to configure the versions without deploying a new update. In the event you would like to change your 'Active' version configuration without updating your function, you can use the 'Configure Active Version' action from the console and the API - - Console: - 1. Navigate to your function - 2. Click 'Actions' - 3. Select 'Configure Active Version' - - API: (update function) - ```yml - "activeVersions": [ - { - "version": "$LATEST", - "percentage": 30 - }, - { - "version": "2", - "percentage": 70 - } - ], - ``` +- Console: + 1. Navigate to your function + 2. Click 'Actions' + 3. Select 'Configure Active Version' + +- API: (update function) +```yml +"activeVersions": [ + { + "version": "$LATEST", + "percentage": 30 + }, + { + "version": "2", + "percentage": 70 + } + ], +``` ### Requirements - The sum of all percentages must be 100% From b53de29cd1dcd8727e3b424b0f6f763dbefee7fd Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 10:49:54 -0800 Subject: [PATCH 052/125] Update active-versions.md --- docs/providers/spotinst/guide/active-versions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 341f403ff5c..b49b80afb75 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -20,7 +20,7 @@ The 'Latest' version refers to the most recent version created by the last updat *Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* Default configuration for activeVersions when a new function is created: -```yml +``` "activeVersions": [ { "version": "$LATEST", @@ -35,7 +35,7 @@ The 'Active' version can point to more than one version of your function, includ For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. ### Examples -```yml +``` "activeVersions": [ { "version": "$LATEST", @@ -46,10 +46,10 @@ For example, say you wanted to test a new version of your function to determine 100% of traffic will go to the most recently published update. -```yml +``` "activeVersions": [ - { - "version": "$LATEST", + { + "version": "$LATEST", "percentage": 80.0 }, { @@ -60,7 +60,7 @@ For example, say you wanted to test a new version of your function to determine ``` 80% of traffic goes to the most recently published update, and 20% goes to version 2. -```yml +``` "activeVersions": [ { "version": "5", @@ -86,7 +86,7 @@ You can configure active versions in the serverless.yml file, but you can also u 3. Select 'Configure Active Version' - API: (update function) -```yml +``` "activeVersions": [ { "version": "$LATEST", From ba43d5a282e8c72e9cb260054c4519dc87a501c0 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 11:02:48 -0800 Subject: [PATCH 053/125] Update active-versions.md --- docs/providers/spotinst/guide/active-versions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index b49b80afb75..50ffa48a157 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -12,7 +12,7 @@ layout: Doc # Spotinst Functions - Active Versions -Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and is incremented by one each update. Each function version is immutable and cannot be changed. +Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and incrementes by one each update. Each function version is immutable and cannot be changed. ## Latest Version The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. From c7e49960b9e5abc0a6287f280f4044bc1bfe7b45 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 11:56:48 -0800 Subject: [PATCH 054/125] added activeVersions attribute --- lib/plugins/create/templates/spotinst-java8/serverless.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index 088814abc58..f09708db82d 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -26,6 +26,12 @@ functions: memory: 128 timeout: 30 access: private +# activeVersions: [ +# { +# "version": "$LATEST", +# "percentage": 100.0 +# } +# ] # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From 8cfa96e4950845abd92706523dd1a04684418ac5 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 11:57:23 -0800 Subject: [PATCH 055/125] added activeVersions attribute --- lib/plugins/create/templates/spotinst-nodejs/serverless.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index b0079fcde84..9acac7e50ae 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -26,6 +26,12 @@ functions: memory: 128 timeout: 30 access: private +# activeVersions: [ +# { +# "version": "$LATEST", +# "percentage": 100.0 +# } +# ] # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From 8c5712b54e98557c4862e93adad744c285be0dfe Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 11:57:48 -0800 Subject: [PATCH 056/125] added activeVersions attribute --- lib/plugins/create/templates/spotinst-python/serverless.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index a78c753b7ef..69e20db4239 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -26,6 +26,12 @@ functions: memory: 128 timeout: 30 access: private +# activeVersions: [ +# { +# "version": "$LATEST", +# "percentage": 100.0 +# } +# ] # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From 563778810de08be4bc5c223065fed380600bd9aa Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Fri, 2 Feb 2018 11:58:45 -0800 Subject: [PATCH 057/125] added activeVersions attribute --- lib/plugins/create/templates/spotinst-ruby/serverless.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 0a1f291023f..684e06bb4b5 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -26,6 +26,12 @@ functions: memory: 128 timeout: 30 access: private +# activeVersions: [ +# { +# "version": "$LATEST", +# "percentage": 100.0 +# } +# ] # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From c18eb70a28d61317007091ae765585adc7cd37f6 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Fri, 2 Feb 2018 19:29:19 -0800 Subject: [PATCH 058/125] Eliminate Deadlocks, Report Hung Promises (#4687) Add a "PromiseTracker" to the Variables class. It provides tracking and reporting of pending variable resolutions during service populations. Decompose populateObject and populateProperty into smaller pieces that are easier to understand. Additionally, remove the immediate recursive resolution of variables, recursing only at the Object or Property entry point (when changes have been made). This avoids a problem where the resolved value, prior to being replaced in the containing object or value refers to itself and thereby is waiting on its own resolution before it will resolve itself. Return the given value in warnIfNotFound so that it can chain. --- lib/classes/Variables.js | 464 +++++++---- lib/classes/Variables.test.js | 1433 ++++++++++++--------------------- 2 files changed, 849 insertions(+), 1048 deletions(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 738a0347cc8..a6842c4b3dc 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -8,12 +8,59 @@ const BbPromise = require('bluebird'); const os = require('os'); const fse = require('../utils/fs/fse'); -class Variables { +class PromiseTracker { + constructor() { + this.promiseList = []; + this.promiseMap = {}; + this.startTime = Date.now(); + } + start() { + this.interval = setInterval(this.report.bind(this), 2500); + } + report() { + const delta = Date.now() - this.startTime; + logWarning('################################################################################'); + logWarning(`# ${delta}: ${this.getSettled().length} of ${ + this.getAll().length} promises have settled`); + const pending = this.getPending(); + logWarning(`# ${delta}: ${pending.length} unsettled promises:`); + pending.forEach((promise) => { + logWarning(`# ${delta}: ${promise.waitList}`); + }); + logWarning('################################################################################'); + } + stop() { + clearInterval(this.interval); + } + add(variable, prms, specifier) { + const promise = prms; + promise.waitList = `${variable} waited on by: ${specifier}`; + promise.state = 'pending'; + promise.then( + (value) => { promise.state = 'resolved'; return Promise.resolve(value); }, + (error) => { promise.state = 'rejected'; return Promise.reject(error); }); + this.promiseList.push(promise); + this.promiseMap[variable] = promise; + return promise; + } + contains(variable) { + return variable in this.promiseMap; + } + get(variable, specifier) { + const promise = this.promiseMap[variable]; + promise.waitList += ` ${specifier}`; + return promise; + } + getPending() { return this.promiseList.filter(p => (p.state === 'pending')); } + getSettled() { return this.promiseList.filter(p => (p.state !== 'pending')); } + getAll() { return this.promiseList; } +} +class Variables { constructor(serverless) { this.serverless = serverless; this.service = this.serverless.service; - this.cache = {}; + this.tracker = new PromiseTracker(); this.overwriteSyntax = RegExp(/,/g); this.fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-/]+?)\)/g); @@ -29,6 +76,9 @@ class Variables { loadVariableSyntax() { this.variableSyntax = RegExp(this.service.provider.variableSyntax, 'g'); } + // ############# + // ## SERVICE ## + // ############# /** * Populate all variables in the service, conviently remove and restore the service attributes * that confuse the population methods. @@ -42,14 +92,117 @@ class Variables { // store const variableSyntaxProperty = this.service.provider.variableSyntax; // remove - this.service.provider.variableSyntax = true; // matches itself - this.serverless.service.serverless = null; - return this.populateObject(this.service).then(() => { - // restore - this.service.provider.variableSyntax = variableSyntaxProperty; - this.serverless.service.serverless = this.serverless; - return BbPromise.resolve(this.service); - }); + this.service.provider.variableSyntax = undefined; // otherwise matches itself + this.service.serverless = undefined; + this.tracker.start(); + return this.populateObject(this.service) + .finally(() => { + // restore + this.tracker.stop(); + this.service.serverless = this.serverless; + this.service.provider.variableSyntax = variableSyntaxProperty; + }) + .then(() => this.service); + } + // ############ + // ## OBJECT ## + // ############ + /** + * The declaration of a terminal property. This declaration includes the path and value of the + * property. + * Example Input: + * { + * foo: { + * bar: 'baz' + * } + * } + * Example Result: + * [ + * { + * path: ['foo', 'bar'] + * value: 'baz + * } + * ] + * @typedef {Object} TerminalProperty + * @property {String[]} path The path to the terminal property + * @property {Date|RegEx|String} The value of the terminal property + */ + /** + * Generate an array of objects noting the terminal properties of the given root object and their + * paths + * @param root The object to generate a terminal property path/value set for + * @param current The current part of the given root that terminal properties are being sought + * within + * @param [context] An array containing the path to the current object root (intended for internal + * use) + * @param [results] An array of current results (intended for internal use) + * @returns {TerminalProperty[]} The terminal properties of the given root object, with the path + * and value of each + */ + getProperties(root, atRoot, current, cntxt, rslts) { + let context = cntxt; + if (!context) { + context = []; + } + let results = rslts; + if (!results) { + results = []; + } + const addContext = (value, key) => + this.getProperties(root, false, value, context.concat(key), results); + if ( + _.isArray(current) + ) { + _.map(current, addContext); + } else if ( + _.isObject(current) && + !_.isDate(current) && + !_.isRegExp(current) && + !_.isFunction(current) + ) { + if (atRoot || current !== root) { + _.mapValues(current, addContext); + } + } else { + results.push({ path: context, value: current }); + } + return results; + } + + /** + * @typedef {TerminalProperty} TerminalPropertyPopulated + * @property {Object} populated The populated value of the value at the path + */ + /** + * Populate the given terminal properties, returning promises to do so + * @param properties The terminal properties to populate + * @returns {Promise[]} The promises that will resolve to the + * populated values of the given terminal properties + */ + populateProperties(properties) { + return _.map(properties, property => + this.populateValue(property.value, false) + .then(populated => _.assign({}, property, { populated }))); + } + /** + * Assign the populated values back to the target object + * @param target The object to which the given populated terminal properties should be applied + * @param populations The fully populated terminal properties + * @returns {Promise} resolving with the number of changes that were applied to the given + * target + */ + assignProperties(target, populations) { // eslint-disable-line class-methods-use-this + return BbPromise.all(populations) + .then((results) => { + let changes = 0; + results.forEach((result) => { + if (result.value !== result.populated) { + changes += 1; + _.set(target, result.path, result.populated); + } + }); + return BbPromise.resolve(changes); + }); } /** * Populate the variables in the given object. @@ -57,78 +210,104 @@ class Variables { * @returns {Promise.|*} A promise resolving to the in-place populated object. */ populateObject(objectToPopulate) { - // Map terminal values of given root (i.e. for every leaf value...) - const forEachLeaf = (root, context, callback) => { - const addContext = (value, key) => forEachLeaf(value, context.concat(key), callback); - if ( - _.isArray(root) - ) { - return _.map(root, addContext); - } else if ( - _.isObject(root) && - !_.isDate(root) && - !_.isRegExp(root) && - !_.isFunction(root) - ) { - return _.extend({}, root, _.mapValues(root, addContext)); - } - return callback(root, context); - }; - // For every leaf value... - const pendingLeaves = []; - forEachLeaf( - objectToPopulate, - [], - (leafValue, leafPath) => { - if (typeof leafValue === 'string') { - pendingLeaves.push(this - .populateProperty(leafValue, true) - .then(leafValuePopulated => _.set(objectToPopulate, leafPath, leafValuePopulated)) - ); + const leaves = this.getProperties(objectToPopulate, true, objectToPopulate); + const populations = this.populateProperties(leaves); + return this.assignProperties(objectToPopulate, populations) + .then((changes) => { + if (changes) { + return this.populateObject(objectToPopulate); } - } - ); - return BbPromise.all(pendingLeaves).then(() => objectToPopulate); + return objectToPopulate; + }); } + // ############## + // ## PROPERTY ## + // ############## /** - * Populate variables, in-place if specified, in the given property value. - * @param propertyToPopulate The property to populate (only strings with variables are altered). - * @param populateInPlace Whether to deeply clone the given property prior to population. - * @returns {Promise.|*} A promise resolving to the populated result. + * @typedef {Object} MatchResult + * @property {String} match The original property value that matched the variable syntax + * @property {String} variable The cleaned variable string that specifies the origin for the + * property value */ - populateProperty(propertyToPopulate, populateInPlace) { - let property = propertyToPopulate; - if (!populateInPlace) { - property = _.cloneDeep(propertyToPopulate); + /** + * Get matches against the configured variable syntax + * @param property The property value to attempt extracting matches from + * @returns {Object|String|MatchResult[]} The given property or the identified matches + */ + getMatches(property) { + if (typeof property !== 'string') { + return property; } - if ( - typeof property !== 'string' || - !property.match(this.variableSyntax) - ) { - return BbPromise.resolve(property); + const matches = property.match(this.variableSyntax); + if (!matches || !matches.length) { + return property; } - const pendingMatches = []; - property.match(this.variableSyntax).forEach((matchedString) => { - const variableString = matchedString - .replace(this.variableSyntax, (match, varName) => varName.trim()) - .replace(/\s/g, ''); - - let pendingMatch; - if (variableString.match(this.overwriteSyntax)) { - pendingMatch = this.overwrite(variableString); - } else { - pendingMatch = this.getValueFromSource(variableString); + return _.map(matches, match => ({ + match, + variable: match.replace(this.variableSyntax, (context, contents) => contents.trim()) + .replace(/\s/g, ''), + })); + } + /** + * Populate the given matches, returning an array of Promises which will resolve to the populated + * values of the given matches + * @param {MatchResult[]} matches The matches to populate + * @returns {Promise[]} Promises for the eventual populated values of the given matches + */ + populateMatches(matches) { + return _.map(matches, (match) => { + if (match.variable.match(this.overwriteSyntax)) { + return this.overwrite(match.variable); } - pendingMatches.push(pendingMatch.then(matchedValue => { - this.warnIfNotFound(variableString, matchedValue); - return this.populateVariable(property, matchedString, matchedValue) - .then((populatedProperty) => { - property = populatedProperty; - }); - })); + return this.getValueFromSource(match.variable, match.match); }); - return BbPromise.all(pendingMatches) - .then(() => this.populateProperty(property, true)); + } + /** + * Render the given matches and their associated results to the given value + * @param value The value into which to render the given results + * @param matches The matches on the given value where the results are to be rendered + * @param results The results that are to be rendered to the given value + * @returns {*} The populated value with the given results rendered according to the given matches + */ + renderMatches(value, matches, results) { + let result = value; + for (let i = 0; i < matches.length; i += 1) { + this.warnIfNotFound(matches[i].match, results[i]); + result = this.populateVariable(result, matches[i].match, results[i]); + } + return result; + } + /** + * Populate the given value, recursively if root is true + * @param valueToPopulate The value to populate variables within + * @param root Whether the caller is the root populator and thereby whether to recursively + * populate + * @returns {PromiseLike} A promise that resolves to the populated value, recursively if root + * is true + */ + populateValue(valueToPopulate, root) { + const property = _.cloneDeep(valueToPopulate); + const matches = this.getMatches(property); + if (!_.isArray(matches)) { + return BbPromise.resolve(property); + } + const populations = this.populateMatches(matches); + return BbPromise.all(populations) + .then(results => this.renderMatches(property, matches, results)) + .then((result) => { + if (root && matches.length) { + return this.populateValue(result); + } + return result; + }); + } + /** + * Populate variables in the given property. + * @param propertyToPopulate The property to populate (replace variables with their values). + * @returns {Promise.|*} A promise resolving to the populated result. + */ + populateProperty(propertyToPopulate) { + return this.populateValue(propertyToPopulate, true); } /** * Populate a given property, given the matched string to replace and the value to replace the @@ -155,8 +334,11 @@ class Variables { ].join(''); throw new this.serverless.classes.Error(errorMessage); } - return BbPromise.resolve(property); + return property; } + // ############### + // ## VARIABLES ## + // ############### /** * Overwrite the given variable string, resolve each variable and resolve to the first valid * value. @@ -167,8 +349,7 @@ class Variables { overwrite(variableStringsString) { const variableStrings = variableStringsString.split(','); const variableValues = variableStrings.map(variableString => - this.getValueFromSource(variableString) - ); + this.getValueFromSource(variableString, variableStringsString)); const validValue = value => ( value !== null && typeof value !== 'undefined' && @@ -176,53 +357,48 @@ class Variables { ); return BbPromise.all(variableValues) .then(values => // find and resolve first valid value, undefined if none - BbPromise.resolve(values.find(validValue)) - ); + BbPromise.resolve(values.find(validValue))); } /** * Given any variable string, return the value it should be populated with. * @param variableString The variable string to retrieve a value for. * @returns {Promise.|*} A promise resolving to the given variables value. */ - getValueFromSource(variableString) { - if (!(variableString in this.cache)) { - let value; + getValueFromSource(variableString, propertyString) { + let ret; + if (this.tracker.contains(variableString)) { + ret = this.tracker.get(variableString, propertyString); + } else { if (variableString.match(this.envRefSyntax)) { - value = this.getValueFromEnv(variableString); + ret = this.getValueFromEnv(variableString); } else if (variableString.match(this.optRefSyntax)) { - value = this.getValueFromOptions(variableString); + ret = this.getValueFromOptions(variableString); } else if (variableString.match(this.selfRefSyntax)) { - value = this.getValueFromSelf(variableString); + ret = this.getValueFromSelf(variableString); } else if (variableString.match(this.fileRefSyntax)) { - value = this.getValueFromFile(variableString); + ret = this.getValueFromFile(variableString); } else if (variableString.match(this.cfRefSyntax)) { - value = this.getValueFromCf(variableString); + ret = this.getValueFromCf(variableString); } else if (variableString.match(this.s3RefSyntax)) { - value = this.getValueFromS3(variableString); + ret = this.getValueFromS3(variableString); } else if (variableString.match(this.stringRefSyntax)) { - value = this.getValueFromString(variableString); + ret = this.getValueFromString(variableString); } else if (variableString.match(this.ssmRefSyntax)) { - value = this.getValueFromSsm(variableString); + ret = this.getValueFromSsm(variableString); } else { const errorMessage = [ `Invalid variable reference syntax for variable ${variableString}.`, ' You can only reference env vars, options, & files.', ' You can check our docs for more info.', ].join(''); - throw new this.serverless.classes.Error(errorMessage); + ret = BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - this.cache[variableString] = BbPromise.resolve(value) - .then(variableValue => { - if (_.isObject(variableValue) && variableValue !== this.service) { - return this.populateObject(variableValue); - } - return variableValue; - }); + this.tracker.add(variableString, ret, propertyString); } - return this.cache[variableString]; + return ret; } - getValueFromEnv(variableString) { + getValueFromEnv(variableString) { // eslint-disable-line class-methods-use-this const requestedEnvVar = variableString.split(':')[1]; let valueToPopulate; if (requestedEnvVar !== '' || '' in process.env) { @@ -233,7 +409,7 @@ class Variables { return BbPromise.resolve(valueToPopulate); } - getValueFromString(variableString) { + getValueFromString(variableString) { // eslint-disable-line class-methods-use-this const valueToPopulate = variableString.replace(/^['"]|['"]$/g, ''); return BbPromise.resolve(valueToPopulate); } @@ -262,13 +438,13 @@ class Variables { .replace('~', os.homedir()); let referencedFileFullPath = (path.isAbsolute(referencedFileRelativePath) ? - referencedFileRelativePath : - path.join(this.serverless.config.servicePath, referencedFileRelativePath)); + referencedFileRelativePath : + path.join(this.serverless.config.servicePath, referencedFileRelativePath)); // Get real path to handle potential symlinks (but don't fatal error) referencedFileFullPath = fse.existsSync(referencedFileFullPath) ? - fse.realpathSync(referencedFileFullPath) : - referencedFileFullPath; + fse.realpathSync(referencedFileFullPath) : + referencedFileFullPath; let fileExtension = referencedFileRelativePath.split('.'); fileExtension = fileExtension[fileExtension.length - 1]; @@ -281,7 +457,8 @@ class Variables { // Process JS files if (fileExtension === 'js') { - const jsFile = require(referencedFileFullPath); // eslint-disable-line global-require + // eslint-disable-next-line global-require, import/no-dynamic-require + const jsFile = require(referencedFileFullPath); const variableArray = variableString.split(':'); let returnValueFunction; if (variableArray[1]) { @@ -293,29 +470,28 @@ class Variables { } if (typeof returnValueFunction !== 'function') { - throw new this.serverless.classes - .Error([ - 'Invalid variable syntax when referencing', - ` file "${referencedFileRelativePath}".`, - ' Check if your javascript is exporting a function that returns a value.', - ].join('')); + const errorMessage = [ + 'Invalid variable syntax when referencing', + ` file "${referencedFileRelativePath}".`, + ' Check if your javascript is exporting a function that returns a value.', + ].join(''); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } valueToPopulate = returnValueFunction.call(jsFile); - return BbPromise.resolve(valueToPopulate).then(valueToPopulateResolved => { + return BbPromise.resolve(valueToPopulate).then((valueToPopulateResolved) => { let deepProperties = variableString.replace(matchedFileRefString, ''); deepProperties = deepProperties.slice(1).split('.'); deepProperties.splice(0, 1); return this.getDeepValue(deepProperties, valueToPopulateResolved) - .then(deepValueToPopulateResolved => { + .then((deepValueToPopulateResolved) => { if (typeof deepValueToPopulateResolved === 'undefined') { const errorMessage = [ 'Invalid variable syntax when referencing', ` file "${referencedFileRelativePath}".`, ' Check if your javascript is returning the correct data.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } return BbPromise.resolve(deepValueToPopulateResolved); }); @@ -334,8 +510,7 @@ class Variables { ` file "${referencedFileRelativePath}" sub properties`, ' Please use ":" to reference sub properties.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } deepProperties = deepProperties.slice(1).split('.'); return this.getDeepValue(deepProperties, valueToPopulate); @@ -352,9 +527,8 @@ class Variables { .request('CloudFormation', 'describeStacks', { StackName: stackName }, - { useCache: true } // Use request cache - ) - .then(result => { + { useCache: true })// Use request cache + .then((result) => { const outputs = result.Stacks[0].Outputs; const output = outputs.find(x => x.OutputKey === outputLogicalId); @@ -364,11 +538,9 @@ class Variables { ` Stack name: "${stackName}"`, ` Requested variable: "${outputLogicalId}".`, ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - - return output.OutputValue; + return BbPromise.resolve(output.OutputValue); }); } @@ -376,47 +548,41 @@ class Variables { const groups = variableString.match(this.s3RefSyntax); const bucket = groups[1]; const key = groups[2]; - return this.serverless.getProvider('aws') - .request('S3', + return this.serverless.getProvider('aws').request( + 'S3', 'getObject', { Bucket: bucket, Key: key, }, - { useCache: true } // Use request cache - ) - .then( - response => response.Body.toString(), - err => { + { useCache: true }) // Use request cache + .then(response => BbPromise.resolve(response.Body.toString())) + .catch((err) => { const errorMessage = `Error getting value for ${variableString}. ${err.message}`; - throw new this.serverless.classes.Error(errorMessage); - } - ); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); + }); } getValueFromSsm(variableString) { const groups = variableString.match(this.ssmRefSyntax); const param = groups[1]; const decrypt = (groups[2] === 'true'); - return this.serverless.getProvider('aws') - .request('SSM', + return this.serverless.getProvider('aws').request( + 'SSM', 'getParameter', { Name: param, WithDecryption: decrypt, }, - { useCache: true } // Use request cache - ) - .then( - response => BbPromise.resolve(response.Parameter.Value), - err => { + { useCache: true }) // Use request cache + .then(response => BbPromise.resolve(response.Parameter.Value)) + .catch((err) => { const expectedErrorMessage = `Parameter ${param} not found.`; if (err.message !== expectedErrorMessage) { - throw new this.serverless.classes.Error(err.message); + return BbPromise.reject(new this.serverless.classes.Error(err.message)); } return BbPromise.resolve(undefined); - } - ); + }); } getDeepValue(deepProperties, valueToPopulate) { @@ -429,7 +595,7 @@ class Variables { } if (typeof computedValueToPopulate === 'string' && computedValueToPopulate.match(this.variableSyntax)) { - return this.populateProperty(computedValueToPopulate, true); + return this.populateValue(computedValueToPopulate, false); } return BbPromise.resolve(computedValueToPopulate); }, valueToPopulate); @@ -453,10 +619,10 @@ class Variables { } else if (variableString.match(this.ssmRefSyntax)) { varType = 'SSM parameter'; } - logWarning( - `A valid ${varType} to satisfy the declaration '${variableString}' could not be found.` - ); + logWarning(`A valid ${varType} to satisfy the declaration '${ + variableString}' could not be found.`); } + return valueToPopulate; } } diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index e59b552aada..04c2f814b0b 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -2,36 +2,43 @@ /* eslint-disable no-unused-expressions */ +const BbPromise = require('bluebird'); +const chai = require('chai'); const jc = require('json-cycle'); +const os = require('os'); const path = require('path'); const proxyquire = require('proxyquire'); +const sinon = require('sinon'); const YAML = require('js-yaml'); -const chai = require('chai'); -const Variables = require('../../lib/classes/Variables'); -const Utils = require('../../lib/classes/Utils'); + +const AwsProvider = require('../plugins/aws/provider/awsProvider'); const fse = require('../utils/fs/fse'); const Serverless = require('../../lib/Serverless'); -const sinon = require('sinon'); -const testUtils = require('../../tests/utils'); const slsError = require('./Error'); -const AwsProvider = require('../plugins/aws/provider/awsProvider'); -const BbPromise = require('bluebird'); -const os = require('os'); +const testUtils = require('../../tests/utils'); +const Utils = require('../../lib/classes/Utils'); +const Variables = require('../../lib/classes/Variables'); + +BbPromise.longStackTraces(true); + +chai.should(); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); const expect = chai.expect; -describe('Variables', () => { - describe('#constructor()', () => { - const serverless = new Serverless(); +describe.only('Variables', () => { + let serverless; + beforeEach(() => { + serverless = new Serverless(); + }); + describe('#constructor()', () => { it('should attach serverless instance', () => { const variablesInstance = new Variables(serverless); - expect(typeof variablesInstance.serverless.version).to.be.equal('string'); + expect(variablesInstance.serverless).to.equal(serverless); }); - it('should not set variableSyntax in constructor', () => { const variablesInstance = new Variables(serverless); expect(variablesInstance.variableSyntax).to.be.undefined; @@ -40,110 +47,115 @@ describe('Variables', () => { describe('#loadVariableSyntax()', () => { it('should set variableSyntax', () => { - const serverless = new Serverless(); - + // eslint-disable-next-line no-template-curly-in-string serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}'; - serverless.variables.loadVariableSyntax(); expect(serverless.variables.variableSyntax).to.be.a('RegExp'); }); }); describe('#populateService()', () => { - it('should call populateProperty method', () => { - const serverless = new Serverless(); - - const populatePropertyStub = sinon - .stub(serverless.variables, 'populateObject').resolves(); - + it('should call loadVariableSyntax and then populateProperty', () => { + const loadVariableSyntaxStub = sinon.stub(serverless.variables, 'loadVariableSyntax') + .returns(); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObject').resolves(); return expect(serverless.variables.populateService()).to.be.fulfilled - .then(() => { - expect(populatePropertyStub.called).to.be.true; - }) - .finally(() => serverless.variables.populateObject.restore()); + .then(() => { + expect(loadVariableSyntaxStub).to.be.calledOnce; + expect(populateObjectStub).to.be.calledOnce; + expect(loadVariableSyntaxStub).to.be.calledBefore(populateObjectStub); + }) + .finally(() => { + populateObjectStub.restore(); + loadVariableSyntaxStub.restore(); + }); }); - - it('should use variableSyntax', () => { - const serverless = new Serverless(); - - const variableSyntax = '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}'; - const fooValue = '${clientId()}'; - const barValue = 'test'; - - serverless.service.provider.variableSyntax = variableSyntax; - - serverless.service.custom = { - var: barValue, - }; - - serverless.service.resources = { - foo: fooValue, - bar: '${{self:custom.var}}', - }; - - return serverless.variables.populateService().then(() => { - expect(serverless.service.provider.variableSyntax).to.equal(variableSyntax); - expect(serverless.service.resources.foo).to.equal(fooValue); - expect(serverless.service.resources.bar).to.equal(barValue); + it('should remove problematic attributes bofore calling populateObject with the service', + () => { + const populateObjectStub = sinon.stub(serverless.variables, 'populateObject', (val) => { + expect(val).to.equal(serverless.service); + expect(val.provider.variableSyntax).to.be.undefined; + expect(val.serverless).to.be.undefined; + return BbPromise.resolve(); + }); + return expect(serverless.variables.populateService()).to.be.fulfilled + .then().finally(() => populateObjectStub.restore()); }); - }); }); - describe('#populateObject()', () => { - it('should call populateProperty method', () => { - const serverless = new Serverless(); - const object = { - stage: '${opt:stage}', + describe('#getProperties', () => { + it('extracts all terminal properties of an object', () => { + const date = new Date(); + const regex = /^.*$/g; + const func = () => {}; + const obj = { + foo: { + bar: 'baz', + biz: 'buz', + }, + b: [ + { c: 'd' }, + { e: 'f' }, + ], + g: date, + h: regex, + i: func, }; - - const populatePropertyStub = sinon - .stub(serverless.variables, 'populateProperty').resolves('prod'); - - return serverless.variables.populateObject(object).then(() => { - expect(populatePropertyStub.called).to.be.true; - }) - .finally(() => serverless.variables.populateProperty.restore()); + const expected = [ + { path: ['foo', 'bar'], value: 'baz' }, + { path: ['foo', 'biz'], value: 'buz' }, + { path: ['b', 0, 'c'], value: 'd' }, + { path: ['b', 1, 'e'], value: 'f' }, + { path: ['g'], value: date }, + { path: ['h'], value: regex }, + { path: ['i'], value: func }, + ]; + const result = serverless.variables.getProperties(obj, true, obj); + expect(result).to.eql(expected); + }); + it('ignores self references', () => { + const obj = {}; + obj.self = obj; + const expected = []; + const result = serverless.variables.getProperties(obj, true, obj); + expect(result).to.eql(expected); }); + }); + describe('#populateObject()', () => { it('should populate object and return it', () => { - const serverless = new Serverless(); const object = { - stage: '${opt:stage}', + stage: '${opt:stage}', // eslint-disable-line no-template-curly-in-string }; const expectedPopulatedObject = { stage: 'prod', }; - sinon.stub(serverless.variables, 'populateProperty').resolves('prod'); + sinon.stub(serverless.variables, 'populateValue').resolves('prod'); - return serverless.variables.populateObject(object).then(populatedObject => { + return serverless.variables.populateObject(object).then((populatedObject) => { expect(populatedObject).to.deep.equal(expectedPopulatedObject); }) - .finally(() => serverless.variables.populateProperty.restore()); + .finally(() => serverless.variables.populateValue.restore()); }); it('should persist keys with dot notation', () => { - const serverless = new Serverless(); const object = { - stage: '${opt:stage}', + stage: '${opt:stage}', // eslint-disable-line no-template-curly-in-string }; object['some.nested.key'] = 'hello'; const expectedPopulatedObject = { stage: 'prod', }; expectedPopulatedObject['some.nested.key'] = 'hello'; - - const populatePropertyStub = sinon.stub(serverless.variables, 'populateProperty'); - populatePropertyStub.onCall(0).resolves('prod'); - populatePropertyStub.onCall(1).resolves('hello'); - - return serverless.variables.populateObject(object).then(populatedObject => { - expect(populatedObject).to.deep.equal(expectedPopulatedObject); - }) - .finally(() => serverless.variables.populateProperty.restore()); + const populateValueStub = sinon.stub(serverless.variables, 'populateValue', + // eslint-disable-next-line no-template-curly-in-string + val => (val === '${opt:stage}' ? BbPromise.resolve('prod') : BbPromise.resolve(val))); + return serverless.variables.populateObject(object) + .should.become(expectedPopulatedObject) + .then().finally(() => populateValueStub.restore()); }); describe('significant variable usage corner cases', () => { - let serverless; let service; const makeDefault = () => ({ service: 'my-service', @@ -152,8 +164,8 @@ describe('Variables', () => { }, }); beforeEach(() => { - serverless = new Serverless(); service = makeDefault(); + // eslint-disable-next-line no-template-curly-in-string service.provider.variableSyntax = '\\${([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}'; // default serverless.variables.service = service; serverless.variables.loadVariableSyntax(); @@ -161,7 +173,7 @@ describe('Variables', () => { }); it('should properly replace self-references', () => { service.custom = { - me: '${self:}', + me: '${self:}', // eslint-disable-line no-template-curly-in-string }; const expected = makeDefault(); expected.custom = { @@ -174,7 +186,7 @@ describe('Variables', () => { it('should properly populate embedded variables', () => { service.custom = { val0: 'my value 0', - val1: '0', + val1: '0', // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.val${self:custom.val1}}', }; const expected = { @@ -188,7 +200,7 @@ describe('Variables', () => { }); it('should properly populate an overwrite with a default value that is a string', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, "string"}', }; const expected = { @@ -201,7 +213,7 @@ describe('Variables', () => { }); it('should properly populate overwrites where the first value is valid', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.val0, self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2}', }; const expected = { @@ -214,7 +226,7 @@ describe('Variables', () => { }); it('should properly populate overwrites where the middle value is valid', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.NOT_A_VAL1, self:custom.val0, self:custom.NOT_A_VAL2}', }; const expected = { @@ -227,7 +239,7 @@ describe('Variables', () => { }); it('should properly populate overwrites where the last value is valid', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, self:custom.val0}', }; const expected = { @@ -241,7 +253,7 @@ describe('Variables', () => { it('should properly populate overwrites with nested variables in the first value', () => { service.custom = { val0: 'my value', - val1: 0, + val1: 0, // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.val${self:custom.val1}, self:custom.NO_1, self:custom.NO_2}', }; const expected = { @@ -256,7 +268,7 @@ describe('Variables', () => { it('should properly populate overwrites with nested variables in the middle value', () => { service.custom = { val0: 'my value', - val1: 0, + val1: 0, // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.NO_1, self:custom.val${self:custom.val1}, self:custom.NO_2}', }; const expected = { @@ -271,7 +283,7 @@ describe('Variables', () => { it('should properly populate overwrites with nested variables in the last value', () => { service.custom = { val0: 'my value', - val1: 0, + val1: 0, // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.NO_1, self:custom.NO_2, self:custom.val${self:custom.val1}}', }; const expected = { @@ -286,8 +298,8 @@ describe('Variables', () => { it('should properly replace duplicate variable declarations', () => { service.custom = { val0: 'my value', - val1: '${self:custom.val0}', - val2: '${self:custom.val0}', + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string }; const expected = { val0: 'my value', @@ -300,10 +312,10 @@ describe('Variables', () => { }); it('should recursively populate, regardless of order and duplication', () => { service.custom = { - val1: '${self:custom.depVal}', - depVal: '${self:custom.val0}', + val1: '${self:custom.depVal}', // eslint-disable-line no-template-curly-in-string + depVal: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string val0: 'my value', - val2: '${self:custom.depVal}', + val2: '${self:custom.depVal}', // eslint-disable-line no-template-curly-in-string }; const expected = { val1: 'my value', @@ -315,11 +327,21 @@ describe('Variables', () => { expect(result).to.eql(expected); })).to.be.fulfilled; }); - const pathAsyncLoadJs = 'async.load.js'; - const makeAsyncLoadJs = () => { - const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); - const fileContent = `'use strict'; + describe('file reading cases', () => { + let tmpDirPath; + beforeEach(() => { + tmpDirPath = testUtils.getTmpDirPath(); + fse.mkdirsSync(tmpDirPath); + serverless.config.update({ servicePath: tmpDirPath }); + }); + afterEach(() => { + fse.removeSync(tmpDirPath); + }); + const makeTempFile = (fileName, fileContent) => { + fse.writeFileSync(path.join(tmpDirPath, fileName), fileContent); + }; + const asyncFileName = 'async.load.js'; + const asyncContent = `'use strict'; let i = 0 const str = () => new Promise((resolve) => { setTimeout(() => { @@ -341,506 +363,380 @@ module.exports = { obj, }; `; - SUtils.writeFileSync(path.join(tmpDirPath, pathAsyncLoadJs), fileContent); - serverless.config.update({ servicePath: tmpDirPath }); - }; - it('should populate any given variable only once', () => { - makeAsyncLoadJs(); - service.custom = { - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val0: `\${file(${pathAsyncLoadJs}):str}`, - }; - const expected = { - val1: 'my-async-value-1', - val2: 'my-async-value-1', - val0: 'my-async-value-1', - }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; - }); - it('should populate any given variable only once regardless of ordering or reference count', - () => { - makeAsyncLoadJs(); + it('should populate any given variable only once', () => { + makeTempFile(asyncFileName, asyncContent); service.custom = { - val9: '${self:custom.val7}', - val7: '${self:custom.val5}', - val5: '${self:custom.val3}', - val3: '${self:custom.val1}', - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val4: '${self:custom.val3}', - val6: '${self:custom.val5}', - val8: '${self:custom.val7}', - val0: `\${file(${pathAsyncLoadJs}):str}`, + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val0: `\${file(${asyncFileName}):str}`, }; const expected = { - val9: 'my-async-value-1', - val7: 'my-async-value-1', - val5: 'my-async-value-1', - val3: 'my-async-value-1', val1: 'my-async-value-1', val2: 'my-async-value-1', - val4: 'my-async-value-1', - val6: 'my-async-value-1', - val8: 'my-async-value-1', val0: 'my-async-value-1', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; - } - ); - it('should populate async objects with contained variables', - () => { - makeAsyncLoadJs(); - serverless.variables.options = { - stage: 'dev', - }; - service.custom = { - obj: `\${file(${pathAsyncLoadJs}):obj}`, - }; - const expected = { - obj: { + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should populate any given variable only once regardless of ordering or reference count', + () => { + makeTempFile(asyncFileName, asyncContent); + service.custom = { + val9: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string + val7: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string + val5: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string + val3: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val4: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string + val6: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string + val8: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string + val0: `\${file(${asyncFileName}):str}`, + }; + const expected = { + val9: 'my-async-value-1', + val7: 'my-async-value-1', + val5: 'my-async-value-1', + val3: 'my-async-value-1', + val1: 'my-async-value-1', + val2: 'my-async-value-1', + val4: 'my-async-value-1', + val6: 'my-async-value-1', + val8: 'my-async-value-1', val0: 'my-async-value-1', - val1: 'dev', - }, - }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; - } - ); - const pathEmptyJs = 'empty.js'; - const makeEmptyJs = () => { - const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); - const fileContent = `'use strict'; + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should populate async objects with contained variables', + () => { + makeTempFile(asyncFileName, asyncContent); + serverless.variables.options = { + stage: 'dev', + }; + service.custom = { + obj: `\${file(${asyncFileName}):obj}`, + }; + const expected = { + obj: { + val0: 'my-async-value-1', + val1: 'dev', + }, + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + const selfFileName = 'self.yml'; + const selfContent = `foo: baz +bar: \${self:custom.self.foo} +`; + it('should populate a "cyclic" reference across an unresolved dependency (issue #4687)', + () => { + makeTempFile(selfFileName, selfContent); + service.custom = { + self: `\${file(${selfFileName})}`, + }; + const expected = { + self: { + foo: 'baz', + bar: 'baz', + }, + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + const emptyFileName = 'empty.js'; + const emptyContent = `'use strict'; module.exports = { func: () => ({ value: 'a value' }), } `; - SUtils.writeFileSync(path.join(tmpDirPath, pathEmptyJs), fileContent); - serverless.config.update({ servicePath: tmpDirPath }); - }; - it('should reject population of an attribute not exported from a file', - () => { - makeEmptyJs(); - service.custom = { - val: `\${file(${pathEmptyJs}):func.notAValue}`, - }; - return expect(serverless.variables.populateObject(service.custom)) - .to.eventually.be.rejected; - } - ); + it('should reject population of an attribute not exported from a file', + () => { + makeTempFile(emptyFileName, emptyContent); + service.custom = { + val: `\${file(${emptyFileName}):func.notAValue}`, + }; + return serverless.variables.populateObject(service.custom) + .should.be.rejectedWith(serverless.classes.Error); + }); + }); }); }); describe('#populateProperty()', () => { - let serverless; - let overwriteStub; - let populateObjectStub; - let getValueFromSourceStub; - let populateVariableStub; - beforeEach(() => { - serverless = new Serverless(); - overwriteStub = sinon.stub(serverless.variables, 'overwrite'); - populateObjectStub = sinon.stub(serverless.variables, 'populateObject'); - getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - populateVariableStub = sinon.stub(serverless.variables, 'populateVariable'); - }); - - afterEach(() => { - serverless.variables.overwrite.restore(); - serverless.variables.populateObject.restore(); - serverless.variables.getValueFromSource.restore(); - serverless.variables.populateVariable.restore(); + serverless.variables.loadVariableSyntax(); }); it('should call overwrite if overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage, self:provider.stage}'; - - serverless.variables.loadVariableSyntax(); - - overwriteStub.resolves('dev'); - populateVariableStub.resolves('my stage is dev'); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(overwriteStub.called).to.equal(true); - expect(populateVariableStub.called).to.equal(true); - expect(newProperty).to.equal('my stage is dev'); - - return BbPromise.resolve(); - }); + serverless.variables.options = { stage: 'dev' }; + serverless.service.provider.stage = 'prod'; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is dev'); }); it('should allow a single-quoted string if overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = "my stage is ${opt:stage, 'prod'}"; - - serverless.variables.loadVariableSyntax(); - - overwriteStub.resolves('\'prod\''); - populateVariableStub.resolves('my stage is prod'); - - return expect(serverless.variables.populateProperty(property)).to.be.fulfilled - .then(newProperty => expect(newProperty).to.equal('my stage is prod')); + serverless.variables.options = {}; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is prod'); }); it('should allow a double-quoted string if overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage, "prod"}'; - - serverless.variables.loadVariableSyntax(); - - overwriteStub.resolves('\'prod\''); - populateVariableStub.resolves('my stage is prod'); - - return expect(serverless.variables.populateProperty(property)).to.be.fulfilled - .then(newProperty => expect(newProperty).to.equal('my stage is prod')); + serverless.variables.options = {}; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is prod'); }); it('should call getValueFromSource if no overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage}'; - - serverless.variables.loadVariableSyntax(); - - getValueFromSourceStub.resolves('prod'); - populateVariableStub.resolves('my stage is prod'); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(getValueFromSourceStub.called).to.be.true; - expect(populateVariableStub.called).to.be.true; - expect(newProperty).to.equal('my stage is prod'); - - return BbPromise.resolve(); - }); - }); - - it('should NOT call populateObject if variable value is a circular object', () => { - serverless.variables.options = { - stage: 'prod', - }; - const property = '${opt:stage}'; - const variableValue = { - stage: '${opt:stage}', - }; - const variableValuePopulated = { - stage: 'prod', - }; - - serverless.variables.cache['opt:stage'] = variableValuePopulated; - - serverless.variables.loadVariableSyntax(); - - populateObjectStub.resolves(variableValuePopulated); - getValueFromSourceStub.resolves(variableValue); - populateVariableStub.resolves(variableValuePopulated); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(populateObjectStub.called).to.equal(false); - expect(getValueFromSourceStub.called).to.equal(true); - expect(populateVariableStub.called).to.equal(true); - expect(newProperty).to.deep.equal(variableValuePopulated); - - return BbPromise.resolve(); - }); + serverless.variables.options = { stage: 'prod' }; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is prod'); }); it('should warn if an SSM parameter does not exist', () => { - const awsProvider = new AwsProvider(serverless, { stage: 'prod', region: 'us-west-2' }); - const param = '/some/path/to/invalidparam'; - const property = `\${ssm:${param}}`; - const error = new Error(`Parameter ${param} not found.`); - - serverless.variables.options = { + const options = { stage: 'prod', region: 'us-east-1', }; - serverless.variables.loadVariableSyntax(); - - serverless.variables.getValueFromSource.restore(); - serverless.variables.populateVariable.restore(); + serverless.variables.options = options; + const awsProvider = new AwsProvider(serverless, options); + const param = '/some/path/to/invalidparam'; + const property = `\${ssm:${param}}`; + const error = new Error(`Parameter ${param} not found.`, 123); const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); const warnIfNotFoundSpy = sinon.spy(serverless.variables, 'warnIfNotFound'); - - return expect(serverless.variables.populateProperty(property) - .then(newProperty => { + return serverless.variables.populateProperty(property) + .should.become(undefined) + .then(() => { expect(requestStub.callCount).to.equal(1); expect(warnIfNotFoundSpy.callCount).to.equal(1); - expect(newProperty).to.be.undefined; }) .finally(() => { - getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - populateVariableStub = sinon.stub(serverless.variables, 'populateVariable'); - })).to.be.fulfilled; + requestStub.restore(); + warnIfNotFoundSpy.restore(); + }); }); it('should throw an Error if the SSM request fails', () => { - const awsProvider = new AwsProvider(serverless, { stage: 'prod', region: 'us-west-2' }); - const param = '/some/path/to/invalidparam'; - const property = `\${ssm:${param}}`; - const error = new Error('Some random failure.'); - - serverless.variables.options = { + const options = { stage: 'prod', region: 'us-east-1', }; - serverless.variables.loadVariableSyntax(); - - serverless.variables.getValueFromSource.restore(); + serverless.variables.options = options; + const awsProvider = new AwsProvider(serverless, options); + const param = '/some/path/to/invalidparam'; + const property = `\${ssm:${param}}`; + const error = new serverless.classes.Error('Some random failure.', 123); const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); - - return expect(serverless.variables.populateProperty(property) - .finally(() => { - getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - expect(requestStub.callCount).to.equal(1); - })).to.be.rejectedWith(serverless.classes.Error); + return serverless.variables.populateProperty(property) + .should.be.rejectedWith(serverless.classes.Error) + .then(() => expect(requestStub.callCount).to.equal(1)) + .finally(() => requestStub.restore()); }); it('should run recursively if nested variables provided', () => { - const property = 'my stage is ${env:${opt.name}}'; - - serverless.variables.loadVariableSyntax(); - - getValueFromSourceStub.onCall(0).resolves('stage'); - getValueFromSourceStub.onCall(1).resolves('dev'); - populateVariableStub.onCall(0).resolves('my stage is ${env:stage}'); - populateVariableStub.onCall(1).resolves('my stage is dev'); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(getValueFromSourceStub.callCount).to.equal(2); - expect(populateVariableStub.callCount).to.equal(2); - expect(newProperty).to.equal('my stage is dev'); - }); + // eslint-disable-next-line no-template-curly-in-string + const property = 'my stage is ${env:${opt:name}}'; + process.env.TEST_VAR = 'dev'; + serverless.variables.options = { name: 'TEST_VAR' }; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is dev') + .then().finally(() => { delete process.env.TEST_VAR; }); }); }); describe('#populateVariable()', () => { it('should populate string variables as sub string', () => { - const serverless = new Serverless(); const valueToPopulate = 'dev'; - const matchedString = '${opt:stage}'; + const matchedString = '${opt:stage}'; // eslint-disable-line no-template-curly-in-string + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage}'; - - return serverless.variables.populateVariable(property, matchedString, valueToPopulate) - .then(newProperty => { - expect(newProperty).to.equal('my stage is dev'); - }); + serverless.variables.populateVariable(property, matchedString, valueToPopulate) + .should.eql('my stage is dev'); }); it('should populate number variables as sub string', () => { - const serverless = new Serverless(); const valueToPopulate = 5; - const matchedString = '${opt:number}'; + const matchedString = '${opt:number}'; // eslint-disable-line no-template-curly-in-string + // eslint-disable-next-line no-template-curly-in-string const property = 'your account number is ${opt:number}'; - - return serverless.variables.populateVariable(property, matchedString, valueToPopulate) - .then(newProperty => { - expect(newProperty).to.equal('your account number is 5'); - }); + serverless.variables.populateVariable(property, matchedString, valueToPopulate) + .should.eql('your account number is 5'); }); it('should populate non string variables', () => { - const serverless = new Serverless(); const valueToPopulate = 5; - const matchedString = '${opt:number}'; - const property = '${opt:number}'; - + const matchedString = '${opt:number}'; // eslint-disable-line no-template-curly-in-string + const property = '${opt:number}'; // eslint-disable-line no-template-curly-in-string return serverless.variables.populateVariable(property, matchedString, valueToPopulate) - .then(newProperty => { - expect(newProperty).to.equal(5); - }); + .should.equal(5); }); it('should throw error if populating non string or non number variable as sub string', () => { - const serverless = new Serverless(); const valueToPopulate = {}; - const matchedString = '${opt:object}'; + const matchedString = '${opt:object}'; // eslint-disable-line no-template-curly-in-string + // eslint-disable-next-line no-template-curly-in-string const property = 'your account number is ${opt:object}'; - expect(() => serverless.variables - .populateVariable(property, matchedString, valueToPopulate)) - .to.throw(Error); + return expect(() => + serverless.variables.populateVariable(property, matchedString, valueToPopulate)) + .to.throw(serverless.classes.Error); }); }); describe('#overwrite()', () => { it('should overwrite undefined and null values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves(null); getValueFromSourceStub.onCall(2).resolves('variableValue'); - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { + .should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSourceStub).to.have.been.calledThrice; }) - .finally(() => serverless.variables.getValueFromSource.restore()); + .finally(() => getValueFromSourceStub.restore()); }); it('should overwrite empty object values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves({}); getValueFromSourceStub.onCall(1).resolves('variableValue'); - - return serverless.variables.overwrite('opt:stage,env:stage').then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromSourceStub).to.have.been.calledTwice; - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite('opt:stage,env:stage').should.be.fulfilled + .then((valueToPopulate) => { + expect(valueToPopulate).to.equal('variableValue'); + expect(getValueFromSourceStub).to.have.been.calledTwice; + }) + .finally(() => getValueFromSourceStub.restore()); }); it('should not overwrite 0 values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(0); getValueFromSourceStub.onCall(1).resolves('variableValue'); - getValueFromSourceStub.onCall(2).resolves('variableValue2'); - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal(0); - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite('opt:stage,env:stage').should.become(0) + .then().finally(() => getValueFromSourceStub.restore()); }); it('should not overwrite false values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(false); getValueFromSourceStub.onCall(1).resolves('variableValue'); - getValueFromSourceStub.onCall(2).resolves('variableValue2'); - - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { - expect(valueToPopulate).to.be.false; - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite('opt:stage,env:stage').should.become(false) + .then().finally(() => getValueFromSourceStub.restore()); }); it('should skip getting values once a value has been found', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves('variableValue'); getValueFromSourceStub.onCall(2).resolves('variableValue2'); - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + .should.be.fulfilled + .then(valueToPopulate => expect(valueToPopulate).to.equal('variableValue')) + .finally(() => getValueFromSourceStub.restore()); }); }); describe('#getValueFromSource()', () => { it('should call getValueFromEnv if referencing env var', () => { - const serverless = new Serverless(); - const getValueFromEnvStub = sinon - .stub(serverless.variables, 'getValueFromEnv').resolves('variableValue'); - return serverless.variables.getValueFromSource('env:TEST_VAR') - .then(valueToPopulate => { + const getValueFromEnvStub = sinon.stub(serverless.variables, 'getValueFromEnv') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('env:TEST_VAR').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromEnvStub).to.have.been.called; - expect(getValueFromEnvStub.calledWith('env:TEST_VAR')).to.equal(true); + expect(getValueFromEnvStub).to.have.been.calledWith('env:TEST_VAR'); }) - .finally(() => serverless.variables.getValueFromEnv.restore()); + .finally(() => getValueFromEnvStub.restore()); }); it('should call getValueFromOptions if referencing an option', () => { - const serverless = new Serverless(); const getValueFromOptionsStub = sinon .stub(serverless.variables, 'getValueFromOptions') .resolves('variableValue'); - - return serverless.variables.getValueFromSource('opt:stage') - .then(valueToPopulate => { + return serverless.variables.getValueFromSource('opt:stage').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromOptionsStub).to.have.been.called; - expect(getValueFromOptionsStub.calledWith('opt:stage')).to.equal(true); + expect(getValueFromOptionsStub).to.have.been.calledWith('opt:stage'); }) - .finally(() => serverless.variables.getValueFromOptions.restore()); + .finally(() => getValueFromOptionsStub.restore()); }); it('should call getValueFromSelf if referencing from self', () => { - const serverless = new Serverless(); - const getValueFromSelfStub = sinon - .stub(serverless.variables, 'getValueFromSelf').resolves('variableValue'); - - return serverless.variables.getValueFromSource('self:provider') - .then(valueToPopulate => { + const getValueFromSelfStub = sinon.stub(serverless.variables, 'getValueFromSelf') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('self:provider').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSelfStub).to.have.been.called; - expect(getValueFromSelfStub.calledWith('self:provider')).to.equal(true); + expect(getValueFromSelfStub).to.have.been.calledWith('self:provider'); }) - .finally(() => serverless.variables.getValueFromSelf.restore()); + .finally(() => getValueFromSelfStub.restore()); }); it('should call getValueFromFile if referencing from another file', () => { - const serverless = new Serverless(); - const getValueFromFileStub = sinon - .stub(serverless.variables, 'getValueFromFile').resolves('variableValue'); - - return serverless.variables.getValueFromSource('file(./config.yml)') - .then(valueToPopulate => { + const getValueFromFileStub = sinon.stub(serverless.variables, 'getValueFromFile') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('file(./config.yml)').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromFileStub).to.have.been.called; expect(getValueFromFileStub).to.have.been.calledWith('file(./config.yml)'); }) - .finally(() => serverless.variables.getValueFromFile.restore()); + .finally(() => getValueFromFileStub.restore()); }); it('should call getValueFromCf if referencing CloudFormation Outputs', () => { - const serverless = new Serverless(); - const getValueFromCfStub = sinon - .stub(serverless.variables, 'getValueFromCf').resolves('variableValue'); - return serverless.variables.getValueFromSource('cf:test-stack.testOutput') - .then(valueToPopulate => { + const getValueFromCfStub = sinon.stub(serverless.variables, 'getValueFromCf') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('cf:test-stack.testOutput').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromCfStub).to.have.been.called; expect(getValueFromCfStub).to.have.been.calledWith('cf:test-stack.testOutput'); }) - .finally(() => serverless.variables.getValueFromCf.restore()); + .finally(() => getValueFromCfStub.restore()); }); it('should call getValueFromS3 if referencing variable in S3', () => { - const serverless = new Serverless(); - const getValueFromS3Stub = sinon - .stub(serverless.variables, 'getValueFromS3').resolves('variableValue'); + const getValueFromS3Stub = sinon.stub(serverless.variables, 'getValueFromS3') + .resolves('variableValue'); return serverless.variables.getValueFromSource('s3:test-bucket/path/to/key') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromS3Stub).to.have.been.called; - expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); - }) - .finally(() => serverless.variables.getValueFromS3.restore()); + .should.be.fulfilled + .then((valueToPopulate) => { + expect(valueToPopulate).to.equal('variableValue'); + expect(getValueFromS3Stub).to.have.been.called; + expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); + }) + .finally(() => getValueFromS3Stub.restore()); }); it('should call getValueFromSsm if referencing variable in SSM', () => { - const serverless = new Serverless(); - const getValueFromSsmStub = sinon - .stub(serverless.variables, 'getValueFromSsm').resolves('variableValue'); + const getValueFromSsmStub = sinon.stub(serverless.variables, 'getValueFromSsm') + .resolves('variableValue'); return serverless.variables.getValueFromSource('ssm:/test/path/to/param') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromSsmStub).to.have.been.called; - expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); - }) - .finally(() => serverless.variables.getValueFromSsm.restore()); + .should.be.fulfilled + .then((valueToPopulate) => { + expect(valueToPopulate).to.equal('variableValue'); + expect(getValueFromSsmStub).to.have.been.called; + expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); + }) + .finally(() => getValueFromSsmStub.restore()); }); - + it('should reject invalid sources', () => + serverless.variables.getValueFromSource('weird:source') + .should.be.rejectedWith(serverless.classes.Error)); describe('caching', () => { const sources = [ { function: 'getValueFromEnv', variableString: 'env:NODE_ENV' }, @@ -853,182 +749,77 @@ module.exports = { ]; sources.forEach((source) => { it(`should only call ${source.function} once, returning the cached value otherwise`, () => { - const serverless = new Serverless(); - const getValueFunctionStub = sinon - .stub(serverless.variables, source.function).resolves('variableValue'); - const firstCall = serverless.variables.getValueFromSource(source.variableString); - const secondCall = BbPromise.delay(100) - .then(() => serverless.variables.getValueFromSource(source.variableString)); - return BbPromise.all([firstCall, secondCall]) - .then(valueToPopulate => { - expect(valueToPopulate).to.deep.equal(['variableValue', 'variableValue']); + const value = 'variableValue'; + const getValueFunctionStub = sinon.stub(serverless.variables, source.function) + .resolves(value); + return BbPromise.all([ + serverless.variables.getValueFromSource(source.variableString).should.become(value), + BbPromise.delay(100).then(() => + serverless.variables.getValueFromSource(source.variableString).should.become(value)), + ]).then(() => { expect(getValueFunctionStub).to.have.been.calledOnce; expect(getValueFunctionStub).to.have.been.calledWith(source.variableString); - }) - .finally(() => serverless.variables[source.function].restore()); + }).finally(() => + getValueFunctionStub.restore()); }); }); }); - - it('should call populateObject if variable value is an object', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - const property = 'self:stage'; - const variableValue = { - stage: '${opt:stage}', - }; - const variableValuePopulated = { - stage: 'prod', - }; - - serverless.variables.loadVariableSyntax(); - - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObject') - .resolves(variableValuePopulated); - const getValueFromSelfStub = sinon - .stub(serverless.variables, 'getValueFromSelf') - .resolves(variableValue); - - return serverless.variables.getValueFromSource(property) - .then(newProperty => { - expect(populateObjectStub.called).to.equal(true); - expect(getValueFromSelfStub.called).to.equal(true); - expect(newProperty).to.deep.equal(variableValuePopulated); - - return BbPromise.resolve(); - }) - .finally(() => { - serverless.variables.populateObject.restore(); - serverless.variables.getValueFromSelf.restore(); - }); - }); - - it('should NOT call populateObject if variable value is already cached', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - const property = 'opt:stage'; - const variableValue = { - stage: '${opt:stage}', - }; - const variableValuePopulated = { - stage: 'prod', - }; - - serverless.variables.cache['opt:stage'] = BbPromise.resolve(variableValuePopulated); - - serverless.variables.loadVariableSyntax(); - - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObject') - .resolves(variableValuePopulated); - const getValueFromOptionsStub = sinon - .stub(serverless.variables, 'getValueFromOptions') - .resolves(variableValue); - - return serverless.variables.getValueFromSource(property) - .then(newProperty => { - expect(populateObjectStub.called).to.equal(false); - expect(getValueFromOptionsStub.called).to.equal(false); - expect(newProperty).to.deep.equal(variableValuePopulated); - - return BbPromise.resolve(); - }) - .finally(() => { - serverless.variables.populateObject.restore(); - serverless.variables.getValueFromOptions.restore(); - }); - }); - - it('should throw error if referencing an invalid source', () => { - const serverless = new Serverless(); - expect(() => serverless.variables.getValueFromSource('weird:source')) - .to.throw(Error); - }); }); describe('#getValueFromEnv()', () => { it('should get variable from environment variables', () => { - const serverless = new Serverless(); process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:TEST_VAR').then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('someValue'); - }) - .finally(() => { - delete process.env.TEST_VAR; - }); + return serverless.variables.getValueFromEnv('env:TEST_VAR') + .finally(() => { delete process.env.TEST_VAR; }) + .should.become('someValue'); }); it('should allow top-level references to the environment variables hive', () => { - const serverless = new Serverless(); process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:').then(valueToPopulate => { + return serverless.variables.getValueFromEnv('env:').then((valueToPopulate) => { expect(valueToPopulate.TEST_VAR).to.be.equal('someValue'); }) - .finally(() => { - delete process.env.TEST_VAR; - }); + .finally(() => { delete process.env.TEST_VAR; }); }); }); describe('#getValueFromOptions()', () => { it('should get variable from options', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - return serverless.variables.getValueFromOptions('opt:stage').then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('prod'); - }); + serverless.variables.options = { stage: 'prod' }; + return serverless.variables.getValueFromOptions('opt:stage').should.become('prod'); }); it('should allow top-level references to the options hive', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - return serverless.variables.getValueFromOptions('opt:').then(valueToPopulate => { - expect(valueToPopulate.stage).to.be.equal('prod'); - }); + serverless.variables.options = { stage: 'prod' }; + return serverless.variables.getValueFromOptions('opt:') + .should.become(serverless.variables.options); }); }); describe('#getValueFromSelf()', () => { it('should get variable from self serverless.yml file', () => { - const serverless = new Serverless(); serverless.variables.service = { service: 'testService', provider: serverless.service.provider, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromSelf('self:service').then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('testService'); - }); + return serverless.variables.getValueFromSelf('self:service').should.become('testService'); }); it('should handle self-references to the root of the serverless.yml file', () => { - const serverless = new Serverless(); serverless.variables.service = { service: 'testService', provider: 'testProvider', defaults: serverless.service.defaults, }; - serverless.variables.loadVariableSyntax(); - - return serverless.variables.getValueFromSelf('self:').then(valueToPopulate => { - expect(valueToPopulate.provider).to.be.equal('testProvider'); - }); + return serverless.variables.getValueFromSelf('self:') + .should.eventually.equal(serverless.variables.service); }); }); describe('#getValueFromFile()', () => { it('should work for absolute paths with ~ ', () => { - const serverless = new Serverless(); const expectedFileName = `${os.homedir()}/somedir/config.yml`; const configYml = { test: 1, @@ -1038,17 +829,11 @@ module.exports = { prob: 'prob', }, }; - const fileExistsStub = sinon - .stub(serverless.utils, 'fileExistsSync').returns(true); - - const realpathSync = sinon - .stub(fse, 'realpathSync').returns(expectedFileName); - - const readFileSyncStub = sinon - .stub(serverless.utils, 'readFileSync').returns(configYml); - - return serverless.variables.getValueFromFile('file(~/somedir/config.yml)') - .then(valueToPopulate => { + const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); + const realpathSync = sinon.stub(fse, 'realpathSync').returns(expectedFileName); + const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns(configYml); + return serverless.variables.getValueFromFile('file(~/somedir/config.yml)').should.be.fulfilled + .then((valueToPopulate) => { expect(realpathSync).to.not.have.been.called; expect(fileExistsStub).to.have.been.calledWithMatch(expectedFileName); expect(readFileSyncStub).to.have.been.calledWithMatch(expectedFileName); @@ -1062,7 +847,6 @@ module.exports = { }); it('should populate an entire variable file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const configYml = { @@ -1073,28 +857,19 @@ module.exports = { prob: 'prob', }, }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), - YAML.dump(configYml)); - + SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./config.yml)').then(valueToPopulate => { - expect(valueToPopulate).to.deep.equal(configYml); - }); + return serverless.variables.getValueFromFile('file(./config.yml)') + .should.eventually.eql(configYml); }); it('should get undefined if non existing file and the second argument is true', () => { - const serverless = new Serverless(); const tmpDirPath = testUtils.getTmpDirPath(); - serverless.config.update({ servicePath: tmpDirPath }); - const realpathSync = sinon.spy(fse, 'realpathSync'); const existsSync = sinon.spy(fse, 'existsSync'); - - return serverless.variables.getValueFromFile('file(./non-existing.yml)') - .then(valueToPopulate => { + return serverless.variables.getValueFromFile('file(./non-existing.yml)').should.be.fulfilled + .then((valueToPopulate) => { expect(realpathSync).to.not.have.been.called; expect(existsSync).to.have.been.calledOnce; expect(valueToPopulate).to.be.undefined; @@ -1106,166 +881,113 @@ module.exports = { }); it('should populate non json/yml files', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); - - SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), - 'hello world'); - + SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world'); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./someFile)').then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + return serverless.variables.getValueFromFile('file(./someFile)') + .should.become('hello world'); }); it('should populate symlinks', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const realFilePath = path.join(tmpDirPath, 'someFile'); const symlinkPath = path.join(tmpDirPath, 'refSomeFile'); SUtils.writeFileSync(realFilePath, 'hello world'); fse.ensureSymlinkSync(realFilePath, symlinkPath); - serverless.config.update({ servicePath: tmpDirPath }); - - return expect(serverless.variables.getValueFromFile('file(./refSomeFile)')).to.be.fulfilled - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }) - .finally(() => { - fse.removeSync(realFilePath); - fse.removeSync(symlinkPath); - }); + return serverless.variables.getValueFromFile('file(./refSomeFile)') + .should.become('hello world') + .then().finally(() => { + fse.removeSync(realFilePath); + fse.removeSync(symlinkPath); + }); }); it('should trim trailing whitespace and new line character', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); - - SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), - 'hello world \n'); - + SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world \n'); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./someFile)').then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + return serverless.variables.getValueFromFile('file(./someFile)') + .should.become('hello world'); }); it('should populate from another file when variable is of any type', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const configYml = { - test: 1, - test2: 'test2', - testObj: { + test0: 0, + test1: 'test1', + test2: { sub: 2, prob: 'prob', }, }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), - YAML.dump(configYml)); - + SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./config.yml):testObj.sub') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal(2); - }); + return serverless.variables.getValueFromFile('file(./config.yml):test2.sub') + .should.become(configYml.test2.sub); }); it('should populate from a javascript file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = 'module.exports.hello=function(){return "hello world";};'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js):hello') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + .should.become('hello world'); }); it('should populate an entire variable exported by a javascript file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = 'module.exports=function(){return { hello: "hello world" };};'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js)') - .then(valueToPopulate => { - expect(valueToPopulate.hello).to.equal('hello world'); - }); + .should.become({ hello: 'hello world' }); }); it('should throw if property exported by a javascript file is not a function', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = 'module.exports={ hello: "hello world" };'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); - - expect(() => serverless.variables - .getValueFromFile('file(./hello.js)')).to.throw(Error); + return serverless.variables.getValueFromFile('file(./hello.js)') + .should.be.rejectedWith(serverless.classes.Error); }); it('should populate deep object from a javascript file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = `module.exports.hello=function(){ return {one:{two:{three: 'hello world'}}} };`; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + .should.become('hello world'); }); it('should preserve the exported function context when executing', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = ` module.exports.one = {two: {three: 'hello world'}} module.exports.hello=function(){ return this; };`; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + .should.become('hello world'); }); - it('should throw error if not using ":" syntax', () => { - const serverless = new Serverless(); + it('should file variable not using ":" syntax', () => { const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const configYml = { @@ -1276,20 +998,15 @@ module.exports = { prob: 'prob', }, }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), - YAML.dump(configYml)); - + SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - - expect(() => serverless.variables - .getValueFromFile('file(./config.yml).testObj.sub')).to.throw(Error); + return serverless.variables.getValueFromFile('file(./config.yml).testObj.sub') + .should.be.rejectedWith(serverless.classes.Error); }); }); describe('#getValueFromCf()', () => { it('should get variable from CloudFormation', () => { - const serverless = new Serverless(); const options = { stage: 'prod', region: 'us-west-2', @@ -1305,27 +1022,22 @@ module.exports = { }], }], }; - - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request') - .resolves(awsResponseMock); + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf:some-stack.MockExport') - .then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('MockValue'); + .should.become('MockValue') + .then(() => { expect(cfStub).to.have.been.calledOnce; expect(cfStub).to.have.been.calledWithExactly( 'CloudFormation', 'describeStacks', - { - StackName: 'some-stack', - }, - { useCache: true } - ); + { StackName: 'some-stack' }, + { useCache: true }); }) - .finally(() => serverless.getProvider('aws').request.restore()); + .finally(() => cfStub.restore()); }); - it('should throw an error when variable from CloudFormation does not exist', () => { - const serverless = new Serverless(); + it('should reject CloudFormation variables that do not exist', () => { const options = { stage: 'prod', region: 'us-west-2', @@ -1341,35 +1053,26 @@ module.exports = { }], }], }; - - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request') - .resolves(awsResponseMock); - + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf:some-stack.DoestNotExist') - .then() - .catch(error => { + .should.be.rejectedWith(serverless.classes.Error, + /to request a non exported variable from CloudFormation/) + .then(() => { expect(cfStub).to.have.been.calledOnce; expect(cfStub).to.have.been.calledWithExactly( 'CloudFormation', 'describeStacks', - { - StackName: 'some-stack', - }, - { useCache: true } - ); - expect(error).to.be.an.instanceof(Error); - expect(error.message).to.match(/to request a non exported variable from CloudFormation/); + { StackName: 'some-stack' }, + { useCache: true }); }) - .finally(() => serverless.getProvider('aws').request.restore()); + .finally(() => cfStub.restore()); }); }); describe('#getValueFromS3()', () => { - let serverless; let awsProvider; - beforeEach(() => { - serverless = new Serverless(); const options = { stage: 'prod', region: 'us-west-2', @@ -1378,45 +1081,48 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; }); - it('should get variable from S3', () => { const awsResponseMock = { Body: 'MockValue', }; - const s3Stub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromS3('s3:some.bucket/path/to/key').then(value => { - expect(value).to.be.equal('MockValue'); - expect(s3Stub).to.have.been.calledOnce; - expect(s3Stub).to.have.been.calledWithExactly( - 'S3', - 'getObject', - { - Bucket: 'some.bucket', - Key: 'path/to/key', - }, - { useCache: true } - ); - }) - .finally(() => serverless.getProvider('aws').request.restore()); + const s3Stub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromS3('s3:some.bucket/path/to/key') + .should.become('MockValue') + .then(() => { + expect(s3Stub).to.have.been.calledOnce; + expect(s3Stub).to.have.been.calledWithExactly( + 'S3', + 'getObject', + { + Bucket: 'some.bucket', + Key: 'path/to/key', + }, + { useCache: true }); + }) + .finally(() => s3Stub.restore()); }); it('should throw error if error getting value from S3', () => { const error = new Error('The specified bucket is not valid'); - sinon.stub(awsProvider, 'request').rejects(error); - + const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); return expect(serverless.variables.getValueFromS3('s3:some.bucket/path/to/key')) - .to.be.rejectedWith('Error getting value for s3:some.bucket/path/to/key. ' + - 'The specified bucket is not valid'); + .to.be.rejectedWith( + serverless.classes.Error, + 'Error getting value for s3:some.bucket/path/to/key. The specified bucket is not valid') + .then().finally(() => requestStub.restore()); }); }); describe('#getValueFromSsm()', () => { - let serverless; + const param = 'Param-01_valid.chars'; + const value = 'MockValue'; + const awsResponseMock = { + Parameter: { + Value: value, + }, + }; let awsProvider; - beforeEach(() => { - serverless = new Serverless(); const options = { stage: 'prod', region: 'us-west-2', @@ -1425,154 +1131,112 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; }); - it('should get variable from Ssm using regular-style param', () => { - const param = 'Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should get variable from Ssm using path-style param', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should get encrypted variable from Ssm using extended syntax', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}~true`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: true, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}~true`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: true, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should get unencrypted variable from Ssm using extended syntax', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}~false`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}~false`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should ignore bad values for extended syntax', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); it('should return undefined if SSM parameter does not exist', () => { - const param = 'ssm:/some/path/to/invalidparam'; const error = new Error(`Parameter ${param} not found.`); - sinon.stub(awsProvider, 'request').rejects(error); - - return expect(() => serverless.variables.getValueFromSsm(param).to.be(undefined)); + const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.become(undefined) + .then().finally(() => requestStub.restore()); }); - it('should throw exception if SSM request returns unexpected error', () => { - const param = 'ssm:/some/path/to/invalidparam'; + it('should reject if SSM request returns unexpected error', () => { const error = new Error( 'User: is not authorized to perform: ssm:GetParameter on resource: '); - sinon.stub(awsProvider, 'request').rejects(error); - - return expect(() => serverless.variables.getValueFromSsm(param).to.throw(error)); + const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.be.rejected + .then().finally(() => requestStub.restore()); }); }); describe('#getDeepValue()', () => { it('should get deep values', () => { - const serverless = new Serverless(); - const valueToPopulateMock = { service: 'testService', custom: { @@ -1581,41 +1245,29 @@ module.exports = { }, }, }; - serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep'], - valueToPopulateMock).then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('deepValue'); - }); + valueToPopulateMock).should.become('deepValue'); }); - it('should not throw error if referencing invalid properties', () => { - const serverless = new Serverless(); - const valueToPopulateMock = { service: 'testService', custom: { subProperty: 'hello', }, }; - serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep', 'deeper'], - valueToPopulateMock).then(valueToPopulate => { - expect(valueToPopulate).to.deep.equal({}); - }); + valueToPopulateMock).should.eventually.deep.equal({}); }); it('should get deep values with variable references', () => { - const serverless = new Serverless(); - serverless.variables.service = { service: 'testService', custom: { - anotherVar: '${self:custom.var}', + anotherVar: '${self:custom.var}', // eslint-disable-line no-template-curly-in-string subProperty: { + // eslint-disable-next-line no-template-curly-in-string deep: '${self:custom.anotherVar.veryDeep}', }, var: { @@ -1624,73 +1276,56 @@ module.exports = { }, provider: serverless.service.provider, }; - serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep'], - serverless.variables.service).then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('someValue'); - }); + serverless.variables.service).should.become('someValue'); }); }); - describe('#warnIfNotFound()', () => { let logWarningSpy; let consoleLogStub; let varProxy; - beforeEach(() => { logWarningSpy = sinon.spy(slsError, 'logWarning'); consoleLogStub = sinon.stub(console, 'log').returns(); - const ProxyQuiredVariables = proxyquire('./Variables.js', { - './Error': logWarningSpy, - }); - varProxy = new ProxyQuiredVariables(new Serverless()); + const ProxyQuiredVariables = proxyquire('./Variables.js', { './Error': logWarningSpy }); + varProxy = new ProxyQuiredVariables(serverless); }); - afterEach(() => { logWarningSpy.restore(); consoleLogStub.restore(); }); - it('should do nothing if variable has valid value.', () => { varProxy.warnIfNotFound('self:service', 'a-valid-value'); expect(logWarningSpy).to.not.have.been.calledOnce; }); - it('should log if variable has null value.', () => { varProxy.warnIfNotFound('self:service', null); expect(logWarningSpy).to.have.been.calledOnce; }); - it('should log if variable has undefined value.', () => { varProxy.warnIfNotFound('self:service', undefined); expect(logWarningSpy).to.have.been.calledOnce; }); - it('should log if variable has empty object value.', () => { varProxy.warnIfNotFound('self:service', {}); expect(logWarningSpy).to.have.been.calledOnce; }); - it('should detect the "environment variable" variable type', () => { varProxy.warnIfNotFound('env:service', null); expect(logWarningSpy).to.have.been.calledOnce; expect(logWarningSpy.args[0][0]).to.contain('environment variable'); }); - it('should detect the "option" variable type', () => { varProxy.warnIfNotFound('opt:service', null); expect(logWarningSpy).to.have.been.calledOnce; expect(logWarningSpy.args[0][0]).to.contain('option'); }); - it('should detect the "service attribute" variable type', () => { varProxy.warnIfNotFound('self:service', null); expect(logWarningSpy).to.have.been.calledOnce; expect(logWarningSpy.args[0][0]).to.contain('service attribute'); }); - it('should detect the "file" variable type', () => { varProxy.warnIfNotFound('file(service)', null); expect(logWarningSpy).to.have.been.calledOnce; From 8f69678a88a0b735e2a52ab19650aae203037c65 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Sat, 3 Feb 2018 13:14:11 -0800 Subject: [PATCH 059/125] remove `.only` from describe for Variables tests add expected rejection message --- lib/classes/Variables.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 04c2f814b0b..0beaf01ee76 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -29,7 +29,7 @@ chai.use(require('sinon-chai')); const expect = chai.expect; -describe.only('Variables', () => { +describe('Variables', () => { let serverless; beforeEach(() => { serverless = new Serverless(); @@ -458,7 +458,8 @@ module.exports = { val: `\${file(${emptyFileName}):func.notAValue}`, }; return serverless.variables.populateObject(service.custom) - .should.be.rejectedWith(serverless.classes.Error); + .should.be.rejectedWith(serverless.classes.Error, + 'Invalid variable syntax when referencing file'); }); }); }); From a0e70268f0e8af5ebad0ad67d89da35660f26ae6 Mon Sep 17 00:00:00 2001 From: Takuro Niitsuma Date: Mon, 5 Feb 2018 10:35:12 +0900 Subject: [PATCH 060/125] Fix typo in Plugins --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 044fb4b6c81..a5382ea2640 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p | **[Raml Serverless](https://github.com/andrewcurioso/raml-serverless)**
    Serverless plugin to work with RAML API spec documents | [andrewcurioso](http://github.com/andrewcurioso) | | **[Serverless Alexa Plugin](https://github.com/rajington/serverless-alexa-plugin)**
    Serverless plugin to support Alexa Lambda events | [rajington](http://github.com/rajington) | | **[Serverless Api Stage](https://github.com/leftclickben/serverless-api-stage)**
    Serverless API Stage plugin, enables stage variables and logging for AWS API Gateway. | [leftclickben](http://github.com/leftclickben) | -| **[Serverless Apig S3](https://github.com/sdd/serverless-apig-s3)**
    Serve static front-end content from S3 via the API Gatewy and deploy client bundle to S3. | [sdd](http://github.com/sdd) | +| **[Serverless Apig S3](https://github.com/sdd/serverless-apig-s3)**
    Serve static front-end content from S3 via the API Gateway and deploy client bundle to S3. | [sdd](http://github.com/sdd) | | **[Serverless Apigateway Plugin](https://github.com/GFG/serverless-apigateway-plugin)**
    Configure the AWS api gateway: Binary support, Headers and Body template mappings | [GFG](http://github.com/GFG) | | **[Serverless Apigw Binary](https://github.com/maciejtreder/serverless-apigw-binary)**
    Plugin to enable binary support in AWS API Gateway. | [maciejtreder](http://github.com/maciejtreder) | | **[Serverless Apigwy Binary](https://github.com/ryanmurakami/serverless-apigwy-binary)**
    Serverless plugin for configuring API Gateway to return binary responses | [ryanmurakami](http://github.com/ryanmurakami) | From 50c229941474ed7346f977fafede113a8a4bd218 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Mon, 5 Feb 2018 10:53:42 -0800 Subject: [PATCH 061/125] Update serverless.yml --- .../create/templates/spotinst-java8/serverless.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index f09708db82d..c5f5212343e 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -26,12 +26,9 @@ functions: memory: 128 timeout: 30 access: private -# activeVersions: [ -# { -# "version": "$LATEST", -# "percentage": 100.0 -# } -# ] +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From fe2b9ad3ab6f1230284beac22b2b94423a510656 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Mon, 5 Feb 2018 10:54:35 -0800 Subject: [PATCH 062/125] Update serverless.yml --- .../create/templates/spotinst-nodejs/serverless.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index 9acac7e50ae..68de54513e8 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -26,12 +26,9 @@ functions: memory: 128 timeout: 30 access: private -# activeVersions: [ -# { -# "version": "$LATEST", -# "percentage": 100.0 -# } -# ] +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From fc6b83a1ee9751e5d92d5767a274b741d7b841f2 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Mon, 5 Feb 2018 10:55:01 -0800 Subject: [PATCH 063/125] Update serverless.yml --- .../create/templates/spotinst-python/serverless.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index 69e20db4239..3acb202ac3f 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -26,12 +26,9 @@ functions: memory: 128 timeout: 30 access: private -# activeVersions: [ -# { -# "version": "$LATEST", -# "percentage": 100.0 -# } -# ] +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From 53e7d87eac61018dc7e7a34c92d930c37a6ffa0b Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Mon, 5 Feb 2018 10:55:19 -0800 Subject: [PATCH 064/125] Update serverless.yml --- .../create/templates/spotinst-ruby/serverless.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 684e06bb4b5..03f6b1bf826 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -26,12 +26,9 @@ functions: memory: 128 timeout: 30 access: private -# activeVersions: [ -# { -# "version": "$LATEST", -# "percentage": 100.0 -# } -# ] +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From 581175248320d5fb13bf5a7b1c8886d53daf4709 Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Mon, 5 Feb 2018 10:57:07 -0800 Subject: [PATCH 065/125] corrected format --- docs/providers/spotinst/guide/serverless.yml.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/providers/spotinst/guide/serverless.yml.md b/docs/providers/spotinst/guide/serverless.yml.md index 1e0f43e0cda..f1d87daa668 100644 --- a/docs/providers/spotinst/guide/serverless.yml.md +++ b/docs/providers/spotinst/guide/serverless.yml.md @@ -54,12 +54,9 @@ functions: memory: 128 timeout: 30 access: private -# activeVersions: [ -# { -# "version": "$LATEST", -# "percentage": 100.0 -# } -# ] +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 # cron: # active: false # value: '*/1 * * * *' From 77582bd341a637faabedea6f337fd8a9c4f28478 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 5 Feb 2018 17:53:39 -0800 Subject: [PATCH 066/125] updated templates, added CORS support and docs --- docs/providers/spotinst/guide/cors.md | 42 +++++++++++++++++++ .../templates/spotinst-java8/serverless.yml | 7 +++- .../templates/spotinst-nodejs/serverless.yml | 5 +++ .../templates/spotinst-python/serverless.yml | 7 +++- .../templates/spotinst-ruby/serverless.yml | 5 +++ 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 docs/providers/spotinst/guide/cors.md diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md new file mode 100644 index 00000000000..08cb8299655 --- /dev/null +++ b/docs/providers/spotinst/guide/cors.md @@ -0,0 +1,42 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/cors) + + +# Spotinst Functions - CORS + +You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. + +Example CORS object: +```yaml + cors: + - enabled: true + origin: "http://foo.example" + headers: "Content-Type,X-PINGOTHER" + methods: "PUT,POST" +``` + +Parameters: + - enabled: Boolean + - Specify if CORS is enabled for the function. + - default: false + - origin: String + - Specifies a domain/origin that may access the resource. A wildcard '*' may be used to allow any origin to access the resource. + - default: '*' + - methods: String + - Comma-separated list of HTTP methods that are allowed to access the resource. This is used in response to a preflight request. + - default: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' + - headers: String + - Comma-separated list of allowed headers. + - default: 'Content-Type,Authorization' + + + + diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index c5f5212343e..066b918daa7 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -27,8 +27,13 @@ functions: timeout: 30 access: private # activeVersions: -# - "version": "$LATEST" +# - "version": "$LATEST" # "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index 68de54513e8..1adeac7ef7b 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -29,6 +29,11 @@ functions: # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index 3acb202ac3f..1722af08fd5 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -28,7 +28,12 @@ functions: access: private # activeVersions: # - "version": "$LATEST" -# "percentage": 100.0 +# "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 03f6b1bf826..4c1807e228d 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -29,6 +29,11 @@ functions: # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' From 42b21320728d16e55ab1e948e83e87259f90352b Mon Sep 17 00:00:00 2001 From: Alex Gaesser <35503263+alexindeed@users.noreply.github.com> Date: Mon, 5 Feb 2018 19:20:58 -0800 Subject: [PATCH 067/125] Update cors.md --- docs/providers/spotinst/guide/cors.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md index 08cb8299655..a0e6cf0f5a8 100644 --- a/docs/providers/spotinst/guide/cors.md +++ b/docs/providers/spotinst/guide/cors.md @@ -11,6 +11,7 @@ layout: Doc # Spotinst Functions - CORS +Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify domains that are safe, and enables out-of-the-box support for preflight HTTP request (via the OPTIONS method) will return the needed ‘access-control-’ headers specified below, and the actual HTTP request will return the ‘access-control-allow-origin’ method. You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. From 3606589ff2ce460038104f350add384ab88e0859 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Tue, 6 Feb 2018 16:35:16 -0800 Subject: [PATCH 068/125] Do not double-reject errors Calling .then here creates a new promise. One that is not returned and generating a rejection within that promise can lead to unhandledRejection events being inappropriately raised. As such, don't declare unneeded variables. --- lib/classes/Variables.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index a6842c4b3dc..ffbe65f3ac3 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -36,9 +36,9 @@ class PromiseTracker { const promise = prms; promise.waitList = `${variable} waited on by: ${specifier}`; promise.state = 'pending'; - promise.then( - (value) => { promise.state = 'resolved'; return Promise.resolve(value); }, - (error) => { promise.state = 'rejected'; return Promise.reject(error); }); + promise.then( // creates a promise with the following effects but that we otherwise ignore + () => { promise.state = 'resolved'; }, + () => { promise.state = 'rejected'; }); this.promiseList.push(promise); this.promiseMap[variable] = promise; return promise; @@ -393,7 +393,7 @@ class Variables { ].join(''); ret = BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - this.tracker.add(variableString, ret, propertyString); + ret = this.tracker.add(variableString, ret, propertyString); } return ret; } From e894983ad0eb690475a7399c1730a3569b1e0f77 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 6 Feb 2018 17:13:08 -0800 Subject: [PATCH 069/125] squashed commit --- CHANGELOG.md | 13 + README.md | 4 +- docker-compose.yml | 10 + docs/providers/aws/events/alexa-skill.md | 31 ++- docs/providers/aws/events/apigateway.md | 2 +- docs/providers/aws/events/cloudwatch-log.md | 2 +- .../aws/examples/hello-world/README.md | 4 +- .../aws/examples/hello-world/csharp/README.md | 42 ++- .../aws/examples/hello-world/fsharp/README.md | 72 +++-- .../hello-world/fsharp/serverless.yml | 2 +- .../aws/examples/hello-world/go/README.md | 116 ++++++++ .../aws/examples/hello-world/node/README.md | 39 ++- .../aws/examples/hello-world/python/README.md | 39 ++- docs/providers/aws/guide/functions.md | 1 + docs/providers/aws/guide/serverless.yml.md | 4 +- docs/providers/aws/guide/services.md | 1 + .../azure/examples/hello-world/node/README.md | 2 +- .../google/cli-reference/invoke-local.md | 56 ++++ .../examples/hello-world/node/README.md | 4 +- docs/providers/kubeless/README.md | 1 + docs/providers/kubeless/guide/functions.md | 146 +++++++++- docs/providers/kubeless/guide/packaging.md | 144 ++++++++++ .../examples/hello-world/node/README.md | 2 +- .../examples/hello-world/php/README.md | 2 +- .../examples/hello-world/python/README.md | 2 +- .../examples/hello-world/swift/README.md | 2 +- .../spotinst/examples/java8/README.md | 2 +- .../spotinst/examples/node/README.md | 2 +- .../spotinst/examples/python/README.md | 2 +- .../spotinst/examples/ruby/README.md | 12 +- docs/providers/spotinst/guide/cors.md | 43 +++ lib/classes/Error.js | 19 +- .../aws/deploy/lib/createStack.test.js | 4 +- .../aws/deploy/lib/extendedValidate.test.js | 10 +- .../aws/deploy/lib/uploadArtifacts.test.js | 27 +- lib/plugins/aws/invoke/index.js | 4 +- lib/plugins/aws/invoke/index.test.js | 3 +- lib/plugins/aws/lib/naming.js | 5 +- lib/plugins/aws/lib/naming.test.js | 8 +- .../compile/events/alexaSkill/index.js | 53 +++- .../compile/events/alexaSkill/index.test.js | 165 +++++++++++- .../compile/events/cognitoUserPool/index.js | 110 +++++--- .../events/cognitoUserPool/index.test.js | 170 +++++++++++- .../aws/package/compile/functions/index.js | 13 + .../package/compile/functions/index.test.js | 60 +++++ lib/plugins/aws/provider/awsProvider.js | 2 - lib/plugins/aws/utils/formatLambdaLogEvent.js | 2 +- .../aws/utils/formatLambdaLogEvent.test.js | 2 +- lib/plugins/create/create.js | 2 + lib/plugins/create/create.test.js | 34 +++ .../templates/aws-csharp/serverless.yml | 2 +- .../templates/aws-fsharp/serverless.yml | 2 +- .../create/templates/aws-go-dep/Gopkg.lock | 19 ++ .../create/templates/aws-go-dep/Gopkg.toml | 25 ++ .../create/templates/aws-go-dep/Makefile | 4 + .../create/templates/aws-go-dep/gitignore | 8 + .../create/templates/aws-go-dep/hello/main.go | 19 ++ .../templates/aws-go-dep/serverless.yml | 104 ++++++++ .../create/templates/aws-go-dep/world/main.go | 19 ++ lib/plugins/create/templates/aws-go/Makefile | 4 + lib/plugins/create/templates/aws-go/gitignore | 5 + .../create/templates/aws-go/hello/main.go | 19 ++ .../create/templates/aws-go/serverless.yml | 104 ++++++++ .../create/templates/aws-go/world/main.go | 19 ++ .../aws-groovy-gradle/serverless.yml | 2 +- .../templates/aws-java-gradle/serverless.yml | 2 +- .../templates/aws-java-maven/serverless.yml | 2 +- .../aws-kotlin-jvm-gradle/serverless.yml | 2 +- .../aws-kotlin-jvm-maven/serverless.yml | 2 +- .../aws-kotlin-nodejs-gradle/serverless.yml | 2 +- .../aws-nodejs-typescript/webpack.config.js | 1 + .../templates/aws-nodejs/serverless.yml | 2 +- .../templates/aws-python/serverless.yml | 2 +- .../templates/aws-python3/serverless.yml | 2 +- .../templates/aws-scala-sbt/serverless.yml | 2 +- .../templates/kubeless-nodejs/package.json | 2 +- .../templates/kubeless-python/package.json | 2 +- .../templates/spotinst-java8/serverless.yml | 7 +- .../templates/spotinst-nodejs/serverless.yml | 5 + .../templates/spotinst-python/serverless.yml | 7 +- .../templates/spotinst-ruby/serverless.yml | 5 + package-lock.json | 250 +++++++++--------- package.json | 2 +- tests/templates/integration-test-template | 5 + tests/templates/test_all_templates | 2 + 85 files changed, 1868 insertions(+), 290 deletions(-) create mode 100644 docs/providers/aws/examples/hello-world/go/README.md create mode 100644 docs/providers/google/cli-reference/invoke-local.md create mode 100644 docs/providers/kubeless/guide/packaging.md create mode 100644 docs/providers/spotinst/guide/cors.md create mode 100644 lib/plugins/create/templates/aws-go-dep/Gopkg.lock create mode 100644 lib/plugins/create/templates/aws-go-dep/Gopkg.toml create mode 100644 lib/plugins/create/templates/aws-go-dep/Makefile create mode 100644 lib/plugins/create/templates/aws-go-dep/gitignore create mode 100644 lib/plugins/create/templates/aws-go-dep/hello/main.go create mode 100644 lib/plugins/create/templates/aws-go-dep/serverless.yml create mode 100644 lib/plugins/create/templates/aws-go-dep/world/main.go create mode 100644 lib/plugins/create/templates/aws-go/Makefile create mode 100644 lib/plugins/create/templates/aws-go/gitignore create mode 100644 lib/plugins/create/templates/aws-go/hello/main.go create mode 100644 lib/plugins/create/templates/aws-go/serverless.yml create mode 100644 lib/plugins/create/templates/aws-go/world/main.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b64b53eb4e..98352edea00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 1.26.0 (29.01.2018) +- [AWS Go support](https://github.com/serverless/serverless/pull/4669) +- [Support for using an existing ApiGateway and Resources](https://github.com/serverless/serverless/pull/4247) +- [Add logRetentionInDays config](https://github.com/serverless/serverless/pull/4591) +- [Add support of `serverless.js` configuration file](https://github.com/serverless/serverless/pull/4590) +- [Add "did you mean..." CLI suggestions](https://github.com/serverless/serverless/pull/4586) +- [Add `--template-path` option to `serverless create`](https://github.com/serverless/serverless/pull/4576) +- [Add support POJO input support for Java invoke local](https://github.com/serverless/serverless/pull/4596) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.25.0...v1.26.0) + + # 1.25.0 (20.12.2017) - [Improve Stage and Region Usage](https://github.com/serverless/serverless/pull/4560) - [Add API Gateway endpoint configuration](https://github.com/serverless/serverless/pull/4531) diff --git a/README.md b/README.md index e6ff0f7fa4e..a5382ea2640 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ The following are services you can instantly install and use by running `serverl ## Features -* Supports Node.js, Python, Java, Scala, C#, F#, Groovy, Kotlin, PHP & Swift. +* Supports Node.js, Python, Java, Scala, C#, F#, Go, Groovy, Kotlin, PHP & Swift. * Manages the lifecycle of your serverless architecture (build, deploy, update, delete). * Safely deploy functions, events and their required resources together via provider resource managers (e.g., AWS CloudFormation). * Functions can be grouped ("serverless services") for easy management of code, resources & processes, across large projects & teams. @@ -158,7 +158,7 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p | **[Raml Serverless](https://github.com/andrewcurioso/raml-serverless)**
    Serverless plugin to work with RAML API spec documents | [andrewcurioso](http://github.com/andrewcurioso) | | **[Serverless Alexa Plugin](https://github.com/rajington/serverless-alexa-plugin)**
    Serverless plugin to support Alexa Lambda events | [rajington](http://github.com/rajington) | | **[Serverless Api Stage](https://github.com/leftclickben/serverless-api-stage)**
    Serverless API Stage plugin, enables stage variables and logging for AWS API Gateway. | [leftclickben](http://github.com/leftclickben) | -| **[Serverless Apig S3](https://github.com/sdd/serverless-apig-s3)**
    Serve static front-end content from S3 via the API Gatewy and deploy client bundle to S3. | [sdd](http://github.com/sdd) | +| **[Serverless Apig S3](https://github.com/sdd/serverless-apig-s3)**
    Serve static front-end content from S3 via the API Gateway and deploy client bundle to S3. | [sdd](http://github.com/sdd) | | **[Serverless Apigateway Plugin](https://github.com/GFG/serverless-apigateway-plugin)**
    Configure the AWS api gateway: Binary support, Headers and Body template mappings | [GFG](http://github.com/GFG) | | **[Serverless Apigw Binary](https://github.com/maciejtreder/serverless-apigw-binary)**
    Plugin to enable binary support in AWS API Gateway. | [maciejtreder](http://github.com/maciejtreder) | | **[Serverless Apigwy Binary](https://github.com/ryanmurakami/serverless-apigwy-binary)**
    Serverless plugin for configuring API Gateway to return binary responses | [ryanmurakami](http://github.com/ryanmurakami) | diff --git a/docker-compose.yml b/docker-compose.yml index f9ae71b3ed3..98c398c8f73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,6 +63,16 @@ services: image: microsoft/dotnet:1.0.4-sdk volumes: - ./tmp/serverless-integration-test-aws-fsharp:/app + aws-go: + image: golang:1.9 + volumes: + - ./tmp/serverless-integration-test-aws-go:/app + - ./tmp/serverless-integration-test-aws-go:/go/src/app + aws-go-dep: + image: yunspace/golang:1.9 + volumes: + - ./tmp/serverless-integration-test-aws-go-dep:/app + - ./tmp/serverless-integration-test-aws-go-dep:/go/src/app aws-nodejs-typescript: image: node:6.10.3 volumes: diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md index 915464bcf1b..3ade42de377 100644 --- a/docs/providers/aws/events/alexa-skill.md +++ b/docs/providers/aws/events/alexa-skill.md @@ -14,14 +14,41 @@ layout: Doc ## Event definition -This will enable your Lambda function to be called by an Alexa skill kit. +This will enable your Lambda function to be called by an Alexa Skill kit. +`amzn1.ask.skill.xx-xx-xx-xx-xx` is a skill ID for Alexa Skills kit. You receive a skill ID once you register and create a skill in [Amazon Developer Console](https://developer.amazon.com/). +After deploying, add your deployed Lambda function ARN to which this event is attached to the Service Endpoint under Configuration on Amazon Developer Console. ```yml functions: mySkill: handler: mySkill.handler events: - - alexaSkill + - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx-xx ``` You can find detailed guides on how to create an Alexa Skill with Serverless using NodeJS [here](https://github.com/serverless/examples/tree/master/aws-node-alexa-skill) as well as in combination with Python [here](https://github.com/serverless/examples/tree/master/aws-python-alexa-skill). + +## Enabling / Disabling + +**Note:** `alexaSkill` events are enabled by default. + +This will create and attach a alexaSkill event for the `mySkill` function which is disabled. If enabled it will call +the `mySkill` function by an Alexa Skill. + +```yaml +functions: + mySkill: + handler: mySkill.handler + events: + - alexaSkill: + appId: amzn1.ask.skill.xx-xx-xx-xx + enabled: false +``` + +## Backwards compatability + +Previous syntax of this event didn't require a skill ID as parameter, but according to [Amazon's documentation](https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html#configuring-the-alexa-skills-kit-trigger) you should restrict your lambda function to be executed only by your skill. + +Omitting the skill id will make your Lambda function available for the public, allowing any other skill developer to invoke it. + +(This is important, as [opposing to custom HTTPS endpoints](https://developer.amazon.com/docs/custom-skills/handle-requests-sent-by-alexa.html#request-verify), there's no way to validate the request was sent by your skill.) \ No newline at end of file diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 7da3daf1b42..2d04400b4ee 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -872,7 +872,7 @@ functions: ``` -In case the application has many chilren and grandchildren paths, you also want to break them out into smaller services. +In case the application has many children and grandchildren paths, you also want to break them out into smaller services. ```yml service: service-a diff --git a/docs/providers/aws/events/cloudwatch-log.md b/docs/providers/aws/events/cloudwatch-log.md index bddf59117da..2072598ab27 100644 --- a/docs/providers/aws/events/cloudwatch-log.md +++ b/docs/providers/aws/events/cloudwatch-log.md @@ -14,7 +14,7 @@ layout: Doc ## Simple event definition -This will enable your Lambda function to be called by an Log Stream. +This will enable your Lambda function to be called by a Log Stream. ```yml functions: diff --git a/docs/providers/aws/examples/hello-world/README.md b/docs/providers/aws/examples/hello-world/README.md index d1f1d070a4c..378b0567756 100644 --- a/docs/providers/aws/examples/hello-world/README.md +++ b/docs/providers/aws/examples/hello-world/README.md @@ -17,6 +17,8 @@ Pick your language of choice: * [JavaScript](./node) * [Python](./python) -* [csharp](./csharp) +* [C#](./csharp) +* [F#](./fsharp) +* [Go](./go) [View all examples](https://www.serverless.com/framework/docs/providers/aws/examples/) diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index 9f0d170a4dc..f230f7f88fc 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -13,13 +13,32 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-csharp --path myService` or `sls create --template aws-csharp --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-csharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-csharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Build using .NET CLI tools and create zip package ``` -# Linux or OSX +# Linux or Mac OS ./build.sh ``` @@ -29,14 +48,23 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins ``` ## 3. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. + ## 4. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -49,4 +77,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index 7c7de6acb48..562c1e498c8 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -7,31 +7,69 @@ layout: Doc # Hello World F# Example -## Prerequisites +Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). -* Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). -* [.Net Core 1.0.1 SDK](https://www.microsoft.com/net/download/core) - * 1.1 isn't currently supported by AWS Lambda -* [NodeJS v4 or higher](https://nodejs.org/en/) -* An AWS Account +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. -## Build and Package +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + +## 1. Create a service + +``` +sls create --template aws-fsharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. + +## 2. Build using .NET CLI tools and create zip package + +``` +# Linux or Mac OS +./build.sh +``` -From the root of this directory, run `build.cmd`, or `build.sh` if on Linux / Mac. +``` +# Windows PowerShell +./build.cmd +``` -This will produce your deployment package at `bin/release/netcoreapp1.0/deploy-package.zip`. +## 3. Deploy -## Deployment and Invocation +``` +sls deploy +``` -Once packaged, you can follow [these instructions](https://github.com/serverless/serverless#quick-start) to deploy and remotely invoke the function on AWS Lambda. +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. -In short the commands you will need to run are (from the root of this directory): +## 4. Invoke deployed function ``` -serverless config credentials --provider aws --key {YourAwsAccessKey} --secret {YourAwsSecret} -serverless deploy -v -serverless invoke -f hello -l -serverless remove +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. + +```bash +{ + "Message": "Go Serverless v1.0! Your function executed successfully!", + "Request": { + "Key1": null, + "Key2": null, + "Key3": null + } +} ``` -By default this template deploys to us-east-1, you can change that in "serverless.yml" under the `region: us-east-1` key. \ No newline at end of file +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml index c90e1089a71..6ef081debe8 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml +++ b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml @@ -66,7 +66,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md new file mode 100644 index 00000000000..1720c009b0d --- /dev/null +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -0,0 +1,116 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/go/) + + +# Hello World Go Example + +Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). + +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + +You should also have [go](https://golang.org/doc/install) and [make](https://www.gnu.org/software/make/) + +It is always good practice to organize your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximize the benefits of go tooling. + +## 1. Create a service +There are two templates for `go`: + +The Serverless Framework includes starter templates for various languages and providers. There are two templates for `go`. + +#### [aws-go](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go) + +`aws-go` fetches dependencies using standard `go get`. + +``` +sls create --template aws-go --path myService +``` + +#### [aws-go-dep](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go-dep) + +`aws-go-dep` uses [go dep](https://github.com/golang/dep) and requires your project to be in `$GOPATH/src` + +``` +sls create --template aws-go-dep --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-nodejs with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. + +Change directories into 'myService' folder and you can see this project has 2 handler functions: `hello` and `world` split into 2 separate go packages (folders): + +``` +. +├── hello/ +│ └── main.go +├── world/ +│ └── main.go +``` + +This because a `main()` function is required as entry point for each handler executable. + +## 2. Build using go build to create static binaries + +Run `make build` to build both functions. Successful build should generate the following binaries: + +``` +. +├── bin/ +│ |── hello +│ └── world +``` + +## 3. Deploy + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. + +## 4. Invoke deployed function + +``` +sls invoke -f hello +``` + +``` +sls invoke -f world +``` + +Invoke either deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. + +```bash +serverless invoke -f hello + +{ + "message": "Go Serverless v1.0! Your function executed successfully!" +} + +serverless invoke -f world + +{ + "message": "Okay so your other function also executed successfully!" +} +``` + +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/node/README.md b/docs/providers/aws/examples/hello-world/node/README.md index 792bd4e8d51..fb927951f61 100644 --- a/docs/providers/aws/examples/hello-world/node/README.md +++ b/docs/providers/aws/examples/hello-world/node/README.md @@ -13,18 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-nodejs --path myService` or `sls create --template aws-nodejs --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-nodejs --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-nodejs with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -33,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/python/README.md b/docs/providers/aws/examples/hello-world/python/README.md index d1e432b603a..c6380de149b 100644 --- a/docs/providers/aws/examples/hello-world/python/README.md +++ b/docs/providers/aws/examples/hello-world/python/README.md @@ -13,18 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-python --path myService` or `sls create --template aws-python --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-python --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-python with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -33,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 2d6a970ed68..468701dce7f 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -37,6 +37,7 @@ functions: runtime: python2.7 # optional overwrite, default is provider runtime 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 ``` The `handler` property points to the file and module containing the code you want to run in your function. diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index c9f1c35a771..16608c5b70a 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -159,7 +159,9 @@ functions: batchSize: 100 startingPosition: LATEST enabled: false - - alexaSkill + - alexaSkill: + appId: amzn1.ask.skill.xx-xx-xx-xx + enabled: true - alexaSmartHome: appId: amzn1.ask.skill.xx-xx-xx-xx enabled: true diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index 20a27a0270d..24d33dab159 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -63,6 +63,7 @@ Here are the available runtimes for AWS Lambda: * aws-scala-sbt * aws-csharp * aws-fsharp +* aws-go Check out the [create command docs](../cli-reference/create) for all the details and options. diff --git a/docs/providers/azure/examples/hello-world/node/README.md b/docs/providers/azure/examples/hello-world/node/README.md index 22fc16bb826..9c18f181873 100644 --- a/docs/providers/azure/examples/hello-world/node/README.md +++ b/docs/providers/azure/examples/hello-world/node/README.md @@ -39,4 +39,4 @@ In your terminal window you should see the response from azure } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/google/cli-reference/invoke-local.md b/docs/providers/google/cli-reference/invoke-local.md new file mode 100644 index 00000000000..54ab1170190 --- /dev/null +++ b/docs/providers/google/cli-reference/invoke-local.md @@ -0,0 +1,56 @@ + + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/invoke-local) + + + +# Google - Invoke Local + +Invokes deployed function locally. It allows to send event data to the function, read logs and display other important information of the function invocation. + +```bash +serverless invoke -f functionName +``` + +## Options + +* `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. + \_ `--data` or `-d` Data you want to pass into the function +* `--path` or `-p` Path to JSON or YAML file holding input data. This path is relative to the root directory of the service. +* `--raw` Pass data as a raw string even if it is JSON. If not set, JSON data are parsed and passed as an object. +* `--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. + +> Keep in mind that if you pass both `--path` and `--data`, the data included in the `--path` file will overwrite the data you passed with the `--data` flag. + +## Examples + +### Local function invocation + +```bash +serverless invoke local -f functionName +``` + +### Local function invocation with data + +```bash +serverless invoke local -f functionName -d '{ "data": "hello world" }' +``` + +### Local function invocation with data passing + +```bash +serverless invoke local -f functionName -p path/to/file.json + +# OR + +serverless invoke local -f functionName -p path/to/file.yaml +``` diff --git a/docs/providers/google/examples/hello-world/node/README.md b/docs/providers/google/examples/hello-world/node/README.md index 4f6f3b10f05..5802b97794d 100644 --- a/docs/providers/google/examples/hello-world/node/README.md +++ b/docs/providers/google/examples/hello-world/node/README.md @@ -31,8 +31,8 @@ Update the `credentials` and your `project` property in the `serverless.yml` fil ## 5. Invoke deployed function -`serverless invoke --function helloWorld` +`serverless invoke --function first` In your terminal window you should see a response from the Google Cloud -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/kubeless/README.md b/docs/providers/kubeless/README.md index b9bba665a2d..aaf72767710 100644 --- a/docs/providers/kubeless/README.md +++ b/docs/providers/kubeless/README.md @@ -30,6 +30,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Functions
  • Events
  • Deploying
  • +
  • Packaging
  • Debugging
  • Workflow
  • diff --git a/docs/providers/kubeless/guide/functions.md b/docs/providers/kubeless/guide/functions.md index b85619a2694..8881110c865 100644 --- a/docs/providers/kubeless/guide/functions.md +++ b/docs/providers/kubeless/guide/functions.md @@ -25,6 +25,9 @@ service: my-service provider: name: kubeless runtime: python2.7 + memorySize: 512M # optional, maximum memory + timeout: 10 # optional, in seconds, default is 180 + namespace: funcions # optional, deployment namespace if not specified it uses "default" plugins: - serverless-kubeless @@ -34,7 +37,12 @@ functions: # and the K8s service object to get a request to call the function hello: # The function to call as a response to the HTTP event - handler: handler.hello + handler: handler.hello # required, handler set + description: Description of what the function does # optional, to set the description as an annotation + memorySize: 512M # optional, maximum memory + timeout: 10 # optional, in seconds, default is 180 + namespace: funcions # optional, deployment namespace, if not specified "default" will be used + port: 8081 # optional, deploy http-based function with a custom port, default is 8080 ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -88,3 +96,139 @@ The Kubeless provider plugin supports the following runtimes. Please see the following repository for sample projects using those runtimes: [https://github.com/serverless/serverless-kubeless/tree/master/examples](https://github.com/serverless/serverless-kubeless/tree/master/examples) + +## Installing dependencies + +For installing dependencies the standard dependency file should be placed in the function folder: + + - For Python functions, it will use the file `requirements.txt` + - For Nodejs functions, `dependencies` in the `package.json` file will be installed + - For Ruby functions, it will use the file `Gemfile.rb` + +If one of the above files is found, the depencies will be installed using a [`Init Container`](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). + +## Environment Variables + +You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key/value collection of strings: + +```yml +# serverless.yml +service: service-name +provider: kubeless +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + environment: + TABLE_NAME: tableName +``` + +Or if you want to apply environment variable configuration to all functions in your service, you can add the configuration to the higher level `provider` object. Environment variables configured at the function level are merged with those at the provider level, so your function with specific environment variables will also have access to the environment variables defined at the provider level. If an environment variable with the same key is defined at both the function and provider levels, the function-specific value overrides the provider-level default value. For example: + +```yml +# serverless.yml +service: service-name +provider: + name: kubeless + environment: + SYSTEM_NAME: mySystem + TABLE_NAME: tableName1 + +plugins: + - serverless-kubeless + +functions: + hello: + # this function will have SYSTEM_NAME=mySystem and TABLE_NAME=tableName1 from the provider-level environment config above + handler: handler.hello + users: + # this function will have SYSTEM_NAME=mySystem from the provider-level environment config above + # but TABLE_NAME will be tableName2 because this more specific config will override the default above + handler: handler.users + environment: + TABLE_NAME: tableName2 +``` + +## Labels + +Using the `labels` configuration makes it possible to add `key` / `value` labels to your functions. + +Those labels will appear in deployments, services and pods and will make it easier to group functions by label or find functions with a common label. + +```yml +provider: + name: kubeless + +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + labels: + foo: bar +``` + +## Custom hostname and path + +It is possible to define a custom hostname and path that will be used to serve a function in a specific endpoint. For doing this, it is necessary to have an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) available in the cluster. + +```yml +provider: + name: kubeless + hostname: myhostname.io +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + events: + - http: + path: /hello +``` + +In the example above, once the Ingress Rule has been processed by the Ingress controller, you can call the function using as endpoing `myhostname.io/hello`. + +If no hostname is given but a function specifies a `path`, the plugin will use the IP of the cluster followed by a DNS mapping service. By default [nip.io](http://nip.io) will be used but this can be configured with the property `defaultDNSResolution`. + +```yml +provider: + name: kubeless + defaultDNSResolution: 'xip.io' +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + events: + - http: + path: /hello +``` + +The above will result in an endpoint like `1.2.3.4.xip.io/hello` where `1.2.3.4` is the IP of the cluster server. + +The final URL in which the function will be listening can be retrieved executing `serverless info`. + +## Custom images (alpha feature) + +It is possible to skip the Kubeless build system and specify a prebuilt image to run a function. This feature is useful for using Kubeless with languages that are still not supported or if the function package [is over 1MB](./packaging.md#package-maximum-size). To get more information about how to use custom images visit the [upstream documentation](https://github.com/kubeless/kubeless/blob/master/docs/runtimes.md#custom-runtime-alpha). + +```yml +service: hello + +provider: + name: kubeless + runtime: python2.7 + +plugins: + - serverless-kubeless + +functions: + hello: + handler: handler.hello + image: tuna/kubeless-python:0.0.6 +``` diff --git a/docs/providers/kubeless/guide/packaging.md b/docs/providers/kubeless/guide/packaging.md new file mode 100644 index 00000000000..2b788756c1b --- /dev/null +++ b/docs/providers/kubeless/guide/packaging.md @@ -0,0 +1,144 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/packaging) + + +# Kubeless - Packaging + +## Package CLI Command + +Using the Serverless CLI tool, you can package your project without deploying with Kubeless. This is best used with CI / CD workflows to ensure consistent deployable artifacts. + +Running the following command will build and save all of the deployment artifacts in the service's .serverless directory: + +```bash +serverless package +``` + +However, you can also use the --package option to add a destination path and Serverless will store your deployment artifacts there (./my-artifacts in the following case): + +```bash +serverless package --package my-artifacts +``` + +## Package Maximum Size + +Kubeless uses [Kubernetes ConfigMaps](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) to store functions configuration and code. These ConfigMaps have a limitation in size of one MB since they are stored as a single entry in a `etcd` database. Due to this limitation, the maximum possible size for a Kubeless function package is no more than one MB. Note that only code and configuration files should be included in this package, dependencies will be installed during the build process. If your function package size is over one MB please [exclude some directories](#exclude-include) or create a [custom image](./functions#custom-images-alpha-feature) with your function. By default, files under the `node_modules` folder will be excluded. + +## Package Configuration + +Sometimes you might like to have more control over your function artifacts and how they are packaged. + +You can use the `package` and `exclude` configuration for more control over the packaging process. + +### Exclude / include + +Exclude and include allows you to define globs that will be excluded / included from the resulting artifact. If you wish to +include files you can use a glob pattern prefixed with `!` such as `!re-include-me/**` in `exclude` or the dedicated `include` config. +Serverless will run the glob patterns in order. + +At first it will apply the globs defined in `exclude`. After that it'll add all the globs from `include`. This way you can always re-include +previously excluded files and directories. + +### Examples + +Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively + +``` yml +package: + exclude: + - node_modules/** + - '!node_modules/node-fetch/**' +``` + +Exclude all files but `handler.js` using `exclude` and `include` + +``` yml +package: + exclude: + - src/** + include: + - src/function/handler.js +``` + +**Note:** Don't forget to use the correct glob syntax if you want to exclude directories + +``` +exclude: + - tmp/** + - .git/** +``` + +### Artifact + +For complete control over the packaging process you can specify your own artifact zip file. +Serverless won't zip your service if this is configured and therefore `exclude` and `include` will be ignored. Either you use artifact or include / exclude. + +The artifact option is especially useful in case your development environment allows you to generate a deployable artifact like Maven does for Java. + +### Example + +```yml +service: my-service +package: + artifact: path/to/my-artifact.zip +``` + +### Packaging functions separately + +If you want even more controls over your functions for deployment you can configure them to be packaged independently. This allows you more control for optimizing your deployment. To enable individual packaging set `individually` to true in the service or function wide packaging settings. + +Then for every function you can use the same `exclude`, `include` or `artifact` config options as you can service wide. The `exclude` and `include` option will be merged with the service wide options to create one `exclude` and `include` config per function during packaging. + +```yml +service: my-service +package: + individually: true + exclude: + - excluded-by-default.json +functions: + hello: + handler: handler.hello + package: + # We're including this file so it will be in the final package of this function only + include: + - excluded-by-default.json + world: + handler: handler.hello + package: + exclude: + - some-file.js +``` + +You can also select which functions to be packaged separately, and have the rest use the service package by setting the `individually` flag at the function level: + +```yml +service: my-service +functions: + hello: + handler: handler.hello + world: + handler: handler.hello + package: + individually: true +``` + +### Development dependencies + +Serverless will auto-detect and exclude development dependencies based on the runtime your service is using. + +This ensures that only the production relevant packages and modules are included in your zip file. Doing this drastically reduces the overall size of the deployment package which will be uploaded to the cloud provider. + +You can opt-out of automatic dev dependency exclusion by setting the `excludeDevDependencies` package config to `false`: + +```yml +package: + excludeDevDependencies: false +``` diff --git a/docs/providers/openwhisk/examples/hello-world/node/README.md b/docs/providers/openwhisk/examples/hello-world/node/README.md index f9a6d399900..4eadbc6006a 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/README.md +++ b/docs/providers/openwhisk/examples/hello-world/node/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/php/README.md b/docs/providers/openwhisk/examples/hello-world/php/README.md index c254887bf06..4ace5e28fac 100644 --- a/docs/providers/openwhisk/examples/hello-world/php/README.md +++ b/docs/providers/openwhisk/examples/hello-world/php/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/python/README.md b/docs/providers/openwhisk/examples/hello-world/python/README.md index 1056a80600d..35faa3edf9a 100644 --- a/docs/providers/openwhisk/examples/hello-world/python/README.md +++ b/docs/providers/openwhisk/examples/hello-world/python/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/swift/README.md b/docs/providers/openwhisk/examples/hello-world/swift/README.md index 93dec3d3087..6dc26cfda13 100644 --- a/docs/providers/openwhisk/examples/hello-world/swift/README.md +++ b/docs/providers/openwhisk/examples/hello-world/swift/README.md @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/spotinst/examples/java8/README.md b/docs/providers/spotinst/examples/java8/README.md index 34e9f2b30d1..8de2ab42bef 100644 --- a/docs/providers/spotinst/examples/java8/README.md +++ b/docs/providers/spotinst/examples/java8/README.md @@ -32,7 +32,7 @@ In your terminal window you should see the response {"hello":"null"} ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/node/README.md b/docs/providers/spotinst/examples/node/README.md index a4b8f43d78e..cd8f956d786 100644 --- a/docs/providers/spotinst/examples/node/README.md +++ b/docs/providers/spotinst/examples/node/README.md @@ -32,7 +32,7 @@ In your terminal window you should see the response {"hello":"from NodeJS8.3 function"} ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/python/README.md b/docs/providers/spotinst/examples/python/README.md index c4f58a6284c..a6ae892364d 100644 --- a/docs/providers/spotinst/examples/python/README.md +++ b/docs/providers/spotinst/examples/python/README.md @@ -34,7 +34,7 @@ In your terminal window you should see the response '{"hello":"from Python2.7 function"}' ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/ruby/README.md b/docs/providers/spotinst/examples/ruby/README.md index e0d7d2e2dd3..a7325a394c6 100644 --- a/docs/providers/spotinst/examples/ruby/README.md +++ b/docs/providers/spotinst/examples/ruby/README.md @@ -11,21 +11,21 @@ layout: Doc # Hello World Ruby Example -Make sure `serverless` is installed. +Make sure `serverless` is installed. ## 1. Create a service `serverless create --template spotinst-ruby --path serviceName` `serviceName` is going to be a new directory there the Ruby template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy -```bash +```bash serverless deploy ``` ## 3. Invoke deployed function ```bash serverless invoke --function hello -``` +``` In your terminal window you should see the response @@ -33,14 +33,14 @@ In your terminal window you should see the response '{"hello":"from Ruby2.4.1 function"}' ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide -`sls` is short hand for serverless cli commands +`sls` is short hand for serverless cli commands `-f` is short hand for `--function` `-t` is short hand for `--template` -`-p` is short hang for `--path` \ No newline at end of file +`-p` is short hang for `--path` diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md new file mode 100644 index 00000000000..a0e6cf0f5a8 --- /dev/null +++ b/docs/providers/spotinst/guide/cors.md @@ -0,0 +1,43 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/cors) + + +# Spotinst Functions - CORS +Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify domains that are safe, and enables out-of-the-box support for preflight HTTP request (via the OPTIONS method) will return the needed ‘access-control-’ headers specified below, and the actual HTTP request will return the ‘access-control-allow-origin’ method. + +You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. + +Example CORS object: +```yaml + cors: + - enabled: true + origin: "http://foo.example" + headers: "Content-Type,X-PINGOTHER" + methods: "PUT,POST" +``` + +Parameters: + - enabled: Boolean + - Specify if CORS is enabled for the function. + - default: false + - origin: String + - Specifies a domain/origin that may access the resource. A wildcard '*' may be used to allow any origin to access the resource. + - default: '*' + - methods: String + - Comma-separated list of HTTP methods that are allowed to access the resource. This is used in response to a preflight request. + - default: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' + - headers: String + - Comma-separated list of allowed headers. + - default: 'Content-Type,Authorization' + + + + diff --git a/lib/classes/Error.js b/lib/classes/Error.js index 47e21897c94..53a97e7c0a7 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -19,7 +19,7 @@ const writeMessage = (messageType, message) => { consoleLog(' '); if (message) { - consoleLog(chalk.white(` ${message}`)); + consoleLog(` ${message}`); } consoleLog(' '); @@ -61,17 +61,16 @@ module.exports.logError = (e) => { consoleLog(' '); } - const platform = chalk.white(process.platform); - const nodeVersion = chalk.white(process.version.replace(/^[v|V]/, '')); - const slsVersion = chalk.white(version); + const platform = process.platform; + const nodeVersion = process.version.replace(/^[v|V]/, ''); + const slsVersion = version; consoleLog(chalk.yellow(' Get Support --------------------------------------------')); - consoleLog(`${chalk.yellow(' Docs: ')}${chalk.white('docs.serverless.com')}`); - consoleLog(`${chalk.yellow(' Bugs: ')}${chalk - .white('github.com/serverless/serverless/issues')}`); - consoleLog(`${chalk.yellow(' Forums: ')}${chalk.white('forum.serverless.com')}`); - consoleLog(`${chalk.yellow(' Chat: ')}${chalk - .white('gitter.im/serverless/serverless')}`); + consoleLog(`${chalk.yellow(' Docs: ')}${'docs.serverless.com'}`); + consoleLog(`${chalk.yellow(' Bugs: ')}${ + 'github.com/serverless/serverless/issues'}`); + consoleLog(`${chalk.yellow(' Forums: ')}${'forum.serverless.com'}`); + consoleLog(`${chalk.yellow(' Chat: ')}${'gitter.im/serverless/serverless'}`); consoleLog(' '); consoleLog(chalk.yellow(' Your Environment Information -----------------------------')); diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index 28d5820e99b..370422d73d3 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -6,7 +6,6 @@ const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const BbPromise = require('bluebird'); const testUtils = require('../../../../../tests/utils'); describe('createStack', () => { @@ -98,8 +97,7 @@ describe('createStack', () => { it('should set the createLater flag and resolve if deployment bucket is provided', () => { awsDeploy.serverless.service.provider.deploymentBucket = 'serverless'; - sandbox.stub(awsDeploy.provider, 'request') - .returns(BbPromise.reject({ message: 'does not exist' })); + sandbox.stub(awsDeploy.provider, 'request').rejects(new Error('does not exist')); return awsDeploy.createStack().then(() => { expect(awsDeploy.createLater).to.equal(true); diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/lib/plugins/aws/deploy/lib/extendedValidate.test.js index 7b1d3d9adcf..7cbabdc5fb9 100644 --- a/lib/plugins/aws/deploy/lib/extendedValidate.test.js +++ b/lib/plugins/aws/deploy/lib/extendedValidate.test.js @@ -1,6 +1,6 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); @@ -8,6 +8,10 @@ const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); const testUtils = require('../../../../../tests/utils'); +chai.use(require('sinon-chai')); + +const expect = chai.expect; + describe('extendedValidate', () => { let awsDeploy; const tmpDirPath = testUtils.getTmpDirPath(); @@ -60,8 +64,8 @@ describe('extendedValidate', () => { }); afterEach(() => { - awsDeploy.serverless.utils.fileExistsSync.restore(); - awsDeploy.serverless.utils.readFileSync.restore(); + fileExistsSyncStub.restore(); + readFileSyncStub.restore(); }); it('should throw error if state file does not exist', () => { diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index df96e23ff68..7c907b34587 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -90,8 +90,8 @@ describe('uploadArtifacts', () => { }); afterEach(() => { - normalizeFiles.normalizeCloudFormationTemplate.restore(); - awsDeploy.provider.request.restore(); + normalizeCloudFormationTemplateStub.restore(); + uploadStub.restore(); }); it('should upload the CloudFormation file to the S3 bucket', () => { @@ -159,8 +159,8 @@ describe('uploadArtifacts', () => { }); afterEach(() => { - fs.readFileSync.restore(); - awsDeploy.provider.request.restore(); + readFileSyncStub.restore(); + uploadStub.restore(); }); it('should throw for null artifact paths', () => { @@ -265,7 +265,7 @@ describe('uploadArtifacts', () => { .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.functions.second.package.artifact); - awsDeploy.uploadZipFile.restore(); + uploadZipFileStub.restore(); }); }); @@ -286,7 +286,7 @@ describe('uploadArtifacts', () => { const uploadZipFileStub = sinon .stub(awsDeploy, 'uploadZipFile').resolves(); - sinon.stub(fs, 'statSync').returns({ size: 1024 }); + const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); @@ -294,8 +294,9 @@ describe('uploadArtifacts', () => { .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.package.artifact); - awsDeploy.uploadZipFile.restore(); - fs.statSync.restore(); + }).finally(() => { + uploadZipFileStub.restore(); + statSyncStub.restore(); }); }); @@ -303,16 +304,16 @@ describe('uploadArtifacts', () => { awsDeploy.serverless.config.servicePath = 'some/path'; awsDeploy.serverless.service.service = 'new-service'; - sinon.stub(fs, 'statSync').returns({ size: 1024 }); - sinon.stub(awsDeploy, 'uploadZipFile').resolves(); + const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); + const uploadZipFileStub = sinon.stub(awsDeploy, 'uploadZipFile').resolves(); sinon.spy(awsDeploy.serverless.cli, 'log'); return awsDeploy.uploadFunctions().then(() => { const expected = 'Uploading service .zip file to S3 (1 KB)...'; expect(awsDeploy.serverless.cli.log.calledWithExactly(expected)).to.be.equal(true); - - fs.statSync.restore(); - awsDeploy.uploadZipFile.restore(); + }).finally(() => { + statSyncStub.restore(); + uploadZipFileStub.restore(); }); }); }); diff --git a/lib/plugins/aws/invoke/index.js b/lib/plugins/aws/invoke/index.js index 58cd837d902..2354a1d282d 100644 --- a/lib/plugins/aws/invoke/index.js +++ b/lib/plugins/aws/invoke/index.js @@ -83,12 +83,12 @@ class AwsInvoke { } log(invocationReply) { - const color = !invocationReply.FunctionError ? 'white' : 'red'; + const color = !invocationReply.FunctionError ? (x => x) : chalk.red; if (invocationReply.Payload) { const response = JSON.parse(invocationReply.Payload); - this.consoleLog(chalk[color](JSON.stringify(response, null, 4))); + this.consoleLog(color(JSON.stringify(response, null, 4))); } if (invocationReply.LogResult) { diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 8f3a7bcda88..91591169587 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -6,7 +6,6 @@ const path = require('path'); const AwsInvoke = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); -const chalk = require('chalk'); const testUtils = require('../../../../tests/utils'); describe('AwsInvoke', () => { @@ -271,7 +270,7 @@ describe('AwsInvoke', () => { }; return awsInvoke.log(invocationReplyMock).then(() => { - const expectedPayloadMessage = `${chalk.white('{\n "testProp": "testValue"\n}')}`; + const expectedPayloadMessage = '{\n "testProp": "testValue"\n}'; expect(consoleLogStub.calledWith(expectedPayloadMessage)).to.equal(true); }); diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index f2c81c776e4..efb408408ce 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -282,8 +282,9 @@ module.exports = { getLambdaApiGatewayPermissionLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionApiGateway`; }, - getLambdaAlexaSkillPermissionLogicalId(functionName) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill`; + getLambdaAlexaSkillPermissionLogicalId(functionName, alexaSkillIndex) { + return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill${ + alexaSkillIndex || '0'}`; }, getLambdaAlexaSmartHomePermissionLogicalId(functionName, alexaSmartHomeIndex) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSmartHome${ diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 7becb143864..530d0621e56 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -464,9 +464,15 @@ describe('#naming()', () => { describe('#getLambdaAlexaSkillPermissionLogicalId()', () => { it('should normalize the function name and append the standard suffix', + () => { + expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName', 2)) + .to.equal('FunctionNameLambdaPermissionAlexaSkill2'); + }); + + it('should normalize the function name and append a default suffix if not defined', () => { expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionAlexaSkill'); + .to.equal('FunctionNameLambdaPermissionAlexaSkill0'); }); }); diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.js index ecc32cb4451..89c863bb4b2 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.js @@ -15,10 +15,45 @@ class AwsCompileAlexaSkillEvents { compileAlexaSkillEvents() { this.serverless.service.getAllFunctions().forEach((functionName) => { const functionObj = this.serverless.service.getFunction(functionName); + let alexaSkillNumberInFunction = 0; if (functionObj.events) { functionObj.events.forEach(event => { - if (event === 'alexaSkill') { + if (event === 'alexaSkill' || event.alexaSkill) { + let enabled = true; + let appId; + if (event === 'alexaSkill') { + const warningMessage = [ + 'Warning! You are using an old syntax for alexaSkill which doesn\'t', + ' restrict the invocation solely to your skill.', + ' Please refer to the documentation for additional information.', + ].join(''); + this.serverless.cli.log(warningMessage); + } else if (_.isString(event.alexaSkill)) { + appId = event.alexaSkill; + } else if (_.isPlainObject(event.alexaSkill)) { + if (!_.isString(event.alexaSkill.appId)) { + const errorMessage = [ + `Missing "appId" property for alexaSkill event in function ${functionName}`, + ' The correct syntax is: appId: amzn1.ask.skill.xx-xx-xx-xx-xx', + ' OR an object with "appId" property.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + appId = event.alexaSkill.appId; + // Parameter `enabled` is optional, hence the explicit non-equal check for false. + enabled = event.alexaSkill.enabled !== false; + } else { + const errorMessage = [ + `Alexa Skill event of function "${functionName}" is not an object or string.`, + ' The correct syntax is: alexaSkill.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + alexaSkillNumberInFunction++; + const lambdaLogicalId = this.provider.naming .getLambdaLogicalId(functionName); @@ -31,13 +66,18 @@ class AwsCompileAlexaSkillEvents { 'Arn', ], }, - Action: 'lambda:InvokeFunction', + Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction', Principal: 'alexa-appkit.amazon.com', }, }; + if (appId) { + permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, ''); + } + const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlexaSkillPermissionLogicalId(functionName); + .getLambdaAlexaSkillPermissionLogicalId(functionName, + alexaSkillNumberInFunction); const permissionCloudForamtionResource = { [lambdaPermissionLogicalId]: permissionTemplate, @@ -45,13 +85,6 @@ class AwsCompileAlexaSkillEvents { _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, permissionCloudForamtionResource); - } else if (event.alexaSkill) { - const errorMessage = [ - `Alexa Skill event of function "${functionName}" is not an object or string.`, - ' The correct syntax is: alexaSkill.', - ' Please check the docs for more info.', - ].join(''); - throw new this.serverless.classes.Error(errorMessage); } }); } diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js index 338a9fd0838..cb9090ae6cc 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js @@ -1,5 +1,7 @@ 'use strict'; +/* eslint-disable no-unused-expressions */ + const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileAlexaSkillEvents = require('./index'); @@ -8,11 +10,19 @@ const Serverless = require('../../../../../../Serverless'); describe('AwsCompileAlexaSkillEvents', () => { let serverless; let awsCompileAlexaSkillEvents; + let consolePrinted; beforeEach(() => { serverless = new Serverless(); serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; serverless.setProvider('aws', new AwsProvider(serverless)); + consolePrinted = ''; + serverless.cli = { + // serverless.cli isn't available in tests, so we will mimic it. + log: txt => { + consolePrinted += `${txt}\r\n`; + }, + }; awsCompileAlexaSkillEvents = new AwsCompileAlexaSkillEvents(serverless); }); @@ -25,7 +35,42 @@ describe('AwsCompileAlexaSkillEvents', () => { }); describe('#compileAlexaSkillEvents()', () => { - it('should throw an error if alexaSkill event is not an string', () => { + it('should show a warning if alexaSkill appId is not specified', () => { + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + 'alexaSkill', + ], + }, + }; + + awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); + + expect(consolePrinted).to.contain.string('old syntax for alexaSkill'); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:InvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.be.undefined; + }); + + it('should throw an error if alexaSkill event is not a string or an object', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { events: [ @@ -39,11 +84,36 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error); }); - it('should create corresponding resources when a alexaSkill event is provided', () => { + it('should throw an error if alexaSkill event appId is not a string', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { events: [ - 'alexaSkill', + { + alexaSkill: { + appId: 42, + }, + }, + ], + }, + }; + + expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error); + }); + + it('should create corresponding resources when multiple alexaSkill events are provided', () => { + const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx'; + const skillId2 = 'amzn1.ask.skill.yy-yy-yy-yy'; + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + alexaSkill: skillId1, + }, + { + alexaSkill: { + appId: skillId2, + }, + }, ], }, }; @@ -52,20 +122,84 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Type + .FirstLambdaPermissionAlexaSkill1.Type ).to.equal('AWS::Lambda::Permission'); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.FunctionName + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.Action + .FirstLambdaPermissionAlexaSkill1.Properties.Action ).to.equal('lambda:InvokeFunction'); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.Principal + .FirstLambdaPermissionAlexaSkill1.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.equal(skillId1); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.Action + ).to.equal('lambda:InvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.EventSourceToken + ).to.equal(skillId2); + }); + + it('should create corresponding resources when a disabled alexaSkill event is provided', () => { + const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx'; + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + alexaSkill: { + appId: skillId1, + enabled: false, + }, + }, + ], + }, + }; + + awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:DisableInvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.equal(skillId1); }); it('should not create corresponding resources when alexaSkill event is not given', () => { @@ -82,5 +216,22 @@ describe('AwsCompileAlexaSkillEvents', () => { .compiledCloudFormationTemplate.Resources ).to.deep.equal({}); }); + + it('should not not throw error when other events are present', () => { + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'get', + path: '/', + }, + }, + ], + }, + }; + + expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.not.throw(); + }); }); }); diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index d94063701c0..76f0a4c054c 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -20,19 +20,20 @@ class AwsCompileCognitoUserPoolEvents { this.hooks = { 'package:compileEvents': this.compileCognitoUserPoolEvents.bind(this), + 'after:package:finalize': this.mergeWithCustomResources.bind(this), }; } - compileCognitoUserPoolEvents() { + findUserPoolsAndFunctions() { const userPools = []; const cognitoUserPoolTriggerFunctions = []; // Iterate through all functions declared in `serverless.yml` - this.serverless.service.getAllFunctions().forEach((functionName) => { + _.forEach(this.serverless.service.getAllFunctions(), (functionName) => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { - functionObj.events.forEach(event => { + _.forEach(functionObj.events, (event) => { if (event.cognitoUserPool) { // Check event definition for `cognitoUserPool` object if (typeof event.cognitoUserPool === 'object') { @@ -80,51 +81,61 @@ class AwsCompileCognitoUserPoolEvents { } }); - // Generate CloudFormation templates for Cognito User Pool changes - _.forEach(userPools, (poolName) => { - // Create a `LambdaConfig` object for the CloudFormation template - const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { - poolName, - }); - - const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { - const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + return { cognitoUserPoolTriggerFunctions, userPools }; + } - // Return a new object to avoid lint errors - return Object.assign({}, result, { - [value.triggerSource]: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], - }, - }); - }, {}); + generateTemplateForPool(poolName, currentPoolTriggerFunctions) { + const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + + // Return a new object to avoid lint errors + return Object.assign({}, result, { + [value.triggerSource]: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + }); + }, {}); - const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); - const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this - .provider.naming.getLambdaLogicalId(value.functionName)); + // Attach `DependsOn` for any relevant Lambdas + const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this + .provider.naming.getLambdaLogicalId(value.functionName)); - const userPoolTemplate = { + return { + [userPoolLogicalId]: { Type: 'AWS::Cognito::UserPool', Properties: { UserPoolName: poolName, LambdaConfig: lambdaConfig, }, DependsOn, - }; + }, + }; + } - const userPoolCFResource = { - [userPoolLogicalId]: userPoolTemplate, - }; + compileCognitoUserPoolEvents() { + const result = this.findUserPoolsAndFunctions(); + const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; + const userPools = result.userPools; + + // Generate CloudFormation templates for Cognito User Pool changes + _.forEach(userPools, (poolName) => { + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); + const userPoolCFResource = this.generateTemplateForPool( + poolName, + currentPoolTriggerFunctions + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, userPoolCFResource); }); // Generate CloudFormation templates for IAM permissions to allow Cognito to trigger Lambda - cognitoUserPoolTriggerFunctions.forEach((cognitoUserPoolTriggerFunction) => { + _.forEach(cognitoUserPoolTriggerFunctions, (cognitoUserPoolTriggerFunction) => { const userPoolLogicalId = this.provider.naming .getCognitoUserPoolLogicalId(cognitoUserPoolTriggerFunction.poolName); const lambdaLogicalId = this.provider.naming @@ -159,6 +170,43 @@ class AwsCompileCognitoUserPoolEvents { permissionCFResource); }); } + + mergeWithCustomResources() { + const result = this.findUserPoolsAndFunctions(); + const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; + const userPools = result.userPools; + + _.forEach(userPools, (poolName) => { + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + + // If overrides exist in `Resources`, merge them in + if (_.has(this.serverless.service.resources, userPoolLogicalId)) { + const customUserPool = this.serverless.service.resources[userPoolLogicalId]; + const generatedUserPool = this.generateTemplateForPool( + poolName, + currentPoolTriggerFunctions + )[userPoolLogicalId]; + + // Merge `DependsOn` clauses + const customUserPoolDependsOn = _.get(customUserPool, 'DependsOn', []); + const DependsOn = generatedUserPool.DependsOn.concat(customUserPoolDependsOn); + + // Merge default and custom resources, and `DependsOn` clause + const mergedTemplate = Object.assign( + {}, + _.merge(generatedUserPool, customUserPool), + { DependsOn } + ); + + // Merge resource back into `Resources` + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + { [userPoolLogicalId]: mergedTemplate } + ); + } + }); + } } module.exports = AwsCompileCognitoUserPoolEvents; diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js index 6ec9a6c2805..a67e80bb712 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileCognitoUserPoolEvents = require('./index'); @@ -115,9 +116,15 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type @@ -153,9 +160,15 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type @@ -195,6 +208,9 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1 .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] @@ -204,6 +220,9 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2 .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] @@ -250,10 +269,14 @@ describe('AwsCompileCognitoUserPoolEvents', () => { .compiledCloudFormationTemplate.Resources .CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig).length - ).to.equal(2); + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(2); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(2); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type @@ -279,4 +302,145 @@ describe('AwsCompileCognitoUserPoolEvents', () => { ).to.deep.equal({}); }); }); + + describe('#mergeWithCustomResources()', () => { + it('does not merge if no custom resource is found in Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = {}; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(2); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should merge custom resources found in Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = { + CognitoUserPoolMyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'ProdMyUserPool', + MfaConfiguration: 'OFF', + EmailVerificationSubject: 'Your verification code', + EmailVerificationMessage: 'Your verification code is {####}.', + SmsVerificationMessage: 'Your verification code is {####}.', + }, + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(6); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(1); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should merge `DependsOn` clauses correctly if being overridden from Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = { + CognitoUserPoolMyUserPool: { + DependsOn: ['Something', 'SomethingElse', ['Nothing', 'NothingAtAll']], + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'ProdMyUserPool', + MfaConfiguration: 'OFF', + EmailVerificationSubject: 'Your verification code', + EmailVerificationMessage: 'Your verification code is {####}.', + SmsVerificationMessage: 'Your verification code is {####}.', + }, + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(4); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(6); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 971bf1e2bd6..d880d0f1115 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -279,6 +279,19 @@ class AwsCompileFunctions { delete newFunction.Properties.VpcConfig; } + if (functionObject.reservedConcurrency) { + if (_.isInteger(functionObject.reservedConcurrency)) { + newFunction.Properties.ReservedConcurrentExecutions = functionObject.reservedConcurrency; + } else { + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', + `${newFunction.Properties.FunctionName}`, + ].join(''); + + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); + } + } + newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)] .concat(newFunction.DependsOn || []); diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index b5e18c68424..d170bdeb217 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -25,6 +25,7 @@ describe('AwsCompileFunctions', () => { }; serverless = new Serverless(options); serverless.setProvider('aws', new AwsProvider(serverless, options)); + serverless.cli = new serverless.classes.CLI(); awsCompileFunctions = new AwsCompileFunctions(serverless, options); awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, @@ -1730,6 +1731,65 @@ describe('AwsCompileFunctions', () => { ); }); }); + + it('should set function declared reserved concurrency limit', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: 5, + }, + }; + 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, + ReservedConcurrentExecutions: 5, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should throw an informative error message if non-integer reserved concurrency limit set ' + + 'on function', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: '1', + }, + }; + + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', + 'new-service-dev-func', + ].join(''); + + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(errorMessage); + }); }); describe('#compileRole()', () => { diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index bcc78e58372..6eae516f637 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -328,8 +328,6 @@ class AwsProvider { enableS3TransferAcceleration(credentials) { this.serverless.cli.log('Using S3 Transfer Acceleration Endpoint...'); credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign - credentials.signatureVersion = 'v2'; // eslint-disable-line no-param-reassign - // see https://github.com/aws/aws-sdk-js/issues/281 } getRegion() { diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.js b/lib/plugins/aws/utils/formatLambdaLogEvent.js index 2c2286e9b98..62efe8f644c 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.js @@ -34,7 +34,7 @@ module.exports = (msgParam) => { } else if (!isNaN((new Date(splitted[1])).getTime())) { date = splitted[1]; reqId = splitted[2]; - level = `${chalk.white(splitted[0])}\t`; + level = `${splitted[0]}\t`; } else { return msg; } diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js index b1ae167f2fa..a5d4639dfc4 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js @@ -37,7 +37,7 @@ describe('#formatLambdaLogEvent()', () => { const momentDate = moment('2016-01-01T12:00:00Z').format('YYYY-MM-DD HH:mm:ss.SSS (Z)'); expectedLogMessage += `${chalk.green(momentDate)}\t`; expectedLogMessage += `${chalk.yellow('99c30000-b01a-11e5-93f7-b8e85631a00e')}\t`; - expectedLogMessage += `${chalk.white('[INFO]')}\t`; + expectedLogMessage += `${'[INFO]'}\t`; expectedLogMessage += 'test'; expect(formatLambdaLogEvent(pythonLoggerLine)).to.equal(expectedLogMessage); diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 8027ba9fb97..84368002a70 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -28,6 +28,8 @@ const validTemplates = [ 'aws-scala-sbt', 'aws-csharp', 'aws-fsharp', + 'aws-go', + 'aws-go-dep', 'azure-nodejs', 'google-nodejs', 'kubeless-python', diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 3bba193faa0..c0bf9d726da 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -726,5 +726,39 @@ describe('Create', () => { expect((/service: my-awesome-service/).test(serverlessYmlfileContent)).to.equal(true); }); }); + + it('should generate scaffolding for "aws-go" template', () => { + process.chdir(tmpDir); + create.options.template = 'aws-go'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include(path.join('hello', 'main.go')); + expect(dirContent).to.include(path.join('world', 'main.go')); + expect(dirContent).to.include('Makefile'); + expect(dirContent).to.include('.gitignore'); + }); + }); + + it('should generate scaffolding for "aws-go-dep" template', () => { + process.chdir(tmpDir); + create.options.template = 'aws-go-dep'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include(path.join('hello', 'main.go')); + expect(dirContent).to.include(path.join('world', 'main.go')); + expect(dirContent).to.include('Gopkg.toml'); + expect(dirContent).to.include('Gopkg.lock'); + expect(dirContent).to.include('Makefile'); + expect(dirContent).to.include('.gitignore'); + }); + }); }); }); diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index fb55c7aacee..12e2196fe43 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -67,7 +67,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index 21dd15dd37d..0f8ca52b410 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -67,7 +67,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-go-dep/Gopkg.lock b/lib/plugins/create/templates/aws-go-dep/Gopkg.lock new file mode 100644 index 00000000000..f93855f76bc --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/Gopkg.lock @@ -0,0 +1,19 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/aws/aws-lambda-go" + packages = [ + "lambda", + "lambda/messages", + "lambdacontext" + ] + revision = "6e2e37798efbb1dfd8e9c6681702e683a6046517" + version = "v1.0.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "85fa166cc59d0fa113a1517ffbb5dee0f1fa4a6795239936afb18c64364af759" + solver-name = "gps-cdcl" + solver-version = 1 \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/Gopkg.toml b/lib/plugins/create/templates/aws-go-dep/Gopkg.toml new file mode 100644 index 00000000000..8fce8929ead --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/Gopkg.toml @@ -0,0 +1,25 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/aws/aws-lambda-go" + version = "^1.0.1" diff --git a/lib/plugins/create/templates/aws-go-dep/Makefile b/lib/plugins/create/templates/aws-go-dep/Makefile new file mode 100644 index 00000000000..46a24c449e1 --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/Makefile @@ -0,0 +1,4 @@ +build: + dep ensure + env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go + env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/gitignore b/lib/plugins/create/templates/aws-go-dep/gitignore new file mode 100644 index 00000000000..99a966a94de --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/gitignore @@ -0,0 +1,8 @@ +# Serverless directories +.serverless + +# golang output binary directory +bin + +# golang vendor (dependencies) directory +vendor \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/hello/main.go b/lib/plugins/create/templates/aws-go-dep/hello/main.go new file mode 100644 index 00000000000..e75c5af0407 --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/hello/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Go Serverless v1.0! Your function executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml new file mode 100644 index 00000000000..76819414366 --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -0,0 +1,104 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: aws-go-dep # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: aws + runtime: go1.x + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can add statements to the Lambda function's IAM Role here +# iamRoleStatements: +# - Effect: "Allow" +# Action: +# - "s3:ListBucket" +# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } +# - Effect: "Allow" +# Action: +# - "s3:PutObject" +# Resource: +# Fn::Join: +# - "" +# - - "arn:aws:s3:::" +# - "Ref" : "ServerlessDeploymentBucket" +# - "/*" + +# you can define service wide environment variables here +# environment: +# variable1: value1 + +package: + exclude: + - ./** + include: + - ./bin/** + +functions: + hello: + handler: bin/hello + world: + handler: bin/world + +# The following are a few example events you can configure +# NOTE: Please make sure to change your handler code to work with those events +# Check the event documentation for details +# events: +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env:BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic +# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx +# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx +# - iot: +# sql: "SELECT * FROM 'some_topic'" +# - cloudwatchEvent: +# event: +# source: +# - "aws.ec2" +# detail-type: +# - "EC2 Instance State-change Notification" +# detail: +# state: +# - pending +# - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp + +# Define function environment variables here +# environment: +# variable2: value2 + +# you can add CloudFormation resource templates here +#resources: +# Resources: +# NewResource: +# Type: AWS::S3::Bucket +# Properties: +# BucketName: my-new-bucket +# Outputs: +# NewOutput: +# Description: "Description for the output" +# Value: "Some output value" diff --git a/lib/plugins/create/templates/aws-go-dep/world/main.go b/lib/plugins/create/templates/aws-go-dep/world/main.go new file mode 100644 index 00000000000..bf46aa6fbcb --- /dev/null +++ b/lib/plugins/create/templates/aws-go-dep/world/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Okay so your other function also executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-go/Makefile b/lib/plugins/create/templates/aws-go/Makefile new file mode 100644 index 00000000000..3b697426e39 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/Makefile @@ -0,0 +1,4 @@ +build: + go get github.com/aws/aws-lambda-go/lambda + env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go + env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go/gitignore b/lib/plugins/create/templates/aws-go/gitignore new file mode 100644 index 00000000000..f5b4c36adc3 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/gitignore @@ -0,0 +1,5 @@ +# Serverless directories +.serverless + +# golang output binary directory +bin \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go/hello/main.go b/lib/plugins/create/templates/aws-go/hello/main.go new file mode 100644 index 00000000000..e75c5af0407 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/hello/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Go Serverless v1.0! Your function executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml new file mode 100644 index 00000000000..47fd7378729 --- /dev/null +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -0,0 +1,104 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: aws-go # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: aws + runtime: go1.x + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can add statements to the Lambda function's IAM Role here +# iamRoleStatements: +# - Effect: "Allow" +# Action: +# - "s3:ListBucket" +# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } +# - Effect: "Allow" +# Action: +# - "s3:PutObject" +# Resource: +# Fn::Join: +# - "" +# - - "arn:aws:s3:::" +# - "Ref" : "ServerlessDeploymentBucket" +# - "/*" + +# you can define service wide environment variables here +# environment: +# variable1: value1 + +package: + exclude: + - ./** + include: + - ./bin/** + +functions: + hello: + handler: bin/hello + world: + handler: bin/world + +# The following are a few example events you can configure +# NOTE: Please make sure to change your handler code to work with those events +# Check the event documentation for details +# events: +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env:BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic +# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx +# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx +# - iot: +# sql: "SELECT * FROM 'some_topic'" +# - cloudwatchEvent: +# event: +# source: +# - "aws.ec2" +# detail-type: +# - "EC2 Instance State-change Notification" +# detail: +# state: +# - pending +# - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp + +# Define function environment variables here +# environment: +# variable2: value2 + +# you can add CloudFormation resource templates here +#resources: +# Resources: +# NewResource: +# Type: AWS::S3::Bucket +# Properties: +# BucketName: my-new-bucket +# Outputs: +# NewOutput: +# Description: "Description for the output" +# Value: "Some output value" diff --git a/lib/plugins/create/templates/aws-go/world/main.go b/lib/plugins/create/templates/aws-go/world/main.go new file mode 100644 index 00000000000..bf46aa6fbcb --- /dev/null +++ b/lib/plugins/create/templates/aws-go/world/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + Message string `json:"message"` +} + +func Handler() (Response, error) { + return Response{ + Message: "Okay so your other function also executed successfully!", + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index a1adaaf4d92..e7956282923 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index e8811732e9e..c1079296fda 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index 106bab8aaca..7f94d0131c5 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index fff1b55109b..434eb3d8155 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index cb892140f01..1387dfc8a84 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index 5fd69aab122..bbe5cbed097 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -60,7 +60,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" 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 b15fcd6d494..41c1876cd1f 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js @@ -3,6 +3,7 @@ const slsw = require('serverless-webpack'); module.exports = { entry: slsw.lib.entries, + devtool: 'source-map', resolve: { extensions: [ '.js', diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 1cce386a131..471855a41df 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index f651608e047..2486ba648b2 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index 1d69fcb9da4..3d77f19acd3 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 7eb89a39da7..ee0e94d284d 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -66,7 +66,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/kubeless-nodejs/package.json b/lib/plugins/create/templates/kubeless-nodejs/package.json index f3231c9d56a..039292997e2 100644 --- a/lib/plugins/create/templates/kubeless-nodejs/package.json +++ b/lib/plugins/create/templates/kubeless-nodejs/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Example function for serverless kubeless", "dependencies": { - "serverless-kubeless": "^0.2.4", + "serverless-kubeless": "^0.3.1", "lodash": "^4.1.0" }, "devDependencies": {}, diff --git a/lib/plugins/create/templates/kubeless-python/package.json b/lib/plugins/create/templates/kubeless-python/package.json index 9809da791b0..2eacb76727a 100644 --- a/lib/plugins/create/templates/kubeless-python/package.json +++ b/lib/plugins/create/templates/kubeless-python/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Sample Kubeless Python serverless framework service.", "dependencies": { - "serverless-kubeless": "^0.2.4" + "serverless-kubeless": "^0.3.1" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index c5f5212343e..066b918daa7 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -27,8 +27,13 @@ functions: timeout: 30 access: private # activeVersions: -# - "version": "$LATEST" +# - "version": "$LATEST" # "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index 68de54513e8..1adeac7ef7b 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -29,6 +29,11 @@ functions: # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index 3acb202ac3f..1722af08fd5 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -28,7 +28,12 @@ functions: access: private # activeVersions: # - "version": "$LATEST" -# "percentage": 100.0 +# "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 03f6b1bf826..4c1807e228d 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -29,6 +29,11 @@ functions: # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' diff --git a/package-lock.json b/package-lock.json index 9c6add94cf9..a238c631184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.25.0", + "version": "1.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -33,9 +33,9 @@ "dev": true }, "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", "dev": true }, "acorn-globals": { @@ -213,7 +213,7 @@ "graphql-anywhere": "3.1.0", "graphql-tag": "2.6.1", "redux": "3.7.2", - "symbol-observable": "1.1.0", + "symbol-observable": "1.2.0", "whatwg-fetch": "2.0.3" } }, @@ -367,9 +367,9 @@ "dev": true }, "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "async": { @@ -389,12 +389,11 @@ "dev": true }, "aws-sdk": { - "version": "2.172.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.172.0.tgz", - "integrity": "sha1-R9+3mQeXbrvVOFYupaJYNbWz810=", + "version": "2.188.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.188.0.tgz", + "integrity": "sha1-kGKrx9umOTRZ+i80I89dKU8ARhE=", "requires": { "buffer": "4.9.1", - "crypto-browserify": "1.0.9", "events": "1.1.1", "jmespath": "0.15.0", "querystring": "0.2.0", @@ -887,7 +886,7 @@ "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "dev": true, "requires": { - "assertion-error": "1.0.2", + "assertion-error": "1.1.0", "deep-eql": "0.1.3", "type-detect": "1.0.0" } @@ -951,13 +950,13 @@ } }, "cli-usage": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.4.tgz", - "integrity": "sha1-fAHg3HBsI0s5yTODjI4gshdXduI=", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.7.tgz", + "integrity": "sha512-x/Q52iLSZsRrRb2ePmTsVYXrGcrPQ8G4yRAY7QpMlumxAfPVrnDOH2X6Z5s8qsAX7AA7YuIi8AXFrvH0wWEesA==", "dev": true, "requires": { - "marked": "0.3.7", - "marked-terminal": "1.7.0" + "marked": "0.3.12", + "marked-terminal": "2.0.0" } }, "cli-width": { @@ -1202,11 +1201,6 @@ "boom": "2.10.1" } }, - "crypto-browserify": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", - "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" - }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -1233,7 +1227,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "damerau-levenshtein": { @@ -1481,9 +1475,9 @@ "dev": true }, "doctrine": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.2.tgz", - "integrity": "sha512-y0tm5Pq6ywp3qSTZ1vPgVdAnbDEoeoc5wlOHXoY1c4Wug/a7JvqHIl7BTvwodaHmejWkK/9dSb3sCYfyo/om8A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "2.0.2" @@ -1535,9 +1529,9 @@ } }, "end-of-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", - "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "1.4.0" } @@ -1585,9 +1579,9 @@ } }, "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "version": "0.10.38", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", "dev": true, "requires": { "es6-iterator": "2.0.3", @@ -1601,7 +1595,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-symbol": "3.1.1" } }, @@ -1612,7 +1606,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-set": "0.1.5", "es6-symbol": "3.1.1", @@ -1632,7 +1626,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", "event-emitter": "0.3.5" @@ -1645,7 +1639,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "es6-weak-map": { @@ -1655,7 +1649,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } @@ -1714,7 +1708,7 @@ "chalk": "1.1.3", "concat-stream": "1.6.0", "debug": "2.6.9", - "doctrine": "2.0.2", + "doctrine": "2.1.0", "escope": "3.6.0", "espree": "3.5.2", "esquery": "1.0.0", @@ -1727,7 +1721,7 @@ "imurmurhash": "0.1.4", "inquirer": "0.12.0", "is-my-json-valid": "2.17.1", - "is-resolvable": "1.0.1", + "is-resolvable": "1.1.0", "js-yaml": "3.10.0", "json-stable-stringify": "1.0.1", "levn": "0.3.0", @@ -1887,7 +1881,7 @@ "doctrine": "1.5.0", "has": "1.0.1", "jsx-ast-utils": "1.4.1", - "object.assign": "4.0.4" + "object.assign": "4.1.0" }, "dependencies": { "doctrine": { @@ -1908,7 +1902,7 @@ "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", "dev": true, "requires": { - "acorn": "5.2.1", + "acorn": "5.3.0", "acorn-jsx": "3.0.1" } }, @@ -1955,7 +1949,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "events": { @@ -2404,9 +2398,9 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, "graphlib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.1.tgz", - "integrity": "sha1-QjUsUrovTQNctWbrkfc5X3bryVE=", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.5.tgz", + "integrity": "sha512-XvtbqCcw+EM5SqQrIetIKKD+uZVNQtDPD1goIg7K73RuRZtVI5rYMdcCVSHm/AS1sCBZ7vt0p5WgXouucHQaOA==", "requires": { "lodash": "4.17.4" } @@ -2416,7 +2410,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.5.tgz", "integrity": "sha512-Q7cx22DiLhwHsEfUnUip1Ww/Vfx7FS0w6+iHItNuN61+XpegHSa3k5U0+6M5BcpavQImBwFiy0z3uYwY7cXMLQ==", "requires": { - "iterall": "1.1.3" + "iterall": "1.1.4" } }, "graphql-anywhere": { @@ -2484,7 +2478,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "commander": "2.12.2", + "commander": "2.13.0", "is-my-json-valid": "2.17.1", "pinkie-promise": "2.0.1" }, @@ -2509,9 +2503,9 @@ } }, "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, "supports-color": { @@ -2549,6 +2543,12 @@ "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz", "integrity": "sha512-JkaetveU7hFbqnAC1EV1sF4rlojU2D4Usc5CmS69l6NfmPDnpnFUegzFg33eDkkpNCxZ0mQp65HwUDrNFS/8MA==" }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", @@ -2794,9 +2794,9 @@ "dev": true }, "is-ci": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", - "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", + "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", "dev": true, "requires": { "ci-info": "1.1.2" @@ -3000,9 +3000,9 @@ } }, "is-resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz", - "integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -3193,7 +3193,7 @@ "babel-types": "6.26.0", "babylon": "6.18.0", "istanbul-lib-coverage": "1.1.1", - "semver": "5.4.1" + "semver": "5.5.0" } }, "istanbul-lib-report": { @@ -3274,9 +3274,9 @@ } }, "iterall": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.3.tgz", - "integrity": "sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.4.tgz", + "integrity": "sha512-eaDsM/PY8D/X5mYQhecVc5/9xvSHED7yPON+ffQroBeTuqUVm7dfphMkK8NksXuImqZlVRoKtrNfMIVCYIqaUQ==" }, "jest-changed-files": { "version": "17.0.2", @@ -3294,7 +3294,7 @@ "callsites": "2.0.0", "chalk": "1.1.3", "graceful-fs": "4.1.11", - "is-ci": "1.0.10", + "is-ci": "1.1.0", "istanbul-api": "1.2.1", "istanbul-lib-coverage": "1.1.1", "istanbul-lib-instrument": "1.9.1", @@ -3823,8 +3823,8 @@ "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.7.tgz", "integrity": "sha1-uesB/in16j6Sh48VrqEK04taz4k=", "requires": { - "commander": "2.12.2", - "graphlib": "2.1.1", + "commander": "2.13.0", + "graphlib": "2.1.5", "js-yaml": "3.10.0", "native-promise-only": "0.8.1", "path-loader": "1.0.4", @@ -3833,9 +3833,9 @@ }, "dependencies": { "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==" + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" } } }, @@ -4354,12 +4354,12 @@ "dev": true }, "markdown-magic": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.19.tgz", - "integrity": "sha1-IKnWWdqgx7DOt64DCVxuLK41KgM=", + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.20.tgz", + "integrity": "sha1-Xw73k0L6G0O7pCr+Y9MCMobj7sM=", "dev": true, "requires": { - "commander": "2.12.2", + "commander": "2.13.0", "deepmerge": "1.5.2", "find-up": "2.1.0", "fs-extra": "1.0.0", @@ -4370,9 +4370,9 @@ }, "dependencies": { "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, "find-up": { @@ -4435,15 +4435,15 @@ } }, "marked": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.7.tgz", - "integrity": "sha512-zBEP4qO1YQp5aXHt8S5wTiOv9i2X74V/LQL0zhUNvVaklt6Ywa6lChxIvS+ibYlCGgADwKwZFhjC3+XfpsvQvQ==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", + "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==", "dev": true }, "marked-terminal": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.7.0.tgz", - "integrity": "sha1-yMRgiBx3LHYEtkNnAH7l938SWQQ=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-2.0.0.tgz", + "integrity": "sha1-Xq9Wi+ZvaGVBr6UqVYKAMQox3i0=", "dev": true, "requires": { "cardinal": "1.0.0", @@ -4742,11 +4742,11 @@ "integrity": "sha1-BW0UJE89zBzq3+aK+c/wxUc6M/M=", "dev": true, "requires": { - "cli-usage": "0.1.4", + "cli-usage": "0.1.7", "growly": "1.3.0", "lodash.clonedeep": "3.0.2", "minimist": "1.2.0", - "semver": "5.4.1", + "semver": "5.5.0", "shellwords": "0.1.1", "which": "1.3.0" } @@ -4768,7 +4768,7 @@ "requires": { "hosted-git-info": "2.5.0", "is-builtin-module": "1.0.0", - "semver": "5.4.1", + "semver": "5.5.0", "validate-npm-package-license": "3.0.1" } }, @@ -4848,13 +4848,14 @@ "dev": true }, "object.assign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", - "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { "define-properties": "1.1.2", "function-bind": "1.1.1", + "has-symbols": "1.0.0", "object-keys": "1.0.11" } }, @@ -4899,9 +4900,9 @@ "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" }, "opn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", - "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", + "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", "requires": { "is-wsl": "1.1.0" } @@ -4975,10 +4976,13 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } }, "p-locate": { "version": "2.0.0", @@ -4986,18 +4990,24 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.1.0" + "p-limit": "1.2.0" } }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "6.7.1", - "registry-auth-token": "3.3.1", + "registry-auth-token": "3.3.2", "registry-url": "3.1.0", - "semver": "5.4.1" + "semver": "5.5.0" } }, "pako": { @@ -5197,9 +5207,9 @@ } }, "promise-queue": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.3.tgz", - "integrity": "sha1-hTTXa/RnPDuqOoK7oBvSlcww8U8=" + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", + "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, "proto-list": { "version": "1.2.4", @@ -5322,9 +5332,9 @@ } }, "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.4.tgz", + "integrity": "sha1-oPYGyq4qO4YrvQ74VILAElsxX6M=", "requires": { "deep-extend": "0.4.2", "ini": "1.3.5", @@ -5420,7 +5430,7 @@ "lodash": "4.17.4", "lodash-es": "4.17.4", "loose-envify": "1.3.1", - "symbol-observable": "1.1.0" + "symbol-observable": "1.2.0" } }, "regenerator-runtime": { @@ -5439,11 +5449,11 @@ } }, "registry-auth-token": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", - "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", "requires": { - "rc": "1.2.2", + "rc": "1.2.4", "safe-buffer": "5.1.1" } }, @@ -5452,7 +5462,7 @@ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { - "rc": "1.2.2" + "rc": "1.2.4" } }, "remarkable": { @@ -5533,7 +5543,7 @@ "stringstream": "0.0.5", "tough-cookie": "2.3.3", "tunnel-agent": "0.4.3", - "uuid": "3.1.0" + "uuid": "3.2.1" }, "dependencies": { "form-data": { @@ -5560,9 +5570,9 @@ "dev": true }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", "dev": true } } @@ -5694,16 +5704,16 @@ } }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { - "semver": "5.4.1" + "semver": "5.5.0" } }, "semver-regex": { @@ -6002,9 +6012,9 @@ } }, "symbol-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", - "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "symbol-tree": { "version": "3.2.2", @@ -6118,7 +6128,7 @@ "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", "requires": { "bl": "1.2.1", - "end-of-stream": "1.4.0", + "end-of-stream": "1.4.1", "readable-stream": "2.3.3", "xtend": "4.0.1" } diff --git a/package.json b/package.json index 6c45371f694..ec049036b40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.25.0", + "version": "1.26.0", "engines": { "node": ">=4.0" }, diff --git a/tests/templates/integration-test-template b/tests/templates/integration-test-template index 468532a571b..4bbd065d859 100755 --- a/tests/templates/integration-test-template +++ b/tests/templates/integration-test-template @@ -37,5 +37,10 @@ serverless deploy -v echo "Invoking Service" serverless invoke --function hello +if [ $template == "aws-go" ] || [ $template == "aws-go-dep" ] +then + serverless invoke --function world +fi + echo "Removing Service" serverless remove -v diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates index 79bbebd5530..fd88105abf8 100755 --- a/tests/templates/test_all_templates +++ b/tests/templates/test_all_templates @@ -10,6 +10,8 @@ function integration-test { integration-test aws-csharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' integration-test aws-fsharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' +integration-test aws-go 'cd /go/src/app && make build' +integration-test aws-go-dep 'cd /go/src/app && make build' integration-test aws-groovy-gradle ./gradlew build integration-test aws-java-gradle ./gradlew build integration-test aws-java-maven mvn package From 9b538167166ebdd3831f3c3bf05f5cc66f35da69 Mon Sep 17 00:00:00 2001 From: jeffnoehren Date: Wed, 7 Feb 2018 16:36:20 -0800 Subject: [PATCH 070/125] Spotinst - reformatting docs to be more readable --- docs/providers/spotinst/README.md | 6 +- .../spotinst/guide/active-versions.md | 2 +- docs/providers/spotinst/guide/cors.md | 4 +- .../spotinst/guide/document-store-sdk.md | 72 ------- .../spotinst/guide/document-store.md | 175 --------------- docs/providers/spotinst/guide/endpoint-api.md | 202 ------------------ .../spotinst/guide/endpoint-setup.md | 85 -------- docs/providers/spotinst/guide/endpoints.md | 32 +++ docs/providers/spotinst/guide/variables.md | 2 +- 9 files changed, 38 insertions(+), 542 deletions(-) delete mode 100644 docs/providers/spotinst/guide/document-store-sdk.md delete mode 100644 docs/providers/spotinst/guide/document-store.md delete mode 100644 docs/providers/spotinst/guide/endpoint-api.md delete mode 100644 docs/providers/spotinst/guide/endpoint-setup.md create mode 100644 docs/providers/spotinst/guide/endpoints.md diff --git a/docs/providers/spotinst/README.md b/docs/providers/spotinst/README.md index cafa3140887..1667f9cc923 100755 --- a/docs/providers/spotinst/README.md +++ b/docs/providers/spotinst/README.md @@ -29,10 +29,8 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Credentials
  • Serverless.yml Reference
  • Variables
  • -
  • Document Store API
  • -
  • Document Store SDK
  • -
  • Endpoint Set Up
  • -
  • Endpoint API Documentation
  • +
  • Endpoints
  • +
  • Cross-Origin Resource Sharing
  • Active Versions Documentation
  • diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 50ffa48a157..b6d0e239b04 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md index a0e6cf0f5a8..091298c75ae 100644 --- a/docs/providers/spotinst/guide/cors.md +++ b/docs/providers/spotinst/guide/cors.md @@ -1,7 +1,7 @@ @@ -16,7 +16,7 @@ Cross-Origin Resource Sharing is a mechanism that allows restricted resources on You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. Example CORS object: -```yaml +```yml cors: - enabled: true origin: "http://foo.example" diff --git a/docs/providers/spotinst/guide/document-store-sdk.md b/docs/providers/spotinst/guide/document-store-sdk.md deleted file mode 100644 index 0ffa6b13d8a..00000000000 --- a/docs/providers/spotinst/guide/document-store-sdk.md +++ /dev/null @@ -1,72 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/document-store-sdk) - - -# Spotinst Functions - Document Store SDK - -We have encapsulated the Document Store API calls for retrieving your documents so you will not have to make an API call within the given function. This will allow for you as the user to access your documents using thegetDoc/get_doc method located in the context variable. Additionally this will also eliminate the need for authentication within the function for accessing your documents. - -## Node -```basg -module.exports.main = (event, context, callback) => { - context.getDoc("myKey", function(err, res) { - if(res) { - console.log('res: ' + res); //myValue - var body = { - res: res - }; - - callback(null, { - statusCode: 200, - body: JSON.stringify(body), - headers: {"Content-Type": "application/json"} - }); - } - }); -} -``` - -## Python -```bash -def main(event, context): - print ('context: %s' % context) - - doc = context.get_doc('myKey') - print(doc) #myValue - - res = { - 'statusCode': 200, - 'body': 'res: %s' % doc, - 'headers': {"Content-Type": "application/json"} - } - return res -``` - -## Java 8 -```bash -public class Java8Template implements RequestHandler { - @Override - public Response handleRequest(Request request, Context context) { - String value = context.getDoc("myKey"); - System.out.println(value); //myValue - - Response response = new Response(200, String.format("value: %s", value)); - - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - - response.setHeaders(headers); - - return response; - } -} -``` - diff --git a/docs/providers/spotinst/guide/document-store.md b/docs/providers/spotinst/guide/document-store.md deleted file mode 100644 index f5887f5939b..00000000000 --- a/docs/providers/spotinst/guide/document-store.md +++ /dev/null @@ -1,175 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) - - -# Spotinst Functions - Document Store API - -Document Store is a way for you to save information across function calls within the same environment without having to access an external database. It is secured by your Spotinst account credentials and can only be accesses within a function. Because you do not need to access an external database you are able to fetch stored documents with low latency (< ~5ms) - -To access the document store you will need to make an API request inside a funciton formatted bellow. - -## Add New Value - -This is how to insert a new key/value pair into your document store in a specific environment - -**HTTPS Request:** - -```bash -POST environment/${environmentId}/userDocument?accountId=${accountId} -``` - -**Host:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - -**Body:** - -```bash -{ - "userDocument" : { - "key": “${Your Key}”, - "value": “${Your Value}” - } -} -``` - - -## Update Value - -This is how to update a current key/value pair in your document store in a specific environment - -**HTTPS Request:** - -```bash -PUT environment/${environmentId}/userDocument/${Key}?accountId=${accountId} -``` - -**Endpoint:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - -**Body:** - -```bash -{ - "userDocument" : { - "value": “${Your Value}” - } -} -``` - - -## Get Values - -There are two ways to get the documents from your store, either by specifing a key which will return both the key and the value or you can just leave this out and you will get all the keys in the environment - -### 1. Get Sinlge Key Pair - -**HTTPS Request:** - -```bash -GET environment/${environmentId}/userDocument/${Key}?accountId=${accountId} -``` - -**Endpoint:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - -### 2. Get All Keys - -**HTTPS Request:** - -```bash -GET environment/${environmentId}/userDocument?accountId=${accountId} -``` - -**Endpoint:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - - -## Delete Value - -This is how to delete a specific key value pair from your document store - -**HTTPS Request:** - -```bash -DELETE environment/${environmentId}/userDocument/${Key}?accountId=${accountId} -``` - -**Endpoint:** - -```bash -https://api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - - -## GitHub - -Check out some examples to help you get started! - -[Get All Values Function](https://github.com/spotinst/spotinst-functions-examples/tree/master/node-docstore-getAll) - -[Insert New Value Function](https://github.com/spotinst/spotinst-functions-examples/tree/master/node-docstore-newValue) \ No newline at end of file diff --git a/docs/providers/spotinst/guide/endpoint-api.md b/docs/providers/spotinst/guide/endpoint-api.md deleted file mode 100644 index 33790666268..00000000000 --- a/docs/providers/spotinst/guide/endpoint-api.md +++ /dev/null @@ -1,202 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) - - -# Spotinst Functions - Endpoint API Documentation - -Here is the full list of API calls that you can make to set alias and patterns. Please check out the full article on Setting Up Endpoints first because it will make more sense. - -## Alias -### Create Alias -Create a new alias to point to your environment - -#### HTTPS Request -```bash -POST alias?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Body -```bash -{ - "alias": { - "host": "myAlias.com", - "environmentId": ${Environment ID} - } -} -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json - ``` - -### Get Alias -Returns a single alias - -#### HTTPS Request -```bash -GET alias/${Alias ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Get All Alias -Returns all the alias in your account - -#### HTTPS Request -```bash -GET alias?accountId=${accountId} -``` -##### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Delete Alias -Deletes a single alias - -#### HTTPS Request -```bash -DELETE alias/${Alias ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - - -## Pattern -### Create Pattern -Create a new pattern that maps to a function - -#### HTTPS Request -```bash -POST pattern?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Body -```bash -{ - "pattern": { - "environmentId":${Environment ID}, - "method": "ALL", - "pattern": "/*", - "functionId": ${Function ID} - } -} -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Update Pattern -Update and existing pattern - -#### HTTPS Request -```bash -PUT pattern/${Pattern ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Body -```bash -{ - "pattern": { - "environmentId":${Environment ID}, - "method": "ALL", - "pattern": "/*", - "functionId": ${Function ID} - } -} -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Get Pattern -Returns a single pattern - -#### HTTPS Request -```bash -GET pattern/${Pattern ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Get All Patterns -Returns all the patterns your account - -#### HTTPS Request -```bash -POST pattern?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Delete Pattern -Delete a single pattern - -#### HTTPS Request -```bash -DELETE pattern/${Pattern ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` diff --git a/docs/providers/spotinst/guide/endpoint-setup.md b/docs/providers/spotinst/guide/endpoint-setup.md deleted file mode 100644 index 19d3c5568ac..00000000000 --- a/docs/providers/spotinst/guide/endpoint-setup.md +++ /dev/null @@ -1,85 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) - - -# Spotinst Functions - Endpoint Setup - -You are able to set an alias URL name as an endpoint for your serverless function to make it more accessible to your users. The way this works is you will point the domain of your choosing to your environment URL's then you will set paths to each of the functions in that environment you wish to bundle in together. To do this you will first need a valid domain. For this example I will be using 'myAlias.com'. - -## Set DNS Record -First you will want to create a DNS record set that will point to your environment URL. Your environment URL can be found in the Spotinst console. When you select the environment you wish to connect you will see a list of functions and their individual URL's. Like this -```bash -https://app-123xyz-raffleapp-execute-function1.spotinst.io/fx-abc987 -``` -We only want the URL starting at app and ending before the function id. Like this -```bash -app-123xyz-raffleapp-execute-function1.spotinst.io -``` -With this you will need to go to a DNS record setter and point your domain to this URL. I used AWS Route 53 to set this up. - -## Set Alias -Next you will need to set the alias in your Spotinst environment by making an API call. This does not need to be done within a function and can be set anyway you are most comfortable. The API request is connecting your domain the environment that you want. This is the API request - -### HTTPS Request -```bash -POST alias?accountId=${accountId} -``` -### Host -```bash -api.spotinst.io/functions/ -``` -### Body -```bash -{ - "alias": { - "host": "myAlias.com", - "environmentId": ${Your Environment ID} - } -} -``` -### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -**Note:** You are able to connect multiple alias to the same environment - -## Set Up Pattern -After you have an alias set up you will need to set up pattern to connect to all the functions in the application. This is again another API call and can be done from anywhere. You specify the pattern that you want, the method that will trigger the function, the function ID and the environment ID. The pattern is what will appear after the domain. For example '/home' would point to 'myAlias.com/home'. The methods you can select are any of the usual HTTP request methods: GET, PUT, POST, DELETE , OPTIONS, PATCH, ALL where “ALL” matches every method - -### HTTPS Request -```bash -POST pattern?accountId=${accountId} -``` -### Host -```bash -api.spotinst.io/functions/ -``` -### Body -``` bash -{ - "pattern": { - "environmentId": ${Your Environment ID}, - "method": "ALL", - "pattern": "/*", - "functionId": ${Your Function ID} - } -} -``` -### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -## API Documentation -The full API documentation has information like delete and get alias and patterns. Check it out [here](./endpoint-api.md) diff --git a/docs/providers/spotinst/guide/endpoints.md b/docs/providers/spotinst/guide/endpoints.md new file mode 100644 index 00000000000..004a8077e1c --- /dev/null +++ b/docs/providers/spotinst/guide/endpoints.md @@ -0,0 +1,32 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) + + +# Spotinst Functions - Endpoints + +You are able to set your custom endpoint path in the serverless.yml file if you do not want to use the console or API. You will have to set up your environment Alias in the console but here you can set the path and method for your individual functions to be mapped to. + +Here is a sample function from a yml file. As you can see at the bottom of the file we have listed an endpoint with a path and method. Both of these will need to be set in order to be deployed properly + +```yml + hello: + runtime: nodejs8.3 + handler: handler.main + memory: 128 + timeout: 30 + access: public + endpoint: + path: /home + method: get + +``` + +For more information on how to set up endpoint alias and patterns check out our documentation [here](https://help.spotinst.com/hc/en-us/articles/115005893709) \ No newline at end of file diff --git a/docs/providers/spotinst/guide/variables.md b/docs/providers/spotinst/guide/variables.md index 69411d98a4e..02db0976abf 100644 --- a/docs/providers/spotinst/guide/variables.md +++ b/docs/providers/spotinst/guide/variables.md @@ -39,7 +39,7 @@ URL parameters can be use when a POST request is made to the endpoint of your fu ### 1. Node JS -To access URL parameters in your NodeJS code you just need to put `event.query.['{Your Parameter Name}']` as needed +To access URL parameters in your NodeJS code you just need to put `event.query['{Your Parameter Name}']` as needed ### 2. Python From 037a9de63e4ee6aa349a35f074e175389b1ceb90 Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Thu, 8 Feb 2018 08:51:06 +0000 Subject: [PATCH 071/125] Google docs intro typo --- docs/providers/google/guide/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/google/guide/intro.md b/docs/providers/google/guide/intro.md index 4fbf37d6a16..6f03ea68423 100644 --- a/docs/providers/google/guide/intro.md +++ b/docs/providers/google/guide/intro.md @@ -25,7 +25,7 @@ Here are the Framework's main concepts and how they pertain to Google Cloud Func ### Functions -A Function is an [Google Cloud Function](https://cloud.google.com/functions/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: +A Function is a [Google Cloud Function](https://cloud.google.com/functions/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: - *Saving a user to the database* - *Processing a file in a database* From bd6b48ab8572fdd856a68bf1df0dd93e0793b9cc Mon Sep 17 00:00:00 2001 From: Simon Males Date: Thu, 8 Feb 2018 15:26:26 +0100 Subject: [PATCH 072/125] Enable TypeScript file source map support in TypeScript template --- .../create/templates/aws-nodejs-typescript/handler.ts | 1 + .../create/templates/aws-nodejs-typescript/package.json | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts index 7dfcf29e08c..02ddeddcfa4 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 { APIGatewayEvent, Callback, Context, Handler } from 'aws-lambda'; +import "source-map-support/register"; export const hello: Handler = (event: APIGatewayEvent, context: Context, cb: Callback) => { const response = { diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index 24d137ae93a..dca32542eab 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -15,5 +15,8 @@ "webpack": "^3.6.0" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", - "license": "MIT" -} \ No newline at end of file + "license": "MIT", + "dependencies": { + "source-map-support": "^0.5.3" + } +} From 396c800fffad2f9a60c291d8dc4173e1962583e6 Mon Sep 17 00:00:00 2001 From: Matheus Fidelis Date: Thu, 8 Feb 2018 18:09:02 -0200 Subject: [PATCH 073/125] Add serverless-architecture-boilerplate --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5382ea2640..7eeba49abcd 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ This table is generated from https://github.com/serverless/examples/blob/master/ --> | Project Name | Author | |:-------------|:------:| +| **[Serverless Architecture Boilerplate](https://github.com/msfidelis/serverless-architecture-boilerplate)**
    Boilerplate to organize and deploy big projects using Serverless and CloudFormation on AWS | [msfidelis](http://github.com/msfidelis) | | **[Jwtauthorizr](https://github.com/serverlessbuch/jwtAuthorizr)**
    Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | [serverlessbuch](http://github.com/serverlessbuch) | | **[Serverless Graphql Api](https://github.com/boazdejong/serverless-graphql-api)**
    Serverless GraphQL API using Lambda and DynamoDB | [boazdejong](http://github.com/boazdejong) | | **[Serverless Screenshot](https://github.com/svdgraaf/serverless-screenshot)**
    Serverless Screenshot Service using PhantomJS | [svdgraaf](http://github.com/svdgraaf) | From 8083fd5ef9ad8134ac98afc8474bafe83676915e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 8 Feb 2018 13:24:20 -0800 Subject: [PATCH 074/125] removed json from yml --- docs/providers/spotinst/guide/serverless.yml.md | 16 ++++++++++------ .../templates/spotinst-java8/serverless.yml | 7 +++---- .../templates/spotinst-nodejs/serverless.yml | 7 +++---- .../templates/spotinst-python/serverless.yml | 7 +++---- .../templates/spotinst-ruby/serverless.yml | 9 ++++----- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/providers/spotinst/guide/serverless.yml.md b/docs/providers/spotinst/guide/serverless.yml.md index f1d87daa668..797166fac52 100644 --- a/docs/providers/spotinst/guide/serverless.yml.md +++ b/docs/providers/spotinst/guide/serverless.yml.md @@ -57,12 +57,16 @@ functions: # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 -# cron: -# active: false -# value: '*/1 * * * *' -# environmentVariables: { -# Key: "Value", -# } +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default +# cron: # Setup scheduled trigger with cron expression +# active: true +# value: '* * * * *' +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index 066b918daa7..8ae47f7f69c 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -34,12 +34,11 @@ functions: # origin: # '*' by default # headers: # 'Content-Type,Authorization' by default # methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default -# cron: # Setup scheduled trigger with cron expression +# cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index 1adeac7ef7b..74d910a25c8 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -34,12 +34,11 @@ functions: # origin: # '*' by default # headers: # 'Content-Type,Authorization' by default # methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default -# cron: # Setup scheduled trigger with cron expression +# cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index 1722af08fd5..ce62ad417d2 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -34,12 +34,11 @@ functions: # origin: # '*' by default # headers: # 'Content-Type,Authorization' by default # methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default -# cron: # Setup scheduled trigger with cron expression +# cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 4c1807e228d..60b6373839b 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -35,11 +35,10 @@ functions: # headers: # 'Content-Type,Authorization' by default # methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression -# active: true -# value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# active: true +# value: '* * * * *' +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins From c1bd397b4c001941f315de2a9e9781aefeec4926 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 8 Feb 2018 14:41:37 -0800 Subject: [PATCH 075/125] refactored json to yml --- .../spotinst/guide/active-versions.md | 69 +++++++------------ docs/providers/spotinst/guide/intro.md | 9 ++- .../spotinst/guide/serverless.yml.md | 6 +- docs/providers/spotinst/guide/variables.md | 5 +- 4 files changed, 32 insertions(+), 57 deletions(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 50ffa48a157..58aac48d5b7 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -21,12 +21,9 @@ The 'Latest' version refers to the most recent version created by the last updat Default configuration for activeVersions when a new function is created: ``` -"activeVersions": [ - { - "version": "$LATEST", - "percentage": 100.0 - } -] +activeVersions: + - version: $LATEST + percentage: 100.0 ``` ## Active Version @@ -36,45 +33,30 @@ For example, say you wanted to test a new version of your function to determine ### Examples ``` -"activeVersions": [ - { - "version": "$LATEST", - "percentage": 100.0 - } -] +activeVersions: + - version: $LATEST + percentage: 100.0 ``` 100% of traffic will go to the most recently published update. ``` -"activeVersions": [ - { - "version": "$LATEST", - "percentage": 80.0 - }, - { - "version": "2", - "percentage": 20.0 - } -] +activeVersions: + - version: $LATEST + percentage: 80.0 + - version: 2 + percentage: 20.0 ``` 80% of traffic goes to the most recently published update, and 20% goes to version 2. ``` -"activeVersions": [ - { - "version": "5", - "percentage": 50.0 - }, - { - "version": "3", - "percentage": 25.0 - }, - { - "version": "1", - "percentage": 25.0 - } -] +activeVersions: + - version: 5 + percentage: 50.0 + - version: 3 + percentage: 25.0 + - version: 1 + percentage: 25.0 ``` Traffic is split between versions 1. 3, and 5. Changes made to your latest version will not affect production traffic. @@ -87,16 +69,11 @@ You can configure active versions in the serverless.yml file, but you can also u - API: (update function) ``` -"activeVersions": [ - { - "version": "$LATEST", - "percentage": 30 - }, - { - "version": "2", - "percentage": 70 - } - ], +activeVersions: + - version: $LATEST + percentage: 70.0 + - version: 2 + percentage: 30.0 ``` ### Requirements diff --git a/docs/providers/spotinst/guide/intro.md b/docs/providers/spotinst/guide/intro.md index ea915470093..f6b2a1ac5ea 100644 --- a/docs/providers/spotinst/guide/intro.md +++ b/docs/providers/spotinst/guide/intro.md @@ -92,11 +92,10 @@ functions: timeout: 30 # access: private # cron: # Setup scheduled trigger with cron expression -# active: true -# value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# active: true +# value: '* * * * *' +# environmentVariables: +# key: value ``` When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. diff --git a/docs/providers/spotinst/guide/serverless.yml.md b/docs/providers/spotinst/guide/serverless.yml.md index 797166fac52..8fe0c6e1212 100644 --- a/docs/providers/spotinst/guide/serverless.yml.md +++ b/docs/providers/spotinst/guide/serverless.yml.md @@ -55,8 +55,8 @@ functions: timeout: 30 access: private # activeVersions: -# - "version": "$LATEST" -# "percentage": 100.0 +# - version: $LATEST +# percentage: 100.0 # cors: # enabled: # false by default # origin: # '*' by default @@ -66,7 +66,7 @@ functions: # active: true # value: '* * * * *' # environmentVariables: -# key: Value +# key: value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/docs/providers/spotinst/guide/variables.md b/docs/providers/spotinst/guide/variables.md index 69411d98a4e..05ab3672bb8 100644 --- a/docs/providers/spotinst/guide/variables.md +++ b/docs/providers/spotinst/guide/variables.md @@ -26,9 +26,8 @@ Also you are able to enter in environment variables in the serverless.yml file. functions: test: handler: handler.main - environmentVariables: { - Key: "Value" - } + environmentVariables: + key: value ``` To access your variables in your code you just need to put `process.env['{Your Key}']` as needed in the handler file. From 4cfa8afaf10d83bb24cbe0cdf2209b1761153c1f Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 8 Feb 2018 15:05:04 -0800 Subject: [PATCH 076/125] formatting, page order corrected --- docs/providers/spotinst/guide/active-versions.md | 10 +++++----- docs/providers/spotinst/guide/cors.md | 8 ++++---- docs/providers/spotinst/guide/document-store-sdk.md | 2 +- docs/providers/spotinst/guide/endpoint-api.md | 2 +- docs/providers/spotinst/guide/endpoint-setup.md | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 58aac48d5b7..d4734a04b1a 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -20,7 +20,7 @@ The 'Latest' version refers to the most recent version created by the last updat *Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* Default configuration for activeVersions when a new function is created: -``` +```yaml activeVersions: - version: $LATEST percentage: 100.0 @@ -32,7 +32,7 @@ The 'Active' version can point to more than one version of your function, includ For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. ### Examples -``` +```yaml activeVersions: - version: $LATEST percentage: 100.0 @@ -40,7 +40,7 @@ activeVersions: 100% of traffic will go to the most recently published update. -``` +```yaml activeVersions: - version: $LATEST percentage: 80.0 @@ -49,7 +49,7 @@ activeVersions: ``` 80% of traffic goes to the most recently published update, and 20% goes to version 2. -``` +```yaml activeVersions: - version: 5 percentage: 50.0 @@ -68,7 +68,7 @@ You can configure active versions in the serverless.yml file, but you can also u 3. Select 'Configure Active Version' - API: (update function) -``` +```yaml activeVersions: - version: $LATEST percentage: 70.0 diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md index a0e6cf0f5a8..0f57d6ddb64 100644 --- a/docs/providers/spotinst/guide/cors.md +++ b/docs/providers/spotinst/guide/cors.md @@ -1,8 +1,8 @@ diff --git a/docs/providers/spotinst/guide/document-store-sdk.md b/docs/providers/spotinst/guide/document-store-sdk.md index 0ffa6b13d8a..80dda09971d 100644 --- a/docs/providers/spotinst/guide/document-store-sdk.md +++ b/docs/providers/spotinst/guide/document-store-sdk.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/spotinst/guide/endpoint-api.md b/docs/providers/spotinst/guide/endpoint-api.md index 33790666268..e5b86b368cf 100644 --- a/docs/providers/spotinst/guide/endpoint-api.md +++ b/docs/providers/spotinst/guide/endpoint-api.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/spotinst/guide/endpoint-setup.md b/docs/providers/spotinst/guide/endpoint-setup.md index 19d3c5568ac..3a1737edf8a 100644 --- a/docs/providers/spotinst/guide/endpoint-setup.md +++ b/docs/providers/spotinst/guide/endpoint-setup.md @@ -1,7 +1,7 @@ From d4598f758f212f4dfd4dbff67e863b82ddd08c38 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 8 Feb 2018 15:25:05 -0800 Subject: [PATCH 077/125] formatting --- docs/providers/spotinst/guide/cors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md index cfba1183a34..485c71dfd53 100644 --- a/docs/providers/spotinst/guide/cors.md +++ b/docs/providers/spotinst/guide/cors.md @@ -15,7 +15,7 @@ Cross-Origin Resource Sharing is a mechanism that allows restricted resources on You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. -Example CORS object: +### Example CORS object: ```yml cors: - enabled: true @@ -24,7 +24,7 @@ Example CORS object: methods: "PUT,POST" ``` -Parameters: +### Parameters: - enabled: Boolean - Specify if CORS is enabled for the function. - default: false From 4218e77bcaff90e703aca639dbb3a477ca69923c Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 8 Feb 2018 15:29:34 -0800 Subject: [PATCH 078/125] updated description --- docs/providers/spotinst/guide/cors.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md index 485c71dfd53..95e23a9b54d 100644 --- a/docs/providers/spotinst/guide/cors.md +++ b/docs/providers/spotinst/guide/cors.md @@ -11,8 +11,7 @@ layout: Doc # Spotinst Functions - CORS -Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify domains that are safe, and enables out-of-the-box support for preflight HTTP request (via the OPTIONS method) will return the needed ‘access-control-’ headers specified below, and the actual HTTP request will return the ‘access-control-allow-origin’ method. - +Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify safe domains, and enables out-of-the-box support for preflight HTTP requests (via the OPTIONS method) that will return the needed ‘access-control-*’ headers specified below. The actual HTTP request will return the ‘access-control-allow-origin’ method. You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. ### Example CORS object: From edd863be01044b211db0d86800c31a456dc33fc5 Mon Sep 17 00:00:00 2001 From: Christopher Osborn Date: Mon, 12 Feb 2018 16:54:00 -0500 Subject: [PATCH 079/125] Wraps Poorly Parsing URL in Angle Brackets As suggested by . [#4674] --- 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 6eae516f637..7d53486c93e 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -250,7 +250,7 @@ class AwsProvider { const errorMessage = [ 'AWS provider credentials not found.', ' Learn how to set up AWS provider credentials', - ` in our docs here: ${chalk.green('http://bit.ly/aws-creds-setup')}.`, + ` in our docs here: <${chalk.green('http://bit.ly/aws-creds-setup')}>.`, ].join(''); message = errorMessage; userStats.track('user_awsCredentialsNotFound'); From 4400ffc9e4c4c04802e32ee7dd25b9cbc3463c37 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Fri, 16 Feb 2018 17:13:03 -0800 Subject: [PATCH 080/125] fix #4311 & #4734, separate PromiseTracker, minutiae #4311 see prepopulateService - attempts to pre-populate the region and stage settings necessary for making a request to AWS, rejecting and dependencies thereon it runs into during that process see the `deep` variable work. this was a knock-on effect of providing pre-population. it actually represents an obscure class of bugs where the recursive population previously in getDeepValue caused the caller not to be in charge of population choices (thus fixing for pre-population) but also caused potential deadlocks resulting from getDeepValue creating circular dependencies. #4734 see splitByComma - a regex to do the splitting but ignore quoted commas was getting very deep and involved. Instead, identify quoted string boundaries, then identify commas (including white space around them) and take those commas not contained by quotes as the locations for splitting the given string. Trim the string as well (removing leading and trailing white space). add sophistication to the overwrite syntax, allowing for whitespace and repetitions (for robustness) add sophistication to the string ref syntax, allowing for use in identifying multiple quoted strings in a variable (i.e. for overwrites) #NotCreated fix a bug I created earlier in the branch that caused reporting to be less informative (see renderMatches) separate PromiseTracker move this class into its own file for the purpose of increasing testability and offering reuse minutiae filter the properties given to populateVariables so as to avoid attempting resolution on non-variables. Efficiency and noise reduction change. Also cleans up the code (e.g. see populateObject and its use) cleaning of overwrite as a side effect offer variable cleaning as a idiom reorder the Variables.js `require`s to be in alphabetical order --- lib/classes/PromiseTracker.js | 52 ++++ lib/classes/PromiseTracker.test.js | 62 ++++ lib/classes/Variables.js | 284 +++++++++++++------ lib/classes/Variables.test.js | 156 +++++++++- lib/plugins/aws/provider/awsProvider.js | 40 ++- lib/plugins/aws/provider/awsProvider.test.js | 237 ++++++++++------ 6 files changed, 630 insertions(+), 201 deletions(-) create mode 100644 lib/classes/PromiseTracker.js create mode 100644 lib/classes/PromiseTracker.test.js diff --git a/lib/classes/PromiseTracker.js b/lib/classes/PromiseTracker.js new file mode 100644 index 00000000000..f19424788b3 --- /dev/null +++ b/lib/classes/PromiseTracker.js @@ -0,0 +1,52 @@ + +const logWarning = require('./Error').logWarning; + +class PromiseTracker { + constructor() { + this.promiseList = []; + this.promiseMap = {}; + this.startTime = Date.now(); + } + start() { + this.interval = setInterval(this.report.bind(this), 2500); + } + report() { + const delta = Date.now() - this.startTime; + logWarning('################################################################################'); + logWarning(`# ${delta}: ${this.getSettled().length} of ${ + this.getAll().length} promises have settled`); + const pending = this.getPending(); + logWarning(`# ${delta}: ${pending.length} unsettled promises:`); + pending.forEach((promise) => { + logWarning(`# ${delta}: ${promise.waitList}`); + }); + logWarning('################################################################################'); + } + stop() { + clearInterval(this.interval); + } + add(variable, prms, specifier) { + const promise = prms; + promise.waitList = `${variable} waited on by: ${specifier}`; + promise.state = 'pending'; + promise.then( // creates a promise with the following effects but that we otherwise ignore + () => { promise.state = 'resolved'; }, + () => { promise.state = 'rejected'; }); + this.promiseList.push(promise); + this.promiseMap[variable] = promise; + return promise; + } + contains(variable) { + return variable in this.promiseMap; + } + get(variable, specifier) { + const promise = this.promiseMap[variable]; + promise.waitList += ` ${specifier}`; + return promise; + } + getPending() { return this.promiseList.filter(p => (p.state === 'pending')); } + getSettled() { return this.promiseList.filter(p => (p.state !== 'pending')); } + getAll() { return this.promiseList; } +} + +module.exports = PromiseTracker; diff --git a/lib/classes/PromiseTracker.test.js b/lib/classes/PromiseTracker.test.js new file mode 100644 index 00000000000..94d9a3c7676 --- /dev/null +++ b/lib/classes/PromiseTracker.test.js @@ -0,0 +1,62 @@ +'use strict'; + +/* eslint-disable no-unused-expressions */ + +const BbPromise = require('bluebird'); +const chai = require('chai'); + +const PromiseTracker = require('../../lib/classes/PromiseTracker'); + +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + +/** + * Mostly this class is tested by its use in peer ~/lib/classes/Variables.js + * + * Mostly, I'm creating coverage but if errors are discovered, coverage for the specific cases + * can be created here. + */ +describe('PromiseTracker', () => { + let promiseTracker; + beforeEach(() => { + promiseTracker = new PromiseTracker(); + }); + it('logs a warning without throwing', () => { + promiseTracker.add('foo', BbPromise.resolve(), '${foo:}'); + promiseTracker.add('foo', BbPromise.delay(10), '${foo:}'); + promiseTracker.report(); // shouldn't throw + }); + it('reports no pending promises when none have been added', () => { + const promises = promiseTracker.getPending(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(0); + }); + it('reports one pending promise when one has been added', () => { + const promise = BbPromise.delay(10); + promiseTracker.add('foo', promise, '${foo:}'); + const promises = promiseTracker.getPending(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); + }); + it('reports no settled promises when none have been added', () => { + const promises = promiseTracker.getSettled(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(0); + }); + it('reports one settled promise when one has been added', () => { + const promise = BbPromise.resolve(); + promiseTracker.add('foo', promise, '${foo:}'); + return BbPromise.delay(1).then(() => { + const promises = promiseTracker.getSettled(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); + }); + }); + it('reports no promises when none have been added', () => { + const promises = promiseTracker.getAll(); + expect(promises).to.be.an('array').that.is.empty; + }); +}); diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index ffbe65f3ac3..4c19f43c712 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -1,60 +1,14 @@ 'use strict'; const _ = require('lodash'); -const path = require('path'); -const replaceall = require('replaceall'); -const logWarning = require('./Error').logWarning; const BbPromise = require('bluebird'); const os = require('os'); -const fse = require('../utils/fs/fse'); +const path = require('path'); +const replaceall = require('replaceall'); -class PromiseTracker { - constructor() { - this.promiseList = []; - this.promiseMap = {}; - this.startTime = Date.now(); - } - start() { - this.interval = setInterval(this.report.bind(this), 2500); - } - report() { - const delta = Date.now() - this.startTime; - logWarning('################################################################################'); - logWarning(`# ${delta}: ${this.getSettled().length} of ${ - this.getAll().length} promises have settled`); - const pending = this.getPending(); - logWarning(`# ${delta}: ${pending.length} unsettled promises:`); - pending.forEach((promise) => { - logWarning(`# ${delta}: ${promise.waitList}`); - }); - logWarning('################################################################################'); - } - stop() { - clearInterval(this.interval); - } - add(variable, prms, specifier) { - const promise = prms; - promise.waitList = `${variable} waited on by: ${specifier}`; - promise.state = 'pending'; - promise.then( // creates a promise with the following effects but that we otherwise ignore - () => { promise.state = 'resolved'; }, - () => { promise.state = 'rejected'; }); - this.promiseList.push(promise); - this.promiseMap[variable] = promise; - return promise; - } - contains(variable) { - return variable in this.promiseMap; - } - get(variable, specifier) { - const promise = this.promiseMap[variable]; - promise.waitList += ` ${specifier}`; - return promise; - } - getPending() { return this.promiseList.filter(p => (p.state === 'pending')); } - getSettled() { return this.promiseList.filter(p => (p.state !== 'pending')); } - getAll() { return this.promiseList; } -} +const fse = require('../utils/fs/fse'); +const logWarning = require('./Error').logWarning; +const PromiseTracker = require('./PromiseTracker'); class Variables { constructor(serverless) { @@ -62,14 +16,16 @@ class Variables { this.service = this.serverless.service; this.tracker = new PromiseTracker(); - this.overwriteSyntax = RegExp(/,/g); + this.deep = []; + this.deepRefSyntax = RegExp(/^(\${)?deep:\d+(\.[^}]+)*()}?$/); + this.overwriteSyntax = RegExp(/\s*(?:,\s*)+/g); this.fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-/]+?)\)/g); this.envRefSyntax = RegExp(/^env:/g); this.optRefSyntax = RegExp(/^opt:/g); this.selfRefSyntax = RegExp(/^self:/g); this.cfRefSyntax = RegExp(/^cf:/g); this.s3RefSyntax = RegExp(/^s3:(.+?)\/(.+)$/); - this.stringRefSyntax = RegExp(/('.*')|(".*")/g); + this.stringRefSyntax = RegExp(/(?:('|").*?\1)/g); this.ssmRefSyntax = RegExp(/^ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); } @@ -79,6 +35,68 @@ class Variables { // ############# // ## SERVICE ## // ############# + prepopulateService() { + const dependentServices = [ + { name: 'CloudFormation', regex: this.cfRefSyntax }, + { name: 'S3', regex: this.s3RefSyntax }, + { name: 'SSM', regex: this.ssmRefSyntax }, + ]; + const dependencyMessage = (configName, configValue, serviceName) => + `Variable Failure: value ${ + configName + } set to '${ + configValue + }' references ${ + serviceName + } which requires a ${ + configName + } value for use.`; + const getVariableParts = (variableString) => { + const matches = this.getMatches(variableString); + return matches.reduce( + (accumulation, current) => + accumulation.concat(this.splitByComma(current.variable)), + []); + }; + const serviceMatch = variablePart => + dependentServices.find((service) => { + let variable = variablePart; + if (variable.match(this.deepRefSyntax)) { + variable = this.getVariableFromDeep(variablePart); + } + return variable.match(service.regex); + }); + const getUntilValid = (config) => { + const parts = getVariableParts(config.value); + const service = parts.reduce( + (accumulation, part) => accumulation || serviceMatch(part), + undefined); + if (service) { + const msg = dependencyMessage(config.name, config.value, service.name); + return BbPromise.reject(new this.serverless.classes.Error(msg)); + } + return this.populateValue(config.value, false) + .then(populated => ( + populated.match(this.variableSyntax) ? + getUntilValid(_.assign(config, { value: populated })) : + _.assign({}, config, { populated }) + )); + }; + + const provider = this.serverless.getProvider('aws'); + if (provider) { + const requiredConfig = [ + _.assign({ name: 'region' }, provider.getRegionSourceValue()), + _.assign({ name: 'stage' }, provider.getStageSourceValue()), + ]; + const configToPopulate = requiredConfig.filter(config => + !_.isUndefined(config.value) && + (_.isString(config.value) && config.value.match(this.variableSyntax))); + const configPromises = configToPopulate.map(getUntilValid); + return this.assignProperties(provider, configPromises); + } + return BbPromise.resolve(); + } /** * Populate all variables in the service, conviently remove and restore the service attributes * that confuse the population methods. @@ -95,7 +113,8 @@ class Variables { this.service.provider.variableSyntax = undefined; // otherwise matches itself this.service.serverless = undefined; this.tracker.start(); - return this.populateObject(this.service) + return this.prepopulateService() + .then(() => this.populateObject(this.service)) .finally(() => { // restore this.tracker.stop(); @@ -179,10 +198,13 @@ class Variables { * @returns {Promise[]} The promises that will resolve to the * populated values of the given terminal properties */ - populateProperties(properties) { - return _.map(properties, property => - this.populateValue(property.value, false) - .then(populated => _.assign({}, property, { populated }))); + populateVariables(properties) { + const variables = properties.filter(property => + _.isString(property.value) && + property.value.match(this.variableSyntax)); + return _.map(variables, + variable => this.populateValue(variable.value, false) + .then(populated => _.assign({}, variable, { populated }))); } /** * Assign the populated values back to the target object @@ -193,16 +215,11 @@ class Variables { */ assignProperties(target, populations) { // eslint-disable-line class-methods-use-this return BbPromise.all(populations) - .then((results) => { - let changes = 0; - results.forEach((result) => { - if (result.value !== result.populated) { - changes += 1; - _.set(target, result.path, result.populated); - } - }); - return BbPromise.resolve(changes); - }); + .then((results) => results.forEach((result) => { + if (result.value !== result.populated) { + _.set(target, result.path, result.populated); + } + })); } /** * Populate the variables in the given object. @@ -211,18 +228,27 @@ class Variables { */ populateObject(objectToPopulate) { const leaves = this.getProperties(objectToPopulate, true, objectToPopulate); - const populations = this.populateProperties(leaves); + const populations = this.populateVariables(leaves); return this.assignProperties(objectToPopulate, populations) - .then((changes) => { - if (changes) { - return this.populateObject(objectToPopulate); - } - return objectToPopulate; - }); + .then(() => (populations.length ? + this.populateObject(objectToPopulate) : + objectToPopulate)); } // ############## // ## PROPERTY ## // ############## + /** + * Standard logic for cleaning a variable + * Example: cleanVariable('${opt:foo}') => 'opt:foo' + * @param match The variable match instance variable part + * @returns {string} The cleaned variable match + */ + cleanVariable(match) { + return match.replace( + this.variableSyntax, + (context, contents) => contents.trim() + ).replace(/\s/g, ''); + } /** * @typedef {Object} MatchResult * @property {String} match The original property value that matched the variable syntax @@ -244,8 +270,7 @@ class Variables { } return _.map(matches, match => ({ match, - variable: match.replace(this.variableSyntax, (context, contents) => contents.trim()) - .replace(/\s/g, ''), + variable: this.cleanVariable(match), })); } /** @@ -256,10 +281,11 @@ class Variables { */ populateMatches(matches) { return _.map(matches, (match) => { - if (match.variable.match(this.overwriteSyntax)) { - return this.overwrite(match.variable); + const parts = this.splitByComma(match.variable); + if (parts.length > 1) { + return this.overwrite(parts, match.match); } - return this.getValueFromSource(match.variable, match.match); + return this.getValueFromSource(parts[0], match.match); }); } /** @@ -272,7 +298,7 @@ class Variables { renderMatches(value, matches, results) { let result = value; for (let i = 0; i < matches.length; i += 1) { - this.warnIfNotFound(matches[i].match, results[i]); + this.warnIfNotFound(matches[i].variable, results[i]); result = this.populateVariable(result, matches[i].match, results[i]); } return result; @@ -340,16 +366,57 @@ class Variables { // ## VARIABLES ## // ############### /** - * Overwrite the given variable string, resolve each variable and resolve to the first valid - * value. + * Split a given string by whitespace padded commas excluding those within single or double quoted + * strings. + * @param string The string to split by comma. + */ + splitByComma(string) { + const input = string.trim(); + const stringMatches = []; + let match = this.stringRefSyntax.exec(input); + while (match) { + stringMatches.push({ + start: match.index, + end: this.stringRefSyntax.lastIndex, + }); + match = this.stringRefSyntax.exec(input); + } + const commaReplacements = []; + const contained = commaMatch => // curry the current commaMatch + stringMatch => // check whether stringMatch containing the commaMatch + stringMatch.start < commaMatch.index && + this.overwriteSyntax.lastIndex < stringMatch.end; + match = this.overwriteSyntax.exec(input); + while (match) { + const matchContained = contained(match); + const containedBy = stringMatches.find(matchContained); + if (!containedBy) { // if uncontained, this comma respresents a splitting location + commaReplacements.push({ + start: match.index, + end: this.overwriteSyntax.lastIndex, + }); + } + match = this.overwriteSyntax.exec(input); + } + let prior = 0; + const results = []; + commaReplacements.forEach((replacement) => { + results.push(input.slice(prior, replacement.start)); + prior = replacement.end; + }); + results.push(input.slice(prior)); + return results; + } + /** + * Resolve the given variable string that expresses a series of fallback values in case the + * initial values are not valid, resolving each variable and resolving to the first valid value. * @param variableStringsString The overwrite string of variables to populate and choose from. * @returns {Promise.|*} A promise resolving to the first validly populating variable * in the given variable strings string. */ - overwrite(variableStringsString) { - const variableStrings = variableStringsString.split(','); + overwrite(variableStrings, propertyString) { const variableValues = variableStrings.map(variableString => - this.getValueFromSource(variableString, variableStringsString)); + this.getValueFromSource(variableString, propertyString)); const validValue = value => ( value !== null && typeof value !== 'undefined' && @@ -385,6 +452,8 @@ class Variables { ret = this.getValueFromString(variableString); } else if (variableString.match(this.ssmRefSyntax)) { ret = this.getValueFromSsm(variableString); + } else if (variableString.match(this.deepRefSyntax)) { + ret = this.getValueFromDeep(variableString); } else { const errorMessage = [ `Invalid variable reference syntax for variable ${variableString}.`, @@ -585,17 +654,52 @@ class Variables { }); } + getDeepIndex(variableString) { + const deepIndexReplace = RegExp(/^deep:|(\.[^}]+)*$/g); + return variableString.replace(deepIndexReplace, ''); + } + getVariableFromDeep(variableString) { + const index = this.getDeepIndex(variableString); + return this.deep[index]; + } + getValueFromDeep(variableString) { + const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g); + const variable = this.getVariableFromDeep(variableString); + const deepRef = variableString.replace(deepPrefixReplace, ''); + const sourceString = `\${deep:\${${variable}}${deepRef.length ? `.${deepRef}` : ''}}`; + return this.getValueFromSource(variable, sourceString); + } + getDeepValue(deepProperties, valueToPopulate) { return BbPromise.reduce(deepProperties, (computedValueToPopulateParam, subProperty) => { let computedValueToPopulate = computedValueToPopulateParam; - if (typeof computedValueToPopulate === 'undefined') { + if ( // in build deep variable mode + _.isString(computedValueToPopulate) && + computedValueToPopulate.match(this.deepRefSyntax) + ) { + if (subProperty !== '') { + computedValueToPopulate = `${ + computedValueToPopulate.slice(0, computedValueToPopulate.length - 1) + }.${ + subProperty + }}`; + } + return BbPromise.resolve(computedValueToPopulate); + } else if (typeof computedValueToPopulate === 'undefined') { // in get deep value mode computedValueToPopulate = {}; } else if (subProperty !== '' || '' in computedValueToPopulate) { computedValueToPopulate = computedValueToPopulate[subProperty]; } - if (typeof computedValueToPopulate === 'string' && - computedValueToPopulate.match(this.variableSyntax)) { - return this.populateValue(computedValueToPopulate, false); + if ( + typeof computedValueToPopulate === 'string' && + computedValueToPopulate.match(this.variableSyntax) + ) { + const computedVariable = this.cleanVariable(computedValueToPopulate); + let index = this.deep.findIndex((item) => computedVariable === item); + if (index < 0) { + index = this.deep.push(computedVariable) - 1; + } + return BbPromise.resolve(`\${deep:${index}}`); } return BbPromise.resolve(computedValueToPopulate); }, valueToPopulate); diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 0beaf01ee76..6520f7c2bf0 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -58,7 +58,8 @@ describe('Variables', () => { it('should call loadVariableSyntax and then populateProperty', () => { const loadVariableSyntaxStub = sinon.stub(serverless.variables, 'loadVariableSyntax') .returns(); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObject').resolves(); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObject') + .returns(BbPromise.resolve()); return expect(serverless.variables.populateService()).to.be.fulfilled .then(() => { expect(loadVariableSyntaxStub).to.be.calledOnce; @@ -82,6 +83,65 @@ describe('Variables', () => { .then().finally(() => populateObjectStub.restore()); }); }); + describe('#prepopulateService', () => { + // TL;DR: call populateService to test prepopulateService (note addition of 'pre') + // + // The prepopulateService method basically assumes invocation of of populateService (i.e. that + // variable syntax is loaded, and that the service object is cleaned up. Just use + // populateService to do that work. + let awsProvider; + let populateObjectStub; + let requestStub; // just in case... don't want to actually call... + beforeEach(() => { + awsProvider = new AwsProvider(serverless, {}); + populateObjectStub = sinon.stub(serverless.variables, 'populateObject', () => + BbPromise.resolve()); + requestStub = sinon.stub(awsProvider, 'request', () => + BbPromise.reject(new Error('unexpected'))); + }); + afterEach(() => { + populateObjectStub.restore(); + requestStub.restore(); + }); + const prepopulatedProperties = [ + { name: 'region', getter: (provider) => provider.getRegion() }, + { name: 'stage', getter: (provider) => provider.getStage() }, + ]; + describe('basic population tests', () => { + prepopulatedProperties.forEach((property) => { + it(`should populate variables in ${property.name} values`, () => { + awsProvider.options[property.name] = '${self:foobar, "default"}'; + return serverless.variables.populateService().should.be.fulfilled + .then(() => expect(property.getter(awsProvider)).to.be.eql('default')); + }); + }); + }); + // + describe('dependent service rejections', () => { + const dependentConfigs = [ + { value: '${cf:stack.value}', name: 'CloudFormation' }, + { value: '${s3:bucket/key}', name: 'S3' }, + { value: '${ssm:/path/param}', name: 'SSM' }, + ]; + prepopulatedProperties.forEach(property => { + dependentConfigs.forEach(config => { + it(`should reject ${config.name} variables in ${property.name} values`, () => { + awsProvider.options[property.name] = config.value; + return serverless.variables.populateService() + .should.be.rejectedWith('Variable Failure'); + }); + }); + }); + it('should reject recursively dependent service dependencies', () => { + serverless.variables.service.custom = { + settings: '${s3:bucket/key}', + }; + awsProvider.options.region = '${self:custom.settings.region}'; + return serverless.variables.populateService() + .should.be.rejectedWith('Variable Failure'); + }); + }); + }); describe('#getProperties', () => { it('extracts all terminal properties of an object', () => { @@ -123,6 +183,9 @@ describe('Variables', () => { }); describe('#populateObject()', () => { + beforeEach(() => { + serverless.variables.loadVariableSyntax(); + }); it('should populate object and return it', () => { const object = { stage: '${opt:stage}', // eslint-disable-line no-template-curly-in-string @@ -593,13 +656,58 @@ module.exports = { }); }); + describe('#splitByComma', () => { + it('should return a given empty string', () => { + const input = ''; + const expected = [input]; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should return a undelimited string', () => { + const input = 'foo:bar'; + const expected = [input]; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should split basic comma delimited strings', () => { + const input = 'my,values,to,split'; + const expected = ['my', 'values', 'to', 'split']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should remove leading and following white space', () => { + const input = ' \t\nfoobar\n\t '; + const expected = ['foobar']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should remove white space surrounding commas', () => { + const input = 'a,b ,c , d, e , f\t,g\n,h,\ti,\nj,\t\n , \n\tk'; + const expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should ignore quoted commas', () => { + const input = '",", \',\', ",\', \',\'", "\',\', \',\'", \',", ","\', \'",", ","\''; + const expected = [ + '","', + '\',\'', + '",\', \',\'"', + '"\',\', \',\'"', + '\',", ","\'', + '\'",", ","\'', + ]; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should deal with a combination of these cases', () => { + const input = ' \t\n\'a\'\t\n , \n\t"foo,bar", opt:foo, ",", \',\', "\',\', \',\'", foo\n\t '; + const expected = ['\'a\'', '"foo,bar"', 'opt:foo', '","', '\',\'', '"\',\', \',\'"', 'foo']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + }); + describe('#overwrite()', () => { it('should overwrite undefined and null values', () => { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves(null); getValueFromSourceStub.onCall(2).resolves('variableValue'); - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') + return serverless.variables.overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) .should.be.fulfilled .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); @@ -612,7 +720,7 @@ module.exports = { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves({}); getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables.overwrite('opt:stage,env:stage').should.be.fulfilled + return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.be.fulfilled .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSourceStub).to.have.been.calledTwice; @@ -624,7 +732,7 @@ module.exports = { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(0); getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables.overwrite('opt:stage,env:stage').should.become(0) + return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.become(0) .then().finally(() => getValueFromSourceStub.restore()); }); @@ -632,7 +740,7 @@ module.exports = { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(false); getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables.overwrite('opt:stage,env:stage').should.become(false) + return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.become(false) .then().finally(() => getValueFromSourceStub.restore()); }); @@ -641,11 +749,20 @@ module.exports = { getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves('variableValue'); getValueFromSourceStub.onCall(2).resolves('variableValue2'); - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') + return serverless.variables.overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) .should.be.fulfilled .then(valueToPopulate => expect(valueToPopulate).to.equal('variableValue')) .finally(() => getValueFromSourceStub.restore()); }); + it('should properly handle string values containing commas', () => { + const str = '"foo,bar"'; + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource') + .resolves(undefined); + return serverless.variables.overwrite(['opt:stage', str]) + .should.be.fulfilled + .then(() => expect(getValueFromSourceStub.getCall(1).args[0]).to.eql(str)) + .finally(() => getValueFromSourceStub.restore()); + }); }); describe('#getValueFromSource()', () => { @@ -1261,25 +1378,36 @@ module.exports = { return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep', 'deeper'], valueToPopulateMock).should.eventually.deep.equal({}); }); - - it('should get deep values with variable references', () => { + it('should return a simple deep variable when final deep value is variable', () => { serverless.variables.service = { service: 'testService', custom: { - anotherVar: '${self:custom.var}', // eslint-disable-line no-template-curly-in-string subProperty: { // eslint-disable-next-line no-template-curly-in-string deep: '${self:custom.anotherVar.veryDeep}', }, - var: { - veryDeep: 'someValue', - }, }, provider: serverless.service.provider, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep'], - serverless.variables.service).should.become('someValue'); + return serverless.variables.getDeepValue( + ['custom', 'subProperty', 'deep'], + serverless.variables.service + ).should.become('${deep:0}'); + }); + it('should return a deep continuation when middle deep value is variable', () => { + serverless.variables.service = { + service: 'testService', + custom: { + anotherVar: '${self:custom.var}', // eslint-disable-line no-template-curly-in-string + }, + provider: serverless.service.provider, + }; + serverless.variables.loadVariableSyntax(); + return serverless.variables.getDeepValue( + ['custom', 'anotherVar', 'veryDeep'], + serverless.variables.service) + .should.become('${deep:0.veryDeep}'); }); }); describe('#warnIfNotFound()', () => { diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 6eae516f637..4d03455f21f 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -330,13 +330,28 @@ class AwsProvider { credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign } + getValues(source, paths) { + return paths.map(path => ({ + path, + value: _.get(source, path.join('.')), + })); + } + firstValue(values) { + return values.reduce((result, current) => (result.value ? result : current), {}); + } + + getRegionSourceValue() { + const values = this.getValues(this, [ + ['options', 'region'], + ['serverless', 'config', 'region'], + ['serverless', 'service', 'provider', 'region'], + ]); + return this.firstValue(values); + } getRegion() { const defaultRegion = 'us-east-1'; - - return _.get(this, 'options.region') - || _.get(this, 'serverless.config.region') - || _.get(this, 'serverless.service.provider.region') - || defaultRegion; + const regionSourceValue = this.getRegionSourceValue(); + return regionSourceValue.value || defaultRegion; } getServerlessDeploymentBucketName() { @@ -352,13 +367,18 @@ class AwsProvider { ).then((result) => result.StackResourceDetail.PhysicalResourceId); } + getStageSourceValue() { + const values = this.getValues(this, [ + ['options', 'stage'], + ['serverless', 'config', 'stage'], + ['serverless', 'service', 'provider', 'stage'], + ]); + return this.firstValue(values); + } getStage() { const defaultStage = 'dev'; - - return _.get(this, 'options.stage') - || _.get(this, 'serverless.config.stage') - || _.get(this, 'serverless.service.provider.stage') - || defaultStage; + const stageSourceValue = this.getStageSourceValue(); + return stageSourceValue.value || defaultStage; } getAccountId() { diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 297b298b670..f69e9777454 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -72,118 +72,118 @@ describe('AwsProvider', () => { // clear env delete process.env.AWS_CLIENT_TIMEOUT; }); - }); - describe('#constructor() certificate authority - environment variable', () => { - afterEach('Environment Variable Cleanup', () => { - // clear env - delete process.env.ca; - }); - it('should set AWS ca single', () => { - process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; - const newAwsProvider = new AwsProvider(serverless, options); + describe('certificate authority - environment variable', () => { + afterEach('Environment Variable Cleanup', () => { + // clear env + delete process.env.ca; + }); + it('should set AWS ca single', () => { + process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); - }); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); - it('should set AWS ca multiple', () => { - const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; - process.env.ca = `${certContents},${certContents}`; - const newAwsProvider = new AwsProvider(serverless, options); + it('should set AWS ca multiple', () => { + const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; + process.env.ca = `${certContents},${certContents}`; + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); }); - }); - describe('#constructor() certificate authority - file', () => { - const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; - const tmpdir = os.tmpdir(); - let file1 = null; - let file2 = null; - beforeEach('Create CA Files and env vars', () => { - file1 = path.join(tmpdir, 'ca1.txt'); - file2 = path.join(tmpdir, 'ca2.txt'); - fs.writeFileSync(file1, certContents); - fs.writeFileSync(file2, certContents); - }); - - afterEach('CA File Cleanup', () => { - // delete files - fs.unlinkSync(file1); - fs.unlinkSync(file2); - // clear env - delete process.env.ca; - delete process.env.cafile; - }); + describe('certificate authority - file', () => { + const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; + const tmpdir = os.tmpdir(); + let file1 = null; + let file2 = null; + beforeEach('Create CA Files and env vars', () => { + file1 = path.join(tmpdir, 'ca1.txt'); + file2 = path.join(tmpdir, 'ca2.txt'); + fs.writeFileSync(file1, certContents); + fs.writeFileSync(file2, certContents); + }); - it('should set AWS cafile single', () => { - process.env.cafile = file1; - const newAwsProvider = new AwsProvider(serverless, options); + afterEach('CA File Cleanup', () => { + // delete files + fs.unlinkSync(file1); + fs.unlinkSync(file2); + // clear env + delete process.env.ca; + delete process.env.cafile; + }); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); - }); + it('should set AWS cafile single', () => { + process.env.cafile = file1; + const newAwsProvider = new AwsProvider(serverless, options); - it('should set AWS cafile multiple', () => { - process.env.cafile = `${file1},${file2}`; - const newAwsProvider = new AwsProvider(serverless, options); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); - }); + it('should set AWS cafile multiple', () => { + process.env.cafile = `${file1},${file2}`; + const newAwsProvider = new AwsProvider(serverless, options); - it('should set AWS ca and cafile', () => { - process.env.ca = certContents; - process.env.cafile = file1; - const newAwsProvider = new AwsProvider(serverless, options); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + it('should set AWS ca and cafile', () => { + process.env.ca = certContents; + process.env.cafile = file1; + const newAwsProvider = new AwsProvider(serverless, options); + + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); }); - }); - describe('when checking for the deploymentBucket config', () => { - it('should do nothing if the deploymentBucket config is not used', () => { - serverless.service.provider.deploymentBucket = undefined; + describe('deploymentBucket configuration', () => { + it('should do nothing if not defined', () => { + serverless.service.provider.deploymentBucket = undefined; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(undefined); - }); + expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(undefined); + }); - it('should do nothing if the deploymentBucket config is a string', () => { - serverless.service.provider.deploymentBucket = 'my.deployment.bucket'; + it('should do nothing if the value is a string', () => { + serverless.service.provider.deploymentBucket = 'my.deployment.bucket'; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal('my.deployment.bucket'); - }); + expect(newAwsProvider.serverless.service.provider.deploymentBucket) + .to.equal('my.deployment.bucket'); + }); - it('should save the object and use the name for the deploymentBucket if provided', () => { - const deploymentBucketObject = { - name: 'my.deployment.bucket', - serverSideEncryption: 'AES256', - }; - serverless.service.provider.deploymentBucket = deploymentBucketObject; + it('should save a given object and use name from it', () => { + const deploymentBucketObject = { + name: 'my.deployment.bucket', + serverSideEncryption: 'AES256', + }; + serverless.service.provider.deploymentBucket = deploymentBucketObject; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal('my.deployment.bucket'); - expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) - .to.deep.equal(deploymentBucketObject); - }); + expect(newAwsProvider.serverless.service.provider.deploymentBucket) + .to.equal('my.deployment.bucket'); + expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) + .to.deep.equal(deploymentBucketObject); + }); - it('should save the object and nullify the name if it is not provided', () => { - const deploymentBucketObject = { - serverSideEncryption: 'AES256', - }; - serverless.service.provider.deploymentBucket = deploymentBucketObject; + it('should save a given object and nullify the name if one is not provided', () => { + const deploymentBucketObject = { + serverSideEncryption: 'AES256', + }; + serverless.service.provider.deploymentBucket = deploymentBucketObject; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal(null); - expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) - .to.deep.equal(deploymentBucketObject); + expect(newAwsProvider.serverless.service.provider.deploymentBucket) + .to.equal(null); + expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) + .to.deep.equal(deploymentBucketObject); + }); }); }); @@ -693,6 +693,69 @@ describe('AwsProvider', () => { }); }); + describe('values', () => { + const obj = { + a: 'b', + c: { + d: 'e', + f: { + g: 'h', + }, + }, + }; + const paths = [ + ['a'], + ['c', 'd'], + ['c', 'f', 'g'], + ]; + const getExpected = [ + { path: paths[0], value: obj.a }, + { path: paths[1], value: obj.c.d }, + { path: paths[2], value: obj.c.f.g }, + ]; + describe('#getValues', () => { + it('should return an array of values given paths to them', () => { + expect(awsProvider.getValues(obj, paths)).to.eql(getExpected); + }); + }); + describe('#firstValue', () => { + it('should ignore entries without a \'value\' attribute', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[2].value; + expect(awsProvider.firstValue(input)).to.eql(getExpected[1]); + }); + it('should ignore entries with an undefined \'value\' attribute', () => { + const input = _.cloneDeep(getExpected); + input[0].value = undefined; + input[2].value = undefined; + expect(awsProvider.firstValue(input)).to.eql(getExpected[1]); + }); + it('should return the first value', () => { + expect(awsProvider.firstValue(getExpected)).to.equal(getExpected[0]); + }); + it('should return the middle value', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[2].value; + expect(awsProvider.firstValue(input)).to.equal(input[1]); + }); + it('should return the last value', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[1].value; + expect(awsProvider.firstValue(input)).to.equal(input[2]); + }); + it('should return the last object if none have valid values', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[1].value; + delete input[2].value; + expect(awsProvider.firstValue(input)).to.equal(input[2]); + }); + }); + }); + describe('#getRegion()', () => { let newAwsProvider; From 91f50f691897a644eac14648ae07bc3febb4d163 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Fri, 16 Feb 2018 17:26:40 -0800 Subject: [PATCH 081/125] add 'use strict' for node 4.x and 5.x --- lib/classes/PromiseTracker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/classes/PromiseTracker.js b/lib/classes/PromiseTracker.js index f19424788b3..f7638535518 100644 --- a/lib/classes/PromiseTracker.js +++ b/lib/classes/PromiseTracker.js @@ -1,3 +1,4 @@ +'use strict'; const logWarning = require('./Error').logWarning; From ffc7686465261d29b2aef8616cc5222ca1dd7829 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Fri, 16 Feb 2018 18:46:28 -0800 Subject: [PATCH 082/125] fix pending promise test bugs --- lib/classes/PromiseTracker.test.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/classes/PromiseTracker.test.js b/lib/classes/PromiseTracker.test.js index 94d9a3c7676..0202770d833 100644 --- a/lib/classes/PromiseTracker.test.js +++ b/lib/classes/PromiseTracker.test.js @@ -33,12 +33,15 @@ describe('PromiseTracker', () => { expect(promises.length).to.equal(0); }); it('reports one pending promise when one has been added', () => { - const promise = BbPromise.delay(10); + let resolve; + const promise = new BbPromise((rslv) => { resolve = rslv; }); promiseTracker.add('foo', promise, '${foo:}'); - const promises = promiseTracker.getPending(); - expect(promises).to.be.an.instanceof(Array); - expect(promises.length).to.equal(1); - expect(promises[0]).to.equal(promise); + return BbPromise.delay(1).then(() => { + const promises = promiseTracker.getPending(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); + }).then(() => { resolve(); }); }); it('reports no settled promises when none have been added', () => { const promises = promiseTracker.getSettled(); @@ -48,12 +51,11 @@ describe('PromiseTracker', () => { it('reports one settled promise when one has been added', () => { const promise = BbPromise.resolve(); promiseTracker.add('foo', promise, '${foo:}'); - return BbPromise.delay(1).then(() => { - const promises = promiseTracker.getSettled(); - expect(promises).to.be.an.instanceof(Array); - expect(promises.length).to.equal(1); - expect(promises[0]).to.equal(promise); - }); + promise.state = 'resolved'; + const promises = promiseTracker.getSettled(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); }); it('reports no promises when none have been added', () => { const promises = promiseTracker.getAll(); From 2e3144f593aae8567a3962cf680fe93d105adf40 Mon Sep 17 00:00:00 2001 From: Simon Males Date: Sat, 17 Feb 2018 21:18:14 +0100 Subject: [PATCH 083/125] Include node_modules when compiling TypeScript --- .../create/templates/aws-nodejs-typescript/tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json b/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json index a98943cf604..cb07c78d842 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json @@ -5,5 +5,8 @@ "es5", "es2015.promise" ] - } + }, + "exclude": [ + "node_modules" + ] } From a35bcc01cd0b4a1040a3c5baa928d456c7668071 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Tue, 20 Feb 2018 12:21:26 -0800 Subject: [PATCH 084/125] Deep values can be overwrites too! Fix issue reported by @laardee (thank you!) Simple oversight that deep variables can reference overwrites too. This fix acknowledges this oversight by using the standard logic for using overwrite instead of getValueFromSource in this case. --- lib/classes/Variables.js | 31 ++++++++++++++++++++----------- lib/classes/Variables.test.js | 12 ++++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 4c19f43c712..4a5393b04c9 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -23,9 +23,9 @@ class Variables { this.envRefSyntax = RegExp(/^env:/g); this.optRefSyntax = RegExp(/^opt:/g); this.selfRefSyntax = RegExp(/^self:/g); + this.stringRefSyntax = RegExp(/(?:('|").*?\1)/g); this.cfRefSyntax = RegExp(/^cf:/g); this.s3RefSyntax = RegExp(/^s3:(.+?)\/(.+)$/); - this.stringRefSyntax = RegExp(/(?:('|").*?\1)/g); this.ssmRefSyntax = RegExp(/^ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); } @@ -279,14 +279,8 @@ class Variables { * @param {MatchResult[]} matches The matches to populate * @returns {Promise[]} Promises for the eventual populated values of the given matches */ - populateMatches(matches) { - return _.map(matches, (match) => { - const parts = this.splitByComma(match.variable); - if (parts.length > 1) { - return this.overwrite(parts, match.match); - } - return this.getValueFromSource(parts[0], match.match); - }); + populateMatches(matches, property) { + return _.map(matches, (match) => this.splitAndGet(match.variable, property)); } /** * Render the given matches and their associated results to the given value @@ -317,7 +311,7 @@ class Variables { if (!_.isArray(matches)) { return BbPromise.resolve(property); } - const populations = this.populateMatches(matches); + const populations = this.populateMatches(matches, valueToPopulate); return BbPromise.all(populations) .then(results => this.renderMatches(property, matches, results)) .then((result) => { @@ -335,6 +329,21 @@ class Variables { populateProperty(propertyToPopulate) { return this.populateValue(propertyToPopulate, true); } + + /** + * Split the cleaned variable string containing one or more comma delimited variables and get a + * final value for the entirety of the string + * @param varible The variable string to split and get a final value for + * @param property The original property string the given variable was extracted from + * @returns {Promise} A promise resolving to the final value of the given variable + */ + splitAndGet(variable, property) { + const parts = this.splitByComma(variable); + if (parts.length > 1) { + return this.overwrite(parts, property); + } + return this.getValueFromSource(parts[0], property); + } /** * Populate a given property, given the matched string to replace and the value to replace the * matched string with. @@ -667,7 +676,7 @@ class Variables { const variable = this.getVariableFromDeep(variableString); const deepRef = variableString.replace(deepPrefixReplace, ''); const sourceString = `\${deep:\${${variable}}${deepRef.length ? `.${deepRef}` : ''}}`; - return this.getValueFromSource(variable, sourceString); + return this.splitAndGet(variable, sourceString); } getDeepValue(deepProperties, valueToPopulate) { diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 6520f7c2bf0..2c582363a05 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -390,6 +390,18 @@ describe('Variables', () => { expect(result).to.eql(expected); })).to.be.fulfilled; }); + it('should handle deep variables that are overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: 'foo${self:custom.val1}', + }; + const expected = { + val1: 'bar', + val2: 'foobar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); describe('file reading cases', () => { let tmpDirPath; beforeEach(() => { From 3b278f4d6b08307642624f29551b18d1c6de652c Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 20 Feb 2018 16:16:55 -0800 Subject: [PATCH 085/125] added IAM roles docs --- docs/providers/spotinst/guide/IAM-roles.md | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/providers/spotinst/guide/IAM-roles.md diff --git a/docs/providers/spotinst/guide/IAM-roles.md b/docs/providers/spotinst/guide/IAM-roles.md new file mode 100644 index 00000000000..3c12b633c45 --- /dev/null +++ b/docs/providers/spotinst/guide/IAM-roles.md @@ -0,0 +1,45 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/iam-roles) + + +# Spotinst Functions - IAM roles +Functions sometimes rely on outside services from Amazon such as S3, and accessing these resources often requires authorization using IAM. Spotinst Functions can be configured with the relevant permissions with the inclusion of IAM role configuration information in the serverless.yml file. See [Amazon's documentation][amazon-docs-url] for more information on IAM roles. + +## Requirements +- You will need to create an IAM role on your AWS account and attach policies with the relevant permissions. +- Only one IAM role per function. +- Multiple functions can be associated with the same IAM role. + +## YML +```yaml +functions: + example: + runtime: nodejs8.3 + handler: handler.main + memory: 128 + timeout: 30 + access: private + iamRoleConfig: + - id: ${role-id} # make sure you use envionrmental variables to obscure the role ID + - credentials: "encrypted-text" +``` + +## Parameters +- id: the role from aws + - AROAIIZKPBKS2LEXAMPLE +- credentials: + + + +For more information on how to set up IAM roles, check out our documentation [here][spotinst-help-center] + +[amazon-docs-url]: https://aws.amazon.com/iam/?sc_channel=PS&sc_campaign=acquisition_US&sc_publisher=google&sc_medium=iam_b&sc_content=amazon_iam_e&sc_detail=amazon%20iam&sc_category=iam&sc_segment=208382128687&sc_matchtype=e&sc_country=US&s_kwcid=AL!4422!3!208382128687!e!!g!!amazon%20iam&ef_id=WoypCQAABVVgCzd0:20180220230233:s +[spotinst-help-center]: https://help.spotinst.com/hc/en-us/articles/ From 8c7db120ae1917346ee9fe76714dfc7497d84a67 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Tue, 20 Feb 2018 20:57:24 -0800 Subject: [PATCH 086/125] Add cases for all dependent services, and a variety of deep variable (DV) cases Solve for these cases. The bug was that DVs can render to DVs when a value is being obtained from them (i.e. they are being de-referenced in getValueFromDeep). This is particularly problematic in the case that the original DV being rendered has a deep reference into the rendered DV. In that case the DV returned by rendering must be replaced by yet another DV. While accomplishing the above, various bits of cleanup were implemented and I added some commentary around getDeeperValue for future engineers. This case is particularly relevant if you have: `serverless.yml`: ``` service: serverless-hello-world provider: name: aws runtime: nodejs6.10 stage: dev environment: SECRET: ${self:custom.secrets.SECRET} functions: helloWorld: handler: handler.helloWorld events: - http: path: hello-world method: get cors: true custom: stage: ${opt:stage, self:provider.stage} secrets: ${file(secrets.${self:custom.stage}.yml)} ``` AND `secrets.dev.yml`: ``` SECRETS: secrets ``` Populating this service should result in the following sequence of rendering phases: ######################### # PHASE 1 # # ${self:custom.secrets.SECRET} # <- ${deep:0.SECRET} # deep[0] = ${file(secrets.${self:custom.stage}.yml)} # # ${opt:stage, self:provider.stage} # <- 'dev' # # ${file(secrets.${self:custom.stage}.yml)} # <- ${file(secrets.${deep:1}.yml)} # deep[1] = ${opt:stage, self:provider.stage} # # RESULT # # service: serverless-hello-world # # provider: # name: aws # runtime: nodejs6.10 # stage: dev # environment: # SECRET: ${deep:0.SECRET} # # functions: # helloWorld: # handler: handler.helloWorld # events: # - http: # path: hello-world # method: get # cors: true # # custom: # stage: dev # secrets: ${file(secrets.${deep:1}.yml)} ######################### ######################### # PHASE 2 # # ${deep:0.SECRET} # <- this.populateValue('${file(secrets.${self:custom.stage}.yml)}') => '${file(secrets.${deep:1}.yml)}' => '${deep:2}' => '${deep:2.SECRET}' # deep[2] = ${file(secrets.${deep:1}.yml)} # # ${file(secrets.${deep:1}.yml)} # <- this.populateValue('${file(secrets.${deep:1}.yml)}') => '${file(secrets.dev.yml)}' => '${deep:3}' # deep[3] = ${file(secrets.dev.yml)} # # RESULT # # service: serverless-hello-world # # provider: # name: aws # runtime: nodejs6.10 # stage: dev # environment: # SECRET: ${deep:2.SECRET} # # functions: # helloWorld: # handler: handler.helloWorld # events: # - http: # path: hello-world # method: get # cors: true # # custom: # stage: dev # secrets: ${deep:3} ######################### ######################### # PHASE 3 # # ${deep:2.SECRET} # <- this.populateValue('${file(secrets.${deep:1}.yml)}') => '${file(secrets.dev.yml)}' => '${deep:3}' => '${deep:3.SECRET}' # deep[3] is already set to ${file(secrets.dev.yml)} # # ${deep:3} # <- this.populateValue('${file(secrets.dev.yml)}') => { SECRET: 'secret' } # # RESULT # # service: serverless-hello-world # # provider: # name: aws # runtime: nodejs6.10 # stage: dev # environment: # SECRET: ${deep:3.SECRET} # # functions: # helloWorld: # handler: handler.helloWorld # events: # - http: # path: hello-world # method: get # cors: true # # custom: # stage: dev # secrets: # SECRET: secret ######################### ######################### # PHASE 4 # # ${deep:3} # <- this.populateValue('${file(secrets.dev.yml)}') => this.getDeeperValue(['SECRET'], { SECRET: 'secret' }) => 'secret' # # RESULT # # service: serverless-hello-world # # provider: # name: aws # runtime: nodejs6.10 # stage: dev # environment: # SECRET: secret # # functions: # helloWorld: # handler: handler.helloWorld # events: # - http: # path: hello-world # method: get # cors: true # # custom: # stage: dev # secrets: # SECRET: secret ######################### --- lib/classes/Variables.js | 108 +++++++++++++----------- lib/classes/Variables.test.js | 150 ++++++++++++++++++++++++++++++---- 2 files changed, 196 insertions(+), 62 deletions(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 4a5393b04c9..8d693ba0ae5 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -17,16 +17,16 @@ class Variables { this.tracker = new PromiseTracker(); this.deep = []; - this.deepRefSyntax = RegExp(/^(\${)?deep:\d+(\.[^}]+)*()}?$/); + this.deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/); this.overwriteSyntax = RegExp(/\s*(?:,\s*)+/g); this.fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-/]+?)\)/g); this.envRefSyntax = RegExp(/^env:/g); this.optRefSyntax = RegExp(/^opt:/g); this.selfRefSyntax = RegExp(/^self:/g); this.stringRefSyntax = RegExp(/(?:('|").*?\1)/g); - this.cfRefSyntax = RegExp(/^cf:/g); - this.s3RefSyntax = RegExp(/^s3:(.+?)\/(.+)$/); - this.ssmRefSyntax = RegExp(/^ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); + this.cfRefSyntax = RegExp(/^(?:\${)?cf:/g); + this.s3RefSyntax = RegExp(/^(?:\${)?s3:(.+?)\/(.+)$/); + this.ssmRefSyntax = RegExp(/^(?:\${)?ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); } loadVariableSyntax() { @@ -58,14 +58,12 @@ class Variables { accumulation.concat(this.splitByComma(current.variable)), []); }; - const serviceMatch = variablePart => - dependentServices.find((service) => { - let variable = variablePart; - if (variable.match(this.deepRefSyntax)) { - variable = this.getVariableFromDeep(variablePart); - } - return variable.match(service.regex); - }); + const serviceMatch = (variablePart) => { + const variable = variablePart.match(this.deepRefSyntax) ? + this.getVariableFromDeep(variablePart) : + variablePart; + return dependentServices.find(service => variable.match(service.regex)); + }; const getUntilValid = (config) => { const parts = getVariableParts(config.value); const service = parts.reduce( @@ -506,7 +504,7 @@ class Variables { getValueFromSelf(variableString) { const valueToPopulate = this.service; const deepProperties = variableString.split(':')[1].split('.'); - return this.getDeepValue(deepProperties, valueToPopulate); + return this.getDeeperValue(deepProperties, valueToPopulate); } getValueFromFile(variableString) { @@ -561,7 +559,7 @@ class Variables { let deepProperties = variableString.replace(matchedFileRefString, ''); deepProperties = deepProperties.slice(1).split('.'); deepProperties.splice(0, 1); - return this.getDeepValue(deepProperties, valueToPopulateResolved) + return this.getDeeperValue(deepProperties, valueToPopulateResolved) .then((deepValueToPopulateResolved) => { if (typeof deepValueToPopulateResolved === 'undefined') { const errorMessage = [ @@ -591,7 +589,7 @@ class Variables { return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } deepProperties = deepProperties.slice(1).split('.'); - return this.getDeepValue(deepProperties, valueToPopulate); + return this.getDeeperValue(deepProperties, valueToPopulate); } } return BbPromise.resolve(valueToPopulate); @@ -675,42 +673,60 @@ class Variables { const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g); const variable = this.getVariableFromDeep(variableString); const deepRef = variableString.replace(deepPrefixReplace, ''); - const sourceString = `\${deep:\${${variable}}${deepRef.length ? `.${deepRef}` : ''}}`; - return this.splitAndGet(variable, sourceString); + let ret = this.populateValue(variable); + if (deepRef.length) { // if there is a deep reference remaining + ret = ret.then((result) => { + if (_.isString(result) && result.match(this.variableSyntax)) { + const deepVariable = this.makeDeepVariable(result); + return BbPromise.resolve(this.appendDeepVariable(deepVariable, deepRef)); + } + return this.getDeeperValue(deepRef.split('.'), result); + }); + } + return ret; } - getDeepValue(deepProperties, valueToPopulate) { - return BbPromise.reduce(deepProperties, (computedValueToPopulateParam, subProperty) => { - let computedValueToPopulate = computedValueToPopulateParam; - if ( // in build deep variable mode - _.isString(computedValueToPopulate) && - computedValueToPopulate.match(this.deepRefSyntax) - ) { - if (subProperty !== '') { - computedValueToPopulate = `${ - computedValueToPopulate.slice(0, computedValueToPopulate.length - 1) - }.${ - subProperty - }}`; + makeDeepVariable(variable) { + let index = this.deep.findIndex((item) => variable === item); + if (index < 0) { + index = this.deep.push(variable) - 1; + } + return `\${deep:${index}}`; + } + appendDeepVariable(variable, subProperty) { + return `${variable.slice(0, variable.length - 1)}.${subProperty}}`; + } + + /** + * Get a value that is within the given valueToPopulate. The deepProperties specify what value + * to retrieve from the given valueToPopulate. The trouble is that anywhere along this chain a + * variable can be discovered. If this occurs, to avoid cyclic dependencies, the resolution of + * the deep value from the given valueToPopulate must be halted. The discovered variable is thus + * set aside into a "deep variable" (see makeDeepVariable). The indexing into the given + * valueToPopulate is then resolved with a replacement ${deep:${index}.${remaining.properties}} + * variable (e.g. ${deep:1.foo}). This pauses the population for continuation during the next + * generation of evaluation (see getValueFromDeep) + * @param deepProperties The "path" of properties to follow in obtaining the deeper value + * @param valueToPopulate The value from which to obtain the deeper value + * @returns {Promise} A promise resolving to the deeper value or to a `deep` variable that + * will later resolve to the deeper value + */ + getDeeperValue(deepProperties, valueToPopulate) { + return BbPromise.reduce(deepProperties, (reducedValueParam, subProperty) => { + let reducedValue = reducedValueParam; + if (_.isString(reducedValue) && reducedValue.match(this.deepRefSyntax)) { // build mode + reducedValue = this.appendDeepVariable(reducedValue, subProperty); + } else { // get mode + if (typeof reducedValue === 'undefined') { + reducedValue = {}; + } else if (subProperty !== '' || '' in reducedValue) { + reducedValue = reducedValue[subProperty]; } - return BbPromise.resolve(computedValueToPopulate); - } else if (typeof computedValueToPopulate === 'undefined') { // in get deep value mode - computedValueToPopulate = {}; - } else if (subProperty !== '' || '' in computedValueToPopulate) { - computedValueToPopulate = computedValueToPopulate[subProperty]; - } - if ( - typeof computedValueToPopulate === 'string' && - computedValueToPopulate.match(this.variableSyntax) - ) { - const computedVariable = this.cleanVariable(computedValueToPopulate); - let index = this.deep.findIndex((item) => computedVariable === item); - if (index < 0) { - index = this.deep.push(computedVariable) - 1; + if (typeof reducedValue === 'string' && reducedValue.match(this.variableSyntax)) { + reducedValue = this.makeDeepVariable(reducedValue); } - return BbPromise.resolve(`\${deep:${index}}`); } - return BbPromise.resolve(computedValueToPopulate); + return BbPromise.resolve(reducedValue); }, valueToPopulate); } diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 2c582363a05..4e7c3bb338b 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -94,8 +94,8 @@ describe('Variables', () => { let requestStub; // just in case... don't want to actually call... beforeEach(() => { awsProvider = new AwsProvider(serverless, {}); - populateObjectStub = sinon.stub(serverless.variables, 'populateObject', () => - BbPromise.resolve()); + populateObjectStub = sinon.stub(serverless.variables, 'populateObject'); + populateObjectStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(new Error('unexpected'))); }); @@ -130,16 +130,16 @@ describe('Variables', () => { return serverless.variables.populateService() .should.be.rejectedWith('Variable Failure'); }); + it(`should reject recursively dependent ${config.name} service dependencies`, () => { + serverless.variables.service.custom = { + settings: config.value, + }; + awsProvider.options.region = '${self:custom.settings.region}'; + return serverless.variables.populateService() + .should.be.rejectedWith('Variable Failure'); + }); }); }); - it('should reject recursively dependent service dependencies', () => { - serverless.variables.service.custom = { - settings: '${s3:bucket/key}', - }; - awsProvider.options.region = '${self:custom.settings.region}'; - return serverless.variables.populateService() - .should.be.rejectedWith('Variable Failure'); - }); }); }); @@ -390,7 +390,67 @@ describe('Variables', () => { expect(result).to.eql(expected); })).to.be.fulfilled; }); - it('should handle deep variables that are overrides', () => { + // see https://github.com/serverless/serverless/pull/4713#issuecomment-366975172 + it('should handle deep references into deep variables', () => { + service.provider.stage = 'dev'; + service.custom = { + stage: '${env:stage, self:provider.stage}', + secrets: '${self:custom.${self:custom.stage}}', + dev: { + SECRET: 'secret', + }, + environment: { + SECRET: '${self:custom.secrets.SECRET}', + }, + }; + const expected = { + stage: 'dev', + secrets: { + SECRET: 'secret', + }, + dev: { + SECRET: 'secret', + }, + environment: { + SECRET: 'secret', + }, + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val1}', + }; + const expected = { + val1: 'bar', + val2: 'bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep references into deep variables', () => { + service.custom = { + val0: { + foo: 'bar', + }, + val1: '${self:custom.val0}', + val2: '${self:custom.val1.foo}', + }; + const expected = { + val0: { + foo: 'bar', + }, + val1: { + foo: 'bar', + }, + val2: 'bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables that reference overrides', () => { service.custom = { val1: '${self:not.a.value, "bar"}', val2: 'foo${self:custom.val1}', @@ -402,6 +462,64 @@ describe('Variables', () => { return serverless.variables.populateObject(service.custom) .should.become(expected); }); + it('should handle referenced deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val1}', + val3: '${self:custom.val2}', + }; + const expected = { + val1: 'bar', + val2: 'bar', + val3: 'bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle partial referenced deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val1}', + val3: 'foo${self:custom.val2}', + }; + const expected = { + val1: 'bar', + val2: 'bar', + val3: 'foobar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle referenced contained deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: 'foo${self:custom.val1}', + val3: '${self:custom.val2}', + }; + const expected = { + val1: 'bar', + val2: 'foobar', + val3: 'foobar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle multiple referenced contained deep variables referencing overrides', () => { + service.custom = { + val0: '${self:not.a.value, "foo"}', + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val0}:${self:custom.val1}', + val3: '${self:custom.val2}', + }; + const expected = { + val0: 'foo', + val1: 'bar', + val2: 'foo:bar', + val3: 'foo:bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); describe('file reading cases', () => { let tmpDirPath; beforeEach(() => { @@ -1365,7 +1483,7 @@ module.exports = { }); }); - describe('#getDeepValue()', () => { + describe('#getDeeperValue()', () => { it('should get deep values', () => { const valueToPopulateMock = { service: 'testService', @@ -1376,7 +1494,7 @@ module.exports = { }, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep'], + return serverless.variables.getDeeperValue(['custom', 'subProperty', 'deep'], valueToPopulateMock).should.become('deepValue'); }); it('should not throw error if referencing invalid properties', () => { @@ -1387,7 +1505,7 @@ module.exports = { }, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep', 'deeper'], + return serverless.variables.getDeeperValue(['custom', 'subProperty', 'deep', 'deeper'], valueToPopulateMock).should.eventually.deep.equal({}); }); it('should return a simple deep variable when final deep value is variable', () => { @@ -1402,7 +1520,7 @@ module.exports = { provider: serverless.service.provider, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue( + return serverless.variables.getDeeperValue( ['custom', 'subProperty', 'deep'], serverless.variables.service ).should.become('${deep:0}'); @@ -1416,7 +1534,7 @@ module.exports = { provider: serverless.service.provider, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeepValue( + return serverless.variables.getDeeperValue( ['custom', 'anotherVar', 'veryDeep'], serverless.variables.service) .should.become('${deep:0.veryDeep}'); From e572563441b7adb1b24db9eba3ce6bfc95086c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreu=20Gallofr=C3=A9?= Date: Wed, 21 Feb 2018 10:06:53 +0100 Subject: [PATCH 087/125] Fixed a typo It's supposed to say python code, but it says nodejs two times --- docs/providers/spotinst/guide/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/spotinst/guide/variables.md b/docs/providers/spotinst/guide/variables.md index 43d3278cefc..eaf7722ca29 100644 --- a/docs/providers/spotinst/guide/variables.md +++ b/docs/providers/spotinst/guide/variables.md @@ -42,4 +42,4 @@ To access URL parameters in your NodeJS code you just need to put `event.query[' ### 2. Python -To access URL parameters in your NodeJS code you just need to put `os.environ['{Your Parameter Name}']` as needed +To access URL parameters in your Python code you just need to put `os.environ['{Your Parameter Name}']` as needed From d1d1b05cea475810c88aa933e2fe3c95065756b8 Mon Sep 17 00:00:00 2001 From: Devin Fee Date: Wed, 21 Feb 2018 09:59:42 -0800 Subject: [PATCH 088/125] Fixed python module parsing (#4755) * Fixed python module parsing Ref: https://github.com/serverless/serverless/issues/4663#issuecomment-366931491 * Update index.js * Update index.js --- lib/plugins/aws/invokeLocal/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index ecc355cf91d..ffc6b8b8c8b 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -130,8 +130,9 @@ class AwsInvokeLocal { } if (runtime === 'python2.7' || runtime === 'python3.6') { - const handlerPath = handler.split('.')[0]; - const handlerName = handler.split('.')[1]; + 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, From 0940fd459576967d5cd18ce8c8797670a86e120e Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Feb 2018 10:39:50 -0800 Subject: [PATCH 089/125] updated templates --- docs/providers/spotinst/guide/IAM-roles.md | 15 +++++++-------- .../templates/spotinst-java8/serverless.yml | 2 ++ .../templates/spotinst-nodejs/serverless.yml | 2 ++ .../templates/spotinst-python/serverless.yml | 2 ++ .../create/templates/spotinst-ruby/serverless.yml | 2 ++ 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/providers/spotinst/guide/IAM-roles.md b/docs/providers/spotinst/guide/IAM-roles.md index 3c12b633c45..747e2d46de0 100644 --- a/docs/providers/spotinst/guide/IAM-roles.md +++ b/docs/providers/spotinst/guide/IAM-roles.md @@ -11,12 +11,13 @@ layout: Doc # Spotinst Functions - IAM roles -Functions sometimes rely on outside services from Amazon such as S3, and accessing these resources often requires authorization using IAM. Spotinst Functions can be configured with the relevant permissions with the inclusion of IAM role configuration information in the serverless.yml file. See [Amazon's documentation][amazon-docs-url] for more information on IAM roles. +Functions sometimes rely on outside services from Amazon such as S3, and accessing these resources often requires authorization using IAM. Spotinst Functions can be configured with the relevant permissions with the inclusion of IAM role information in the serverless.yml file. See [Amazon's documentation][amazon-docs-url] for more information on IAM roles. ## Requirements - You will need to create an IAM role on your AWS account and attach policies with the relevant permissions. -- Only one IAM role per function. -- Multiple functions can be associated with the same IAM role. +- A spotinst role will be generated and linked with your AWS role +- Only one Spotinst role per function. +- Multiple functions can be associated with the same Spotinst role. ## YML ```yaml @@ -28,14 +29,12 @@ functions: timeout: 30 access: private iamRoleConfig: - - id: ${role-id} # make sure you use envionrmental variables to obscure the role ID - - credentials: "encrypted-text" + - id: ${role-id} ``` ## Parameters -- id: the role from aws - - AROAIIZKPBKS2LEXAMPLE -- credentials: +- id: the role created on the console + - ex : sfr-5ea76784 diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index 8ae47f7f69c..7301c3c4314 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -26,6 +26,8 @@ functions: memory: 128 timeout: 30 access: private +# iamRoleConfig: +# - id: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index 74d910a25c8..a8aebf3592e 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -26,6 +26,8 @@ functions: memory: 128 timeout: 30 access: private +# iamRoleConfig: +# - id: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index ce62ad417d2..059f308dc9d 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -26,6 +26,8 @@ functions: memory: 128 timeout: 30 access: private +# iamRoleConfig: +# - id: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 60b6373839b..204543ca6e7 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -26,6 +26,8 @@ functions: memory: 128 timeout: 30 access: private +# iamRoleConfig: +# - id: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 From ecf37ebaab4b2c3b99921f880929de203f48405c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Feb 2018 10:49:29 -0800 Subject: [PATCH 090/125] updated links --- docs/providers/spotinst/guide/IAM-roles.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/spotinst/guide/IAM-roles.md b/docs/providers/spotinst/guide/IAM-roles.md index 747e2d46de0..a02339eb3fe 100644 --- a/docs/providers/spotinst/guide/IAM-roles.md +++ b/docs/providers/spotinst/guide/IAM-roles.md @@ -1,7 +1,7 @@ @@ -41,4 +41,4 @@ functions: For more information on how to set up IAM roles, check out our documentation [here][spotinst-help-center] [amazon-docs-url]: https://aws.amazon.com/iam/?sc_channel=PS&sc_campaign=acquisition_US&sc_publisher=google&sc_medium=iam_b&sc_content=amazon_iam_e&sc_detail=amazon%20iam&sc_category=iam&sc_segment=208382128687&sc_matchtype=e&sc_country=US&s_kwcid=AL!4422!3!208382128687!e!!g!!amazon%20iam&ef_id=WoypCQAABVVgCzd0:20180220230233:s -[spotinst-help-center]: https://help.spotinst.com/hc/en-us/articles/ +[spotinst-help-center]: https://help.spotinst.com/hc/en-us/articles/360000317585?flash_digest=59d5566c556b5d4def591c69a62a56b6c1e16c61 From aa24154ca9880f1f23acbb46cee1a69b07a49fd9 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Feb 2018 11:10:43 -0800 Subject: [PATCH 091/125] updated menu --- docs/providers/spotinst/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/spotinst/README.md b/docs/providers/spotinst/README.md index 1667f9cc923..97709c9144d 100755 --- a/docs/providers/spotinst/README.md +++ b/docs/providers/spotinst/README.md @@ -32,6 +32,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Endpoints
  • Cross-Origin Resource Sharing
  • Active Versions Documentation
  • +
  • IAM Roles
  • From 5618c5a453aee1997543cec94c50620b2f56360c Mon Sep 17 00:00:00 2001 From: Jakob Fix <170240+jfix@users.noreply.github.com> Date: Wed, 21 Feb 2018 23:01:04 +0100 Subject: [PATCH 092/125] Fix typo in sub-heading compatability -> compatibility (nitpicking, I know) --- docs/providers/aws/events/alexa-skill.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md index 3ade42de377..5f105ba0628 100644 --- a/docs/providers/aws/events/alexa-skill.md +++ b/docs/providers/aws/events/alexa-skill.md @@ -45,10 +45,10 @@ functions: enabled: false ``` -## Backwards compatability +## Backwards compatibility Previous syntax of this event didn't require a skill ID as parameter, but according to [Amazon's documentation](https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html#configuring-the-alexa-skills-kit-trigger) you should restrict your lambda function to be executed only by your skill. Omitting the skill id will make your Lambda function available for the public, allowing any other skill developer to invoke it. -(This is important, as [opposing to custom HTTPS endpoints](https://developer.amazon.com/docs/custom-skills/handle-requests-sent-by-alexa.html#request-verify), there's no way to validate the request was sent by your skill.) \ No newline at end of file +(This is important, as [opposing to custom HTTPS endpoints](https://developer.amazon.com/docs/custom-skills/handle-requests-sent-by-alexa.html#request-verify), there's no way to validate the request was sent by your skill.) From 90f89ec2fd66d8ddacbc649b3b1daad645613fd8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Feb 2018 18:02:04 -0800 Subject: [PATCH 093/125] fixed attribute --- docs/providers/spotinst/guide/IAM-roles.md | 4 ++-- lib/plugins/create/templates/spotinst-java8/serverless.yml | 2 +- lib/plugins/create/templates/spotinst-nodejs/serverless.yml | 2 +- lib/plugins/create/templates/spotinst-python/serverless.yml | 2 +- lib/plugins/create/templates/spotinst-ruby/serverless.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/providers/spotinst/guide/IAM-roles.md b/docs/providers/spotinst/guide/IAM-roles.md index a02339eb3fe..ba2ec0516a1 100644 --- a/docs/providers/spotinst/guide/IAM-roles.md +++ b/docs/providers/spotinst/guide/IAM-roles.md @@ -29,11 +29,11 @@ functions: timeout: 30 access: private iamRoleConfig: - - id: ${role-id} + roleId: ${role-id} ``` ## Parameters -- id: the role created on the console +- roleId: the role created on the console - ex : sfr-5ea76784 diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index 7301c3c4314..d4eff03c485 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -27,7 +27,7 @@ functions: timeout: 30 access: private # iamRoleConfig: -# - id: # role-id +# roleId: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index a8aebf3592e..6fb1a26ee6f 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -27,7 +27,7 @@ functions: timeout: 30 access: private # iamRoleConfig: -# - id: # role-id +# roleId: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index 059f308dc9d..78308ba502e 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -27,7 +27,7 @@ functions: timeout: 30 access: private # iamRoleConfig: -# - id: # role-id +# roleId: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 204543ca6e7..1ff0eb2283b 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -27,7 +27,7 @@ functions: timeout: 30 access: private # iamRoleConfig: -# - id: # role-id +# roleId: # role-id # activeVersions: # - "version": "$LATEST" # "percentage": 100.0 From a4de7288f37400722763d1ac3524cee48ce50bdb Mon Sep 17 00:00:00 2001 From: icj217 Date: Thu, 22 Feb 2018 13:21:50 -0500 Subject: [PATCH 094/125] Added name property to CloudFormation template Now when a user specifies a name property under the cloudwatchEvent, it will be carried over to the CF template. --- .../aws/package/compile/events/cloudWatchEvent/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js index d8ef7b2e998..2f62e5b0f81 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js @@ -26,6 +26,7 @@ class AwsCompileCloudWatchEventEvents { let Input; let InputPath; let Description; + let Name; if (typeof event.cloudwatchEvent === 'object') { if (!event.cloudwatchEvent.event) { @@ -45,6 +46,7 @@ class AwsCompileCloudWatchEventEvents { Input = event.cloudwatchEvent.input; InputPath = event.cloudwatchEvent.inputPath; Description = event.cloudwatchEvent.description; + Name = event.cloudwatchEvent.name; if (Input && InputPath) { const errorMessage = [ @@ -87,6 +89,7 @@ class AwsCompileCloudWatchEventEvents { "EventPattern": ${EventPattern.replace(/\\n|\\r/g, '')}, "State": "${State}", ${Description ? `"Description": "${Description}",` : ''} + ${Name ? `"Name": "${Name}",` : ''} "Targets": [{ ${Input ? `"Input": "${Input.replace(/\\n|\\r/g, '')}",` : ''} ${InputPath ? `"InputPath": "${InputPath.replace(/\r?\n/g, '')}",` : ''} From dc3a4aa6af010b9d937afdce8b43904be4a8833d Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Thu, 22 Feb 2018 16:30:04 -0800 Subject: [PATCH 095/125] Fix `print`, clean pre-population, fix cyclic bug Fix `print` The print command is highly linked to the `Variables` and `Service` codebases, keep those in sync and leave reminders about the link. Made these explicit and separately implemented to avoid complexity. Additionally, the print command re-populates an object with the *very similar* content as the previously pre-populated service (just not augmented as just mentioned). This can lead to cross contamination between the two. As such, all caches must be cleared per unique invocation of service/object/property population. Add tests for some expected but previously unverified behaviors. Clean pre-population The previous implementation worked okay but was unnecessary and would have been a maintenance problem. Instead, just knock out the population of variables depending on those config dependent services and use the standard means of resolution. Fix cyclic bug (resulting from running print against a self-referencing serverless.yml) The caching of values could lead to a cyclic object remaining in the caches for variable population. This causes crashes and pain. Solved by the cache cleaning logic. --- lib/classes/PromiseTracker.js | 5 ++ lib/classes/Service.js | 6 ++ lib/classes/Variables.js | 148 ++++++++++++++++++-------------- lib/classes/Variables.test.js | 93 ++++++++++++++------ lib/plugins/print/print.js | 91 +++++++++++++++----- lib/plugins/print/print.test.js | 34 ++++++-- 6 files changed, 258 insertions(+), 119 deletions(-) diff --git a/lib/classes/PromiseTracker.js b/lib/classes/PromiseTracker.js index f7638535518..ea710720bbc 100644 --- a/lib/classes/PromiseTracker.js +++ b/lib/classes/PromiseTracker.js @@ -4,11 +4,15 @@ const logWarning = require('./Error').logWarning; class PromiseTracker { constructor() { + this.reset(); + } + reset() { this.promiseList = []; this.promiseMap = {}; this.startTime = Date.now(); } start() { + this.reset(); this.interval = setInterval(this.report.bind(this), 2500); } report() { @@ -25,6 +29,7 @@ class PromiseTracker { } stop() { clearInterval(this.interval); + this.reset(); } add(variable, prms, specifier) { const promise = prms; diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 68991debd7e..27bb4882e3b 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -8,6 +8,9 @@ const semver = require('semver'); class Service { constructor(serverless, data) { + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### this.serverless = serverless; // Default properties @@ -84,6 +87,9 @@ class Service { } loadServiceFileParam(serviceFilename, serverlessFileParam) { + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### const that = this; const serverlessFile = serverlessFileParam; diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 8d693ba0ae5..afbd0dc04b5 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -10,6 +10,33 @@ const fse = require('../utils/fs/fse'); const logWarning = require('./Error').logWarning; const PromiseTracker = require('./PromiseTracker'); +/** + * Maintainer's notes: + * + * This is a tricky class to modify and maintain. A few rules on how it works... + * + * 0. The population of a service consists of pre-population, followed by population. Pre- + * population consists of populating only those settings which are required for variable + * resolution. Current examples include region and stage as they must be resolved to a + * concrete value before they can be used in the provider's `request` method (used by + * `getValueFromCf`, `getValueFromS3`, and `getValueFromSsm`) to resolve the associated values. + * Original issue: #4725 + * 1. All variable populations occur in generations. Each of these generations resolves each + * present variable in the given object or property (i.e. terminal string properties and/or + * property parts) once. This is to say that recursive resolutions should not be made. This is + * because cyclic references are allowed [i.e. ${self:} and the like]) and doing so leads to + * dependency and dead-locking issues. This leads to a problem with deep value population (i.e. + * populating ${self:foo.bar} when ${self:foo} has a value of {opt:bar}). To pause that, one must + * pause population, noting the continued depth to traverse. This motivated "deep" variables. + * Original issue #4687 + * 2. The resolution of variables can get very confusing if the same object is used multiple times. + * An example of this is the print command which *implicitly/invisibly* populates the + * serverless.yml and then *explicitly/visibily* renders the same file again, without the + * adornments that the framework's components make to the originally loaded service. As a result, + * it is important to reset this object for each use. + * 3. Note that despite some AWS code herein that this class is used in all plugins. Obviously + * users avoid the AWS-specific variable types when targetting other clouds. + */ class Variables { constructor(serverless) { this.serverless = serverless; @@ -32,66 +59,53 @@ class Variables { loadVariableSyntax() { this.variableSyntax = RegExp(this.service.provider.variableSyntax, 'g'); } + + initialCall(func) { + this.deep = []; + this.tracker.start(); + return func().finally(() => { + this.tracker.stop(); + this.deep = []; + }); + } + // ############# // ## SERVICE ## // ############# - prepopulateService() { + prepopulateObject(objectToPopulate) { const dependentServices = [ - { name: 'CloudFormation', regex: this.cfRefSyntax }, - { name: 'S3', regex: this.s3RefSyntax }, - { name: 'SSM', regex: this.ssmRefSyntax }, + { name: 'CloudFormation', method: 'getValueFromCf', original: this.getValueFromCf }, + { name: 'S3', method: 'getValueFromS3', original: this.getValueFromS3 }, + { name: 'SSM', method: 'getValueFromSsm', original: this.getValueFromSsm }, ]; const dependencyMessage = (configName, configValue, serviceName) => - `Variable Failure: value ${ - configName - } set to '${ - configValue - }' references ${ - serviceName - } which requires a ${ - configName - } value for use.`; - const getVariableParts = (variableString) => { - const matches = this.getMatches(variableString); - return matches.reduce( - (accumulation, current) => - accumulation.concat(this.splitByComma(current.variable)), - []); - }; - const serviceMatch = (variablePart) => { - const variable = variablePart.match(this.deepRefSyntax) ? - this.getVariableFromDeep(variablePart) : - variablePart; - return dependentServices.find(service => variable.match(service.regex)); - }; - const getUntilValid = (config) => { - const parts = getVariableParts(config.value); - const service = parts.reduce( - (accumulation, part) => accumulation || serviceMatch(part), - undefined); - if (service) { - const msg = dependencyMessage(config.name, config.value, service.name); - return BbPromise.reject(new this.serverless.classes.Error(msg)); - } - return this.populateValue(config.value, false) - .then(populated => ( - populated.match(this.variableSyntax) ? - getUntilValid(_.assign(config, { value: populated })) : - _.assign({}, config, { populated }) - )); - }; - + `Variable Failure: value ${configName} set to '${configValue}' references ${ + serviceName} which requires a ${configName} value for use.`; + // replace and then restore the methods for obtaining values from dependent services. the + // replacement naturally rejects dependencies on these services that occur during prepopulation. + // prepopulation is, of course, the process of obtaining the required configuration for using + // these services. + dependentServices.forEach((dependentService) => { // knock out + this[dependentService.method] = (variableString) => BbPromise.reject( + dependencyMessage(objectToPopulate.name, variableString, dependentService.name)); + }); + return this.populateValue(objectToPopulate.value, true) // populate + .then(populated => _.assign(objectToPopulate, { populated })) + .finally(() => { + dependentServices.forEach((dependentService) => { // restore + this[dependentService.method] = dependentService.original; + }); + }); + } + prepopulateService() { const provider = this.serverless.getProvider('aws'); if (provider) { const requiredConfig = [ _.assign({ name: 'region' }, provider.getRegionSourceValue()), _.assign({ name: 'stage' }, provider.getStageSourceValue()), ]; - const configToPopulate = requiredConfig.filter(config => - !_.isUndefined(config.value) && - (_.isString(config.value) && config.value.match(this.variableSyntax))); - const configPromises = configToPopulate.map(getUntilValid); - return this.assignProperties(provider, configPromises); + const prepopulations = requiredConfig.map(this.prepopulateObject.bind(this)); + return this.assignProperties(provider, prepopulations); } return BbPromise.resolve(); } @@ -102,6 +116,9 @@ class Variables { * @returns {Promise.|*} A promise resolving to the populated service. */ populateService(processedOptions) { + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### this.options = processedOptions || {}; this.loadVariableSyntax(); @@ -110,16 +127,15 @@ class Variables { // remove this.service.provider.variableSyntax = undefined; // otherwise matches itself this.service.serverless = undefined; - this.tracker.start(); - return this.prepopulateService() - .then(() => this.populateObject(this.service)) - .finally(() => { - // restore - this.tracker.stop(); - this.service.serverless = this.serverless; - this.service.provider.variableSyntax = variableSyntaxProperty; - }) - .then(() => this.service); + return this.initialCall(() => + this.prepopulateService() + .then(() => this.populateObjectImpl(this.service) + .finally(() => { + // restore + this.service.serverless = this.serverless; + this.service.provider.variableSyntax = variableSyntaxProperty; + })) + .then(() => this.service)); } // ############ // ## OBJECT ## @@ -225,12 +241,16 @@ class Variables { * @returns {Promise.|*} A promise resolving to the in-place populated object. */ populateObject(objectToPopulate) { + return this.initialCall(() => this.populateObjectImpl(objectToPopulate)); + } + populateObjectImpl(objectToPopulate) { const leaves = this.getProperties(objectToPopulate, true, objectToPopulate); const populations = this.populateVariables(leaves); + if (populations.length === 0) { + return BbPromise.resolve(objectToPopulate); + } return this.assignProperties(objectToPopulate, populations) - .then(() => (populations.length ? - this.populateObject(objectToPopulate) : - objectToPopulate)); + .then(() => this.populateObjectImpl(objectToPopulate)); } // ############## // ## PROPERTY ## @@ -304,7 +324,7 @@ class Variables { * is true */ populateValue(valueToPopulate, root) { - const property = _.cloneDeep(valueToPopulate); + const property = valueToPopulate; const matches = this.getMatches(property); if (!_.isArray(matches)) { return BbPromise.resolve(property); @@ -325,7 +345,7 @@ class Variables { * @returns {Promise.|*} A promise resolving to the populated result. */ populateProperty(propertyToPopulate) { - return this.populateValue(propertyToPopulate, true); + return this.initialCall(() => this.populateValue(propertyToPopulate, true)); } /** @@ -503,7 +523,7 @@ class Variables { getValueFromSelf(variableString) { const valueToPopulate = this.service; - const deepProperties = variableString.split(':')[1].split('.'); + const deepProperties = variableString.split(':')[1].split('.').filter(property => property); return this.getDeeperValue(deepProperties, valueToPopulate); } diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 4e7c3bb338b..d1bcee17ba9 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -21,11 +21,11 @@ const Variables = require('../../lib/classes/Variables'); BbPromise.longStackTraces(true); -chai.should(); - chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); +chai.should(); + const expect = chai.expect; @@ -55,32 +55,73 @@ describe('Variables', () => { }); describe('#populateService()', () => { - it('should call loadVariableSyntax and then populateProperty', () => { - const loadVariableSyntaxStub = sinon.stub(serverless.variables, 'loadVariableSyntax') - .returns(); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObject') - .returns(BbPromise.resolve()); - return expect(serverless.variables.populateService()).to.be.fulfilled - .then(() => { - expect(loadVariableSyntaxStub).to.be.calledOnce; - expect(populateObjectStub).to.be.calledOnce; - expect(loadVariableSyntaxStub).to.be.calledBefore(populateObjectStub); - }) - .finally(() => { - populateObjectStub.restore(); - loadVariableSyntaxStub.restore(); - }); - }); - it('should remove problematic attributes bofore calling populateObject with the service', + it('should remove problematic attributes bofore calling populateObjectImpl with the service', () => { - const populateObjectStub = sinon.stub(serverless.variables, 'populateObject', (val) => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') + .returns(BbPromise.resolve()); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', (val) => { expect(val).to.equal(serverless.service); expect(val.provider.variableSyntax).to.be.undefined; expect(val.serverless).to.be.undefined; return BbPromise.resolve(); }); - return expect(serverless.variables.populateService()).to.be.fulfilled - .then().finally(() => populateObjectStub.restore()); + return serverless.variables.populateService().should.be.fulfilled + .then().finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + it('should clear caches and remaining state *before* [pre]populating service', + () => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService', + (val) => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + return BbPromise.resolve(val); + }); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', + (val) => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + return BbPromise.resolve(val); + }); + serverless.variables.deep.push('${foo:}'); + const prms = BbPromise.resolve('foo'); + serverless.variables.tracker.add('foo:', prms, '${foo:}'); + prms.state = 'resolved'; + return serverless.variables.populateService().should.be.fulfilled + .then().finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + it('should clear caches and remaining *after* [pre]populating service', + () => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService', + (val) => { + serverless.variables.deep.push('${foo:}'); + const promise = BbPromise.resolve(val); + serverless.variables.tracker.add('foo:', promise, '${foo:}'); + promise.state = 'resolved'; + return BbPromise.resolve(); + }); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', + (val) => { + serverless.variables.deep.push('${bar:}'); + const promise = BbPromise.resolve(val); + serverless.variables.tracker.add('bar:', promise, '${bar:}'); + promise.state = 'resolved'; + return BbPromise.resolve(); + }); + return serverless.variables.populateService().should.be.fulfilled + .then(() => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + }) + .finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); }); }); describe('#prepopulateService', () => { @@ -90,17 +131,17 @@ describe('Variables', () => { // variable syntax is loaded, and that the service object is cleaned up. Just use // populateService to do that work. let awsProvider; - let populateObjectStub; + let populateObjectImplStub; let requestStub; // just in case... don't want to actually call... beforeEach(() => { awsProvider = new AwsProvider(serverless, {}); - populateObjectStub = sinon.stub(serverless.variables, 'populateObject'); - populateObjectStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); + populateObjectImplStub = sinon.stub(serverless.variables, 'populateObjectImpl'); + populateObjectImplStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(new Error('unexpected'))); }); afterEach(() => { - populateObjectStub.restore(); + populateObjectImplStub.restore(); requestStub.restore(); }); const prepopulatedProperties = [ diff --git a/lib/plugins/print/print.js b/lib/plugins/print/print.js index b48f26e36f8..80cf59a53f7 100644 --- a/lib/plugins/print/print.js +++ b/lib/plugins/print/print.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const BbPromise = require('bluebird'); const getServerlessConfigFile = require('../../utils/getServerlessConfigFile'); const jc = require('json-cycle'); @@ -8,6 +9,7 @@ const YAML = require('js-yaml'); class Print { constructor(serverless) { this.serverless = serverless; + this.cache = {}; this.commands = { print: { @@ -23,29 +25,76 @@ class Print { }; } + adorn(svc) { + const service = svc; + // service object treatment + this.cache.serviceService = service.service; + if (_.isObject(this.cache.serviceService)) { + service.service = service.service.name; + service.serviceObject = this.cache.serviceService; + } + // provider treatment and defaults + this.cache.serviceProvider = service.provider; + if (_.isString(this.cache.serviceProvider)) { + service.provider = { name: this.cache.serviceProvider }; + } + service.provider = _.merge({ + stage: 'dev', + region: 'us-east-1', + variableSyntax: '\\${([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}', + }, service.provider); + } + strip(svc) { + const service = svc; + if (_.isObject(this.cache.serviceService)) { + service.service = this.cache.serviceService; + delete service.serviceObject; + } + if (_.isString(this.cache.serviceProvider)) { + service.provider = this.cache.serviceProvider; + } else { // is object + if (!this.cache.serviceProvider.stage) { + delete service.provider.stage; + } + if (!this.cache.serviceProvider.region) { + delete service.provider.region; + } + if (this.cache.serviceProvider.variableSyntax) { + service.provider.variableSyntax = this.cache.serviceProvider.variableSyntax; + } + } + } print() { - let variableSyntax; - this.serverless.variables.options = this.serverless.processedInput.options; - this.serverless.variables.loadVariableSyntax(); + // ##################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Variables.js ## + // ## there, see `populateService` ## + // ## here, see below ## + // ##################################################################### + // ################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Service.js ## + // ## there, see `constructor` and `loadServiceFileParam` ## + // ## here, see `strip` and `adorn` ## + // ################################################################### + // The *already loaded* Service (i.e. serverless.yml) is adorned with extras for use by the + // framework and throughout its codebase. We could try using the populated service but that + // would require playing whack-a-mole on issues as changes are made to the service anywhere in + // the codebase. Avoiding that, this method must read the serverless.yml file itself, adorn it + // as the Service class would and then populate it, reversing the adornments thereafter in + // preparation for printing the service for the user. return getServerlessConfigFile(process.cwd()) - .then((data) => { - const conf = data; - // Need to delete variableSyntax to avoid potential matching errors - if (conf.provider.variableSyntax) { - variableSyntax = conf.provider.variableSyntax; - delete conf.provider.variableSyntax; - } - this.serverless.variables.service = conf; - this.serverless.variables.cache = {}; - return this.serverless.variables.populateObject(conf); - }) - .then((data) => { - const conf = data; - if (variableSyntax !== undefined) { - conf.provider.variableSyntax = variableSyntax; - } - const out = JSON.parse(jc.stringify(conf)); - this.serverless.cli.consoleLog(YAML.dump(out, { noRefs: true })); + .then((svc) => { + const service = svc; + this.adorn(service); + // Need to delete variableSyntax to avoid self-matching errors + this.serverless.variables.loadVariableSyntax(); + delete service.provider.variableSyntax; // cached by adorn, restored by strip + this.serverless.variables.service = service; + return this.serverless.variables.populateObject(service) + .then((populated) => { + this.strip(populated); + const out = JSON.parse(jc.stringify(populated)); + this.serverless.cli.consoleLog(YAML.dump(out, { noRefs: true })); + }); }); } diff --git a/lib/plugins/print/print.test.js b/lib/plugins/print/print.test.js index ec4dc1950e0..7ed46582ef0 100644 --- a/lib/plugins/print/print.test.js +++ b/lib/plugins/print/print.test.js @@ -19,9 +19,9 @@ describe('Print', () => { '../../utils/getServerlessConfigFile': getServerlessConfigFileStub, }); serverless = new Serverless(); - serverless.processedInput = { - commands: ['print'], - options: { stage: undefined, region: undefined }, + serverless.variables.options = { + stage: 'dev', + region: 'us-east-1', }; serverless.cli = new CLI(serverless); print = new PrintPlugin(serverless); @@ -48,7 +48,25 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.have.string(YAML.dump(conf)); + expect(YAML.load(message)).to.eql(conf); + }); + }); + + it('should print special service object and provider string configs', () => { + const conf = { + service: { + name: 'my-service', + }, + provider: 'aws', + }; + getServerlessConfigFileStub.resolves(conf); + + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(YAML.load(message)).to.eql(conf); }); }); @@ -80,7 +98,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); @@ -115,7 +133,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); @@ -154,7 +172,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); @@ -186,7 +204,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); }); From bf5a8c9fd4a90946f1862e1837e6899b542d4cb2 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Thu, 22 Feb 2018 17:17:55 -0800 Subject: [PATCH 096/125] Fix 4744 and associated The framework's Service class modifies a user's service, mutating a given `service: { name: 'string' }` to `service: 'string'` but doesn't account for this in the variables system. This is the basis for #4744. While working this area, I discovered that the Service class also does this to the provider, accepting `provider: 'aws'` and replacing it with `provider: { name: 'aws' }`, causing the natural `${self:provider}` to fail. Catching either 'self:service.name' or 'self:provider' and replacing them with the mutated reference solves this user confusion. --- lib/classes/Variables.js | 11 ++++++++++- lib/classes/Variables.test.js | 23 ++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index afbd0dc04b5..b9faccc14e2 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -522,8 +522,17 @@ class Variables { } getValueFromSelf(variableString) { + let variable = variableString; + // The loaded service is altered during load in ~/lib/classes/Service (see loadServiceFileParam) + // Account for these so that user's reference to their file populate properly + if (variable === 'self:service.name') { + variable = 'self:service'; + } + if (variable === 'self:provider') { + variable = 'self:provider.name'; + } const valueToPopulate = this.service; - const deepProperties = variableString.split(':')[1].split('.').filter(property => property); + const deepProperties = variable.split(':')[1].split('.').filter(property => property); return this.getDeeperValue(deepProperties, valueToPopulate); } diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index d1bcee17ba9..8962814e371 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -1086,22 +1086,39 @@ module.exports = { }); describe('#getValueFromSelf()', () => { + beforeEach(() => { + serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}'; + serverless.variables.loadVariableSyntax(); + delete serverless.service.provider.variableSyntax; + }); it('should get variable from self serverless.yml file', () => { serverless.variables.service = { service: 'testService', provider: serverless.service.provider, }; - serverless.variables.loadVariableSyntax(); return serverless.variables.getValueFromSelf('self:service').should.become('testService'); }); - + it('should redirect ${self:service.name} to ${self:service}', () => { + serverless.variables.service = { + service: 'testService', + provider: serverless.service.provider, + }; + return serverless.variables.getValueFromSelf('self:service.name') + .should.become('testService'); + }); + it('should redirect ${self:provider} to ${self:provider.name}', () => { + serverless.variables.service = { + service: 'testService', + provider: { name: 'aws' }, + }; + return serverless.variables.getValueFromSelf('self:provider').should.become('aws'); + }); it('should handle self-references to the root of the serverless.yml file', () => { serverless.variables.service = { service: 'testService', provider: 'testProvider', defaults: serverless.service.defaults, }; - serverless.variables.loadVariableSyntax(); return serverless.variables.getValueFromSelf('self:') .should.eventually.equal(serverless.variables.service); }); From 0d652f287c72f15beeb7be2cb56d5c793597aa1b Mon Sep 17 00:00:00 2001 From: Yunspace Date: Fri, 23 Feb 2018 20:50:00 +1100 Subject: [PATCH 097/125] upgraded fsharp example to dotnet 2.0, updated docs, unit and integration tests. Removed duplicate code from docs section --- .gitignore | 4 + docker-compose.yml | 2 +- .../examples/hello-world/fsharp/Handler.fs | 17 ---- .../aws/examples/hello-world/fsharp/README.md | 18 ++-- .../hello-world/fsharp/aws-fsharp.fsproj | 24 ----- .../hello-world/fsharp/serverless.yml | 87 ------------------- lib/plugins/create/create.test.js | 2 +- .../templates/aws-fsharp/aws-fsharp.fsproj | 7 +- .../create/templates/aws-fsharp/build.cmd | 2 +- .../create/templates/aws-fsharp/build.sh | 2 +- .../create/templates/aws-fsharp/global.json | 5 -- .../templates/aws-fsharp/serverless.yml | 4 +- tests/templates/test_all_templates | 2 +- 13 files changed, 23 insertions(+), 153 deletions(-) delete mode 100644 docs/providers/aws/examples/hello-world/fsharp/Handler.fs delete mode 100644 docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj delete mode 100644 docs/providers/aws/examples/hello-world/fsharp/serverless.yml delete mode 100644 lib/plugins/create/templates/aws-fsharp/global.json diff --git a/.gitignore b/.gitignore index 4d98e961769..54f8012af4b 100755 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,7 @@ jest # VIM *.swp + +# DotNet +[Bb]in/ +[Oo]bj/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 98c398c8f73..4c721f1afc4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,7 +60,7 @@ services: volumes: - ./tmp/serverless-integration-test-aws-csharp:/app aws-fsharp: - image: microsoft/dotnet:1.0.4-sdk + image: microsoft/dotnet:2.0-sdk volumes: - ./tmp/serverless-integration-test-aws-fsharp:/app aws-go: diff --git a/docs/providers/aws/examples/hello-world/fsharp/Handler.fs b/docs/providers/aws/examples/hello-world/fsharp/Handler.fs deleted file mode 100644 index b9e1806fea2..00000000000 --- a/docs/providers/aws/examples/hello-world/fsharp/Handler.fs +++ /dev/null @@ -1,17 +0,0 @@ -namespace AwsDotnetFsharp -open Amazon.Lambda.Core - -[)>] -do () - -type Request = { Key1 : string; Key2 : string; Key3 : string } -type Response = { Message : string; Request : Request } - -module Handler = - open System - open System.IO - open System.Text - - let hello(request:Request) = - { Message="Go Serverless v1.0! Your function executed successfully!" - Request=request } \ No newline at end of file diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index 562c1e498c8..b973a9c4640 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -23,15 +23,15 @@ Commands ## 1. Create a service -``` -sls create --template aws-fsharp --path myService -``` - -Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. - -The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. - -## 2. Build using .NET CLI tools and create zip package +``` +sls create --template aws-fsharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. + +## 2. Build using .NET Core 2.X CLI tools and create zip package ``` # Linux or Mac OS diff --git a/docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj b/docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj deleted file mode 100644 index f3a1aafd44b..00000000000 --- a/docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - netcoreapp1.0 - FsharpHandlers - aws-fsharp - - - - - - - - - - - - - - - - - - diff --git a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml deleted file mode 100644 index 6ef081debe8..00000000000 --- a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Welcome to Serverless! -# -# This file is the main config file for your service. -# It's very minimal at this point and uses default values. -# You can always add more config options for more control. -# We've included some commented out config examples here. -# Just uncomment any of them to get that config option. -# -# For full config options, check the docs: -# docs.serverless.com -# -# Happy Coding! - -service: aws-fsharp # NOTE: update this with your service name - -# You can pin your service to only deploy with a specific Serverless version -# Check out our docs for more details -# frameworkVersion: "=X.X.X" - -provider: - name: aws - runtime: dotnetcore1.0 - -# you can overwrite defaults here -# stage: dev -# region: us-east-1 - -# you can add statements to the Lambda function's IAM Role here -# iamRoleStatements: -# - Effect: "Allow" -# Action: -# - "s3:ListBucket" -# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } -# - Effect: "Allow" -# Action: -# - "s3:PutObject" -# Resource: -# Fn::Join: -# - "" -# - - "arn:aws:s3:::" -# - "Ref" : "ServerlessDeploymentBucket" - -# you can define service wide environment variables here -# environment: -# variable1: value1 - -# you can add packaging information here -package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - -functions: - hello: - handler: FsharpHandlers::AwsDotnetFsharp.Handler::hello - -# The following are a few example events you can configure -# NOTE: Please make sure to change your handler code to work with those events -# Check the event documentation for details -# events: -# - http: -# path: users/create -# method: get -# - s3: ${env:BUCKET} -# - schedule: rate(10 minutes) -# - sns: greeter-topic -# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx -# - iot: -# sql: "SELECT * FROM 'some_topic'" - -# Define function environment variables here -# environment: -# variable2: value2 - -# you can add CloudFormation resource templates here -#resources: -# Resources: -# NewResource: -# Type: AWS::S3::Bucket -# Properties: -# BucketName: my-new-bucket -# Outputs: -# NewOutput: -# Description: "Description for the output" -# Value: "Some output value" diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index c0bf9d726da..4ea33d8b885 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -159,7 +159,7 @@ describe('Create', () => { expect(dirContent).to.include('build.sh'); expect(dirContent).to.include('build.cmd'); expect(dirContent).to.include('aws-fsharp.fsproj'); - expect(dirContent).to.include('global.json'); + expect(dirContent).to.not.include('global.json'); }); }); diff --git a/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj b/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj index f3a1aafd44b..cb30c3303b2 100644 --- a/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj +++ b/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj @@ -1,7 +1,7 @@ - + - netcoreapp1.0 + netcoreapp2.0 FsharpHandlers aws-fsharp @@ -14,11 +14,10 @@ - - + diff --git a/lib/plugins/create/templates/aws-fsharp/build.cmd b/lib/plugins/create/templates/aws-fsharp/build.cmd index 468f8503abc..9bd8efb21a6 100644 --- a/lib/plugins/create/templates/aws-fsharp/build.cmd +++ b/lib/plugins/create/templates/aws-fsharp/build.cmd @@ -1,2 +1,2 @@ dotnet restore -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-fsharp/build.sh b/lib/plugins/create/templates/aws-fsharp/build.sh index f57aebe45af..3f92f87ce9e 100644 --- a/lib/plugins/create/templates/aws-fsharp/build.sh +++ b/lib/plugins/create/templates/aws-fsharp/build.sh @@ -12,4 +12,4 @@ fi dotnet restore #create deployment package -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-fsharp/global.json b/lib/plugins/create/templates/aws-fsharp/global.json deleted file mode 100644 index 8af244a4642..00000000000 --- a/lib/plugins/create/templates/aws-fsharp/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "1.0.4" - } -} diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index 0f8ca52b410..e12d4231b3f 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -19,7 +19,7 @@ service: aws-fsharp # NOTE: update this with your service name provider: name: aws - runtime: dotnetcore1.0 + runtime: dotnetcore2.0 # you can overwrite defaults here # stage: dev @@ -47,7 +47,7 @@ provider: # you can add packaging information here package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip + artifact: bin/release/netcoreapp2.0/deploy-package.zip # exclude: # - exclude-me.js # - exclude-me-dir/** diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates index fd88105abf8..88980ab509f 100755 --- a/tests/templates/test_all_templates +++ b/tests/templates/test_all_templates @@ -9,7 +9,7 @@ function integration-test { } integration-test aws-csharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' -integration-test aws-fsharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' +integration-test aws-fsharp './build.sh' integration-test aws-go 'cd /go/src/app && make build' integration-test aws-go-dep 'cd /go/src/app && make build' integration-test aws-groovy-gradle ./gradlew build From 91a10940eb938a0257368a5c5807447f8e747b0e Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Fri, 23 Feb 2018 10:09:18 -0800 Subject: [PATCH 098/125] Add further comments noting code entanglement --- lib/classes/Service.js | 11 ++++++++--- lib/classes/Variables.js | 4 ++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 27bb4882e3b..6fdbbd4c3f1 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -87,9 +87,6 @@ class Service { } loadServiceFileParam(serviceFilename, serverlessFileParam) { - // ####################################################################### - // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## - // ####################################################################### const that = this; const serverlessFile = serverlessFileParam; @@ -113,6 +110,14 @@ class Service { throw new ServerlessError(`"provider" property is missing in ${serviceFilename}`); } + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### + // ##################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Variables.js ## + // ## there, see `getValueFromSelf` ## + // ## here, see below ## + // ##################################################################### if (!_.isObject(serverlessFile.provider)) { const providerName = serverlessFile.provider; serverlessFile.provider = { diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index b9faccc14e2..4926f31dec7 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -523,6 +523,10 @@ class Variables { getValueFromSelf(variableString) { let variable = variableString; + // ################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Service.js ## + // ## there, see `loadServiceFileParam` ## + // ################################################################### // The loaded service is altered during load in ~/lib/classes/Service (see loadServiceFileParam) // Account for these so that user's reference to their file populate properly if (variable === 'self:service.name') { From 97d3a031c8468db87da20e48a8f9434c548e96b5 Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Sat, 24 Feb 2018 12:18:03 -0500 Subject: [PATCH 099/125] Allow UsagePlan's to be created without ApiKeys defined. Addresses issue #4459. --- .../aws/package/compile/events/apiGateway/lib/usagePlan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js index 7bc199bb34e..e877b2aed09 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js @@ -5,7 +5,7 @@ const BbPromise = require('bluebird'); module.exports = { compileUsagePlan() { - if (this.serverless.service.provider.apiKeys) { + if (this.serverless.service.provider.usagePlan || this.serverless.service.provider.apiKeys) { this.apiGatewayUsagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId(); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.apiGatewayUsagePlanLogicalId]: { From 0ee0a1e239bddaac39e798dcaa6ee3c75e2f585c Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Sat, 24 Feb 2018 09:34:50 -0800 Subject: [PATCH 100/125] Only disable and restore dependent service resolution methods once. Otherwise, subsequent removals may cache the previous replacements. If they restore last then they will restore with the replacements, breaking standard usage. Add tests for this guarantee. --- lib/classes/Variables.js | 23 +++++++++++++---------- lib/classes/Variables.test.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 4926f31dec7..764b34924e5 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -72,25 +72,24 @@ class Variables { // ############# // ## SERVICE ## // ############# - prepopulateObject(objectToPopulate) { + disableDepedentServices(func) { const dependentServices = [ { name: 'CloudFormation', method: 'getValueFromCf', original: this.getValueFromCf }, { name: 'S3', method: 'getValueFromS3', original: this.getValueFromS3 }, { name: 'SSM', method: 'getValueFromSsm', original: this.getValueFromSsm }, ]; - const dependencyMessage = (configName, configValue, serviceName) => - `Variable Failure: value ${configName} set to '${configValue}' references ${ - serviceName} which requires a ${configName} value for use.`; + const dependencyMessage = (configValue, serviceName) => + `Variable dependency failure: variable '${configValue}' references service ${ + serviceName} but using that service requires a concrete value to be called.`; // replace and then restore the methods for obtaining values from dependent services. the // replacement naturally rejects dependencies on these services that occur during prepopulation. // prepopulation is, of course, the process of obtaining the required configuration for using // these services. dependentServices.forEach((dependentService) => { // knock out this[dependentService.method] = (variableString) => BbPromise.reject( - dependencyMessage(objectToPopulate.name, variableString, dependentService.name)); + dependencyMessage(variableString, dependentService.name)); }); - return this.populateValue(objectToPopulate.value, true) // populate - .then(populated => _.assign(objectToPopulate, { populated })) + return func() .finally(() => { dependentServices.forEach((dependentService) => { // restore this[dependentService.method] = dependentService.original; @@ -100,12 +99,16 @@ class Variables { prepopulateService() { const provider = this.serverless.getProvider('aws'); if (provider) { - const requiredConfig = [ + const requiredConfigs = [ _.assign({ name: 'region' }, provider.getRegionSourceValue()), _.assign({ name: 'stage' }, provider.getStageSourceValue()), ]; - const prepopulations = requiredConfig.map(this.prepopulateObject.bind(this)); - return this.assignProperties(provider, prepopulations); + return this.disableDepedentServices(() => { + const prepopulations = requiredConfigs.map(config => + this.populateValue(config.value, true) // populate + .then(populated => _.assign(config, { populated }))); + return this.assignProperties(provider, prepopulations); + }); } return BbPromise.resolve(); } diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 8962814e371..5d523092ab0 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -169,7 +169,7 @@ describe('Variables', () => { it(`should reject ${config.name} variables in ${property.name} values`, () => { awsProvider.options[property.name] = config.value; return serverless.variables.populateService() - .should.be.rejectedWith('Variable Failure'); + .should.be.rejectedWith('Variable dependency failure'); }); it(`should reject recursively dependent ${config.name} service dependencies`, () => { serverless.variables.service.custom = { @@ -177,11 +177,36 @@ describe('Variables', () => { }; awsProvider.options.region = '${self:custom.settings.region}'; return serverless.variables.populateService() - .should.be.rejectedWith('Variable Failure'); + .should.be.rejectedWith('Variable dependency failure'); }); }); }); }); + describe('dependent service non-interference', () => { + const stateCombinations = [ + { region: 'foo', state: 'bar' }, + { region: 'foo', state: '${self:bar, "bar"}' }, + { region: '${self:foo, "foo"}', state: 'bar' }, + { region: '${self:foo, "foo"}', state: '${self:bar, "bar"}' }, + ]; + stateCombinations.forEach((combination) => { + it('must leave the dependent services in their original state', () => { + const dependentMethods = [ + { name: 'getValueFromCf', original: serverless.variables.getValueFromCf }, + { name: 'getValueFromS3', original: serverless.variables.getValueFromS3 }, + { name: 'getValueFromSsm', original: serverless.variables.getValueFromSsm }, + ]; + awsProvider.options.region = combination.region; + awsProvider.options.state = combination.state; + return serverless.variables.populateService().should.be.fulfilled + .then(() => { + dependentMethods.forEach((method) => { + expect(serverless.variables[method.name]).to.equal(method.original); + }); + }); + }); + }); + }); }); describe('#getProperties', () => { From 39d07e9aefb23b52b34c896ad589b6b41d378cad Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Sat, 24 Feb 2018 14:33:55 -0500 Subject: [PATCH 101/125] Expand UsagePlan testing to cover default and custom cases. --- .../events/apiGateway/lib/usagePlan.test.js | 80 ++++++++++++++----- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js index 370a8c59471..7ccbcfb7989 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js @@ -17,21 +17,6 @@ describe('#compileUsagePlan()', () => { serverless = new Serverless(); serverless.setProvider('aws', new AwsProvider(serverless, options)); serverless.service.service = 'first-service'; - serverless.service.provider = { - name: 'aws', - apiKeys: ['1234567890'], - usagePlan: { - quota: { - limit: 500, - offset: 10, - period: 'MONTH', - }, - throttle: { - burstLimit: 200, - rateLimit: 100, - }, - }, - }; serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, Outputs: {}, @@ -41,8 +26,67 @@ describe('#compileUsagePlan()', () => { awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'; }); - it('should compile usage plan resource', () => - awsCompileApigEvents.compileUsagePlan().then(() => { + it('should compile default usage plan resource', () => { + serverless.service.provider['apiKeys'] = ['1234567890']; + return awsCompileApigEvents.compileUsagePlan().then(() => { + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Type + ).to.equal('AWS::ApiGateway::UsagePlan'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].DependsOn + ).to.equal('ApiGatewayDeploymentTest'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.ApiStages[0].ApiId.Ref + ).to.equal('ApiGatewayRestApi'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.ApiStages[0].Stage + ).to.equal('dev'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.Description + ).to.equal('Usage plan for first-service dev stage'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.UsagePlanName + ).to.equal('first-service-dev'); + }) + }); + + it('should compile custom usage plan resource', () => { + serverless.service.provider['usagePlan'] = { + quota: { + limit: 500, + offset: 10, + period: 'MONTH', + }, + throttle: { + burstLimit: 200, + rateLimit: 100, + } + }; + + return awsCompileApigEvents.compileUsagePlan().then(() => { expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ @@ -106,5 +150,5 @@ describe('#compileUsagePlan()', () => { ].Properties.UsagePlanName ).to.equal('first-service-dev'); }) - ); + }); }); From 2b19a196d67640f30dcb8fd51514440c04db01b2 Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Sat, 24 Feb 2018 14:52:32 -0500 Subject: [PATCH 102/125] Lint corrections for usagePlan.test.js --- .../compile/events/apiGateway/lib/usagePlan.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js index 7ccbcfb7989..d43e8fe4746 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js @@ -27,7 +27,7 @@ describe('#compileUsagePlan()', () => { }); it('should compile default usage plan resource', () => { - serverless.service.provider['apiKeys'] = ['1234567890']; + serverless.service.provider.apiKeys = ['1234567890']; return awsCompileApigEvents.compileUsagePlan().then(() => { expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -70,11 +70,11 @@ describe('#compileUsagePlan()', () => { awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() ].Properties.UsagePlanName ).to.equal('first-service-dev'); - }) + }); }); it('should compile custom usage plan resource', () => { - serverless.service.provider['usagePlan'] = { + serverless.service.provider.usagePlan = { quota: { limit: 500, offset: 10, @@ -83,7 +83,7 @@ describe('#compileUsagePlan()', () => { throttle: { burstLimit: 200, rateLimit: 100, - } + }, }; return awsCompileApigEvents.compileUsagePlan().then(() => { @@ -149,6 +149,6 @@ describe('#compileUsagePlan()', () => { awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() ].Properties.UsagePlanName ).to.equal('first-service-dev'); - }) + }); }); }); From 818f58ac80613318a22b03edaf6c7dc4122e2e30 Mon Sep 17 00:00:00 2001 From: Marty Jones Date: Sun, 25 Feb 2018 21:38:14 -0600 Subject: [PATCH 103/125] Pass authorizer custom context to target lambda Per https://github.com/serverless/serverless/issues/4374 --- .../compile/events/apiGateway/lib/method/integration.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js index 1a900bd9d9b..c5050e3729f 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js @@ -26,6 +26,9 @@ const DEFAULT_COMMON_TEMPLATE = ` "sub": "$context.authorizer.claims.sub" }, + #set( $map = $context.authorizer ) + "enhancedAuthContext": $loop, + #set( $map = $input.params().header ) "headers": $loop, From 93c37e4d39d1ee2bb971c2c1a7d5b65efe7f42e9 Mon Sep 17 00:00:00 2001 From: horike37 Date: Tue, 27 Feb 2018 01:44:21 +0900 Subject: [PATCH 104/125] set default statusCodes to response if it is empty --- .../compile/events/apiGateway/lib/validate.js | 5 ++ .../events/apiGateway/lib/validate.test.js | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index bbafbb75163..936931cdbe8 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -78,6 +78,11 @@ module.exports = { http.request = this.getRequest(http); http.request.passThrough = this.getRequestPassThrough(http); http.response = this.getResponse(http); + if (http.integration === 'AWS' && _.isEmpty(http.response)) { + http.response = { + statusCodes: DEFAULT_STATUS_CODES, + }; + } } else if (http.integration === 'AWS_PROXY' || http.integration === 'HTTP_PROXY') { // show a warning when request / response config is used with AWS_PROXY (LAMBDA-PROXY) if (http.request) { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index a2132f048e0..4e481c9d8b5 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -1728,4 +1728,54 @@ describe('#validate()', () => { expect(validated.events).to.be.an('Array').with.length(1); expect(validated.events[0].http.request.passThrough).to.equal(undefined); }); + + it('should set default statusCodes to response for lambda by default', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'GET', + path: 'users/list', + integration: 'lambda', + integrationMethod: 'GET', + }, + }, + ], + }, + }; + + const validated = awsCompileApigEvents.validate(); + expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events[0].http.response.statusCodes).to.deep.equal({ + 200: { + pattern: '', + }, + 400: { + pattern: '[\\s\\S]*\\[400\\][\\s\\S]*', + }, + 401: { + pattern: '[\\s\\S]*\\[401\\][\\s\\S]*', + }, + 403: { + pattern: '[\\s\\S]*\\[403\\][\\s\\S]*', + }, + 404: { + pattern: '[\\s\\S]*\\[404\\][\\s\\S]*', + }, + 422: { + pattern: '[\\s\\S]*\\[422\\][\\s\\S]*', + }, + 500: { + pattern: + '[\\s\\S]*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\])[\\s\\S]*', + }, + 502: { + pattern: '[\\s\\S]*\\[502\\][\\s\\S]*', + }, + 504: { + pattern: '([\\s\\S]*\\[504\\][\\s\\S]*)|(^[Task timed out].*)', + }, + }); + }); }); From 53cc8fb607444ed7688ac1c0b922e53d399a02e9 Mon Sep 17 00:00:00 2001 From: Craig Burdulis Date: Mon, 26 Feb 2018 20:26:51 -0500 Subject: [PATCH 105/125] Added test case and documentation for name property --- docs/providers/aws/events/cloudwatch-event.md | 21 ++++++++++++++ .../events/cloudWatchEvent/index.test.js | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/docs/providers/aws/events/cloudwatch-event.md b/docs/providers/aws/events/cloudwatch-event.md index 3d75da37476..7e40bbab444 100644 --- a/docs/providers/aws/events/cloudwatch-event.md +++ b/docs/providers/aws/events/cloudwatch-event.md @@ -111,3 +111,24 @@ functions: state: - pending ``` + +## Specifying a Name + +You can also specify a CloudWatch Event name. Keep in mind that the name must begin with a letter; contain only ASCII letters, digits, and hyphens; and not end with a hyphen or contain two consecutive hyphens. More infomation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html). + +```yml +functions: + myCloudWatch: + handler: myCloudWatch.handler + events: + - cloudwatchEvent: + name: 'my-cloudwatch-event-name' + event: + source: + - "aws.ec2" + detail-type: + - "EC2 Instance State-change Notification" + detail: + state: + - pending +``` 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 081bc08c7d3..8ea00df2854 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js @@ -246,6 +246,34 @@ describe('awsCompileCloudWatchEventEvents', () => { ).to.equal('test description'); }); + it('should respect name variable', () => { + 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"}', + description: 'test-event-name', + }, + }, + ], + }, + }; + + awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); + + expect(awsCompileCloudWatchEventEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 + .Properties.Name + ).to.equal('test-event-name'); + }); + it('should respect input variable as an object', () => { awsCompileCloudWatchEventEvents.serverless.service.functions = { first: { From 52af64368e2f6d698a0ec954225da2f51c9c0d5b Mon Sep 17 00:00:00 2001 From: Craig Burdulis Date: Mon, 26 Feb 2018 20:31:15 -0500 Subject: [PATCH 106/125] Fixed test script event name property --- .../aws/package/compile/events/cloudWatchEvent/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8ea00df2854..0dd5123a2d7 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js @@ -259,7 +259,7 @@ describe('awsCompileCloudWatchEventEvents', () => { }, enabled: false, input: '{"key":"value"}', - description: 'test-event-name', + name: 'test-event-name', }, }, ], From 5e98476210c4c771611ffcfb95f7eb8be4d5f6de Mon Sep 17 00:00:00 2001 From: Marty Jones Date: Tue, 27 Feb 2018 07:26:32 -0600 Subject: [PATCH 107/125] adds enhancedAuthContext --- docs/providers/aws/events/apigateway.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 2d04400b4ee..9a0960ebdf4 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -497,6 +497,7 @@ This method is more complicated and involves a lot more configuration of the `ht "cognitoPoolClaims": { "sub": "" }, + "enhancedAuthContext": {}, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", From 93c50b8006caccbaeb26806eda472b802d39f1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Guimar=C3=A3es?= Date: Wed, 28 Feb 2018 09:29:30 -0300 Subject: [PATCH 108/125] Adding source map support for all files --- .../create/templates/aws-nodejs-typescript/package.json | 3 +++ .../templates/aws-nodejs-typescript/source-map-install.js | 1 + .../templates/aws-nodejs-typescript/webpack.config.js | 8 +++++++- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index 24d137ae93a..8a6405cacf4 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -6,6 +6,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "dependencies": { + "source-map-support": "^0.5.0" + }, "devDependencies": { "@types/aws-lambda": "0.0.22", "@types/node": "^8.0.57", 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 new file mode 100644 index 00000000000..ef7457f72e7 --- /dev/null +++ b/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js @@ -0,0 +1 @@ +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 41c1876cd1f..46a0b7e61fd 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js @@ -1,8 +1,14 @@ 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 = { - entry: slsw.lib.entries, + entry: entries, devtool: 'source-map', resolve: { extensions: [ From 011a75abb8396ae923a2d2862d1b4854c6da8c2e Mon Sep 17 00:00:00 2001 From: Simon Males Date: Wed, 28 Feb 2018 18:21:10 +0100 Subject: [PATCH 109/125] Removing duplicate dependencies blocks --- .../create/templates/aws-nodejs-typescript/handler.ts | 1 - .../create/templates/aws-nodejs-typescript/package.json | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts index 02ddeddcfa4..7dfcf29e08c 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts +++ b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts @@ -1,5 +1,4 @@ import { APIGatewayEvent, Callback, Context, Handler } from 'aws-lambda'; -import "source-map-support/register"; export const hello: Handler = (event: APIGatewayEvent, context: Context, cb: Callback) => { const response = { diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index 6aa23afb120..525a1e9973e 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "source-map-support": "^0.5.0" + "source-map-support": "^0.5.0" }, "devDependencies": { "@types/aws-lambda": "0.0.22", @@ -18,8 +18,5 @@ "webpack": "^3.6.0" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", - "license": "MIT", - "dependencies": { - "source-map-support": "^0.5.3" - } + "license": "MIT" } From f6df4f54420d4d752d6519b52483cb51c991044f Mon Sep 17 00:00:00 2001 From: Dhenain Ambroise Date: Wed, 28 Feb 2018 22:55:44 +0100 Subject: [PATCH 110/125] Doc - Add AWS-only alternative for variableSyntax that is compatible with serverless way (credits to @dschep) --- docs/providers/aws/guide/variables.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/aws/guide/variables.md b/docs/providers/aws/guide/variables.md index 70f20e32d0f..03a4da7ff96 100644 --- a/docs/providers/aws/guide/variables.md +++ b/docs/providers/aws/guide/variables.md @@ -426,6 +426,7 @@ provider: name: aws runtime: nodejs6.10 variableSyntax: "\\${{([ ~:a-zA-Z0-9._\\'\",\\-\\/\\(\\)]+?)}}" # notice the double quotes for yaml to ignore the escape characters! +# variableSyntax: "\\${((?!AWS)[ ~:a-zA-Z0-9._'\",\\-\\/\\(\\)]+?)}" # Use like this: ${AWS::stuff} - N.B: Only works for AWS! custom: myStage: ${{opt:stage}} From 90cbfdcae2f8b22b4898700800b6e7a247ec2a02 Mon Sep 17 00:00:00 2001 From: Simon Males Date: Wed, 28 Feb 2018 23:04:39 +0100 Subject: [PATCH 111/125] Sync CHANGELOG to represent 1.26.1 release --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98352edea00..f0fc70501b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.26.1 (27.02.2018) +- [Fix lambda integration regression](https://github.com/serverless/serverless/pull/4775) + # 1.26.0 (29.01.2018) - [AWS Go support](https://github.com/serverless/serverless/pull/4669) - [Support for using an existing ApiGateway and Resources](https://github.com/serverless/serverless/pull/4247) From 064b977e478568e05d5645dcad47b091e5726b6f Mon Sep 17 00:00:00 2001 From: Dhenain Ambroise Date: Wed, 28 Feb 2018 23:10:39 +0100 Subject: [PATCH 112/125] Accept change (peer-review) - better comment --- docs/providers/aws/guide/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/guide/variables.md b/docs/providers/aws/guide/variables.md index 03a4da7ff96..2795dde58b9 100644 --- a/docs/providers/aws/guide/variables.md +++ b/docs/providers/aws/guide/variables.md @@ -426,7 +426,7 @@ provider: name: aws runtime: nodejs6.10 variableSyntax: "\\${{([ ~:a-zA-Z0-9._\\'\",\\-\\/\\(\\)]+?)}}" # notice the double quotes for yaml to ignore the escape characters! -# variableSyntax: "\\${((?!AWS)[ ~:a-zA-Z0-9._'\",\\-\\/\\(\\)]+?)}" # Use like this: ${AWS::stuff} - N.B: Only works for AWS! +# variableSyntax: "\\${((?!AWS)[ ~:a-zA-Z0-9._'\",\\-\\/\\(\\)]+?)}" # Use this for allowing CloudFormation Pseudo-Parameters in your serverless.yml -- e.g. ${AWS::Region}. All other Serverless variables work as usual. custom: myStage: ${{opt:stage}} From 92b856818c8a58a49a0a4e158570b03afe359db0 Mon Sep 17 00:00:00 2001 From: horike37 Date: Thu, 1 Mar 2018 19:37:48 +0900 Subject: [PATCH 113/125] minor tweak --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fc70501b9..fd98104f096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # 1.26.1 (27.02.2018) - [Fix lambda integration regression](https://github.com/serverless/serverless/pull/4775) +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.26.0...v1.26.1) + # 1.26.0 (29.01.2018) - [AWS Go support](https://github.com/serverless/serverless/pull/4669) - [Support for using an existing ApiGateway and Resources](https://github.com/serverless/serverless/pull/4247) From 3db1e7957122b735555f3eea056cbf4cfa29417f Mon Sep 17 00:00:00 2001 From: Michael Mior Date: Thu, 1 Mar 2018 19:14:57 -0500 Subject: [PATCH 114/125] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7eeba49abcd..6e42a6e2a4b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ **The Serverless Framework** – Build applications comprised of microservices that run in response to events, auto-scale for you, and only charge you when they run. This lowers the total cost of maintaining your apps, enabling you to build more logic, faster. -The Framework uses new event-driven compute services, like AWS Lambda, Google CloudFunctions, and more. It's a command-line tool, providing scaffolding, workflow automation and best practices for developing and deploying your serverless architecture. It's also completely extensible via plugins. +The Framework uses new event-driven compute services, like AWS Lambda, Google Cloud Functions, and more. It's a command-line tool, providing scaffolding, workflow automation and best practices for developing and deploying your serverless architecture. It's also completely extensible via plugins. Serverless is an MIT open-source project, actively maintained by a full-time, venture-backed team. From 2cbfa8df66ceaf49434874b19cc6dfef6ce3d4c1 Mon Sep 17 00:00:00 2001 From: Jason Butz Date: Fri, 2 Mar 2018 09:56:05 -0500 Subject: [PATCH 115/125] Fix Typos In GCF Labels Section --- docs/providers/google/guide/functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/google/guide/functions.md b/docs/providers/google/guide/functions.md index 9e1d6f9da74..53c9ec7ab4a 100644 --- a/docs/providers/google/guide/functions.md +++ b/docs/providers/google/guide/functions.md @@ -111,7 +111,7 @@ Labels can be applied globally, to all functions in your configuration file, and provider: name: google labels: - application: Severless Example + application: Serverless Example functions: first: @@ -129,7 +129,7 @@ functions: ``` With the above configuration the `httpFirst` function would have two labels applied, `application` and `team`. -The value of the `application` label would be `Severless Example`, the value of the `team` label would be `GCF Team`. +The value of the `application` label would be `Serverless Example`, the value of the `team` label would be `GCF Team`. The `httpSecond` function would have only one label applied, `application`, and it would have a value of `Serverless Example - Documentation`. From d5c031b23c628138740637552faf4c9c13d98ce1 Mon Sep 17 00:00:00 2001 From: Deniss Alimovs Date: Sun, 4 Mar 2018 17:41:30 +1100 Subject: [PATCH 116/125] Fix typo --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 9a0960ebdf4..d356f812c82 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -321,7 +321,7 @@ functions: identityValidationExpression: someRegex ``` -You can also use the Request Type Authorizer by setting the `type` property. In this case, your `identitySource` could contain multiple entries for you policy cache. The default `type` is 'token'. +You can also use the Request Type Authorizer by setting the `type` property. In this case, your `identitySource` could contain multiple entries for your policy cache. The default `type` is 'token'. ```yml functions: From 30bb45116ac86cad573f6c8ce9384a279bc369b0 Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Sun, 4 Mar 2018 11:54:50 +0000 Subject: [PATCH 117/125] Improve readability --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 9a0960ebdf4..9da9f4cd1a5 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -858,7 +858,7 @@ Now that you have these two CloudFormation templates defined in your `serverless ## Share API Gateway and API Resources -As you application grows, you will have idea to break it out into multiple services. However, each serverless project generates new API Gateway by default. If you want to share same API Gateway for muliple projects, you 'll need to reference REST API ID and Root Resource ID into serverless.yml files +As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway by default. However, you can share the same API Gateway for multiple projects by referencing its' REST API ID and Root Resource ID in `serverless.yml` as follows: ```yml service: service-name From 004d0094d522cac9b9c9bac3c75e1f7d5e67099c Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Sun, 4 Mar 2018 12:12:45 +0000 Subject: [PATCH 118/125] Update apigateway.md --- docs/providers/aws/events/apigateway.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 9da9f4cd1a5..538e514092a 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -873,7 +873,7 @@ functions: ``` -In case the application has many children and grandchildren paths, you also want to break them out into smaller services. +If your application has many nested paths, you might also want to break them out into smaller services. ```yml service: service-a @@ -907,7 +907,7 @@ functions: path: /posts/{id}/comments ``` -They reference the same parent path `/posts`. Cloudformation will throw error if we try to generate existed one. To avoid that, we must reference source ID of `/posts`. +The above example services both reference the same parent path `/posts`. However, Cloudformation will throw an error if we try to generate an existing path resource. To avoid that, we reference the resource ID of `/posts`. ```yml service: service-a @@ -937,7 +937,8 @@ functions: ``` -You can define more than one path resource. Otherwise, serverless will generate paths from root resource. `restApiRootResourceId` can be optional if there isn't path that need to be generated from the root +You can define more than one path resource, but by default, Serverless will generate them from the root resource. +`restApiRootResourceId` is optional if there a path resource isn't required for the root (`/`). ```yml service: service-a @@ -967,4 +968,4 @@ functions: ``` -For best practice and CI, CD friendly, we should define Cloudformation resources from early service, then use Cross-Stack References for another ones. +To be more in line with best practices and to be CI/CD friendly, we should define CloudFormation resources from an earlier service, then use Cross-Stack References from it in future projects. From 3f5a0416780b921886903149c7698f4979536de4 Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Sun, 4 Mar 2018 08:25:19 -0500 Subject: [PATCH 119/125] Continue recursion for #4687 --- lib/classes/Variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 4926f31dec7..1bb6db87d1a 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -334,7 +334,7 @@ class Variables { .then(results => this.renderMatches(property, matches, results)) .then((result) => { if (root && matches.length) { - return this.populateValue(result); + return this.populateValue(result, root); } return result; }); From cbda1b7b109bf72624cca17ee4825fc387e31cf7 Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Mon, 5 Mar 2018 06:49:56 +0000 Subject: [PATCH 120/125] Update apigateway.md --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 538e514092a..f442b67d1f0 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -858,7 +858,7 @@ Now that you have these two CloudFormation templates defined in your `serverless ## Share API Gateway and API Resources -As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway by default. However, you can share the same API Gateway for multiple projects by referencing its' REST API ID and Root Resource ID in `serverless.yml` as follows: +As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway by default. However, you can share the same API Gateway between multiple projects by referencing its' REST API ID and Root Resource ID in `serverless.yml` as follows: ```yml service: service-name From f067e16ec9b30153eed908f8f7b0412d244bb9fa Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Mon, 5 Mar 2018 06:50:30 +0000 Subject: [PATCH 121/125] Update apigateway.md --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index f442b67d1f0..1bdc4cd316d 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -907,7 +907,7 @@ functions: path: /posts/{id}/comments ``` -The above example services both reference the same parent path `/posts`. However, Cloudformation will throw an error if we try to generate an existing path resource. To avoid that, we reference the resource ID of `/posts`. +The above example services both reference the same parent path `/posts`. However, Cloudformation will throw an error if we try to generate an existing path resource. To avoid that, we reference the resource ID of `/posts`: ```yml service: service-a From fc4869ca74ae61d0e5b7ad72cd0d58893ff470c4 Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Mon, 5 Mar 2018 06:51:21 +0000 Subject: [PATCH 122/125] Update apigateway.md --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 1bdc4cd316d..4c140a0c745 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -938,7 +938,7 @@ functions: ``` You can define more than one path resource, but by default, Serverless will generate them from the root resource. -`restApiRootResourceId` is optional if there a path resource isn't required for the root (`/`). +`restApiRootResourceId` is optional if a path resource isn't required for the root (`/`). ```yml service: service-a From 8f32a59c7f0981f140507ba79afda6122bf38187 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Mon, 5 Mar 2018 13:33:18 -0800 Subject: [PATCH 123/125] add test that fails prior to the fix in #4800 and succeeds after --- lib/classes/Variables.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 8962814e371..684f3372770 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -787,6 +787,24 @@ module.exports = { .should.eventually.eql('my stage is dev') .then().finally(() => { delete process.env.TEST_VAR; }); }); + it('should run recursively through many nested variables', () => { + // eslint-disable-next-line no-template-curly-in-string + const property = 'my stage is ${env:${opt:name}}'; + process.env.TEST_VAR = 'dev'; + serverless.variables.options = { + name: 'T${opt:lvl0}', + lvl0: 'E${opt:lvl1}', + lvl1: 'S${opt:lvl2}', + lvl2: 'T${opt:lvl3}', + lvl3: '_${opt:lvl4}', + lvl4: 'V${opt:lvl5}', + lvl5: 'A${opt:lvl6}', + lvl6: 'R', + }; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is dev') + .then().finally(() => { delete process.env.TEST_VAR; }); + }); }); describe('#populateVariable()', () => { From ed9506e527a262f904c1ff98549b28d7e0388381 Mon Sep 17 00:00:00 2001 From: Erik Erikson Date: Mon, 5 Mar 2018 17:19:07 -0800 Subject: [PATCH 124/125] Add CloudWatch event missing attributes --- docs/providers/aws/guide/serverless.yml.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index d06f4803e89..a10c2767d31 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -146,6 +146,8 @@ functions: - prefix: uploads/ - suffix: .jpg - schedule: + name: my scheduled event + description: a description of my scheduled event's purpose rate: rate(10 minutes) enabled: false input: @@ -153,6 +155,7 @@ functions: key2: value2 stageParams: stage: dev + inputPath: '$.stageVariables' - sns: topicName: aggregate displayName: Data aggregation pipeline From dd09dc7f4e1dee14f421892ec304e9e7e7b27cdd Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Tue, 6 Mar 2018 03:53:14 +0000 Subject: [PATCH 125/125] Update apigateway.md --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 4c140a0c745..b068f1f3d8e 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -858,7 +858,7 @@ Now that you have these two CloudFormation templates defined in your `serverless ## Share API Gateway and API Resources -As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway by default. However, you can share the same API Gateway between multiple projects by referencing its' REST API ID and Root Resource ID in `serverless.yml` as follows: +As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway. However, you can share the same API Gateway between multiple projects by referencing its REST API ID and Root Resource ID in `serverless.yml` as follows: ```yml service: service-name