-
Notifications
You must be signed in to change notification settings - Fork 4k
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(lambda): sns topic dlq #4763
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); | |
import ec2 = require('@aws-cdk/aws-ec2'); | ||
import iam = require('@aws-cdk/aws-iam'); | ||
import logs = require('@aws-cdk/aws-logs'); | ||
import sns = require('@aws-cdk/aws-sns'); | ||
import sqs = require('@aws-cdk/aws-sqs'); | ||
import { Construct, Duration, Fn, Lazy, Stack, Token } from '@aws-cdk/core'; | ||
import { Code, CodeConfig } from './code'; | ||
|
@@ -33,6 +34,110 @@ export enum Tracing { | |
DISABLED = "Disabled" | ||
} | ||
|
||
/** | ||
* Describes the type of resource to use as a Lambda dead-letter queue | ||
* | ||
* @internal | ||
*/ | ||
export enum DeadLetterQueueType { | ||
/** | ||
* SQS DLQ | ||
*/ | ||
SQS = 'SQS', | ||
/** | ||
* SNS DLQ | ||
*/ | ||
SNS = 'SNS', | ||
} | ||
|
||
/** | ||
* Resource to be used as a dead-letter queue | ||
* | ||
* @internal | ||
*/ | ||
export interface DeadLetterQueueOptions { | ||
/** | ||
* SQS queue | ||
* | ||
* @default - no queue | ||
*/ | ||
readonly queue?: sqs.IQueue; | ||
/** | ||
* SNS topic | ||
* | ||
* @default - no topic | ||
*/ | ||
readonly topic?: sns.ITopic; | ||
} | ||
|
||
/** | ||
* Dead-letter queue settings | ||
*/ | ||
export class DeadLetterQueue { | ||
/** | ||
* Use a SQS queue as dead-letter queue | ||
* | ||
* @param queue The SQS queue to associate. | ||
* If not provided, a new SQS queue will be created with a 14 day retention period | ||
*/ | ||
public static fromSqsQueue(queue?: sqs.IQueue): DeadLetterQueue { | ||
return new DeadLetterQueue(DeadLetterQueueType.SQS, { queue }); | ||
} | ||
|
||
/** | ||
* Use a SNS topic as dead-letter queue | ||
* | ||
* @param topic The SNS topic to associate. | ||
* If not provided, a new SNS topic will be created | ||
*/ | ||
public static fromSnsTopic(topic?: sns.ITopic): DeadLetterQueue { | ||
return new DeadLetterQueue(DeadLetterQueueType.SNS, { topic }); | ||
} | ||
|
||
/** | ||
* Retrieve the ARN identifying the dead-letter queue resource | ||
* | ||
* @returns ARN of the DLQ | ||
*/ | ||
public get targetArn(): string { | ||
if (!this.queue && !this.topic) { | ||
throw new Error('never - bind(function) method should have been invoked first'); | ||
} | ||
|
||
return this.queue ? this.queue.queueArn : this.topic!.topicArn; | ||
} | ||
|
||
private queue?: sqs.IQueue; | ||
private topic?: sns.ITopic; | ||
private constructor(public readonly type: DeadLetterQueueType, options: DeadLetterQueueOptions = {}) { | ||
this.queue = options.queue; | ||
this.topic = options.topic; | ||
} | ||
|
||
/** | ||
* Creates the appropriate IAM policy required for the Lambda function to send its dead events. | ||
* | ||
* This method also generates the default resource (queue or topic) if it was not provided on construction | ||
* | ||
* @param fn The Lambda function to bind | ||
*/ | ||
public bind(fn: Function) { | ||
switch (this.type) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of this, just make export abstract class DeadLetterQueue {
public static fromSqsQueue(queue?: sqs.IQueue): DeadLetterQueue {
return {
bind: fn => { /* code for SQS queue */ }
};
}
public static fromSnsTopic(topic?: sns.ITopic): DeadLetterQueue {
return {
bind: fn => { /* code for SNS */ }
}
public abstract bind(fn: lambda.Function): void;
} |
||
case DeadLetterQueueType.SQS: | ||
this.queue = this.queue || new sqs.Queue(fn, 'DeadLetterQueue', { retentionPeriod: Duration.days(14) }); | ||
break; | ||
case DeadLetterQueueType.SNS: | ||
this.topic = this.topic || new sns.Topic(fn, 'DeadLetterTopic', {}); | ||
break; | ||
} | ||
|
||
fn.addToRolePolicy(new iam.PolicyStatement({ | ||
actions: [this.queue ? 'sqs:SendMessage' : 'sns:Publish'], | ||
resources: [this.targetArn] | ||
})); | ||
} | ||
} | ||
|
||
export interface FunctionProps { | ||
/** | ||
* The source code of your Lambda function. You can point to a file in an | ||
|
@@ -164,18 +269,29 @@ export interface FunctionProps { | |
*/ | ||
readonly allowAllOutbound?: boolean; | ||
|
||
/** | ||
* Dead-letter queue behavior | ||
* | ||
* @default - no dead-letter queue will be used or created | ||
*/ | ||
readonly dlq?: DeadLetterQueue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a fan of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
|
||
/** | ||
* Enabled DLQ. If `deadLetterQueue` is undefined, | ||
* an SQS queue with default options will be defined for your Function. | ||
* | ||
* @default - false unless `deadLetterQueue` is set, which implies DLQ is enabled. | ||
* | ||
* @deprecated see {@link FunctionProps.dlq} with {@link DeadLetterQueue.fromSqsQueue} | ||
*/ | ||
readonly deadLetterQueueEnabled?: boolean; | ||
|
||
/** | ||
* The SQS queue to use if DLQ is enabled. | ||
* | ||
* @default - SQS queue with 14 day retention period if `deadLetterQueueEnabled` is `true` | ||
* | ||
* @deprecated see {@link FunctionProps.dlq} with {@link DeadLetterQueue.fromSqsQueue} | ||
*/ | ||
readonly deadLetterQueue?: sqs.IQueue; | ||
|
||
|
@@ -598,25 +714,26 @@ export class Function extends FunctionBase { | |
} | ||
|
||
private buildDeadLetterConfig(props: FunctionProps) { | ||
if (props.deadLetterQueue && props.deadLetterQueueEnabled === false) { | ||
throw Error('deadLetterQueue defined but deadLetterQueueEnabled explicitly set to false'); | ||
if (props.dlq && (props.deadLetterQueue || props.deadLetterQueueEnabled)) { | ||
throw Error('dlq cannot be used with deadLetterQueue or deadLetterQueueEnabled. Please only use dlq'); | ||
} | ||
|
||
if (!props.deadLetterQueue && !props.deadLetterQueueEnabled) { | ||
return undefined; | ||
} | ||
let dlq = props.dlq; | ||
if (props.deadLetterQueue || props.deadLetterQueueEnabled) { | ||
if (props.deadLetterQueueEnabled === false) { | ||
throw Error('deadLetterQueue defined but deadLetterQueueEnabled explicitly set to false'); | ||
} | ||
|
||
const deadLetterQueue = props.deadLetterQueue || new sqs.Queue(this, 'DeadLetterQueue', { | ||
retentionPeriod: Duration.days(14) | ||
}); | ||
dlq = DeadLetterQueue.fromSqsQueue(props.deadLetterQueue); | ||
} | ||
|
||
this.addToRolePolicy(new iam.PolicyStatement({ | ||
actions: ['sqs:SendMessage'], | ||
resources: [deadLetterQueue.queueArn] | ||
})); | ||
if (!dlq) { | ||
return undefined; | ||
} | ||
|
||
dlq.bind(this); | ||
return { | ||
targetArn: deadLetterQueue.queueArn | ||
targetArn: dlq.targetArn, | ||
}; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that in this case the
from
prefix doesn't feel right because we allow no parameters and so "from what?". I'd argue thatlambda.DeadLetterQueue.sqsQueue()
reads better in this case and cal also accomnodateDeadLetterQueue.sqsQueue(x)
(@rix0rrr, thoughts?).Alternatively we can do:
fromNewQueue()
andfromQueue(x)
.