diff --git a/docs/providers/aws/events/msk.md b/docs/providers/aws/events/msk.md index 322fbd9891ab..a5bd06c01181 100644 --- a/docs/providers/aws/events/msk.md +++ b/docs/providers/aws/events/msk.md @@ -8,7 +8,7 @@ layout: Doc -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/cognito-user-pool) +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/msk) diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 01c0d72da366..1294786add53 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -408,11 +408,14 @@ module.exports = { // MSK getMSKEventLogicalId(functionName, clusterName, topicName) { - return `${this.getNormalizedFunctionName( - functionName - )}EventSourceMappingMSK${this.normalizeNameToAlphaNumericOnly( - clusterName - )}${this.normalizeNameToAlphaNumericOnly(topicName)}`; + const normalizedFunctionName = this.getNormalizedFunctionName(functionName); + // Both clusterName and topicName are trimmed to 79 chars to avoid going over 255 character limit + const normalizedClusterName = this.normalizeNameToAlphaNumericOnly(clusterName).substring( + 0, + 79 + ); + const normalizedTopicName = this.normalizeNameToAlphaNumericOnly(topicName).substring(0, 79); + return `${normalizedFunctionName}EventSourceMappingMSK${normalizedClusterName}${normalizedTopicName}`; }, // ALB diff --git a/lib/plugins/aws/package/compile/events/msk/getMskClusterNameToken.js b/lib/plugins/aws/package/compile/events/msk/getMskClusterNameToken.js new file mode 100644 index 000000000000..ce0fdd5d37a5 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/msk/getMskClusterNameToken.js @@ -0,0 +1,13 @@ +'use strict'; + +const getMskClusterNameToken = eventSourceArn => { + if (eventSourceArn['Fn::ImportValue']) { + return eventSourceArn['Fn::ImportValue']; + } else if (eventSourceArn.Ref) { + return eventSourceArn.Ref; + } + + return eventSourceArn.split('/')[1]; +}; + +module.exports = getMskClusterNameToken; diff --git a/lib/plugins/aws/package/compile/events/msk/getMskClusterNameToken.test.js b/lib/plugins/aws/package/compile/events/msk/getMskClusterNameToken.test.js new file mode 100644 index 000000000000..d401dfd71c17 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/msk/getMskClusterNameToken.test.js @@ -0,0 +1,27 @@ +'use strict'; + +const chai = require('chai'); +const getMskClusterNameToken = require('./getMskClusterNameToken'); + +const { expect } = chai; + +describe('getMskClusterNameToken', () => { + it('with ARN', () => { + const eventSourceArn = + 'arn:aws:kafka:us-east-1:111111111111:cluster/ClusterName/a1a1a1a1a1a1a1a1a'; + const result = getMskClusterNameToken(eventSourceArn); + expect(result).to.equal('ClusterName'); + }); + + it('with Fn::ImportValue', () => { + const eventSourceArn = { 'Fn::ImportValue': 'importvalue' }; + const result = getMskClusterNameToken(eventSourceArn); + expect(result).to.equal('importvalue'); + }); + + it('with Ref', () => { + const eventSourceArn = { Ref: 'ReferencedResource' }; + const result = getMskClusterNameToken(eventSourceArn); + expect(result).to.equal('ReferencedResource'); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/msk/index.js b/lib/plugins/aws/package/compile/events/msk/index.js index 3737d4fc1b72..abaf678446b9 100644 --- a/lib/plugins/aws/package/compile/events/msk/index.js +++ b/lib/plugins/aws/package/compile/events/msk/index.js @@ -1,5 +1,7 @@ 'use strict'; +const getMskClusterNameToken = require('./getMskClusterNameToken'); + class AwsCompileMSKEvents { constructor(serverless) { this.serverless = serverless; @@ -14,11 +16,7 @@ class AwsCompileMSKEvents { properties: { arn: { oneOf: [ - { - type: 'string', - pattern: - '^arn:aws[a-zA-Z-]*:kafka:[a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-[1-9]{1}:[0-9]{12}:cluster', - }, + { $ref: '#/definitions/awsArnString' }, { $ref: '#/definitions/awsCfImport' }, { $ref: '#/definitions/awsCfRef' }, ], @@ -44,51 +42,6 @@ class AwsCompileMSKEvents { }); } - resolveDependsOn(funcRole) { - let dependsOn = 'IamRoleLambdaExecution'; - - if (funcRole) { - if ( - // check whether the custom role is an ARN - typeof funcRole === 'string' && - funcRole.indexOf(':') !== -1 - ) { - dependsOn = []; - } else if ( - // otherwise, check if we have an in-service reference to a role ARN - typeof funcRole === 'object' && - 'Fn::GetAtt' in funcRole && - Array.isArray(funcRole['Fn::GetAtt']) && - funcRole['Fn::GetAtt'].length === 2 && - typeof funcRole['Fn::GetAtt'][0] === 'string' && - typeof funcRole['Fn::GetAtt'][1] === 'string' && - funcRole['Fn::GetAtt'][1] === 'Arn' - ) { - dependsOn = funcRole['Fn::GetAtt'][0]; - } else if ( - // otherwise, check if we have an import or parameters ref - typeof funcRole === 'object' && - ('Fn::ImportValue' in funcRole || 'Ref' in funcRole) - ) { - dependsOn = []; - } else if (typeof funcRole === 'string') { - dependsOn = funcRole; - } - } - - return dependsOn; - } - - getMSKClusterName(eventSourceArn) { - if (eventSourceArn['Fn::ImportValue']) { - return eventSourceArn['Fn::ImportValue']; - } else if (eventSourceArn.Ref) { - return eventSourceArn.Ref; - } - - return eventSourceArn.split('/')[1]; - } - compileMSKEvents() { this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); @@ -116,21 +69,20 @@ class AwsCompileMSKEvents { functionObj.events.forEach(event => { if (event.msk) { - const EventSourceArn = event.msk.arn; - const Topic = event.msk.topic; - const BatchSize = event.msk.batchSize || 100; - const Enabled = event.msk.enabled != null ? event.msk.enabled : true; - const StartingPosition = event.msk.startingPosition || 'TRIM_HORIZON'; + const eventSourceArn = event.msk.arn; + const topic = event.msk.topic; + const batchSize = event.msk.batchSize; + const enabled = event.msk.enabled; + const startingPosition = event.msk.startingPosition || 'TRIM_HORIZON'; - const mskClusterName = this.getMSKClusterName(EventSourceArn); + const mskClusterNameToken = getMskClusterNameToken(eventSourceArn); const mskEventLogicalId = this.provider.naming.getMSKEventLogicalId( functionName, - mskClusterName, - Topic + mskClusterNameToken, + topic ); - const funcRole = functionObj.role || this.serverless.service.provider.role; - const dependsOn = this.resolveDependsOn(funcRole); + const dependsOn = this.provider.resolveFunctionIamRoleResourceName(functionObj) || []; const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); @@ -138,24 +90,26 @@ class AwsCompileMSKEvents { Type: 'AWS::Lambda::EventSourceMapping', DependsOn: dependsOn, Properties: { - BatchSize, - EventSourceArn, + EventSourceArn: eventSourceArn, FunctionName: { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, - StartingPosition, - Enabled, - Topics: [Topic], + StartingPosition: startingPosition, + Topics: [topic], }, }; - mskStatement.Resource.push(EventSourceArn); + if (batchSize) { + mskResource.Properties.BatchSize = batchSize; + } - const newMSKObject = { - [mskEventLogicalId]: mskResource, - }; + if (enabled != null) { + mskResource.Properties.Enabled = enabled; + } + + mskStatement.Resource.push(eventSourceArn); - Object.assign(cfTemplate.Resources, newMSKObject); + cfTemplate.Resources[mskEventLogicalId] = mskResource; } }); diff --git a/lib/plugins/aws/package/compile/events/msk/index.test.js b/lib/plugins/aws/package/compile/events/msk/index.test.js index 386c0bfb8e2f..696e8137e864 100644 --- a/lib/plugins/aws/package/compile/events/msk/index.test.js +++ b/lib/plugins/aws/package/compile/events/msk/index.test.js @@ -3,9 +3,6 @@ const chai = require('chai'); const runServerless = require('../../../../../../../test/utils/run-serverless'); const fixtures = require('../../../../../../../test/fixtures'); -const AwsCompileMSKEvents = require('./index'); -const Serverless = require('../../../../../../Serverless'); -const AwsProvider = require('../../../../provider/awsProvider'); const { expect } = chai; @@ -16,9 +13,13 @@ describe('AwsCompileMSKEvents', () => { const arn = 'arn:aws:kafka:us-east-1:111111111111:cluster/ClusterName/a1a1a1a1a1a1a1a1a'; const topic = 'TestingTopic'; + const enabled = false; + const startingPosition = 'LATEST'; + const batchSize = 5000; - describe('when using default parameters', () => { - let eventSourceMappingResource; + describe('when there are msk events defined', () => { + let minimalEventSourceMappingResource; + let allParamsEventSourceMappingResource; let defaultIamRole; let naming; @@ -36,6 +37,19 @@ describe('AwsCompileMSKEvents', () => { }, ], }, + other: { + events: [ + { + msk: { + topic, + arn, + batchSize, + enabled, + startingPosition, + }, + }, + ], + }, }, }) .then(fixturePath => @@ -44,19 +58,21 @@ describe('AwsCompileMSKEvents', () => { cliArgs: ['package'], }).then(({ awsNaming, cfTemplate }) => { naming = awsNaming; - eventSourceMappingResource = + minimalEventSourceMappingResource = cfTemplate.Resources[ naming.getMSKEventLogicalId('foo', 'ClusterName', 'TestingTopic') ]; + allParamsEventSourceMappingResource = + cfTemplate.Resources[ + naming.getMSKEventLogicalId('other', 'ClusterName', 'TestingTopic') + ]; defaultIamRole = cfTemplate.Resources.IamRoleLambdaExecution; }) ) ); - it('should correctly compile EventSourceMapping resource properties', () => { - expect(eventSourceMappingResource.Properties).to.deep.equal({ - BatchSize: 100, - Enabled: true, + it('should correctly compile EventSourceMapping resource properties with minimal configuration', () => { + expect(minimalEventSourceMappingResource.Properties).to.deep.equal({ EventSourceArn: arn, StartingPosition: 'TRIM_HORIZON', Topics: [topic], @@ -90,55 +106,21 @@ describe('AwsCompileMSKEvents', () => { }); it('should correctly compile EventSourceMapping resource DependsOn ', () => { - expect(eventSourceMappingResource.DependsOn).to.equal('IamRoleLambdaExecution'); + expect(minimalEventSourceMappingResource.DependsOn).to.equal('IamRoleLambdaExecution'); + expect(allParamsEventSourceMappingResource.DependsOn).to.equal('IamRoleLambdaExecution'); }); - }); - - describe('when using all parameters', () => { - it('should correctly compile EventSourceMapping resource', () => { - const enabled = false; - const startingPosition = 'LATEST'; - const batchSize = 5000; - return fixtures - .extend('function', { - functions: { - foo: { - events: [ - { - msk: { - topic, - arn, - batchSize, - enabled, - startingPosition, - }, - }, - ], - }, - }, - }) - .then(fixturePath => - runServerless({ - cwd: fixturePath, - cliArgs: ['package'], - }).then(({ awsNaming, cfTemplate }) => { - const resource = - cfTemplate.Resources[ - awsNaming.getMSKEventLogicalId('foo', 'ClusterName', 'TestingTopic') - ]; - expect(resource.Properties).to.deep.equal({ - BatchSize: batchSize, - Enabled: enabled, - EventSourceArn: arn, - StartingPosition: startingPosition, - Topics: [topic], - FunctionName: { - 'Fn::GetAtt': [awsNaming.getLambdaLogicalId('foo'), 'Arn'], - }, - }); - }) - ); + it('should correctly complie EventSourceMapping resource with all parameters', () => { + expect(allParamsEventSourceMappingResource.Properties).to.deep.equal({ + BatchSize: batchSize, + Enabled: enabled, + EventSourceArn: arn, + StartingPosition: startingPosition, + Topics: [topic], + FunctionName: { + 'Fn::GetAtt': [naming.getLambdaLogicalId('other'), 'Arn'], + }, + }); }); }); @@ -171,72 +153,3 @@ describe('AwsCompileMSKEvents', () => { }); }); }); - -describe('getMSKClusterName', () => { - let awsCompileMSKEvents; - - before(() => { - const serverless = new Serverless(); - serverless.setProvider('aws', new AwsProvider(serverless)); - awsCompileMSKEvents = new AwsCompileMSKEvents(serverless); - }); - - it('with ARN', () => { - const eventSourceArn = - 'arn:aws:kafka:us-east-1:111111111111:cluster/ClusterName/a1a1a1a1a1a1a1a1a'; - const result = awsCompileMSKEvents.getMSKClusterName(eventSourceArn); - expect(result).to.equal('ClusterName'); - }); - - it('with Fn::ImportValue', () => { - const eventSourceArn = { 'Fn::ImportValue': 'importvalue' }; - const result = awsCompileMSKEvents.getMSKClusterName(eventSourceArn); - expect(result).to.equal('importvalue'); - }); - - it('with Ref', () => { - const eventSourceArn = { Ref: 'ReferencedResource' }; - const result = awsCompileMSKEvents.getMSKClusterName(eventSourceArn); - expect(result).to.equal('ReferencedResource'); - }); -}); - -describe('resolveDependsOn', () => { - let awsCompileMSKEvents; - - before(() => { - const serverless = new Serverless(); - serverless.setProvider('aws', new AwsProvider(serverless)); - awsCompileMSKEvents = new AwsCompileMSKEvents(serverless); - }); - - it('with ARN', () => { - const funcRole = 'arn:aws:iam::xxxxxxxxxxxx:role/xxxx'; - const result = awsCompileMSKEvents.resolveDependsOn(funcRole); - expect(result).to.deep.equal([]); - }); - - it('with string', () => { - const funcRole = 'role'; - const result = awsCompileMSKEvents.resolveDependsOn(funcRole); - expect(result).to.equal(funcRole); - }); - - it('with Fn::ImportValue', () => { - const funcRole = { 'Fn::ImportValue': 'importvalue' }; - const result = awsCompileMSKEvents.resolveDependsOn(funcRole); - expect(result).to.deep.equal([]); - }); - - it('with Ref', () => { - const funcRole = { Ref: 'ReferencedResource' }; - const result = awsCompileMSKEvents.resolveDependsOn(funcRole); - expect(result).to.deep.equal([]); - }); - - it('with Fn::GetAtt reference to role ARN', () => { - const funcRole = { 'Fn::GetAtt': ['Resource', 'Arn'] }; - const result = awsCompileMSKEvents.resolveDependsOn(funcRole); - expect(result).to.equal('Resource'); - }); -});