From 318eae6c9eca456e0c34ed21855dad9d2bfa2a0f Mon Sep 17 00:00:00 2001 From: Kazuho Cryer-Shinozuka Date: Sat, 19 Oct 2024 18:37:13 +0900 Subject: [PATCH] feat(events): dead letter queue for an Event Bus (#30628) ### Issue # (if applicable) Closes #30531. ### Reason for this change EventBus L2 construct doesn't support for configuring dead letter queue. ### Description of changes Add `deadLetterQueue` prop to `EventBusProps` ### Description of how you validated changes Add both unit and integ test ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../Stack.assets.json | 4 +-- .../Stack.template.json | 13 ++++++++ .../integ.eventbus.js.snapshot/manifest.json | 8 ++++- .../test/integ.eventbus.js.snapshot/tree.json | 30 +++++++++++++++++++ .../test/aws-events/test/integ.eventbus.ts | 5 ++++ packages/aws-cdk-lib/aws-events/README.md | 16 ++++++++++ .../aws-cdk-lib/aws-events/lib/event-bus.ts | 13 ++++++++ .../aws-events/test/event-bus.test.ts | 26 ++++++++++++---- 8 files changed, 107 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.assets.json index cd6099e293e32..8283986c144e7 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "2671a34b39f9d75f10baaa810e7901cbcaa8901ff9852aa7a49bf43a87af0155": { + "4c3d1a4b2e322a4753eb4eaf49c45451cc6a8bd2662a6d2c740c369f3e55d10f": { "source": { "path": "Stack.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "2671a34b39f9d75f10baaa810e7901cbcaa8901ff9852aa7a49bf43a87af0155.json", + "objectKey": "4c3d1a4b2e322a4753eb4eaf49c45451cc6a8bd2662a6d2c740c369f3e55d10f.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.template.json index ec1b73099f571..0ca0b5065f5e4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/Stack.template.json @@ -1,8 +1,21 @@ { "Resources": { + "DLQ581697C4": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "BusEA82B648": { "Type": "AWS::Events::EventBus", "Properties": { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DLQ581697C4", + "Arn" + ] + } + }, "Description": "myEventBus", "Name": "StackBusAA0A1E4B" } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/manifest.json index 48b493126292a..bf76759facbc8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2671a34b39f9d75f10baaa810e7901cbcaa8901ff9852aa7a49bf43a87af0155.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4c3d1a4b2e322a4753eb4eaf49c45451cc6a8bd2662a6d2c740c369f3e55d10f.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -34,6 +34,12 @@ "Stack.assets" ], "metadata": { + "/Stack/DLQ/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DLQ581697C4" + } + ], "/Stack/Bus/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/tree.json index 70659c5fb56dd..05f7895c8c043 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.js.snapshot/tree.json @@ -8,6 +8,28 @@ "id": "Stack", "path": "Stack", "children": { + "DLQ": { + "id": "DLQ", + "path": "Stack/DLQ", + "children": { + "Resource": { + "id": "Resource", + "path": "Stack/DLQ/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, "Bus": { "id": "Bus", "path": "Stack/Bus", @@ -18,6 +40,14 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Events::EventBus", "aws:cdk:cloudformation:props": { + "deadLetterConfig": { + "arn": { + "Fn::GetAtt": [ + "DLQ581697C4", + "Arn" + ] + } + }, "description": "myEventBus", "name": "StackBusAA0A1E4B" } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.ts index c7ce9708f4094..331dec2c9bb63 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-events/test/integ.eventbus.ts @@ -1,11 +1,16 @@ import * as iam from 'aws-cdk-lib/aws-iam'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; import { App, Stack } from 'aws-cdk-lib'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import { EventBus } from 'aws-cdk-lib/aws-events'; const app = new App(); const stack = new Stack(app, 'Stack'); + +const dlq = new sqs.Queue(stack, 'DLQ'); + const bus = new EventBus(stack, 'Bus', { + deadLetterQueue: dlq, description: 'myEventBus', }); diff --git a/packages/aws-cdk-lib/aws-events/README.md b/packages/aws-cdk-lib/aws-events/README.md index 14e4ed01b8926..9a8af87acee4c 100644 --- a/packages/aws-cdk-lib/aws-events/README.md +++ b/packages/aws-cdk-lib/aws-events/README.md @@ -222,6 +222,22 @@ bus.archive('MyArchive', { }); ``` +## Dead-Letter Queue for EventBus + +It is possible to configure a [Dead Letter Queue for an EventBus](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-event-delivery.html#eb-rule-dlq). This is useful when you want to capture events that could not be delivered to any of the targets. + +To configure a Dead Letter Queue for an EventBus, you can use the `deadLetterQueue` property of the `EventBus` construct. + +```ts +import * as sqs from 'aws-cdk-lib/aws-sqs'; + +const dlq = new sqs.Queue(this, 'DLQ'); + +const bus = new events.EventBus(this, 'Bus', { + deadLetterQueue: dlq, +}); +``` + ## Granting PutEvents to an existing EventBus To import an existing EventBus into your CDK application, use `EventBus.fromEventBusArn`, `EventBus.fromEventBusAttributes` diff --git a/packages/aws-cdk-lib/aws-events/lib/event-bus.ts b/packages/aws-cdk-lib/aws-events/lib/event-bus.ts index f7b7e94d745e5..c60b34fc09818 100644 --- a/packages/aws-cdk-lib/aws-events/lib/event-bus.ts +++ b/packages/aws-cdk-lib/aws-events/lib/event-bus.ts @@ -3,6 +3,7 @@ import { Archive, BaseArchiveProps } from './archive'; import { CfnEventBus, CfnEventBusPolicy } from './events.generated'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; +import * as sqs from '../../aws-sqs'; import { ArnFormat, IResource, Lazy, Names, Resource, Stack, Token } from '../../core'; /** @@ -80,6 +81,15 @@ export interface EventBusProps { */ readonly eventSourceName?: string; + /** + * Dead-letter queue for the event bus + * + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-event-delivery.html#eb-rule-dlq + * + * @default - no dead-letter queue + */ + readonly deadLetterQueue?: sqs.IQueue; + /** * The event bus description. * @@ -343,6 +353,9 @@ export class EventBus extends EventBusBase { const eventBus = new CfnEventBus(this, 'Resource', { name: this.physicalName, eventSourceName, + deadLetterConfig: props?.deadLetterQueue ? { + arn: props.deadLetterQueue.queueArn, + } : undefined, description: props?.description, kmsKeyIdentifier: props?.kmsKey?.keyArn, }); diff --git a/packages/aws-cdk-lib/aws-events/test/event-bus.test.ts b/packages/aws-cdk-lib/aws-events/test/event-bus.test.ts index 0bfa70b3534aa..bc48153cf9ee1 100644 --- a/packages/aws-cdk-lib/aws-events/test/event-bus.test.ts +++ b/packages/aws-cdk-lib/aws-events/test/event-bus.test.ts @@ -1,8 +1,8 @@ import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Template } from '../../assertions'; import * as iam from '../../aws-iam'; -import { Effect } from '../../aws-iam'; import * as kms from '../../aws-kms'; +import * as sqs from '../../aws-sqs'; import { Aws, CfnResource, Stack, Arn, App, PhysicalName, CfnOutput } from '../../core'; import { EventBus } from '../lib'; @@ -568,7 +568,7 @@ describe('event bus', () => { // WHEN bus.addToResourcePolicy(new iam.PolicyStatement({ - effect: Effect.ALLOW, + effect: iam.Effect.ALLOW, principals: [new iam.AccountPrincipal('111111111111111')], actions: ['events:PutEvents'], sid: '123', @@ -616,7 +616,7 @@ describe('event bus', () => { const bus = new EventBus(stack, 'Bus'); const statement1 = new iam.PolicyStatement({ - effect: Effect.ALLOW, + effect: iam.Effect.ALLOW, principals: [new iam.ArnPrincipal('arn')], actions: ['events:PutEvents'], sid: 'statement1', @@ -624,7 +624,7 @@ describe('event bus', () => { }); const statement2 = new iam.PolicyStatement({ - effect: Effect.ALLOW, + effect: iam.Effect.ALLOW, principals: [new iam.ArnPrincipal('arn')], actions: ['events:DeleteRule'], sid: 'statement2', @@ -649,12 +649,28 @@ describe('event bus', () => { // THEN expect(() => bus.addToResourcePolicy(new iam.PolicyStatement({ - effect: Effect.ALLOW, + effect: iam.Effect.ALLOW, principals: [new iam.ArnPrincipal('arn')], actions: ['events:PutEvents'], }))).toThrow('Event Bus policy statements must have a sid'); }); + test('set dead letter queue', () => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + const dlq = new sqs.Queue(stack, 'DLQ'); + new EventBus(stack, 'Bus', { + deadLetterQueue: dlq, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBus', { + DeadLetterConfig: { + Arn: { + 'Fn::GetAtt': ['DLQ581697C4', 'Arn'], + }, + }, + }); + }); test('Event Bus with a customer managed key', () => { // GIVEN const app = new App();