From b3a31c27efc46dcca5d1440c2b9b8f6480aaca99 Mon Sep 17 00:00:00 2001 From: DaWyz Date: Sat, 6 Mar 2021 14:35:41 -0800 Subject: [PATCH] add dlq support for step function event target --- .../@aws-cdk/aws-events-targets/README.md | 36 +++++++ .../aws-events-targets/lib/state-machine.ts | 20 +++- .../test/stepfunctions/statemachine.test.ts | 93 +++++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-events-targets/README.md b/packages/@aws-cdk/aws-events-targets/README.md index cd843ef130a3a..4b795ce5f19b5 100644 --- a/packages/@aws-cdk/aws-events-targets/README.md +++ b/packages/@aws-cdk/aws-events-targets/README.md @@ -89,3 +89,39 @@ const rule = new events.Rule(this, 'rule', { rule.addTarget(new targets.CloudWatchLogGroup(logGroup)); ``` + +## Trigger a State Machine + +Use the `SfnStateMachine` target to trigger a State Machine. + +The code snippet below creates a Simple StateMachine that is triggered every minute with a +dummy object as input. +You can optionally attach a +[dead letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html) +to the target. + +```ts +import * as iam from '@aws-sdk/aws-iam'; +import * as sqs from '@aws-sdk/aws-sqs'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as targets from "@aws-cdk/aws-events-targets"; + +const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), +}); + +const dlq = new sqs.Queue(stack, 'DeadLetterQueue'); + +const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), +}); +const stateMachine = new sfn.StateMachine(stack, 'SM', { + definition: new sfn.Wait(stack, 'Hello', { time: sfn.WaitTime.duration(cdk.Duration.seconds(10)) }), + role, +}); + +rule.addTarget(new targets.SfnStateMachine(stateMachine, { + input: events.RuleTargetInput.fromObject({ SomeParam: 'SomeValue' }), + deadLetterQueue: dlq, +})); +``` diff --git a/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts b/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts index acffea0eea6f4..a2a791715e94c 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/state-machine.ts @@ -1,7 +1,8 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import * as sqs from '@aws-cdk/aws-sqs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; -import { singletonEventRole } from './util'; +import { addToDeadLetterQueueResourcePolicy, singletonEventRole } from './util'; /** * Customize the Step Functions State Machine target @@ -20,6 +21,18 @@ export interface SfnStateMachineProps { * @default - a new role will be created */ readonly role?: iam.IRole; + + /** + * The SQS queue to be used as deadLetterQueue. + * Check out the [considerations for using a dead-letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html#dlq-considerations). + * + * The events not successfully delivered are automatically retried for a specified period of time, + * depending on the retry policy of the target. + * If an event is not delivered before all retry attempts are exhausted, it will be sent to the dead letter queue. + * + * @default - no dead-letter queue + */ + readonly deadLetterQueue?: sqs.IQueue; } /** @@ -43,9 +56,14 @@ export class SfnStateMachine implements events.IRuleTarget { * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sns-permissions */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { + if (this.props.deadLetterQueue) { + addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue); + } + return { id: '', arn: this.machine.stateMachineArn, + deadLetterConfig: this.props.deadLetterQueue ? { arn: this.props.deadLetterQueue?.queueArn } : undefined, role: this.role, input: this.props.input, targetResource: this.machine, diff --git a/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts b/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts index 3b50ef569f004..9dc3b2d0a4e83 100644 --- a/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert/jest'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import * as sqs from '@aws-cdk/aws-sqs'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import * as targets from '../../lib'; @@ -110,3 +111,95 @@ test('Existing role can be used for State machine Rule target', () => { }, }); }); + +test('use a Dead Letter Queue for the rule target', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), + }); + + const dlq = new sqs.Queue(stack, 'DeadLetterQueue'); + + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('events.amazonaws.com'), + }); + const stateMachine = new sfn.StateMachine(stack, 'SM', { + definition: new sfn.Wait(stack, 'Hello', { time: sfn.WaitTime.duration(cdk.Duration.seconds(10)) }), + role, + }); + + // WHEN + rule.addTarget(new targets.SfnStateMachine(stateMachine, { + input: events.RuleTargetInput.fromObject({ SomeParam: 'SomeValue' }), + deadLetterQueue: dlq, + })); + + // the Permission resource should be in the event stack + expect(stack).toHaveResource('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 minute)', + State: 'ENABLED', + Targets: [ + { + Arn: { + Ref: 'SM934E715A', + }, + DeadLetterConfig: { + Arn: { + 'Fn::GetAtt': [ + 'DeadLetterQueue9F481546', + 'Arn', + ], + }, + }, + Id: 'Target0', + Input: '{"SomeParam":"SomeValue"}', + RoleArn: { + 'Fn::GetAtt': [ + 'SMEventsRoleB320A902', + 'Arn', + ], + }, + }, + ], + }); + + expect(stack).toHaveResource('AWS::SQS::QueuePolicy', { + PolicyDocument: { + Statement: [ + { + Action: 'sqs:SendMessage', + Condition: { + ArnEquals: { + 'aws:SourceArn': { + 'Fn::GetAtt': [ + 'Rule4C995B7F', + 'Arn', + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: 'events.amazonaws.com', + }, + Resource: { + 'Fn::GetAtt': [ + 'DeadLetterQueue9F481546', + 'Arn', + ], + }, + Sid: 'AllowEventRuleStackRuleF6E31DD0', + }, + ], + Version: '2012-10-17', + }, + Queues: [ + { + Ref: 'DeadLetterQueue9F481546', + }, + ], + }); +});