diff --git a/README.md b/README.md index ed0c1c06..18723be5 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Serverless Framework v2.32.0 or later is required. - [Specifying a RoleArn](#specifying-a-rolearn) - [Specifying a custom CloudWatch EventBus](#specifying-a-custom-cloudwatch-eventbus) - [Specifying a custom EventBridge EventBus](#specifying-a-custom-eventbridge-eventbus) + - [Specifying a DeadLetterQueue](#specifying-a-deadletterqueue) - [Tags](#tags) - [Commands](#commands) - [deploy](#deploy) @@ -1156,6 +1157,73 @@ stepFunctions: ... ``` +#### Specifying a DeadLetterQueue + +You can configure a target queue to send dead-letter queue events to: + +```yml +stepFunctions: + stateMachines: + exampleEventBridgeEventStartsMachine: + events: + - eventBridge: + eventBusName: 'my-custom-event-bus' + event: + source: + - "my.custom.source" + detail-type: + - "My Event Type" + detail: + state: + - pending + deadLetterConfig: 'arn:aws:sqs:us-east-1:012345678910:my-dlq' # SQS Arn + definition: + ... +``` +##### Important point +Don't forget to [Grant permissions to the dead-letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-dlq.html#eb-dlq-perms), to do that you may need to have the `ARN` of the generated `EventBridge Rule`. + +In order to get the `ARN` you can use [intrinsic functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html) against the `logicalId`, this plugin generates `logicalIds` following this format: +```ts +`${StateMachineName}EventsRuleCloudWatchEvent${index}` +``` +Given this example 👇 +```yml +stepFunctions: + stateMachines: + hellostepfunc1: # <---- StateMachineName + events: + - eventBridge: + eventBusName: 'my-custom-event-bus' + event: + source: + - "my.custom.source" + - eventBridge: + eventBusName: 'my-custom-event-bus' + event: + source: + - "my.custom.source" + deadLetterConfig: 'arn:aws:sqs:us-east-1:012345678910:my-dlq' + name: myStateMachine + definition: + Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function" + StartAt: HelloWorld1 + States: + HelloWorld1: + Type: Task + Resource: + Fn::GetAtt: [hello, Arn] + End: true +``` +Then +```yaml +# to get the Arn of the 1st EventBridge rule +!GetAtt Hellostepfunc1EventsRuleCloudWatchEvent1.Arn + +# to get the Arn of the 2nd EventBridge rule +!GetAtt Hellostepfunc1EventsRuleCloudWatchEvent2.Arn +``` + ## Tags You can specify tags on each state machine. Additionally any global tags (specified under `provider` section in your `serverless.yml`) would be merged in as well. diff --git a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js index 1057a425..ac83455b 100644 --- a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js +++ b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js @@ -23,6 +23,7 @@ module.exports = { let Name; let EventBusName; let IamRole; + let DeadLetterConfig; if (typeof eventRule === 'object') { if (!eventRule.event) { @@ -45,6 +46,7 @@ module.exports = { Name = eventRule.name; EventBusName = JSON.stringify(eventRule.eventBusName); IamRole = eventRule.iamRole; + DeadLetterConfig = JSON.stringify(eventRule.deadLetterConfig); if (Input && InputPath) { const errorMessage = [ @@ -94,6 +96,7 @@ module.exports = { ${InputPath ? `"InputPath": "${InputPath.replace(/\r?\n/g, '')}",` : ''} "Arn": { "Ref": "${stateMachineLogicalId}" }, "Id": "${cloudWatchId}", + ${DeadLetterConfig ? `"DeadLetterConfig":{ "Arn" : ${DeadLetterConfig} },` : ''} ${IamRole ? `"RoleArn":"${IamRole}"` : `"RoleArn": { "Fn::GetAtt": [ "${cloudWatchIamRoleLogicalId}", diff --git a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js index c89e9a0c..17a47172 100644 --- a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js +++ b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js @@ -327,6 +327,68 @@ describe('awsCompileCloudWatchEventEvents', () => { .Properties.EventBusName).to.equal('{"Fn::If": [isLocal, "develop", "production"}'); }); + itParam('should respect deadLetterConfig variable', ['cloudwatchEvent', 'eventBridge'], (source) => { + serverlessStepFunctions.serverless.service.stepFunctions = { + stateMachines: { + first: { + events: [ + { + [source]: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + input: '{"key":"value"}', + name: 'test-event-name', + eventBusName: 'custom-event-bus', + deadLetterConfig: 'arn:aws:sqs:us-east-1:012345678910:my-dlq', + }, + }, + ], + }, + }, + }; + + serverlessStepFunctions.compileCloudWatchEventEvents(); + + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 + .Properties.Targets[0].DeadLetterConfig.Arn).to.equal('arn:aws:sqs:us-east-1:012345678910:my-dlq'); + }); + + itParam('should respect deadLetterConfig intrinsic function', ['cloudwatchEvent', 'eventBridge'], (source) => { + serverlessStepFunctions.serverless.service.stepFunctions = { + stateMachines: { + first: { + events: [ + { + [source]: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + input: '{"key":"value"}', + name: 'test-event-name', + eventBusName: 'custom-event-bus', + deadLetterConfig: '{"Fn::GetAtt":["DLQ","Arn"]}', + }, + }, + ], + }, + }, + }; + + serverlessStepFunctions.compileCloudWatchEventEvents(); + + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 + .Properties.Targets[0].DeadLetterConfig.Arn).to.equal('{"Fn::GetAtt":["DLQ","Arn"]}'); + }); + itParam('should respect input variable as an object', ['cloudwatchEvent', 'eventBridge'], (source) => { serverlessStepFunctions.serverless.service.stepFunctions = { stateMachines: {