From 57532e6bd709536e5ec9f032a992975195ab06a9 Mon Sep 17 00:00:00 2001 From: Marcos Rava Date: Tue, 25 Jul 2023 16:42:47 -0300 Subject: [PATCH] feat(node): change node version to 14 --- .github/workflows/integrate.yml | 4 +- .github/workflows/validate.yml | 2 +- CHANGELOG.md | 2 +- docs/providers/aws/cli-reference/print.md | 4 +- docs/providers/aws/events/apigateway.md | 4 +- docs/providers/aws/events/websocket.md | 2 +- .../examples/hello-world/node/serverless.yml | 2 +- docs/providers/aws/guide/credentials.md | 4 +- docs/providers/aws/guide/functions.md | 14 +- docs/providers/aws/guide/serverless.yml.md | 2 +- docs/providers/aws/guide/services.md | 6 +- docs/providers/aws/guide/variables.md | 2 +- docs/providers/azure/guide/serverless.yml.md | 2 +- lib/plugins/aws/customResources/index.js | 2 +- .../package/compile/functions/index.test.js | 2846 +++++++++++++++++ lib/plugins/aws/provider.js | 4 +- .../aws-alexa-typescript/serverless.yml | 2 +- .../aws-clojurescript-gradle/serverless.yml | 2 +- .../aws-kotlin-nodejs-gradle/serverless.yml | 2 +- .../aws-nodejs-ecma-script/serverless.yml | 2 +- .../templates/aws-nodejs/serverless.yml | 2 +- .../azure-nodejs-typescript/serverless.yml | 2 +- .../templates/azure-nodejs/serverless.yml | 2 +- .../templates/hello-world/serverless.yml | 2 +- lib/plugins/package/lib/packageService.js | 2 +- test/fixtures/cli/variables/serverless.yml | 2 +- .../apiGatewayExtended/serverless.yml | 2 +- .../cognitoUserPool/serverless.yml | 2 +- .../programmatic/eventBridge/serverless.yml | 2 +- .../programmatic/functionEfs/serverless.yml | 2 +- .../programmatic/functionMsk/serverless.yml | 2 +- test/fixtures/programmatic/iot/serverless.yml | 2 +- .../iotFleetProvisioning/serverless.yml | 2 +- .../provisionedConcurrency/serverless.yml | 2 +- test/fixtures/programmatic/s3/serverless.yml | 2 +- .../programmatic/schedule/serverless.yml | 2 +- test/fixtures/programmatic/sns/serverless.yml | 2 +- test/fixtures/programmatic/sqs/serverless.yml | 2 +- .../programmatic/stream/serverless.yml | 2 +- .../variables-legacy/serverless.yml | 2 +- .../programmatic/websocket/serverless.yml | 2 +- .../cloudformation.tests.js | 6 +- .../fixtures/artifact/serverless.yml | 2 +- .../individually-function/serverless.yml | 2 +- .../fixtures/individually/serverless.yml | 2 +- .../fixtures/regular/serverless.yml | 2 +- .../plugins/aws/customResources/index.test.js | 6 +- .../lib/plugins/aws/invokeLocal/index.test.js | 16 +- .../aws/package/compile/functions.test.js | 44 +- .../aws/package/compile/layers.test.js | 4 +- .../utils/telemetry/generatePayload.test.js | 22 +- 51 files changed, 2952 insertions(+), 106 deletions(-) create mode 100644 lib/plugins/aws/package/compile/functions/index.test.js diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index f83c5dd3f30..f774fa51f52 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -100,7 +100,7 @@ jobs: - name: Unit tests run: npm test -- -b - linuxNode12: + linuxnode14: name: '[Linux] Node.js 12: Isolated unit tests' runs-on: ubuntu-latest steps: @@ -173,7 +173,7 @@ jobs: integrate: name: Integrate runs-on: ubuntu-latest - needs: [linuxNode14, windowsNode14, linuxNode12, linuxNode10] + needs: [linuxNode14, windowsNode14, linuxnode14, linuxNode10] timeout-minutes: 30 # Default is 360 env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 0e1be5766a9..c98722f18a8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -104,7 +104,7 @@ jobs: - name: Unit tests run: npm test -- -b - linuxNode12: + linuxnode14: name: '[Linux] Node.js 12: Isolated unit tests' runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index db7b0984909..406b28d6d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2036,7 +2036,7 @@ All notable changes to this project will be documented in this file. See [standa - [Updates to CI/CD settings for the beta](https://github.com/serverless/serverless/pull/6972) - [rename output variables to outputs](https://github.com/serverless/serverless/pull/6971) - [Fix Tencent Template and Readme](https://github.com/serverless/serverless/pull/6984) -- [Default to Nodejs12.x runtime](https://github.com/serverless/serverless/pull/6983) +- [Default to nodejs14.x runtime](https://github.com/serverless/serverless/pull/6983) - [#6162: Support multiple schemas, don't overwrite RequestModels for each](https://github.com/serverless/serverless/pull/6954) - [Support empty deploymentPrefix](https://github.com/serverless/serverless/pull/6941) diff --git a/docs/providers/aws/cli-reference/print.md b/docs/providers/aws/cli-reference/print.md index 135db376ff7..cc91addc782 100644 --- a/docs/providers/aws/cli-reference/print.md +++ b/docs/providers/aws/cli-reference/print.md @@ -44,7 +44,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x functions: hello: @@ -67,7 +67,7 @@ custom: bucketName: test provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x stage: dev # <-- Resolved functions: hello: diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 9df1dcc0599..da43c0511a7 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1447,7 +1447,7 @@ service: my-api provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x stage: dev region: eu-west-2 @@ -1635,7 +1635,7 @@ Resource policies are policy documents that are used to control the invocation o ```yml provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x resourcePolicy: - Effect: Allow diff --git a/docs/providers/aws/events/websocket.md b/docs/providers/aws/events/websocket.md index 2d890601433..76125eca409 100644 --- a/docs/providers/aws/events/websocket.md +++ b/docs/providers/aws/events/websocket.md @@ -75,7 +75,7 @@ service: serverless-ws-test provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x websocketsApiName: custom-websockets-api-name websocketsApiRouteSelectionExpression: $request.body.action # custom routes are selected by the value of the action property in the body diff --git a/docs/providers/aws/examples/hello-world/node/serverless.yml b/docs/providers/aws/examples/hello-world/node/serverless.yml index 909cabc93c2..90e6e6a30c7 100644 --- a/docs/providers/aws/examples/hello-world/node/serverless.yml +++ b/docs/providers/aws/examples/hello-world/node/serverless.yml @@ -3,7 +3,7 @@ service: hello-world # Service Name provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x functions: helloWorld: diff --git a/docs/providers/aws/guide/credentials.md b/docs/providers/aws/guide/credentials.md index eabd2d50a22..3131453fe81 100644 --- a/docs/providers/aws/guide/credentials.md +++ b/docs/providers/aws/guide/credentials.md @@ -149,7 +149,7 @@ You can even set up different profiles for different accounts, which can be used service: new-service provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x stage: dev profile: devProfile ``` @@ -197,7 +197,7 @@ This example `serverless.yml` snippet will load the profile depending upon the s service: new-service provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x profile: ${self:custom.profiles.${sls:stage}} custom: profiles: diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index ea1bfceeb06..af6191ac393 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -26,7 +26,7 @@ service: myService provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 versionFunctions: false # optional, default is true @@ -62,7 +62,7 @@ service: myService provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x functions: functionOne: @@ -82,7 +82,7 @@ service: myService provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x memorySize: 512 # will be inherited by all functions functions: @@ -98,7 +98,7 @@ service: myService provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x functions: functionOne: @@ -134,7 +134,7 @@ service: myService provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x iam: role: statements: # permissions for all of your functions can be set here @@ -489,7 +489,7 @@ service: service provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x functions: hello: @@ -542,7 +542,7 @@ service: myService provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x tracing: lambda: true ``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 33ff1ce7187..350ca2fc21b 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -300,7 +300,7 @@ functions: memorySize: 512 # memorySize for this specific function. reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit provisionedConcurrency: 3 # optional, Count of provisioned lambda instances - runtime: nodejs12.x # Runtime for this specific function. Overrides the default which is set on the provider level + runtime: nodejs14.x # Runtime for this specific function. Overrides the default which is set on the provider level timeout: 10 # Timeout for this specific function. Overrides the default set above. role: arn:aws:iam::XXXXXX:role/role # IAM role which will be used for this function onError: arn:aws:sns:us-east-1:XXXXXX:sns-topic # Optional SNS topic / SQS arn (Ref, Fn::GetAtt and Fn::ImportValue are supported as well) which will be used for the DeadLetterConfig diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index 3e243ae4d03..fb901bd4d82 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -104,7 +104,7 @@ service: users provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x stage: dev # Set the default stage used. Default is dev region: us-east-1 # Overwrite the default region used. Default is us-east-1 stackName: my-custom-stack-name-${sls:stage} # Overwrite default CloudFormation stack name. Default is ${self:service}-${sls:stage} @@ -234,7 +234,7 @@ service: users provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x memorySize: 512 … @@ -251,7 +251,7 @@ service: users provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x memorySize: 512 … diff --git a/docs/providers/aws/guide/variables.md b/docs/providers/aws/guide/variables.md index c0eb6e6abf9..cbf281f3ddb 100644 --- a/docs/providers/aws/guide/variables.md +++ b/docs/providers/aws/guide/variables.md @@ -202,7 +202,7 @@ You can add such custom output to CloudFormation stack. For example: service: another-service provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x region: ap-northeast-1 memorySize: 512 functions: diff --git a/docs/providers/azure/guide/serverless.yml.md b/docs/providers/azure/guide/serverless.yml.md index 9b4f01c0b78..c075b207d2f 100644 --- a/docs/providers/azure/guide/serverless.yml.md +++ b/docs/providers/azure/guide/serverless.yml.md @@ -25,7 +25,7 @@ frameworkVersion: '2' provider: name: azure region: West US 2 - runtime: nodejs12.x + runtime: nodejs14.x prefix: sample # prefix of generated resource name subscriptionId: 00000000-0000-0000-0000-000000000000 stage: dev # Default stage to be used diff --git a/lib/plugins/aws/customResources/index.js b/lib/plugins/aws/customResources/index.js index ebdf1f1302c..6cfaafd89e9 100644 --- a/lib/plugins/aws/customResources/index.js +++ b/lib/plugins/aws/customResources/index.js @@ -168,7 +168,7 @@ async function addCustomResourceToService(awsProvider, resourceName, iamRoleStat FunctionName: absoluteFunctionName, Handler, MemorySize: 1024, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 180, }, DependsOn: [], diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js new file mode 100644 index 00000000000..f7c809b8f44 --- /dev/null +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -0,0 +1,2846 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const fse = require('fs-extra'); +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 Serverless = require('../../../../../Serverless'); +const runServerless = require('../../../../../../test/utils/run-serverless'); +const fixtures = require('../../../../../../test/fixtures'); + +const { getTmpDirPath, createTmpFile } = require('../../../../../../test/utils/fs'); + +chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); + +const expect = chai.expect; + +describe('AwsCompileFunctions', () => { + let serverless; + let awsProvider; + let awsCompileFunctions; + const functionName = 'test'; + const compiledFunctionName = 'TestLambdaFunction'; + + beforeEach(() => { + const options = { + stage: 'dev', + region: 'us-east-1', + }; + serverless = new Serverless(options); + awsProvider = new AwsProvider(serverless, options); + serverless.setProvider('aws', awsProvider); + serverless.service.provider.name = 'aws'; + serverless.cli = new serverless.classes.CLI(); + awsCompileFunctions = new AwsCompileFunctions(serverless, options); + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { + Resources: {}, + Outputs: {}, + }; + + const serviceArtifact = 'new-service.zip'; + const individualArtifact = 'test.zip'; + awsCompileFunctions.packagePath = getTmpDirPath(); + // The contents of the test artifacts need to be predictable so the hashes stay the same + serverless.utils.writeFileSync( + path.join(awsCompileFunctions.packagePath, serviceArtifact), + 'foobar' + ); + serverless.utils.writeFileSync( + path.join(awsCompileFunctions.packagePath, individualArtifact), + 'barbaz' + ); + + awsCompileFunctions.serverless.service.service = 'new-service'; + awsCompileFunctions.serverless.service.package.artifactDirectoryName = 'somedir'; + awsCompileFunctions.serverless.service.package.artifact = path.join( + awsCompileFunctions.packagePath, + serviceArtifact + ); + awsCompileFunctions.serverless.service.functions = {}; + awsCompileFunctions.serverless.service.functions[functionName] = { + name: 'test', + package: { + artifact: path.join(awsCompileFunctions.packagePath, individualArtifact), + }, + handler: 'handler.hello', + }; + }); + + describe('#constructor()', () => { + it('should set the provider variable to an instance of AwsProvider', () => + expect(awsCompileFunctions.provider).to.be.instanceof(AwsProvider)); + }); + + describe('#downloadPackageArtifacts()', () => { + let requestStub; + let testFilePath; + const s3BucketName = 'test-bucket'; + const s3ArtifactName = 's3-hosted-artifact.zip'; + + beforeEach(() => { + testFilePath = createTmpFile('dummy-artifact'); + requestStub = sinon.stub(AWS, 'S3').returns({ + getObject: () => ({ + createReadStream() { + return fse.createReadStream(testFilePath); + }, + }), + }); + }); + + afterEach(() => { + AWS.S3.restore(); + }); + + it('should download the file and replace the artifact path for function packages', () => { + awsCompileFunctions.serverless.service.package.individually = true; + awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; + + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled.then(() => { + const artifactFileName = awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact + .split(path.sep) + .pop(); + + expect(requestStub.callCount).to.equal(1); + expect(artifactFileName).to.equal(s3ArtifactName); + }); + }); + + it('should download the file and replace the artifact path for service-wide packages', () => { + awsCompileFunctions.serverless.service.package.individually = false; + awsCompileFunctions.serverless.service.functions[functionName].package.artifact = false; + awsCompileFunctions.serverless.service.package.artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; + + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled.then(() => { + const artifactFileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + + expect(requestStub.callCount).to.equal(1); + expect(artifactFileName).to.equal(s3ArtifactName); + }); + }); + + it('should not access AWS.S3 if URL is not an S3 URl', () => { + AWS.S3.restore(); + const myRequestStub = sinon.stub(AWS, 'S3').returns({ + getObject: () => { + throw new Error('should not be invoked'); + }, + }); + awsCompileFunctions.serverless.service.functions[functionName].package.artifact = + 'https://s33amazonaws.com/this/that'; + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled.then(() => { + expect(myRequestStub.callCount).to.equal(1); + }); + }); + }); + + describe('#compileFunctions()', () => { + it('should use service artifact if not individually', () => { + awsCompileFunctions.serverless.service.package.individually = false; + const artifactTemp = awsCompileFunctions.serverless.service.functions.test.package.artifact; + awsCompileFunctions.serverless.service.functions.test.package.artifact = false; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const functionResource = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledFunctionName + ]; + + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + + expect(functionResource.Properties.Code.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); + awsCompileFunctions.serverless.service.functions.test.package.artifact = artifactTemp; + }); + }); + + it('should use function artifact if individually', () => { + awsCompileFunctions.serverless.service.package.individually = true; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const functionResource = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledFunctionName + ]; + + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact + .split(path.sep) + .pop(); + + expect(functionResource.Properties.Code.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); + }); + }); + + it('should use function artifact if individually at function level', () => { + awsCompileFunctions.serverless.service.functions[functionName].package.individually = true; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const functionResource = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledFunctionName + ]; + + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact + .split(path.sep) + .pop(); + + expect(functionResource.Properties.Code.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); + awsCompileFunctions.serverless.service.functions[functionName].package = { + individually: false, + }; + }); + }); + + it('should add an ARN provider role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.provider.role = 'arn:aws:xxx:*:*'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role); + }); + }); + + it('should add a logical role name provider role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.provider.role = 'LogicalNameRole'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup', 'LogicalNameRole']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal({ + 'Fn::GetAtt': [awsCompileFunctions.serverless.service.provider.role, 'Arn'], + }); + }); + }); + + it('should add a "Fn::GetAtt" Object provider role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.provider.role = { + 'Fn::GetAtt': ['LogicalRoleName', 'Arn'], + }; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup', 'LogicalRoleName']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role); + }); + }); + + it('should add an ARN function role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + role: 'arn:aws:xxx:*:*', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role); + }); + }); + + it('should add a logical role name function role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + role: 'LogicalRoleName', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup', 'LogicalRoleName']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal({ + 'Fn::GetAtt': [awsCompileFunctions.serverless.service.functions.func.role, 'Arn'], + }); + }); + }); + + it('should add a "Fn::GetAtt" Object function role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + role: { + 'Fn::GetAtt': ['LogicalRoleName', 'Arn'], + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup', 'LogicalRoleName']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role); + }); + }); + + it('should add a "Fn::ImportValue" Object function role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + role: { + 'Fn::ImportValue': 'Foo', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role); + }); + }); + + it('should prefer function declared role over provider declared role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.provider.role = 'arn:aws:xxx:*:*'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + role: 'arn:aws:xxx:*:*', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role + ).to.equal(awsCompileFunctions.serverless.service.functions.func.role); + }); + }); + + it('should add function declared roles', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.functions = { + func0: { + handler: 'func.function.handler', + name: 'new-service-dev-func0', + role: 'arn:aws:xx0:*:*', + }, + func1: { + handler: 'func.function.handler', + name: 'new-service-dev-func1', + role: 'arn:aws:xx1:*:*', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.DependsOn + ).to.deep.equal(['Func0LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func0.role); + + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.DependsOn + ).to.deep.equal(['Func1LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func1.role); + }); + }); + + it('should add function declared role and fill in with provider role', () => { + awsCompileFunctions.serverless.service.provider.name = 'aws'; + awsCompileFunctions.serverless.service.provider.role = 'arn:aws:xxx:*:*'; + awsCompileFunctions.serverless.service.functions = { + func0: { + handler: 'func.function.handler', + name: 'new-service-dev-func0', + }, + func1: { + handler: 'func.function.handler', + name: 'new-service-dev-func1', + role: 'arn:aws:xx1:*:*', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.DependsOn + ).to.deep.equal(['Func0LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role); + + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.DependsOn + ).to.deep.equal(['Func1LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.Properties.Role + ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func1.role); + }); + }); + + it('should reject if the function handler is not present', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + name: 'new-service-dev-func', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); + }); + + it('should create a simple function resource', () => { + 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', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with provider level vpc config', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.provider.vpc = { + securityGroupIds: ['xxx'], + subnetIds: ['xxx'], + }; + + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + VpcConfig: { + SecurityGroupIds: ['xxx'], + SubnetIds: ['xxx'], + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with function level vpc config', () => { + 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', + vpc: { + securityGroupIds: ['xxx'], + subnetIds: ['xxx'], + }, + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + VpcConfig: { + SecurityGroupIds: ['xxx'], + SubnetIds: ['xxx'], + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with provider level tags', () => { + 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', + }, + }; + + awsCompileFunctions.serverless.service.provider.tags = { + foo: 'bar', + baz: 'qux', + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'baz', Value: 'qux' }, + ], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with function level tags', () => { + 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', + tags: { + foo: 'bar', + baz: 'qux', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'baz', Value: 'qux' }, + ], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with provider and function level tags', () => { + 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', + tags: { + foo: 'bar', + baz: 'qux', + }, + }, + }; + + awsCompileFunctions.serverless.service.provider.tags = { + foo: 'quux', + corge: 'uier', + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'corge', Value: 'uier' }, + { Key: 'baz', Value: 'qux' }, + ], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + describe('when using onError config', () => { + let s3Folder; + let s3FileName; + + beforeEach(() => { + s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + s3FileName = awsCompileFunctions.serverless.service.package.artifact.split(path.sep).pop(); + }); + + describe('when IamRoleLambdaExecution is used', () => { + beforeEach(() => { + // pretend that the IamRoleLambdaExecution is used + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], + }, + }, + ], + }, + }; + }); + + it('should create necessary resources if a SNS arn is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + onError: 'arn:aws:sns:region:accountid:foo', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + DeadLetterConfig: { + TargetArn: 'arn:aws:sns:region:accountid:foo', + }, + }, + }; + + const compiledDlqStatement = { + Effect: 'Allow', + Action: ['sns:Publish'], + Resource: ['arn:aws:sns:region:accountid:foo'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + const dlqStatement = + compiledCfTemplate.Resources.IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement[0]; + + expect(functionResource).to.deep.equal(compiledFunction); + expect(dlqStatement).to.deep.equal(compiledDlqStatement); + }); + }); + + it('should create necessary resources if a Ref is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + onError: { + Ref: 'DLQ', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + DeadLetterConfig: { + TargetArn: { + Ref: 'DLQ', + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should create necessary resources if a Fn::ImportValue is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + onError: { + 'Fn::ImportValue': 'DLQ', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + DeadLetterConfig: { + TargetArn: { + 'Fn::ImportValue': 'DLQ', + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should create necessary resources if a Fn::GetAtt is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + onError: { + 'Fn::GetAtt': ['DLQ', 'Arn'], + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + DeadLetterConfig: { + TargetArn: { + 'Fn::GetAtt': ['DLQ', 'Arn'], + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + }); + + describe('when IamRoleLambdaExecution is not used', () => { + it('should create necessary function resources if a SNS arn is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + onError: 'arn:aws:sns:region:accountid:foo', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + DeadLetterConfig: { + TargetArn: 'arn:aws:sns:region:accountid:foo', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + }); + }); + + describe('when using awsKmsKeyArn config', () => { + let s3Folder; + let s3FileName; + + beforeEach(() => { + s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + s3FileName = awsCompileFunctions.serverless.service.package.artifact.split(path.sep).pop(); + }); + + it('should allow if config is provided as a Fn::GetAtt', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + awsKmsKeyArn: { + 'Fn::GetAtt': ['MyKms', 'Arn'], + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + S3Key: 'somedir/new-service.zip', + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: { 'Fn::GetAtt': ['MyKms', 'Arn'] }, + }, + DependsOn: ['FuncLogGroup'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should allow if config is provided as a Ref', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + awsKmsKeyArn: { + Ref: 'foobar', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + S3Key: 'somedir/new-service.zip', + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: { Ref: 'foobar' }, + }, + DependsOn: ['FuncLogGroup'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should allow if config is provided as a Fn::ImportValue', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + awsKmsKeyArn: { + 'Fn::ImportValue': 'KmsKey', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + S3Key: 'somedir/new-service.zip', + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: { 'Fn::ImportValue': 'KmsKey' }, + }, + DependsOn: ['FuncLogGroup'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should use a the service KMS key arn if provided', () => { + awsCompileFunctions.serverless.service.serviceObject = { + name: 'new-service', + awsKmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', + }; + + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should prefer a function KMS key arn over a service KMS key arn', () => { + awsCompileFunctions.serverless.service.serviceObject = { + name: 'new-service', + awsKmsKeyArn: 'arn:aws:kms:region:accountid:foo/service', + }; + + awsCompileFunctions.serverless.service.functions = { + func1: { + handler: 'func1.function.handler', + name: 'new-service-dev-func1', + awsKmsKeyArn: 'arn:aws:kms:region:accountid:foo/function', + }, + func2: { + handler: 'func2.function.handler', + name: 'new-service-dev-func2', + }, + }; + + const compiledFunction1 = { + Type: 'AWS::Lambda::Function', + DependsOn: ['Func1LogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func1', + Handler: 'func1.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: 'arn:aws:kms:region:accountid:foo/function', + }, + }; + + const compiledFunction2 = { + Type: 'AWS::Lambda::Function', + DependsOn: ['Func2LogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func2', + Handler: 'func2.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: 'arn:aws:kms:region:accountid:foo/service', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const function1Resource = compiledCfTemplate.Resources.Func1LambdaFunction; + const function2Resource = compiledCfTemplate.Resources.Func2LambdaFunction; + expect(function1Resource).to.deep.equal(compiledFunction1); + expect(function2Resource).to.deep.equal(compiledFunction2); + }); + }); + + describe('when IamRoleLambdaExecution is used', () => { + beforeEach(() => { + // pretend that the IamRoleLambdaExecution is used + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], + }, + }, + ], + }, + }; + }); + + it('should create necessary resources if a KMS key arn is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + awsKmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', + }, + }; + + const compiledKmsStatement = { + Effect: 'Allow', + Action: ['kms:Decrypt'], + Resource: ['arn:aws:kms:region:accountid:foo/bar'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + const dlqStatement = + compiledCfTemplate.Resources.IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement[0]; + + expect(functionResource).to.deep.equal(compiledFunction); + expect(dlqStatement).to.deep.equal(compiledKmsStatement); + }); + }); + }); + + describe('when IamRoleLambdaExecution is not used', () => { + it('should create necessary function resources if a KMS key arn is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + awsKmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + }); + }); + + describe('when using tracing config', () => { + let s3Folder; + let s3FileName; + + beforeEach(() => { + s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + s3FileName = awsCompileFunctions.serverless.service.package.artifact.split(path.sep).pop(); + }); + + it('should use a the provider wide tracing config if provided', () => { + Object.assign(awsCompileFunctions.serverless.service.provider, { + tracing: { + lambda: true, + }, + }); + + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + + it('should prefer a function tracing config over a provider config', () => { + Object.assign(awsCompileFunctions.serverless.service.provider, { + tracing: { + lambda: 'PassThrough', + }, + }); + + awsCompileFunctions.serverless.service.functions = { + func1: { + handler: 'func1.function.handler', + name: 'new-service-dev-func1', + tracing: 'Active', + }, + func2: { + handler: 'func2.function.handler', + name: 'new-service-dev-func2', + }, + }; + + const compiledFunction1 = { + Type: 'AWS::Lambda::Function', + DependsOn: ['Func1LogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func1', + Handler: 'func1.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + const compiledFunction2 = { + Type: 'AWS::Lambda::Function', + DependsOn: ['Func2LogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func2', + Handler: 'func2.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + TracingConfig: { + Mode: 'PassThrough', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const function1Resource = compiledCfTemplate.Resources.Func1LambdaFunction; + const function2Resource = compiledCfTemplate.Resources.Func2LambdaFunction; + expect(function1Resource).to.deep.equal(compiledFunction1); + expect(function2Resource).to.deep.equal(compiledFunction2); + }); + }); + + describe('when IamRoleLambdaExecution is used', () => { + beforeEach(() => { + // pretend that the IamRoleLambdaExecution is used + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], + }, + }, + ], + }, + }; + }); + + it('should create necessary resources if a tracing config is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tracing: 'Active', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + TracingConfig: { + Mode: 'Active', + }, + }, + }; + + const compiledXrayStatement = { + Effect: 'Allow', + Action: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'], + Resource: ['*'], + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + const xrayStatement = + compiledCfTemplate.Resources.IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement[0]; + + expect(functionResource).to.deep.equal(compiledFunction); + expect(xrayStatement).to.deep.equal(compiledXrayStatement); + }); + }); + }); + + describe('when IamRoleLambdaExecution is not used', () => { + it('should create necessary resources if a tracing config is provided', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tracing: 'PassThrough', + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + TracingConfig: { + Mode: 'PassThrough', + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; + + const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; + + expect(functionResource).to.deep.equal(compiledFunction); + }); + }); + }); + }); + + it('should create a function resource with environment config', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + test1: 'test1', + test2: 'test2', + }, + }, + }; + + awsCompileFunctions.serverless.service.provider.environment = { + providerTest1: 'providerTest1', + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Environment: { + Variables: { + test1: 'test1', + test2: 'test2', + providerTest1: 'providerTest1', + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with function level environment config', () => { + 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', + environment: { + test1: 'test1', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Environment: { + Variables: { + test1: 'test1', + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with provider level environment config', () => { + 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', + }, + }; + + awsCompileFunctions.serverless.service.provider.environment = { + providerTest1: 'providerTest1', + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Environment: { + Variables: { + providerTest1: 'providerTest1', + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should overwrite a provider level environment config when function config is given', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.provider.environment = { + variable: 'overwrite-me', + }; + + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + variable: 'overwritten', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Environment: { + Variables: { + variable: 'overwritten', + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should accept an environment variable with a not-string value', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + counter: 18, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Environment.Variables.counter + ).to.equal(18); + }); + }); + + it('should accept an environment variable with CF ref and functions', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + counter: { + Ref: 'TestVariable', + }, + list: { + 'Fn::Join:': [', ', ['a', 'b', 'c']], + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Environment.Variables.counter + ).to.eql({ Ref: 'TestVariable' }); + }); + }); + + it('should consider function based config when creating a function resource', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + name: 'customized-func-function', + handler: 'func.function.handler', + memorySize: 128, + timeout: 10, + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'customized-func-function', + Handler: 'func.function.handler', + MemorySize: 128, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 10, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it( + 'should allow functions to use a different runtime' + + ' than the service default runtime if specified', + () => { + 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', + runtime: 'python2.7', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'python2.7', + Timeout: 6, + }, + }; + + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + } + ); + + it('should default to the nodejs14.x runtime when no provider runtime is given', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.provider.runtime = null; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should consider the providers runtime and memorySize when creating a function resource', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.provider.runtime = 'python2.7'; + awsCompileFunctions.serverless.service.provider.memorySize = 128; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 128, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'python2.7', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should use a custom bucket if specified', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + const bucketName = 'com.serverless.deploys'; + + awsCompileFunctions.serverless.service.package.deploymentBucket = bucketName; + awsCompileFunctions.serverless.service.provider.runtime = 'python2.7'; + awsCompileFunctions.serverless.service.provider.memorySize = 128; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: bucketName, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 128, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'python2.7', + Timeout: 6, + }, + }; + const coreCloudFormationTemplate = awsCompileFunctions.serverless.utils.readFileSync( + path.join(__dirname, '..', '..', 'lib', 'core-cloudformation-template.json') + ); + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = coreCloudFormationTemplate; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should include description if specified', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + description: 'Lambda function description', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Description + ).to.equal('Lambda function description'); + }); + }); + + it('should create corresponding function output and version objects', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + }, + anotherFunc: { + handler: 'anotherFunc.function.handler', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .FuncLambdaFunctionQualifiedArn + ).to.exist; + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .AnotherFuncLambdaFunctionQualifiedArn + ).to.exist; + }); + }); + + it('should create a new version object if only the configuration changed', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + }, + anotherFunc: { + handler: 'anotherFunc.function.handler', + }, + }; + + let firstOutputs; + return expect(awsCompileFunctions.compileFunctions()) + .to.be.fulfilled.then(() => { + firstOutputs = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs; + + // Change configuration + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { + Resources: {}, + Outputs: {}, + }; + + _.set( + awsCompileFunctions, + 'serverless.service.functions.func.environment.MY_ENV_VAR', + 'myvalue' + ); + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled; + }) + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.not.deep.equal(firstOutputs); + }); + }); + + it('should include description under version too if function is specified', () => { + const lambdaDescription = 'Lambda function description'; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + description: lambdaDescription, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + let versionDescription; + for (const [key, value] of _.entries( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + )) { + if (key.startsWith('FuncLambdaVersion')) { + versionDescription = value.Properties.Description; + break; + } + } + return expect(versionDescription).to.equal(lambdaDescription); + }); + }); + + it('should not create function output objects when "versionFunctions" is false', () => { + awsCompileFunctions.serverless.service.provider.versionFunctions = false; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + }, + anotherFunc: { + handler: 'anotherFunc.function.handler', + }, + }; + + const expectedOutputs = {}; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.deep.equal(expectedOutputs); + }); + }); + + 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'], + 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: 'nodejs14.x', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should set function declared provisioned concurrency limit', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + provisionedConcurrency: 5, + versionFunction: false, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncProvConcLambdaAlias.Properties.ProvisionedConcurrencyConfig + ).to.deep.equal({ ProvisionedConcurrentExecutions: 5 }); + }); + }); + + it('should set function declared reserved concurrency limit even if it is zero', () => { + 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: 0, + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + ReservedConcurrentExecutions: 0, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs14.x', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should version only function that is flagged to be versioned', () => { + awsCompileFunctions.serverless.service.provider.versionFunctions = false; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + versionFunction: true, + }, + anotherFunc: { + handler: 'anotherFunc.function.handler', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .FuncLambdaFunctionQualifiedArn + ).to.exist; + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .AnotherFuncLambdaFunctionQualifiedArn + ).to.not.exist; + }); + }); + }); + + describe('#compileRole()', () => { + it('adds the default role without DependsOn values', () => { + const role = 'IamRoleLambdaExecution'; + const resource = { Properties: {} }; + awsCompileFunctions.compileRole(resource, role); + + expect(resource).to.deep.equal({ + Properties: { + Role: { + 'Fn::GetAtt': [role, 'Arn'], + }, + }, + }); + }); + + it('adds a role based on a logical name with DependsOn values', () => { + const role = 'LogicalRoleName'; + const resource = { Properties: {} }; + awsCompileFunctions.compileRole(resource, role); + + expect(resource).to.deep.equal({ + DependsOn: [role], + Properties: { + Role: { + 'Fn::GetAtt': [role, 'Arn'], + }, + }, + }); + }); + + it('adds a role based on a Fn::GetAtt with DependsOn values', () => { + const role = { 'Fn::GetAtt': ['Foo', 'Arn'] }; + const resource = { Properties: {} }; + awsCompileFunctions.compileRole(resource, role); + + expect(resource).to.deep.equal({ + DependsOn: ['Foo'], + Properties: { + Role: role, + }, + }); + }); + + it('adds a role based on a Fn::ImportValue', () => { + const role = { 'Fn::ImportValue': 'Foo' }; + const resource = { Properties: {} }; + awsCompileFunctions.compileRole(resource, role); + + expect(resource).to.deep.equal({ + Properties: { + Role: role, + }, + }); + }); + + it('adds a role based on a predefined arn string', () => { + const role = 'arn:aws:xxx:*:*'; + const resource = { Properties: {} }; + awsCompileFunctions.compileRole(resource, role); + + expect(resource).to.deep.equal({ + Properties: { + Role: role, + }, + }); + }); + + it('should not set unset properties when not specified in yml (layers, vpc, etc)', () => { + 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', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should set Layers when specified', () => { + 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', + layers: ['arn:aws:xxx:*:*'], + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup'], + 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: 'nodejs14.x', + Timeout: 6, + Layers: ['arn:aws:xxx:*:*'], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should set Condition when specified', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + condition: 'IsE2eTest', + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Condition + ).to.equal('IsE2eTest'); + }); + }); + + it('should include DependsOn when specified', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + dependsOn: ['MyThing', 'MyOtherThing'], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup', 'MyThing', 'MyOtherThing']); + }); + }); + }); +}); + +describe('AwsCompileFunctions #2', () => { + describe('Asynchronous Invocations', () => { + it('Should reference function from same service as destination', () => + runServerless({ + fixture: 'functionDestinations', + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const destinationConfig = + cfResources[naming.getLambdaEventConfigLogicalId('trigger')].Properties.DestinationConfig; + + expect(destinationConfig).to.deep.equal({ + OnSuccess: { + Destination: { 'Fn::GetAtt': [naming.getLambdaLogicalId('target'), 'Arn'] }, + }, + }); + expect(destinationConfig).to.not.have.property('OnFailure'); + })); + + it('Should support OnFailure destinations', () => + runServerless({ + fixture: 'functionDestinations', + configExt: { + functions: { trigger: { destinations: { onSuccess: null, onFailure: 'target' } } }, + }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const destinationConfig = + cfResources[naming.getLambdaEventConfigLogicalId('trigger')].Properties.DestinationConfig; + + expect(destinationConfig).to.not.have.property('OnSuccess'); + expect(destinationConfig).to.deep.equal({ + OnFailure: { + Destination: { 'Fn::GetAtt': [naming.getLambdaLogicalId('target'), 'Arn'] }, + }, + }); + })); + + it('Should support ARN to external function as destination', () => { + const arn = 'arn:aws:lambda:us-east-1:12313231:function:external'; + return runServerless({ + fixture: 'functionDestinations', + configExt: { + functions: { trigger: { destinations: { onSuccess: arn } } }, + }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const destinationConfig = + cfResources[naming.getLambdaEventConfigLogicalId('trigger')].Properties.DestinationConfig; + + expect(destinationConfig).to.deep.equal({ OnSuccess: { Destination: arn } }); + }); + }); + + it('Should respect `role` setting', () => + runServerless({ + fixture: 'functionDestinations', + configExt: { provider: { role: ' arn:aws:iam::XXXXXX:role/role' } }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const destinationConfig = + cfResources[naming.getLambdaEventConfigLogicalId('trigger')].Properties.DestinationConfig; + + expect(destinationConfig).to.deep.equal({ + OnSuccess: { + Destination: { 'Fn::GetAtt': [naming.getLambdaLogicalId('target'), 'Arn'] }, + }, + }); + expect(destinationConfig).to.not.have.property('OnFailure'); + })); + + it('Should not have logGroup in depends on if disableLogs is true', () => { + return runServerless({ + fixture: 'function', + configExt: { functions: { foo: { disableLogs: true } } }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const dependsOn = cfResources[naming.getLambdaLogicalId('foo')].DependsOn; + expect(dependsOn).to.be.undefined; + }); + }); + + it('Should support maximumEventAge defined on function', () => { + const maximumEventAge = 3600; + return runServerless({ + fixture: 'function', + configExt: { + functions: { foo: { maximumEventAge } }, + }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const eventInvokeConfig = + cfResources[naming.getLambdaEventConfigLogicalId('foo')].Properties; + + expect(eventInvokeConfig.MaximumEventAgeInSeconds).to.equal(maximumEventAge); + }); + }); + + it('Should support maximumRetryAttempts defined on function', () => { + const maximumRetryAttempts = 0; + return runServerless({ + fixture: 'function', + configExt: { + functions: { foo: { maximumRetryAttempts } }, + }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const eventInvokeConfig = + cfResources[naming.getLambdaEventConfigLogicalId('foo')].Properties; + + expect(eventInvokeConfig.MaximumRetryAttempts).to.equal(maximumRetryAttempts); + }); + }); + }); + + describe('when using fileSystemConfig', () => { + const arn = + 'arn:aws:elasticfilesystem:us-east-1:111111111111:access-point/fsap-a1a1a1a1a1a1a1a1a'; + const localMountPath = '/mnt/path'; + const securityGroupIds = ['sg-0a0a0a0a']; + const subnetIds = ['subnet-01010101']; + + let functionConfig; + let defaultIamRole; + + before(() => + runServerless({ + fixture: 'function', + configExt: { + functions: { + foo: { + vpc: { + subnetIds, + securityGroupIds, + }, + fileSystemConfig: { + localMountPath, + arn, + }, + }, + }, + }, + cliArgs: ['package'], + }).then(({ awsNaming, cfTemplate }) => { + functionConfig = cfTemplate.Resources[awsNaming.getLambdaLogicalId('foo')].Properties; + defaultIamRole = cfTemplate.Resources.IamRoleLambdaExecution; + }) + ); + + it('should correctly set Arn and LocalMountPath', () => { + expect(functionConfig.FileSystemConfigs).to.deep.equal([ + { + Arn: arn, + LocalMountPath: localMountPath, + }, + ]); + }); + + it('should update default IAM role', () => { + expect(defaultIamRole.Properties.Policies[0].PolicyDocument.Statement).to.deep.include({ + Effect: 'Allow', + Action: ['elasticfilesystem:ClientMount', 'elasticfilesystem:ClientWrite'], + Resource: [arn], + }); + }); + + it('should support vpc defined on provider level', () => { + return runServerless({ + fixture: 'function', + configExt: { + provider: { + vpc: { + subnetIds, + securityGroupIds, + }, + }, + functions: { + foo: { + fileSystemConfig: { + localMountPath, + arn, + }, + }, + }, + }, + cliArgs: ['package'], + }).then(({ cfTemplate, awsNaming }) => { + const cfResources = cfTemplate.Resources; + const naming = awsNaming; + const fnConfig = cfResources[naming.getLambdaLogicalId('foo')].Properties; + + expect(fnConfig.FileSystemConfigs).to.deep.equal([ + { + Arn: arn, + LocalMountPath: localMountPath, + }, + ]); + }); + }); + + it('should throw error when function has no vpc configured', () => { + return runServerless({ + fixture: 'function', + configExt: { + functions: { + foo: { + fileSystemConfig: { + localMountPath, + arn, + }, + }, + }, + }, + cliArgs: ['package'], + }).catch(error => { + expect(error).to.have.property('code', 'LAMBDA_FILE_SYSTEM_CONFIG_MISSING_VPC'); + }); + }); + }); + + describe('when function versions are used with layers', () => { + let firstCfTemplate; + let servicePath; + let updateConfig; + const mockDescribeStackResponse = { + CloudFormation: { + describeStacks: { Stacks: [{ Outputs: [{ OutputKey: 'test' }] }] }, + }, + }; + + beforeEach(async () => { + const serviceData = await fixtures.setup('functionLayers'); + ({ servicePath, updateConfig } = serviceData); + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + firstCfTemplate = data.cfTemplate; + }); + + it('should create different version ids for identical lambdas with and without layers', () => { + expect(firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref).to.not.equal( + firstCfTemplate.Outputs.NoLayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should generate different lambda version id when lambda layer properties are different', async () => { + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + layers: { testLayer: { path: 'testLayer', description: 'Different description' } }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should ignore changing character of S3Key paths when generating layer version id', async () => { + // the S3Key path is timestamped and so changes on every deployment regardless of layer changes, and should + // therefore not be included in the version id digest + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + const firstS3Key = firstCfTemplate.Resources.TestLayerLambdaLayer.Properties.Content.S3Key; + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstS3Key).to.not.equal( + data.cfTemplate.Resources.TestLayerLambdaLayer.Properties.Content.S3Key + ); + expect(firstVersionId).to.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should ignore properties order when generating layer version id', async () => { + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + layerFunc: { layers: [{ Ref: 'TestLayerLambdaLayer' }], handler: 'index.handler' }, + }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should create different lambda version id for different property keys (but no different values)', async () => { + const firstVersionId = + firstCfTemplate.Outputs.LayerFuncWithConfigLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + layerFuncWithConfig: { handler: 'index.handler', timeout: 128 }, + }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.LayerFuncWithConfigLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should create same version id when layer source and config are unchanged', async () => { + const firstVersionId = firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + it('should generate different lambda version id when lambda layer arns are different', async () => { + const firstVersionId = + firstCfTemplate.Outputs.ArnLayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + arnLayerFunc: { + handler: 'index.handler', + layers: ['arn:aws:lambda:us-east-2:123456789012:layer:my-layer:2'], + }, + }, + }); + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.ArnLayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + + describe('when layer content is changed', () => { + let originalLayer; + let sourceChangeLayer; + let backupLayer; + + beforeEach(async () => { + originalLayer = path.join(servicePath, 'testLayer'); + sourceChangeLayer = path.join(servicePath, 'extra_layers', 'testLayerSourceChange'); + backupLayer = path.join(servicePath, 'extra_layers', 'testLayerBackup'); + + await fse.rename(originalLayer, backupLayer); + await fse.rename(sourceChangeLayer, originalLayer); + }); + + afterEach(async () => { + await fse.rename(originalLayer, sourceChangeLayer); + await fse.rename(backupLayer, originalLayer); + }); + + it('should create different lambda version id', async () => { + const firstVersionId = + firstCfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref; + + const data = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + awsRequestStubMap: mockDescribeStackResponse, + }); + + expect(firstVersionId).to.not.equal( + data.cfTemplate.Outputs.LayerFuncLambdaFunctionQualifiedArn.Value.Ref + ); + }); + }); + }); + + describe('function config', () => { + it('should not create a new version object if only function-wide configuration changed', async () => { + const { servicePath, updateConfig } = await fixtures.setup('function'); + + const { cfTemplate: originalTemplate } = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + }); + const originalVersionArn = originalTemplate.Outputs.FooLambdaFunctionQualifiedArn.Value.Ref; + + await updateConfig({ + functions: { + foo: { + tags: { + foo: 'bar', + }, + reservedConcurrency: 1, + }, + }, + }); + const { cfTemplate: updatedTemplate } = await runServerless({ + cwd: servicePath, + cliArgs: ['package'], + }); + const updatedVersionArn = updatedTemplate.Outputs.FooLambdaFunctionQualifiedArn.Value.Ref; + + expect( + updatedTemplate.Resources.FooLambdaFunction.Properties.ReservedConcurrentExecutions + ).to.equal(1); + + expect(originalVersionArn).to.equal(updatedVersionArn); + }); + }); +}); diff --git a/lib/plugins/aws/provider.js b/lib/plugins/aws/provider.js index a943e079fc9..43de4c970d2 100644 --- a/lib/plugins/aws/provider.js +++ b/lib/plugins/aws/provider.js @@ -486,7 +486,7 @@ class AwsProvider { 'java8', 'java8.al2', 'nodejs10.x', - 'nodejs12.x', + 'nodejs14.x', 'nodejs14.x', 'provided', 'provided.al2', @@ -1499,7 +1499,7 @@ class AwsProvider { } getRuntime(runtime) { - const defaultRuntime = 'nodejs12.x'; + const defaultRuntime = 'nodejs14.x'; const runtimeSourceValue = this.getRuntimeSourceValue(); return runtime || runtimeSourceValue.value || defaultRuntime; } diff --git a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml index 9f6b529bda6..5b5572cad9b 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml @@ -12,7 +12,7 @@ plugins: provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 custom: diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml index 1583f3f660a..085b9c0eba3 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml @@ -22,7 +22,7 @@ frameworkVersion: '2' provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 # you can overwrite defaults here 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 a6b7ea71822..597f9ec918f 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -22,7 +22,7 @@ frameworkVersion: '2' provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 # you can overwrite defaults here diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml index a88a6be4d4b..388f8a7d2df 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml @@ -11,7 +11,7 @@ plugins: provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 functions: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index b522ed1b2a8..27343fe63c7 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -22,7 +22,7 @@ frameworkVersion: '2' provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 # you can overwrite defaults here diff --git a/lib/plugins/create/templates/azure-nodejs-typescript/serverless.yml b/lib/plugins/create/templates/azure-nodejs-typescript/serverless.yml index 3d551ae68b8..e6e0e0eda62 100644 --- a/lib/plugins/create/templates/azure-nodejs-typescript/serverless.yml +++ b/lib/plugins/create/templates/azure-nodejs-typescript/serverless.yml @@ -25,7 +25,7 @@ custom: provider: name: azure region: West US 2 - runtime: nodejs12 + runtime: nodejs14 # os: windows # windows is default, linux is available # prefix: "sample" # prefix of generated resource name # subscriptionId: A356AC8C-E310-44F4-BF85-C7F29044AF99 diff --git a/lib/plugins/create/templates/azure-nodejs/serverless.yml b/lib/plugins/create/templates/azure-nodejs/serverless.yml index 733d86ab345..0c36f920521 100644 --- a/lib/plugins/create/templates/azure-nodejs/serverless.yml +++ b/lib/plugins/create/templates/azure-nodejs/serverless.yml @@ -20,7 +20,7 @@ frameworkVersion: '2' provider: name: azure region: West US 2 - runtime: nodejs12 + runtime: nodejs14 # os: windows # windows is default, linux is available # prefix: "sample" # prefix of generated resource name # subscriptionId: A356AC8C-E310-44F4-BF85-C7F29044AF99 diff --git a/lib/plugins/create/templates/hello-world/serverless.yml b/lib/plugins/create/templates/hello-world/serverless.yml index 572e28eb625..3281111ac39 100644 --- a/lib/plugins/create/templates/hello-world/serverless.yml +++ b/lib/plugins/create/templates/hello-world/serverless.yml @@ -12,7 +12,7 @@ frameworkVersion: '2' # The `provider` block defines where your service will be deployed provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x # The `functions` block defines what code to deploy functions: diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 363ed6849d8..477483566fa 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -26,7 +26,7 @@ module.exports = { }, getRuntime(runtime) { - const defaultRuntime = 'nodejs12.x'; + const defaultRuntime = 'nodejs14.x'; return runtime || this.serverless.service.provider.runtime || defaultRuntime; }, diff --git a/test/fixtures/cli/variables/serverless.yml b/test/fixtures/cli/variables/serverless.yml index 19fd0867ead..71a3d8ed9e4 100644 --- a/test/fixtures/cli/variables/serverless.yml +++ b/test/fixtures/cli/variables/serverless.yml @@ -5,7 +5,7 @@ variablesResolutionMode: 20210326 provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x custom: importedFile: ${file(config.json)} diff --git a/test/fixtures/programmatic/apiGatewayExtended/serverless.yml b/test/fixtures/programmatic/apiGatewayExtended/serverless.yml index 644130f89c1..be2d0ca3ae9 100644 --- a/test/fixtures/programmatic/apiGatewayExtended/serverless.yml +++ b/test/fixtures/programmatic/apiGatewayExtended/serverless.yml @@ -5,7 +5,7 @@ frameworkVersion: '*' provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false apiGateway: shouldStartNameWithService: true diff --git a/test/fixtures/programmatic/cognitoUserPool/serverless.yml b/test/fixtures/programmatic/cognitoUserPool/serverless.yml index 7fd58d0fa9a..69e1a139f85 100644 --- a/test/fixtures/programmatic/cognitoUserPool/serverless.yml +++ b/test/fixtures/programmatic/cognitoUserPool/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/eventBridge/serverless.yml b/test/fixtures/programmatic/eventBridge/serverless.yml index 072f1604433..c6753f53329 100644 --- a/test/fixtures/programmatic/eventBridge/serverless.yml +++ b/test/fixtures/programmatic/eventBridge/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/functionEfs/serverless.yml b/test/fixtures/programmatic/functionEfs/serverless.yml index 6e4970e2ad3..ce1d807e605 100644 --- a/test/fixtures/programmatic/functionEfs/serverless.yml +++ b/test/fixtures/programmatic/functionEfs/serverless.yml @@ -7,7 +7,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/functionMsk/serverless.yml b/test/fixtures/programmatic/functionMsk/serverless.yml index 02a66625baf..ce4f94dddd0 100644 --- a/test/fixtures/programmatic/functionMsk/serverless.yml +++ b/test/fixtures/programmatic/functionMsk/serverless.yml @@ -8,7 +8,7 @@ frameworkVersion: '*' provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/iot/serverless.yml b/test/fixtures/programmatic/iot/serverless.yml index ca84f45ca9a..ed7b74a5707 100644 --- a/test/fixtures/programmatic/iot/serverless.yml +++ b/test/fixtures/programmatic/iot/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/iotFleetProvisioning/serverless.yml b/test/fixtures/programmatic/iotFleetProvisioning/serverless.yml index eada68f71cd..3c75a3d9039 100644 --- a/test/fixtures/programmatic/iotFleetProvisioning/serverless.yml +++ b/test/fixtures/programmatic/iotFleetProvisioning/serverless.yml @@ -5,7 +5,7 @@ frameworkVersion: '*' provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/provisionedConcurrency/serverless.yml b/test/fixtures/programmatic/provisionedConcurrency/serverless.yml index 78a23b46765..d45abdbf584 100644 --- a/test/fixtures/programmatic/provisionedConcurrency/serverless.yml +++ b/test/fixtures/programmatic/provisionedConcurrency/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/s3/serverless.yml b/test/fixtures/programmatic/s3/serverless.yml index fc65014165d..04a63c620c8 100644 --- a/test/fixtures/programmatic/s3/serverless.yml +++ b/test/fixtures/programmatic/s3/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false s3: customBucket: diff --git a/test/fixtures/programmatic/schedule/serverless.yml b/test/fixtures/programmatic/schedule/serverless.yml index d405b9f7d11..7e5d185f971 100644 --- a/test/fixtures/programmatic/schedule/serverless.yml +++ b/test/fixtures/programmatic/schedule/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/sns/serverless.yml b/test/fixtures/programmatic/sns/serverless.yml index c5466898e0d..acdf3458cc9 100644 --- a/test/fixtures/programmatic/sns/serverless.yml +++ b/test/fixtures/programmatic/sns/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/sqs/serverless.yml b/test/fixtures/programmatic/sqs/serverless.yml index 65863d6e9da..fecd384e4de 100644 --- a/test/fixtures/programmatic/sqs/serverless.yml +++ b/test/fixtures/programmatic/sqs/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/stream/serverless.yml b/test/fixtures/programmatic/stream/serverless.yml index 4973b90de83..09c030bb8a6 100644 --- a/test/fixtures/programmatic/stream/serverless.yml +++ b/test/fixtures/programmatic/stream/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false functions: diff --git a/test/fixtures/programmatic/variables-legacy/serverless.yml b/test/fixtures/programmatic/variables-legacy/serverless.yml index 9198589928c..8956eee2b3d 100644 --- a/test/fixtures/programmatic/variables-legacy/serverless.yml +++ b/test/fixtures/programmatic/variables-legacy/serverless.yml @@ -2,7 +2,7 @@ service: service provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x custom: importedFile: ${file(config.json)} diff --git a/test/fixtures/programmatic/websocket/serverless.yml b/test/fixtures/programmatic/websocket/serverless.yml index fc4d0b0ec27..35b209ddca0 100644 --- a/test/fixtures/programmatic/websocket/serverless.yml +++ b/test/fixtures/programmatic/websocket/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x versionFunctions: false logs: websocket: true diff --git a/test/integrationPackage/cloudformation.tests.js b/test/integrationPackage/cloudformation.tests.js index 7726636d577..eeea4b05306 100644 --- a/test/integrationPackage/cloudformation.tests.js +++ b/test/integrationPackage/cloudformation.tests.js @@ -44,7 +44,7 @@ describe('Integration test - Packaging - CloudFormation', () => { Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'], }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, DependsOn: ['HelloLogGroup'], @@ -75,7 +75,7 @@ describe('Integration test - Packaging - CloudFormation', () => { Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'], }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, DependsOn: ['HelloLogGroup'], @@ -109,7 +109,7 @@ describe('Integration test - Packaging - CloudFormation', () => { Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'], }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, DependsOn: ['HelloLogGroup'], diff --git a/test/integrationPackage/fixtures/artifact/serverless.yml b/test/integrationPackage/fixtures/artifact/serverless.yml index afdb2ec60a0..192988c6750 100644 --- a/test/integrationPackage/fixtures/artifact/serverless.yml +++ b/test/integrationPackage/fixtures/artifact/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 functions: diff --git a/test/integrationPackage/fixtures/individually-function/serverless.yml b/test/integrationPackage/fixtures/individually-function/serverless.yml index aba9cf5d283..3fe4f920e58 100644 --- a/test/integrationPackage/fixtures/individually-function/serverless.yml +++ b/test/integrationPackage/fixtures/individually-function/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 functions: diff --git a/test/integrationPackage/fixtures/individually/serverless.yml b/test/integrationPackage/fixtures/individually/serverless.yml index 68377a33170..2a18af6bac7 100644 --- a/test/integrationPackage/fixtures/individually/serverless.yml +++ b/test/integrationPackage/fixtures/individually/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 package: diff --git a/test/integrationPackage/fixtures/regular/serverless.yml b/test/integrationPackage/fixtures/regular/serverless.yml index 8e99372996b..e25d4102d1f 100644 --- a/test/integrationPackage/fixtures/regular/serverless.yml +++ b/test/integrationPackage/fixtures/regular/serverless.yml @@ -4,7 +4,7 @@ configValidationMode: error provider: name: aws - runtime: nodejs12.x + runtime: nodejs14.x lambdaHashingVersion: 20201221 functions: diff --git a/test/unit/lib/plugins/aws/customResources/index.test.js b/test/unit/lib/plugins/aws/customResources/index.test.js index 8fa7b0b2c74..08830d37839 100644 --- a/test/unit/lib/plugins/aws/customResources/index.test.js +++ b/test/unit/lib/plugins/aws/customResources/index.test.js @@ -109,7 +109,7 @@ describe('#addCustomResourceToService()', () => { Role: { 'Fn::GetAtt': ['IamRoleCustomResourcesLambdaExecution', 'Arn'], }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 180, }, DependsOn: ['IamRoleCustomResourcesLambdaExecution'], @@ -128,7 +128,7 @@ describe('#addCustomResourceToService()', () => { Role: { 'Fn::GetAtt': ['IamRoleCustomResourcesLambdaExecution', 'Arn'], }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 180, }, DependsOn: ['IamRoleCustomResourcesLambdaExecution'], @@ -147,7 +147,7 @@ describe('#addCustomResourceToService()', () => { Role: { 'Fn::GetAtt': ['IamRoleCustomResourcesLambdaExecution', 'Arn'], }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 180, }, DependsOn: ['IamRoleCustomResourcesLambdaExecution'], diff --git a/test/unit/lib/plugins/aws/invokeLocal/index.test.js b/test/unit/lib/plugins/aws/invokeLocal/index.test.js index 68b6b560f0e..6a1eab22081 100644 --- a/test/unit/lib/plugins/aws/invokeLocal/index.test.js +++ b/test/unit/lib/plugins/aws/invokeLocal/index.test.js @@ -421,7 +421,7 @@ describe('AwsInvokeLocal', () => { it(`should call invokeLocalNodeJs for any node.js runtime version for ${item.path}`, async () => { awsInvokeLocal.options.functionObj.handler = item.path; - awsInvokeLocal.options.functionObj.runtime = 'nodejs12.x'; + awsInvokeLocal.options.functionObj.runtime = 'nodejs14.x'; await awsInvokeLocal.invokeLocal(); expect(invokeLocalNodeJsStub.calledOnce).to.be.equal(true); expect( @@ -504,8 +504,8 @@ describe('AwsInvokeLocal', () => { expect(invokeLocalDockerStub.calledWithExactly()).to.be.equal(true); }); - it('should call invokeLocalDocker if using --docker option with nodejs12.x', async () => { - awsInvokeLocal.options.functionObj.runtime = 'nodejs12.x'; + it('should call invokeLocalDocker if using --docker option with nodejs14.x', async () => { + awsInvokeLocal.options.functionObj.runtime = 'nodejs14.x'; awsInvokeLocal.options.functionObj.handler = 'handler.foobar'; awsInvokeLocal.options.docker = true; await awsInvokeLocal.invokeLocal(); @@ -1107,7 +1107,7 @@ describe('AwsInvokeLocal', () => { handler: 'handler.hello', name: 'hello', timeout: 4, - runtime: 'nodejs12.x', + runtime: 'nodejs14.x', environment: { functionVar: 'functionValue', }, @@ -1130,17 +1130,17 @@ describe('AwsInvokeLocal', () => { it('calls docker with packaged artifact', async () => { await awsInvokeLocal.invokeLocalDocker(); - const dockerfilePath = path.join('.serverless', 'invokeLocal', 'nodejs12.x', 'Dockerfile'); + const dockerfilePath = path.join('.serverless', 'invokeLocal', 'nodejs14.x', 'Dockerfile'); expect(pluginMangerSpawnPackageStub.calledOnce).to.equal(true); expect(spawnExtStub.getCall(0).args).to.deep.equal(['docker', ['version']]); expect(spawnExtStub.getCall(1).args).to.deep.equal([ 'docker', - ['images', '-q', 'lambci/lambda:nodejs12.x'], + ['images', '-q', 'lambci/lambda:nodejs14.x'], ]); expect(spawnExtStub.getCall(2).args).to.deep.equal([ 'docker', - ['build', '-t', 'sls-docker-nodejs12.x', 'servicePath', '-f', dockerfilePath], + ['build', '-t', 'sls-docker-nodejs14.x', 'servicePath', '-f', dockerfilePath], ]); expect(spawnExtStub.getCall(3).args).to.deep.equal([ 'docker', @@ -1171,7 +1171,7 @@ describe('AwsInvokeLocal', () => { 'commandLineEnvVar=commandLineEnvVarValue', '-p', '9292:9292', - 'sls-docker-nodejs12.x', + 'sls-docker-nodejs14.x', 'handler.hello', '{}', ], diff --git a/test/unit/lib/plugins/aws/package/compile/functions.test.js b/test/unit/lib/plugins/aws/package/compile/functions.test.js index b787974cbf9..b768fc01494 100644 --- a/test/unit/lib/plugins/aws/package/compile/functions.test.js +++ b/test/unit/lib/plugins/aws/package/compile/functions.test.js @@ -380,7 +380,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, }; @@ -421,7 +421,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, Tags: [ { Key: 'foo', Value: 'bar' }, @@ -484,7 +484,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, DeadLetterConfig: { TargetArn: 'arn:aws:sns:region:accountid:foo', @@ -535,7 +535,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, DeadLetterConfig: { TargetArn: { @@ -577,7 +577,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, DeadLetterConfig: { TargetArn: { @@ -619,7 +619,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, DeadLetterConfig: { TargetArn: { @@ -661,7 +661,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, DeadLetterConfig: { TargetArn: 'arn:aws:sns:region:accountid:foo', @@ -712,7 +712,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, KmsKeyArn: { 'Fn::GetAtt': ['MyKms', 'Arn'] }, }, @@ -749,7 +749,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, KmsKeyArn: { Ref: 'foobar' }, }, @@ -786,7 +786,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, KmsKeyArn: { 'Fn::ImportValue': 'KmsKey' }, }, @@ -838,7 +838,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', }, @@ -912,7 +912,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, TracingConfig: { Mode: 'Active', @@ -969,7 +969,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, Environment: { Variables: { @@ -1036,7 +1036,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 128, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 10, }, }; @@ -1049,7 +1049,7 @@ describe('AwsCompileFunctions', () => { }); }); - it('should default to the nodejs12.x runtime when no provider runtime is given', () => { + it('should default to the nodejs14.x runtime when no provider runtime is given', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact .split(path.sep) @@ -1073,7 +1073,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, }; @@ -1207,7 +1207,7 @@ describe('AwsCompileFunctions', () => { MemorySize: 1024, ReservedConcurrentExecutions: 5, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, }; @@ -1263,7 +1263,7 @@ describe('AwsCompileFunctions', () => { MemorySize: 1024, ReservedConcurrentExecutions: 0, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, }; @@ -1368,7 +1368,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, }, }; @@ -1406,7 +1406,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs12.x', + Runtime: 'nodejs14.x', Timeout: 6, Layers: ['arn:aws:xxx:*:*'], }, @@ -1513,7 +1513,7 @@ describe('lib/plugins/aws/package/compile/functions/index.test.js', () => { sharedEnvVar: 'valueFromFunction', }, memorySize: 2048, - runtime: 'nodejs12.x', + runtime: 'nodejs14.x', versionFunction: true, }, fnFileSystemConfig: { @@ -2081,7 +2081,7 @@ describe('lib/plugins/aws/package/compile/functions/index.test.js', () => { // https://github.com/serverless/serverless/blob/d8527d8b57e7e5f0b94ba704d9f53adb34298d99/lib/plugins/aws/package/compile/functions/index.test.js#L1784-L1820 }); - it.skip('TODO: should default to "nodejs12.x" runtime`', () => { + it.skip('TODO: should default to "nodejs14.x" runtime`', () => { // Replacement for // https://github.com/serverless/serverless/blob/d8527d8b57e7e5f0b94ba704d9f53adb34298d99/lib/plugins/aws/package/compile/functions/index.test.js#L1864-L1899 }); diff --git a/test/unit/lib/plugins/aws/package/compile/layers.test.js b/test/unit/lib/plugins/aws/package/compile/layers.test.js index 0c9091eeba6..9c63ce625a7 100644 --- a/test/unit/lib/plugins/aws/package/compile/layers.test.js +++ b/test/unit/lib/plugins/aws/package/compile/layers.test.js @@ -48,7 +48,7 @@ describe('lib/plugins/aws/package/compile/layers/index.test.js', () => { layerTwo: { description: 'Layer two example', path: 'layer', - compatibleRuntimes: ['nodejs12.x'], + compatibleRuntimes: ['nodejs14.x'], licenseInfo: 'GPL', allowedAccounts: ['123456789012', '123456789013'], }, @@ -185,7 +185,7 @@ describe('lib/plugins/aws/package/compile/layers/index.test.js', () => { const layerOne = cfResources[layerResourceName]; expect(layerOne.Type).to.equals('AWS::Lambda::LayerVersion'); - expect(layerOne.Properties.CompatibleRuntimes).to.deep.equals(['nodejs12.x']); + expect(layerOne.Properties.CompatibleRuntimes).to.deep.equals(['nodejs14.x']); }); it('should support `layers[].licenseInfo`', () => { diff --git a/test/unit/lib/utils/telemetry/generatePayload.test.js b/test/unit/lib/utils/telemetry/generatePayload.test.js index fc2af4df733..a598d53a7f8 100644 --- a/test/unit/lib/utils/telemetry/generatePayload.test.js +++ b/test/unit/lib/utils/telemetry/generatePayload.test.js @@ -75,16 +75,16 @@ describe('lib/utils/telemetry/generatePayload', () => { config: { provider: { name: 'aws', - runtime: 'nodejs12.x', + runtime: 'nodejs14.x', stage: 'dev', region: 'us-east-1', }, plugins: [], functions: [ - { runtime: 'nodejs12.x', events: [{ type: 'httpApi' }, { type: 'httpApi' }] }, - { runtime: 'nodejs12.x', events: [{ type: 'httpApi' }] }, - { runtime: 'nodejs12.x', events: [] }, - { runtime: 'nodejs12.x', events: [] }, + { runtime: 'nodejs14.x', events: [{ type: 'httpApi' }, { type: 'httpApi' }] }, + { runtime: 'nodejs14.x', events: [{ type: 'httpApi' }] }, + { runtime: 'nodejs14.x', events: [] }, + { runtime: 'nodejs14.x', events: [] }, { runtime: '$containerimage', events: [] }, ], }, @@ -173,7 +173,7 @@ describe('lib/utils/telemetry/generatePayload', () => { config: { provider: { name: 'aws', - runtime: 'nodejs12.x', + runtime: 'nodejs14.x', stage: 'dev', region: 'us-east-1', }, @@ -250,16 +250,16 @@ describe('lib/utils/telemetry/generatePayload', () => { config: { provider: { name: 'aws', - runtime: 'nodejs12.x', + runtime: 'nodejs14.x', stage: 'dev', region: 'us-east-1', }, plugins: [], functions: [ - { runtime: 'nodejs12.x', events: [{ type: 'httpApi' }, { type: 'httpApi' }] }, - { runtime: 'nodejs12.x', events: [{ type: 'httpApi' }] }, - { runtime: 'nodejs12.x', events: [] }, - { runtime: 'nodejs12.x', events: [] }, + { runtime: 'nodejs14.x', events: [{ type: 'httpApi' }, { type: 'httpApi' }] }, + { runtime: 'nodejs14.x', events: [{ type: 'httpApi' }] }, + { runtime: 'nodejs14.x', events: [] }, + { runtime: 'nodejs14.x', events: [] }, ], }, isAutoUpdateEnabled: false,