Skip to content

Commit

Permalink
feat(events): dead-letter queue support for StepFunctions (#13450)
Browse files Browse the repository at this point in the history
Resolves #13449

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
DaWyz authored Mar 8, 2021
1 parent 6c5b1f4 commit 0ebcb41
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 1 deletion.
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-events-targets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}));
```
20 changes: 19 additions & 1 deletion packages/@aws-cdk/aws-events-targets/lib/state-machine.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
}

/**
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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',
},
],
});
});

0 comments on commit 0ebcb41

Please sign in to comment.