This repository has been archived by the owner on Jan 31, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Lambda subscription on SNS topic (#974)
* feat: Lambda subscription on SNS topic * fix: typo * test: update snapshot * fix: accept alias as lambda input * pass lambda reference via arn * add permissions to invoke lambda * fix: target lambda base rather than alias add additional docs * feat: add DLQ as public instance property
- Loading branch information
1 parent
e867673
commit 5abb010
Showing
4 changed files
with
350 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Testing } from 'cdktf'; | ||
import { lambdafunction } from '@cdktf/provider-aws'; | ||
import { ApplicationLambdaSnsTopicSubscription } from './ApplicationLambdaSnsTopicSubscription'; | ||
|
||
describe('ApplicationSqsSnsTopicSubscription', () => { | ||
const getConfig = (stack) => ({ | ||
name: 'test-sns-subscription', | ||
snsTopicArn: 'arn:aws:sns:TopicName', | ||
lambda: new lambdafunction.DataAwsLambdaFunction(stack, 'lambda', { | ||
functionName: 'test-lambda', | ||
}), | ||
}); | ||
|
||
it('renders an Lambda <> SNS subscription without tags', () => { | ||
const synthed = Testing.synthScope((stack) => { | ||
new ApplicationLambdaSnsTopicSubscription( | ||
stack, | ||
'lambda-sns-subscription', | ||
getConfig(stack) | ||
); | ||
}); | ||
expect(synthed).toMatchSnapshot(); | ||
}); | ||
|
||
it('renders an SQS SNS subscription with tags', () => { | ||
const synthed = Testing.synthScope((stack) => { | ||
new ApplicationLambdaSnsTopicSubscription( | ||
stack, | ||
'lambda-sns-subscription', | ||
{ | ||
...getConfig(stack), | ||
tags: { hello: 'there' }, | ||
} | ||
); | ||
}); | ||
expect(synthed).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { Resource, TerraformResource } from 'cdktf'; | ||
import { Construct } from 'constructs'; | ||
import { sqs, sns, iam, lambdafunction } from '@cdktf/provider-aws'; | ||
import { SnsTopicSubscriptionConfig } from '@cdktf/provider-aws/lib/sns'; | ||
|
||
/** The config props type of [[`ApplicationLambdaSnsTopicSubscription]] */ | ||
export interface ApplicationLambdaSnsTopicSubscriptionProps { | ||
/** The prefix used to help identify related resources */ | ||
name: string; | ||
/** The SNS topic to subscribe the Lambda to */ | ||
snsTopicArn: string; | ||
/** The Lambda that should be invoked by incoming messages to the SNS topic */ | ||
lambda: lambdafunction.DataAwsLambdaFunction | lambdafunction.LambdaFunction; | ||
/** Tags to apply to the resource(s), where applicable (in this case only the DLQ for the SNS) */ | ||
tags?: { [key: string]: string }; | ||
/** Optional list of resource dependencies */ | ||
dependsOn?: TerraformResource[]; | ||
} | ||
|
||
/** | ||
* Creates an SNS to Lambda subscription with a DLQ for any messages | ||
* that failed to send to the Lambda (e.g. due to permissions error). | ||
* Automatically adds policies/permissions for the SNS topic to send | ||
* messages to the DLQ and invoke the Lambda function. | ||
* | ||
* Artifacts: | ||
* * {@link https://www.terraform.io/docs/providers/aws/r/sns_topic_subscription aws_sns_topic_subscription} Resource to subscribe Lambda to SNS topic | ||
* * {@link https://www.terraform.io/docs/providers/aws/r/sqs_queue aws_sqs_queue} Resource (DLQ for the SNS topic) | ||
* * {@link https://www.terraform.io/docs/providers/aws/r/sqs_queue_policy aws_sqs_queue_policy} Resource policy for SNS to send messages to DLQ | ||
* * {@link https://www.terraform.io/docs/providers/aws/r/lambda_permission aws_lambda_permission} Resource permission for SNS to invoke Lambda | ||
*/ | ||
export class ApplicationLambdaSnsTopicSubscription extends Resource { | ||
/** the {@link https://www.terraform.io/docs/providers/aws/r/sns_topic_subscription aws_sns_topic_subscription} resource */ | ||
public readonly snsTopicSubscription: sns.SnsTopicSubscription; | ||
/** the {@link https://www.terraform.io/docs/providers/aws/r/sqs_queue aws_sqs_queue} (DLQ) resource */ | ||
public readonly snsTopicDlq: sqs.SqsQueue; | ||
|
||
constructor( | ||
scope: Construct, | ||
private name: string, | ||
private config: ApplicationLambdaSnsTopicSubscriptionProps | ||
) { | ||
super(scope, name); | ||
|
||
this.snsTopicDlq = this.createSqsSubscriptionDlq(); | ||
this.snsTopicSubscription = this.createSnsTopicSubscription( | ||
this.snsTopicDlq | ||
); | ||
this.createDlqPolicy(this.snsTopicDlq); | ||
this.createLambdaPolicy(); | ||
} | ||
|
||
/** | ||
* Create a dead-letter queue for failed SNS messages | ||
* @private | ||
*/ | ||
private createSqsSubscriptionDlq(): sqs.SqsQueue { | ||
return new sqs.SqsQueue(this, 'sns-topic-dlq', { | ||
name: `${this.config.name}-SNS-Topic-DLQ`, | ||
tags: this.config.tags, | ||
}); | ||
} | ||
|
||
/** | ||
* Create an SNS subscription for Lambda | ||
* @param snsTopicDlq the DLQ for messages that failed to be processed | ||
* @private | ||
*/ | ||
private createSnsTopicSubscription( | ||
snsTopicDlq: sqs.SqsQueue | ||
): sns.SnsTopicSubscription { | ||
return new sns.SnsTopicSubscription(this, 'sns-subscription', { | ||
topicArn: this.config.snsTopicArn, | ||
protocol: 'lambda', | ||
endpoint: this.config.lambda.arn, | ||
redrivePolicy: JSON.stringify({ | ||
deadLetterTargetArn: snsTopicDlq.arn, | ||
}), | ||
dependsOn: [ | ||
snsTopicDlq, | ||
this.config.lambda.arn, | ||
...(this.config.dependsOn ? this.config.dependsOn : []), | ||
], | ||
} as SnsTopicSubscriptionConfig); | ||
} | ||
|
||
/** | ||
* Grant permissions for SNS topic to invoke lambda | ||
* Cannot be applied to an alias; must use the base lambda function | ||
*/ | ||
private createLambdaPolicy(): void { | ||
new lambdafunction.LambdaPermission( | ||
this, | ||
`${this.name}-lambda-permission`, | ||
{ | ||
principal: 'sns.amazonaws.com', | ||
action: 'lambda:InvokeFunction', | ||
functionName: this.config.lambda.functionName, | ||
sourceArn: this.config.snsTopicArn, | ||
} | ||
); | ||
} | ||
|
||
/** | ||
* Create IAM policies to allow SNS write to the dead-letter queue | ||
* @param snsTopicDlq the SQS resource (used as DLQ) to grant permissions on | ||
* @private | ||
*/ | ||
private createDlqPolicy(snsTopicDlq: sqs.SqsQueue): void { | ||
const queue = { name: 'sns-dlq', resource: snsTopicDlq }; | ||
const policy = new iam.DataAwsIamPolicyDocument( | ||
this, | ||
`${queue.name}-policy-document`, | ||
{ | ||
statement: [ | ||
{ | ||
effect: 'Allow', | ||
actions: ['sqs:SendMessage'], | ||
resources: [queue.resource.arn], | ||
principals: [ | ||
{ | ||
identifiers: ['sns.amazonaws.com'], | ||
type: 'Service', | ||
}, | ||
], | ||
condition: [ | ||
{ | ||
test: 'ArnEquals', | ||
variable: 'aws:SourceArn', | ||
values: [this.config.snsTopicArn], | ||
}, | ||
], | ||
}, | ||
], | ||
dependsOn: [queue.resource] as TerraformResource[], | ||
} | ||
).json; | ||
|
||
new sqs.SqsQueuePolicy(this, `${queue.name}-policy`, { | ||
queueUrl: queue.resource.url, | ||
policy: policy, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
src/base/__snapshots__/ApplicationLambdaSnsTopicSubscription.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`ApplicationSqsSnsTopicSubscription renders an Lambda <> SNS subscription without tags 1`] = ` | ||
"{ | ||
\\"data\\": { | ||
\\"aws_iam_policy_document\\": { | ||
\\"lambda-sns-subscription_sns-dlq-policy-document_8DAB362F\\": { | ||
\\"depends_on\\": [ | ||
\\"aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199\\" | ||
], | ||
\\"statement\\": [ | ||
{ | ||
\\"actions\\": [ | ||
\\"sqs:SendMessage\\" | ||
], | ||
\\"condition\\": [ | ||
{ | ||
\\"test\\": \\"ArnEquals\\", | ||
\\"values\\": [ | ||
\\"arn:aws:sns:TopicName\\" | ||
], | ||
\\"variable\\": \\"aws:SourceArn\\" | ||
} | ||
], | ||
\\"effect\\": \\"Allow\\", | ||
\\"principals\\": [ | ||
{ | ||
\\"identifiers\\": [ | ||
\\"sns.amazonaws.com\\" | ||
], | ||
\\"type\\": \\"Service\\" | ||
} | ||
], | ||
\\"resources\\": [ | ||
\\"\${aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199.arn}\\" | ||
] | ||
} | ||
] | ||
} | ||
}, | ||
\\"aws_lambda_function\\": { | ||
\\"lambda\\": { | ||
\\"function_name\\": \\"test-lambda\\" | ||
} | ||
} | ||
}, | ||
\\"resource\\": { | ||
\\"aws_lambda_permission\\": { | ||
\\"lambda-sns-subscription_lambda-sns-subscription-lambda-permission_03B5A953\\": { | ||
\\"action\\": \\"lambda:InvokeFunction\\", | ||
\\"function_name\\": \\"\${data.aws_lambda_function.lambda.function_name}\\", | ||
\\"principal\\": \\"sns.amazonaws.com\\", | ||
\\"source_arn\\": \\"arn:aws:sns:TopicName\\" | ||
} | ||
}, | ||
\\"aws_sns_topic_subscription\\": { | ||
\\"lambda-sns-subscription_1ED18AE9\\": { | ||
\\"depends_on\\": [ | ||
\\"aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199\\" | ||
], | ||
\\"endpoint\\": \\"\${data.aws_lambda_function.lambda.arn}\\", | ||
\\"protocol\\": \\"lambda\\", | ||
\\"redrive_policy\\": \\"{\\\\\\"deadLetterTargetArn\\\\\\":\\\\\\"\${aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199.arn}\\\\\\"}\\", | ||
\\"topic_arn\\": \\"arn:aws:sns:TopicName\\" | ||
} | ||
}, | ||
\\"aws_sqs_queue\\": { | ||
\\"lambda-sns-subscription_sns-topic-dlq_C5D5F199\\": { | ||
\\"name\\": \\"test-sns-subscription-SNS-Topic-DLQ\\" | ||
} | ||
}, | ||
\\"aws_sqs_queue_policy\\": { | ||
\\"lambda-sns-subscription_sns-dlq-policy_31243636\\": { | ||
\\"policy\\": \\"\${data.aws_iam_policy_document.lambda-sns-subscription_sns-dlq-policy-document_8DAB362F.json}\\", | ||
\\"queue_url\\": \\"\${aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199.url}\\" | ||
} | ||
} | ||
} | ||
}" | ||
`; | ||
|
||
exports[`ApplicationSqsSnsTopicSubscription renders an SQS SNS subscription with tags 1`] = ` | ||
"{ | ||
\\"data\\": { | ||
\\"aws_iam_policy_document\\": { | ||
\\"lambda-sns-subscription_sns-dlq-policy-document_8DAB362F\\": { | ||
\\"depends_on\\": [ | ||
\\"aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199\\" | ||
], | ||
\\"statement\\": [ | ||
{ | ||
\\"actions\\": [ | ||
\\"sqs:SendMessage\\" | ||
], | ||
\\"condition\\": [ | ||
{ | ||
\\"test\\": \\"ArnEquals\\", | ||
\\"values\\": [ | ||
\\"arn:aws:sns:TopicName\\" | ||
], | ||
\\"variable\\": \\"aws:SourceArn\\" | ||
} | ||
], | ||
\\"effect\\": \\"Allow\\", | ||
\\"principals\\": [ | ||
{ | ||
\\"identifiers\\": [ | ||
\\"sns.amazonaws.com\\" | ||
], | ||
\\"type\\": \\"Service\\" | ||
} | ||
], | ||
\\"resources\\": [ | ||
\\"\${aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199.arn}\\" | ||
] | ||
} | ||
] | ||
} | ||
}, | ||
\\"aws_lambda_function\\": { | ||
\\"lambda\\": { | ||
\\"function_name\\": \\"test-lambda\\" | ||
} | ||
} | ||
}, | ||
\\"resource\\": { | ||
\\"aws_lambda_permission\\": { | ||
\\"lambda-sns-subscription_lambda-sns-subscription-lambda-permission_03B5A953\\": { | ||
\\"action\\": \\"lambda:InvokeFunction\\", | ||
\\"function_name\\": \\"\${data.aws_lambda_function.lambda.function_name}\\", | ||
\\"principal\\": \\"sns.amazonaws.com\\", | ||
\\"source_arn\\": \\"arn:aws:sns:TopicName\\" | ||
} | ||
}, | ||
\\"aws_sns_topic_subscription\\": { | ||
\\"lambda-sns-subscription_1ED18AE9\\": { | ||
\\"depends_on\\": [ | ||
\\"aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199\\" | ||
], | ||
\\"endpoint\\": \\"\${data.aws_lambda_function.lambda.arn}\\", | ||
\\"protocol\\": \\"lambda\\", | ||
\\"redrive_policy\\": \\"{\\\\\\"deadLetterTargetArn\\\\\\":\\\\\\"\${aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199.arn}\\\\\\"}\\", | ||
\\"topic_arn\\": \\"arn:aws:sns:TopicName\\" | ||
} | ||
}, | ||
\\"aws_sqs_queue\\": { | ||
\\"lambda-sns-subscription_sns-topic-dlq_C5D5F199\\": { | ||
\\"name\\": \\"test-sns-subscription-SNS-Topic-DLQ\\", | ||
\\"tags\\": { | ||
\\"hello\\": \\"there\\" | ||
} | ||
} | ||
}, | ||
\\"aws_sqs_queue_policy\\": { | ||
\\"lambda-sns-subscription_sns-dlq-policy_31243636\\": { | ||
\\"policy\\": \\"\${data.aws_iam_policy_document.lambda-sns-subscription_sns-dlq-policy-document_8DAB362F.json}\\", | ||
\\"queue_url\\": \\"\${aws_sqs_queue.lambda-sns-subscription_sns-topic-dlq_C5D5F199.url}\\" | ||
} | ||
} | ||
} | ||
}" | ||
`; |