UNPKG

serverless

Version:

Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more

1,346 lines (1,208 loc) • 102 kB
'use strict'; const AWS = require('aws-sdk'); const fs = require('fs'); 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 { 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('#isArnRefGetAttOrImportValue()', () => { it('should accept a Ref', () => expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Ref: 'DLQ' })).to.equal(true)); it('should accept a Fn::GetAtt', () => expect( awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::GetAtt': ['DLQ', 'Arn'] }) ).to.equal(true)); it('should accept a Fn::ImportValue', () => expect( awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::ImportValue': 'DLQ' }) ).to.equal(true)); it('should reject other objects', () => expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Blah: 'vtha' })).to.equal(false)); }); 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 fs.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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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(); }); it('should reject if config is provided as a number', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', onError: 12, }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); }); it('should reject if config is provided as an object', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', onError: { foo: 'bar', }, }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); }); it('should reject if config is not a SNS or SQS arn', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', onError: 'foo', }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); }); 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: 'nodejs12.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 throw an informative error message if a SQS arn is provided', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', onError: 'arn:aws:sqs:region:accountid:foo', }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( 'only supports SNS' ); }); 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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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); }); }); it('should reject with an informative error message if a SQS arn is provided', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', onError: 'arn:aws:sqs:region:accountid:foo', }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( 'only supports SNS' ); }); }); }); 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 reject if config is provided as a number', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', awsKmsKeyArn: 12, }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( 'provided as an arn string' ); }); 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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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 reject if config is provided as an invalid object', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', awsKmsKeyArn: { foo: 'bar', }, }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( 'provided as an arn string' ); }); it('should throw an error if config is not a KMS key arn', () => { awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', name: 'new-service-dev-func', awsKmsKeyArn: 'foo', }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith('KMS key arn'); }); 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: 'nodejs12.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: 'nodejs12.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: 'nodejs12.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: [],