UNPKG

serverless

Version:

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

1,373 lines (1,238 loc) • 76.4 kB
'use strict'; const _ = require('lodash'); const path = require('path'); const chai = require('chai'); const AwsProvider = require('../../../provider/awsProvider'); const AwsCompileFunctions = require('./index'); const testUtils = require('../../../../../../tests/utils'); const Serverless = require('../../../../../Serverless'); chai.use(require('chai-as-promised')); const expect = chai.expect; describe('AwsCompileFunctions', () => { let serverless; let awsCompileFunctions; const functionName = 'test'; const compiledFunctionName = 'TestLambdaFunction'; beforeEach(() => { const options = { stage: 'dev', region: 'us-east-1', }; serverless = new Serverless(options); serverless.setProvider('aws', new AwsProvider(serverless, options)); serverless.cli = new serverless.classes.CLI(); awsCompileFunctions = new AwsCompileFunctions(serverless, options); awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, Outputs: {}, }; const serviceArtifact = 'new-service.zip'; const individualArtifact = 'test.zip'; awsCompileFunctions.packagePath = testUtils.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('#isArnRefOrImportValue()', () => { it('should accept a Ref', () => expect(awsCompileFunctions.isArnRefOrImportValue({ Ref: 'DLQ' })).to.equal(true)); it('should accept a Fn::ImportValue', () => expect(awsCompileFunctions.isArnRefOrImportValue({ 'Fn::ImportValue': 'DLQ' })) .to.equal(true)); it('should reject other objects', () => expect(awsCompileFunctions.isArnRefOrImportValue({ Blah: 'vtha' })).to.equal(false)); }); 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', }, }; 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, }, }; return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled .then(() => { expect( awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate .Resources.FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); it('should 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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); }); }); }); 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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 a string'); }); it('should reject if config is provided as an 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 a 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func1', Handler: 'func1.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/function', }, }; const compiledFunction2 = { Type: 'AWS::Lambda::Function', DependsOn: [ 'Func2LogGroup', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func2', Handler: 'func2.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs4.3', Timeout: 6, 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', 'IamRoleLambdaExecution', ], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, FunctionName: 'new-service-dev-func', Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runt