Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aww-eventbridge-sqs): add a dlq for the event rule #1253

Merged
merged 12 commits into from
Jan 31, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ constructStack.getEncryptionKey().addToResourcePolicy(policyStatement);
|existingEventBusInterface?|[`events.IEventBus`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events.IEventBus.html)| Optional user-provided custom EventBus for construct to use. Providing both this and `eventBusProps` results an error.|
|eventBusProps?|[`events.EventBusProps`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events.EventBusProps.html)|Optional user-provided properties to override the default properties when creating a custom EventBus. Setting this value to `{}` will create a custom EventBus using all default properties. If neither this nor `existingEventBusInterface` is provided the construct will use the `default` EventBus. Providing both this and `existingEventBusInterface` results an error.|
|eventRuleProps|[`events.RuleProps`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events.RuleProps.html)|User provided eventRuleProps to override the defaults. |
|deployRuleDlq?|boolean|Whether to deploy a DLQ for the Rule itself (this DLQ is would receive messages that can't be delivered to the target SQS queue). This is new, so defaulting to false to avoid surprising existing clients. Defaults to false|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Proposing this language for the property description:

|deployRuleDlq?|boolean|Whether to deploy a DLQ for the Event Rule. If set to `true`, this DLQ will receive any messages that can't be delivered to the target SQS queue. Defaults to `false`.|

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

|existingQueueObj?|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sqs.Queue.html)|An optional, existing SQS queue to be used instead of the default queue. Providing both this and `queueProps` will cause an error.|
|queueProps?|[`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sqs.QueueProps.html)|User provided props to override the default props for the SQS Queue. |
|enableQueuePurging?|`boolean`|Whether to grant additional permissions to the Lambda function enabling it to purge the SQS queue. Defaults to `false`.|
Expand All @@ -131,6 +132,7 @@ constructStack.getEncryptionKey().addToResourcePolicy(policyStatement);
|:-------------|:----------------|-----------------|
|eventBus?|[`events.IEventBus`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events.IEventBus.html)|Returns the instance of events.IEventBus used by the construct|
|eventsRule|[`events.Rule`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events.Rule.html)|Returns an instance of events.Rule created by the construct|
|eventRuleDlq?|`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sqs.Queue.html)|If the client set deployRuleDlq to 'true', then this value will contain the DLQ set up for the rule.|
|sqsQueue|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sqs.Queue.html)|Returns an instance of sqs.Queue created by the construct|
|encryptionKey?|[`kms.Key`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_kms.Key.html)|Returns an instance of kms Key used for the SQS queue.|
|deadLetterQueue?|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sqs.Queue.html)|Returns an instance of the dead-letter SQS queue created by the pattern.|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as events from 'aws-cdk-lib/aws-events';
import * as eventtargets from 'aws-cdk-lib/aws-events-targets';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as defaults from '@aws-solutions-constructs/core';
import { ServicePrincipal } from 'aws-cdk-lib/aws-iam';
Expand Down Expand Up @@ -42,6 +43,15 @@ export interface EventbridgeToSqsProps {
* @default - None
*/
readonly eventRuleProps: events.RuleProps;
/**
* Whether to deploy a DLQ for the Rule itself
* (this DLQ is would receive messages that can't be delivered to
* the target SQS queue))
*
* This is new, so defaulting to false to avoid surprising existing clients
* @default - false
*/
readonly deployRuleDlq?: boolean;
/**
* Existing instance of SQS queue object, providing both this and queueProps will cause an error.
*
Expand Down Expand Up @@ -105,6 +115,7 @@ export class EventbridgeToSqs extends Construct {
public readonly eventBus?: events.IEventBus;
public readonly eventsRule: events.Rule;
public readonly encryptionKey?: kms.IKey;
public readonly eventRuleDlq?: sqs.Queue;

/**
* @summary Constructs a new instance of the EventbridgeToSqs class.
Expand Down Expand Up @@ -140,12 +151,19 @@ export class EventbridgeToSqs extends Construct {
this.encryptionKey = buildQueueResponse.key;
this.deadLetterQueue = buildQueueResponse.dlq;

const sqsEventTarget: events.IRuleTarget = {
bind: () => ({
id: this.sqsQueue.queueName,
arn: this.sqsQueue.queueArn
})
};
let sqsEventTargetProps: eventtargets.SqsQueueProps = {};

if (defaults.CheckBooleanWithDefault(props.deployRuleDlq, false)) {
this.eventRuleDlq = defaults.buildQueue(this, 'ruleDlq', {
deployDeadLetterQueue: false,
enableEncryptionWithCustomerManagedKey: enableEncryptionParam,
encryptionKey: this.encryptionKey,
}).queue;

sqsEventTargetProps = defaults.consolidateProps(sqsEventTargetProps, { deadLetterQueue: this.eventRuleDlq });
}

const sqsEventTarget = new eventtargets.SqsQueue(this.sqsQueue, sqsEventTargetProps);

// build an event bus if existingEventBus is provided or eventBusProps are provided
this.eventBus = defaults.buildEventBus(this, {
Expand All @@ -163,7 +181,5 @@ export class EventbridgeToSqs extends Construct {
this.sqsQueue.grantPurge(new ServicePrincipal('events.amazonaws.com'));
}

// Policy for event to be able to send messages to the queue and Grant Event Bridge service access to the SQS queue encryption key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's allowing events to be sent to the queue now without this in there anymore? (Mostly curious but wanted to point it out just in case too)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a new aws-event-targets library since we first wrote this, the SqsQueue target object adds the permissions automatically. It doesn't add Purge however.

this.sqsQueue.grantSendMessages(new ServicePrincipal('events.amazonaws.com'));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as cdk from 'aws-cdk-lib';
import { EventbridgeToSqs, EventbridgeToSqsProps } from '../lib';
import * as events from "aws-cdk-lib/aws-events";
import * as sqs from "aws-cdk-lib/aws-sqs";
import { Template } from 'aws-cdk-lib/assertions';
import { Match, Template } from 'aws-cdk-lib/assertions';
import * as defaults from '@aws-solutions-constructs/core';

function deployNewStack(stack: cdk.Stack) {
Expand Down Expand Up @@ -495,7 +495,7 @@ test('Queue purging flag grants correct permissions', () => {
},
{
Action: [
"sqs:PurgeQueue",
"sqs:SendMessage",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
],
Expand All @@ -512,7 +512,7 @@ test('Queue purging flag grants correct permissions', () => {
},
{
Action: [
"sqs:SendMessage",
"sqs:PurgeQueue",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
],
Expand Down Expand Up @@ -559,3 +559,53 @@ test('check that CheckSqsProps is being called', () => {
};
expect(app).toThrowError("Error - Either provide queueProps or existingQueueObj, but not both.\n");
});

test('Check that rule dlq is not created by default', () => {
const stack = new cdk.Stack();
const props: EventbridgeToSqsProps = {
eventRuleProps: {
schedule: events.Schedule.rate(cdk.Duration.minutes(5))
}
};
const testConstruct = new EventbridgeToSqs(stack, 'test-eventbridge-sqs', props);
expect(testConstruct.eventRuleDlq).toBeUndefined();
const template = Template.fromStack(stack);
template.resourceCountIs("AWS::SQS::Queue", 2);
template.hasResourceProperties("AWS::Events::Rule", {
Targets: [
{
Id: "Target0",
DeadLetterConfig: Match.absent(),
}
]
});
});

test('Check that rule dlq is created when requested', () => {
const stack = new cdk.Stack();
const props: EventbridgeToSqsProps = {
eventRuleProps: {
schedule: events.Schedule.rate(cdk.Duration.minutes(5))
},
deployRuleDlq: true
};
const testConstruct = new EventbridgeToSqs(stack, 'test-eventbridge-sqs', props);
expect(testConstruct.eventRuleDlq).toBeDefined();
const template = Template.fromStack(stack);
template.resourceCountIs("AWS::SQS::Queue", 3);
template.hasResourceProperties("AWS::Events::Rule", {
Targets: [
{
Id: "Target0",
DeadLetterConfig: {
Arn: {
"Fn::GetAtt": [
Match.stringLikeRegexp("testeventbridgesqsruleDlq.*"),
"Arn"
]
}
},
}
]
});
});
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"36.0.0"}
{"version":"39.0.0"}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "36.0.0",
"version": "39.0.0",
"files": {
"7af5ea5f919ee11dba0f3d6b1c862aaeb33516536f0772e7558e77a6bf765846": {
"9acaa89ceee5e5b8ab6d77a48e9ba3aaa89df75c8f22ffdbeccaf7ef4ef6a3fd": {
"source": {
"path": "evtsqs-exist-bus.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "7af5ea5f919ee11dba0f3d6b1c862aaeb33516536f0772e7558e77a6bf765846.json",
"objectKey": "9acaa89ceee5e5b8ab6d77a48e9ba3aaa89df75c8f22ffdbeccaf7ef4ef6a3fd.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
"kms:GenerateDataKey*",
"kms:ReEncrypt*"
],
"Condition": {
"StringEquals": {
"aws:SourceAccount": {
"Ref": "AWS::AccountId"
}
}
},
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
Expand Down Expand Up @@ -73,6 +80,13 @@
"sqs:GetQueueUrl",
"sqs:SendMessage"
],
"Condition": {
"StringEquals": {
"aws:SourceAccount": {
"Ref": "AWS::AccountId"
}
}
},
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
Expand Down Expand Up @@ -120,12 +134,7 @@
"Arn"
]
},
"Id": {
"Fn::GetAtt": [
"MyQueueE6CA6235",
"QueueName"
]
}
"Id": "Target0"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "36.0.0",
"version": "39.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "36.0.0",
"version": "39.0.0",
"testCases": {
"evtsqs-exist-bus/Integ/DefaultTest": {
"stacks": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "36.0.0",
"version": "39.0.0",
"artifacts": {
"evtsqsexistbusIntegDefaultTestDeployAssertD6166996.assets": {
"type": "cdk:asset-manifest",
Expand Down Expand Up @@ -66,7 +66,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7af5ea5f919ee11dba0f3d6b1c862aaeb33516536f0772e7558e77a6bf765846.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9acaa89ceee5e5b8ab6d77a48e9ba3aaa89df75c8f22ffdbeccaf7ef4ef6a3fd.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down
Loading