From 181828789f0f098a2ff2470fc09ecb955c16f707 Mon Sep 17 00:00:00 2001 From: Julian Michel Date: Sun, 24 Apr 2022 22:41:17 +0200 Subject: [PATCH 1/2] feat(events-targets): Add DLQ support for SNS target --- .../@aws-cdk/aws-events-targets/README.md | 2 +- .../@aws-cdk/aws-events-targets/lib/sns.ts | 8 ++++- .../test/sns/integ.sns-event-rule-target.ts | 6 +++- .../aws-events-targets/test/sns/sns.test.ts | 34 +++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/README.md b/packages/@aws-cdk/aws-events-targets/README.md index 34157fef412b5..342f9b8b9b620 100644 --- a/packages/@aws-cdk/aws-events-targets/README.md +++ b/packages/@aws-cdk/aws-events-targets/README.md @@ -36,7 +36,7 @@ EventBridge. ## Event retry policy and using dead-letter queues -The Codebuild, CodePipeline, Lambda, StepFunctions, LogGroup and SQSQueue targets support attaching a [dead letter queue and setting retry policies](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html). See the [lambda example](#invoke-a-lambda-function). +The Codebuild, CodePipeline, Lambda, StepFunctions, LogGroup, SQSQueue and SNSTopic targets support attaching a [dead letter queue and setting retry policies](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html). See the [lambda example](#invoke-a-lambda-function). Use [escape hatches](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html) for the other target types. ## Invoke a Lambda function diff --git a/packages/@aws-cdk/aws-events-targets/lib/sns.ts b/packages/@aws-cdk/aws-events-targets/lib/sns.ts index 81e5d4916718f..bff1e99883123 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/sns.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/sns.ts @@ -1,11 +1,12 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; +import { addToDeadLetterQueueResourcePolicy, TargetBaseProps, bindBaseTargetConfig } from './util'; /** * Customize the SNS Topic Event Target */ -export interface SnsTopicProps { +export interface SnsTopicProps extends TargetBaseProps { /** * The message to send to the topic * @@ -38,7 +39,12 @@ export class SnsTopic implements events.IRuleTarget { // deduplicated automatically this.topic.grantPublish(new iam.ServicePrincipal('events.amazonaws.com')); + if (this.props.deadLetterQueue) { + addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue); + } + return { + ...bindBaseTargetConfig(this.props), arn: this.topic.topicArn, input: this.props.message, targetResource: this.topic, diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts index 1f0656f1378fc..5e2bf6235f65a 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts @@ -22,6 +22,10 @@ const event = new events.Rule(stack, 'EveryMinute', { const queue = new sqs.Queue(stack, 'MyQueue'); topic.addSubscription(new subs.SqsSubscription(queue)); -event.addTarget(new targets.SnsTopic(topic)); +const deadLetterQueue = new sqs.Queue(stack, 'MyDeadLetterQueue'); + +event.addTarget(new targets.SnsTopic(topic, { + deadLetterQueue, +})); app.synth(); diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts b/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts index ac1d0dc5e740b..f4673ff9e02ed 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts @@ -1,6 +1,7 @@ import { Template } from '@aws-cdk/assertions'; import * as events from '@aws-cdk/aws-events'; import * as sns from '@aws-cdk/aws-sns'; +import * as sqs from '@aws-cdk/aws-sqs'; import { Duration, Stack } from '@aws-cdk/core'; import * as targets from '../../lib'; @@ -74,3 +75,36 @@ test('multiple uses of a topic as a target results in a single policy statement' Topics: [{ Ref: 'MyTopic86869434' }], }); }); + +test('dead letter queue is configured correctly', () => { + const stack = new Stack(); + const topic = new sns.Topic(stack, 'MyTopic'); + const deadLetterQueue = new sqs.Queue(stack, 'MyDeadLetterQueue'); + const rule = new events.Rule(stack, 'MyRule', { + schedule: events.Schedule.rate(Duration.hours(1)), + }); + + // WHEN + rule.addTarget(new targets.SnsTopic(topic, { + deadLetterQueue, + })); + + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 hour)', + State: 'ENABLED', + Targets: [ + { + Arn: { Ref: 'MyTopic86869434' }, + Id: 'Target0', + DeadLetterConfig: { + Arn: { + 'Fn::GetAtt': [ + 'MyDeadLetterQueueD997968A', + 'Arn', + ], + }, + }, + }, + ], + }); +}); \ No newline at end of file From 08566f02a1c3565f43d17dbcb8bce2a8fe930814 Mon Sep 17 00:00:00 2001 From: Julian Michel Date: Mon, 16 May 2022 20:12:42 +0200 Subject: [PATCH 2/2] update integration test --- .../aws-cdk-sns-event-target.template.json | 52 +++++++++++ .../cdk.out | 2 +- .../integ.json | 4 +- .../manifest.json | 14 ++- .../tree.json | 87 +++++++++++++++++++ 5 files changed, 155 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/aws-cdk-sns-event-target.template.json b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/aws-cdk-sns-event-target.template.json index 1120caf2456b0..463d052b9a240 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/aws-cdk-sns-event-target.template.json +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/aws-cdk-sns-event-target.template.json @@ -39,6 +39,14 @@ "Arn": { "Ref": "MyTopic86869434" }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "MyDeadLetterQueueD997968A", + "Arn" + ] + } + }, "Id": "Target0" } ] @@ -98,6 +106,50 @@ ] } } + }, + "MyDeadLetterQueueD997968A": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "MyDeadLetterQueuePolicyCC35D52C": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EveryMinute2BBCEA8F", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "MyDeadLetterQueueD997968A", + "Arn" + ] + }, + "Sid": "AllowEventRuleawscdksnseventtargetEveryMinuteD1FC5963" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "MyDeadLetterQueueD997968A" + } + ] + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/cdk.out index 90bef2e09ad39..ccdfc1ff96a9d 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"19.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/integ.json b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/integ.json index 3a5371f0accfa..730b15c72bcd8 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "19.0.0", "testCases": { - "aws-events-targets/test/sns/integ.sns-event-rule-target": { + "sns/integ.sns-event-rule-target": { "stacks": [ "aws-cdk-sns-event-target" ], diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/manifest.json index 620867b3c6a9b..52a1954dd84e0 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "19.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -50,6 +50,18 @@ "type": "aws:cdk:logicalId", "data": "MyQueueawscdksnseventtargetMyTopicB7575CD87304D383" } + ], + "/aws-cdk-sns-event-target/MyDeadLetterQueue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyDeadLetterQueueD997968A" + } + ], + "/aws-cdk-sns-event-target/MyDeadLetterQueue/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyDeadLetterQueuePolicyCC35D52C" + } ] }, "displayName": "aws-cdk-sns-event-target" diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/tree.json b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/tree.json index 6f4f51146a312..c12e2889e391a 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns-event-rule-target.integ.snapshot/tree.json @@ -99,6 +99,14 @@ "id": "Target0", "arn": { "Ref": "MyTopic86869434" + }, + "deadLetterConfig": { + "arn": { + "Fn::GetAtt": [ + "MyDeadLetterQueueD997968A", + "Arn" + ] + } } } ] @@ -222,6 +230,85 @@ "fqn": "@aws-cdk/aws-sqs.Queue", "version": "0.0.0" } + }, + "MyDeadLetterQueue": { + "id": "MyDeadLetterQueue", + "path": "aws-cdk-sns-event-target/MyDeadLetterQueue", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-sns-event-target/MyDeadLetterQueue/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.CfnQueue", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "aws-cdk-sns-event-target/MyDeadLetterQueue/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-sns-event-target/MyDeadLetterQueue/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::QueuePolicy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "EveryMinute2BBCEA8F", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "MyDeadLetterQueueD997968A", + "Arn" + ] + }, + "Sid": "AllowEventRuleawscdksnseventtargetEveryMinuteD1FC5963" + } + ], + "Version": "2012-10-17" + }, + "queues": [ + { + "Ref": "MyDeadLetterQueueD997968A" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.CfnQueuePolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.QueuePolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-sqs.Queue", + "version": "0.0.0" + } } }, "constructInfo": {