diff --git a/lib/plugins/aws/package/compile/events/eventBridge/index.js b/lib/plugins/aws/package/compile/events/eventBridge/index.js index 8366de831..ce59eb00d 100644 --- a/lib/plugins/aws/package/compile/events/eventBridge/index.js +++ b/lib/plugins/aws/package/compile/events/eventBridge/index.js @@ -20,6 +20,7 @@ class AwsCompileEventBridgeEvents { const { provider } = service; const { compiledCloudFormationTemplate } = provider; const iamRoleStatements = []; + const eventBusIamRoleStatements = new Map(); service.getAllFunctions().forEach(functionName => { let funcUsesEventBridge = false; @@ -159,11 +160,14 @@ class AwsCompileEventBridgeEvents { ], ], }; - iamRoleStatements.push({ - Effect: 'Allow', - Resource: eventBusResource, - Action: ['events:CreateEventBus', 'events:DeleteEventBus'], - }); + + if (!eventBusIamRoleStatements.has(eventBusName)) { + eventBusIamRoleStatements.set(eventBusName, { + Effect: 'Allow', + Resource: eventBusResource, + Action: ['events:CreateEventBus', 'events:DeleteEventBus'], + }); + } } iamRoleStatements.push({ @@ -209,6 +213,8 @@ class AwsCompileEventBridgeEvents { } }); + iamRoleStatements.unshift(...eventBusIamRoleStatements.values()); + if (iamRoleStatements.length) { return addCustomResourceToService(this.provider, 'eventBridge', iamRoleStatements); } diff --git a/lib/plugins/aws/package/compile/events/eventBridge/index.test.js b/lib/plugins/aws/package/compile/events/eventBridge/index.test.js index 902c4cd10..e768f60c4 100644 --- a/lib/plugins/aws/package/compile/events/eventBridge/index.test.js +++ b/lib/plugins/aws/package/compile/events/eventBridge/index.test.js @@ -395,6 +395,216 @@ describe('AwsCompileEventBridgeEvents', () => { ); }); + it('should create the necessary resources with only one create/delete event bus policy', () => { + awsCompileEventBridgeEvents.serverless.service.functions = { + first: { + name: 'first', + events: [ + { + eventBridge: { + eventBus: 'some-event-bus', + schedule: 'rate(10 minutes)', + pattern: { + 'source': ['aws.cloudformation'], + 'detail-type': ['AWS API Call via CloudTrail'], + 'detail': { + eventSource: ['cloudformation.amazonaws.com'], + }, + }, + }, + }, + ], + }, + second: { + name: 'second', + events: [ + { + eventBridge: { + eventBus: 'some-event-bus', + schedule: 'rate(10 minutes)', + pattern: { + 'source': ['aws.cloudformation'], + 'detail-type': ['AWS API Call via CloudTrail'], + 'detail': { + eventSource: ['cloudformation.amazonaws.com'], + }, + }, + }, + }, + ], + }, + }; + + return expect(awsCompileEventBridgeEvents.compileEventBridgeEvents()).to.be.fulfilled.then( + () => { + const { + Resources, + } = awsCompileEventBridgeEvents.serverless.service.provider.compiledCloudFormationTemplate; + + expect(addCustomResourceToServiceStub).to.have.been.calledOnce; + expect(addCustomResourceToServiceStub.args[0][1]).to.equal('eventBridge'); + expect(addCustomResourceToServiceStub.args[0][2]).to.deep.equal([ + { + Action: ['events:CreateEventBus', 'events:DeleteEventBus'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + ':', + [ + 'arn', + { + Ref: 'AWS::Partition', + }, + 'events', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'event-bus/some-event-bus', + ], + ], + }, + }, + { + Action: [ + 'events:PutRule', + 'events:RemoveTargets', + 'events:PutTargets', + 'events:DeleteRule', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + ':', + [ + 'arn', + { + Ref: 'AWS::Partition', + }, + 'events', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'rule/some-event-bus/first-rule-1', + ], + ], + }, + }, + { + Action: ['lambda:AddPermission', 'lambda:RemovePermission'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + ':', + [ + 'arn', + { + Ref: 'AWS::Partition', + }, + 'lambda', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'function', + 'first', + ], + ], + }, + }, + { + Action: [ + 'events:PutRule', + 'events:RemoveTargets', + 'events:PutTargets', + 'events:DeleteRule', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + ':', + [ + 'arn', + { + Ref: 'AWS::Partition', + }, + 'events', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'rule/some-event-bus/second-rule-1', + ], + ], + }, + }, + { + Action: ['lambda:AddPermission', 'lambda:RemovePermission'], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + ':', + [ + 'arn', + { + Ref: 'AWS::Partition', + }, + 'lambda', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'function', + 'second', + ], + ], + }, + }, + ]); + expect(Resources.FirstCustomEventBridge1).to.deep.equal({ + Type: 'Custom::EventBridge', + Version: 1, + DependsOn: [ + 'FirstLambdaFunction', + 'CustomDashresourceDasheventDashbridgeLambdaFunction', + ], + Properties: { + ServiceToken: { + 'Fn::GetAtt': ['CustomDashresourceDasheventDashbridgeLambdaFunction', 'Arn'], + }, + FunctionName: 'first', + EventBridgeConfig: { + EventBus: 'some-event-bus', + Input: undefined, + InputPath: undefined, + InputTransformer: undefined, + Pattern: { + 'detail': { + eventSource: ['cloudformation.amazonaws.com'], + }, + 'detail-type': ['AWS API Call via CloudTrail'], + 'source': ['aws.cloudformation'], + }, + RuleName: 'first-rule-1', + Schedule: 'rate(10 minutes)', + }, + }, + }); + } + ); + }); + it('should create the necessary resources when using an own event bus arn', () => { awsCompileEventBridgeEvents.serverless.service.functions = { first: {