From 2749269cccbf412e621b2a780ff7f30ca63067f1 Mon Sep 17 00:00:00 2001 From: Julian Michel Date: Tue, 14 Jun 2022 21:54:31 +0200 Subject: [PATCH] feat(events-targets): Add DLQ support for SNS target (#20062) Add DLQ support for SNS target. Closes #19741. ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [X] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [X] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@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-cdk-sns-event-target.template.json | 52 +++++++++++ .../cdk.out | 2 +- .../integ.json | 4 +- .../manifest.json | 14 ++- .../tree.json | 87 +++++++++++++++++++ .../aws-events-targets/test/sns/sns.test.ts | 34 ++++++++ 9 files changed, 202 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/README.md b/packages/@aws-cdk/aws-events-targets/README.md index 0b720411a3097..290c6215e37d8 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-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": { 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