Skip to content

Commit 4dc2460

Browse files
authored
Merge pull request #12 from dwhswenson/single-sns-topic
Single SNS topic, instead of topic per result type
2 parents 2f388a8 + 00caf1a commit 4dc2460

File tree

20 files changed

+198
-198
lines changed

20 files changed

+198
-198
lines changed

IDEA.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,35 @@ This is intended to be a framework that can be used by client code to define the
66

77
## Components
88

9-
1. **Scheduled Lambda Functions**: The goal here is that the client can provide their own lambda function (as a container image) and, from that, we will run it on a schedule defined by the client. The lambda function needs to know 1 or more SNS topics to which it will publish messages when it runs; different "types" of messages can go to different SNS topics, which will then be subscribed to by different notification channels. This will include the lambda execution role and the scheduled events.
10-
2. **SNS Topics**: These will be manually created by the client code, but ARNs might be needed to give the lambda permissions to publish.
11-
3. **Notification Channels**: We will provide modules for different notification channels (e.g., email via SES, SMS via Twilio, etc.). Each notification module owns the SNS->SQS->Lambda wiring: it provisions the FIFO SQS queue/subscription used for deduplication and triggers its handler; the user should not create that queue manually. Each channel ships its own container image (build or republish) that renders a Jinja2 template and delivers via its notifier.
9+
1. **Scheduled Lambda Functions**: The goal here is that the client can provide their own lambda function (as a container image) and, from that, we will run it on a schedule defined by the client. The lambda function will publish to a single SNS topic and set a `result_type` (or similar) message attribute; notification channels will filter on one or more types. This will include the lambda execution role and the scheduled events.
10+
2. **SNS Topic**: A single topic is manually created by the client code; its ARN is needed to give the lambda permissions to publish. Filtering is done via subscription filter policies, not separate topics.
11+
3. **Notification Channels**: We will provide modules for different notification channels (e.g., email via SES, SMS via Twilio, etc.). Each notification module owns the SNS->SQS->Lambda wiring: it provisions the FIFO SQS queue/subscription used for deduplication and triggers its handler, with an SNS filter policy on the result type(s); the user should not create that queue manually. Each channel ships its own container image (build or republish) that renders a Jinja2 template and delivers via its notifier.
1212
4. **Lambda Image Utilities**: In addition to republishing an existing Lambda container, we will provide a module to build an image from a local directory containing a Dockerfile and publish it to ECR for use by the scheduled-lambda module.
1313
5. **Python Runtime Library**: Provide reusable Python code in `src/cloud_cron/` that makes authoring custom scheduled lambdas easy (task base class, SNS dispatch helpers, and ergonomic handler wiring). This includes a template provider abstraction so notification handlers can source templates from env vars, URLs, or S3.
1414

1515

1616
The goal is that the user will need to:
1717

18-
1. Write a lambda function that publishes to the desired SNS topics (provided as envionment variables).
18+
1. Write a lambda function that publishes to the shared SNS topic and sets a `result_type` message attribute (provided as an environment variable or constant).
1919
2. Write a small terraform module that looks something like:
2020

2121
```hcl
22-
resource "aws_sns_topic" "example_topic" {
23-
name = "example-topic"
22+
resource "aws_sns_topic" "results" {
23+
name = "cloud-cron-results"
2424
}
2525
2626
module "my_scheduled_lambda" {
2727
source = "./modules/scheduled-lambda"
2828
2929
lambda_image_uri = "123456789012.dkr.ecr.us-west-2.amazonaws.com/my-lambda:latest"
3030
schedule_expression = "rate(5 minutes)"
31-
sns_topic_arns = {
32-
example = aws_sns_topic.example_topic.arn
33-
}
31+
sns_topic_arn = aws_sns_topic.results.arn
3432
}
3533
3634
module "my_email_notification" {
3735
source = "./modules/email-notification"
38-
sns_topic_arn = aws_sns_topic.example_topic.arn
36+
sns_topic_arn = aws_sns_topic.results.arn
37+
result_types = ["example"]
3938
template_file = "path/to/email/template.html"
4039
email_sender = "me@example.com"
4140
email_recipients = [
@@ -47,6 +46,10 @@ module "my_email_notification" {
4746

4847
(There may be additional parameters needed, but this is the general idea.)
4948

49+
Notes on the single-topic approach:
50+
- It simplifies wiring and allows channels to subscribe to multiple result types via filter policies.
51+
- It reduces per-type IAM/topic configuration, but also means topic-level settings (KMS, delivery policy, metrics) are shared.
52+
5053
## Additional convenience
5154

5255
We should also provide a module that allows us to take an externally-defined lambda and redeploy it in a local environment. The idea is that the lambdas users use are served from their own accounts, and are copies of our official release lambdas.

PLAN.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,36 @@
2727
- [x] Example touchpoint: allow `examples/basic` to build/push a simple placeholder Lambda image from a local Dockerfile as an alternative to the republish module.
2828

2929
## Phase 2: Build scheduled Lambda module (`modules/scheduled-lambda`)
30-
- [x] Define inputs: `lambda_image_uri`, `schedule_expression`, `sns_topic_arns` (map topic key->ARN), optional `lambda_env`, `timeout`, `memory_size`, `tags`.
31-
- [x] Create resources: IAM role/policy (CloudWatch Logs + `sns:Publish` to provided ARNs), Lambda from container image, EventBridge rule/target/permission.
30+
- [x] Define inputs: `lambda_image_uri`, `schedule_expression`, `sns_topic_arn` (single), optional `lambda_env`, `timeout`, `memory_size`, `tags`.
31+
- [x] Create resources: IAM role/policy (CloudWatch Logs + `sns:Publish` to provided ARN), Lambda from container image, EventBridge rule/target/permission.
3232
- [x] Outputs: Lambda ARN, execution role ARN, log group name, schedule rule name.
3333
- [x] Docs: README with usage matching IDEA example.
3434
- [x] Example touchpoint: scaffold `examples/basic` with this module + stub SNS topic(s) and the container image outputs from Phase 1; `terraform validate/plan` should pass to prove schedule wiring.
3535

36+
## Phase 2.5: Consolidate result routing into a single SNS topic
37+
38+
Overview: replace per-result-type topics with one shared SNS topic and use message attributes + subscription filter policies to route results to notification channels.
39+
40+
Success criteria:
41+
42+
- Scheduled lambdas publish to one topic and set a `result_type` message attribute.
43+
- Notification modules accept `result_types` (list) and apply SNS filter policies to their subscriptions.
44+
- Examples and docs reflect the single-topic pattern.
45+
46+
Decisions and motivations:
47+
48+
- Use SNS filter policies to reduce infrastructure and make multi-type subscriptions easy.
49+
- Keep result types as message attributes to avoid changing payload shapes or handler code.
50+
51+
To-do:
52+
53+
- [x] Update `modules/scheduled-lambda` to accept `sns_topic_arn` (single) and adjust IAM to `sns:Publish` on that ARN.
54+
- [x] Update notification plumbing module to accept `result_types` and apply SNS filter policy on the subscription.
55+
- [x] Update existing per-channel modules to pass through `result_types` and document the attribute name.
56+
- [x] Update `src/cloud_cron/` helpers to publish with a `result_type` attribute and validate allowed types.
57+
- [x] Update `examples/basic` to use one topic and a single `result_types` subscription.
58+
- [x] Update module READMEs and `IDEA.md` usage examples to match the new wiring.
59+
3660
## Phase 3: Python runtime library for custom lambdas (`src/cloud_cron/`)
3761

3862
Overview: turn the existing Python helpers into a reusable, testable library that makes it easy for users to author scheduled lambdas while keeping SNS wiring and logging consistent. This package will also host shared notification handler code (under `src/cloud_cron/notifications/`), while deployment/container wiring remains in Terraform modules.

examples/basic/main.tf

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,11 @@ locals {
5858
active_print_image_uri = module.print_lambda_image_build.image_uri_with_digest
5959
}
6060

61-
module "sns_topics" {
62-
source = "../../modules/sns-topics"
63-
64-
topic_names = {
65-
example = "example-topic.fifo"
66-
}
67-
68-
tags = local.common_tags
61+
resource "aws_sns_topic" "results" {
62+
name = "cloud-cron-results.fifo"
63+
fifo_topic = true
64+
content_based_deduplication = true
65+
tags = local.common_tags
6966
}
7067

7168
module "scheduled_lambda" {
@@ -74,15 +71,15 @@ module "scheduled_lambda" {
7471
lambda_image_uri = local.active_lambda_image_uri
7572
schedule_expression = var.schedule_expression
7673
lambda_name = var.lambda_name
77-
sns_topic_arns = module.sns_topics.topic_arns
74+
sns_topic_arn = aws_sns_topic.results.arn
7875

7976
tags = local.common_tags
8077
}
8178

8279
module "print_notification" {
8380
source = "../../modules/print-notification"
8481

85-
sns_topic_arn = module.sns_topics.topic_arns.example
82+
sns_topic_arn = aws_sns_topic.results.arn
8683
fifo_queue_name = "example-print.fifo"
8784
lambda_image_uri = local.active_print_image_uri
8885
template_file = "${path.module}/templates/print.txt"

modules/notification-plumbing/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ module "notification_plumbing" {
1111
sns_topic_arn = aws_sns_topic.example.arn
1212
lambda_function_arn = aws_lambda_function.print.arn
1313
fifo_queue_name = "example-notifications.fifo"
14+
result_types = ["example"]
1415
}
1516
```
1617

1718
## Inputs
1819

1920
- `sns_topic_arn` (string): SNS topic ARN that feeds the notification queue.
2021
- `lambda_function_arn` (string): ARN of the Lambda function that processes SQS messages.
22+
- `result_types` (list(string)): Result types to subscribe to. Empty means all.
2123
- `fifo_queue_name` (string): Name of the FIFO SQS queue (must end with `.fifo`).
2224
- `content_based_deduplication` (bool): Enable content-based deduplication. Default `true`.
2325
- `visibility_timeout_seconds` (number): Visibility timeout for the queue. Default `30`.

modules/notification-plumbing/main.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
locals {
22
tags = merge({ managed_by = "cloudcron" }, var.tags)
3+
filter_policy = length(var.result_types) > 0 ? jsonencode({
4+
result_type = var.result_types
5+
}) : null
36
}
47

58
resource "aws_sqs_queue" "dlq" {
@@ -57,6 +60,8 @@ resource "aws_sns_topic_subscription" "queue" {
5760
endpoint = aws_sqs_queue.queue.arn
5861

5962
raw_message_delivery = true
63+
filter_policy = local.filter_policy
64+
filter_policy_scope = length(var.result_types) > 0 ? "MessageAttributes" : null
6065
}
6166

6267
resource "aws_lambda_event_source_mapping" "sqs" {

modules/notification-plumbing/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ variable "lambda_function_arn" {
1212
type = string
1313
}
1414

15+
variable "result_types" {
16+
description = "Result types to subscribe to; empty means all."
17+
type = list(string)
18+
default = []
19+
}
20+
1521
variable "fifo_queue_name" {
1622
description = "Name of the FIFO SQS queue (must end with .fifo)."
1723
type = string

modules/print-notification/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module "print_notification" {
99
source = "./modules/print-notification"
1010
1111
sns_topic_arn = aws_sns_topic.example.arn
12+
result_types = ["example"]
1213
fifo_queue_name = "example-print.fifo"
1314
lambda_image_uri = module.print_image.image_uri_with_digest
1415
@@ -19,6 +20,7 @@ module "print_notification" {
1920
## Inputs
2021

2122
- `sns_topic_arn` (string): SNS topic ARN that feeds the notification queue.
23+
- `result_types` (list(string)): Result types to subscribe to. Empty means all.
2224
- `fifo_queue_name` (string): Name of the FIFO SQS queue (must end with `.fifo`).
2325
- `lambda_image_uri` (string): URI of the Lambda container image.
2426
- `lambda_name` (string): Optional name for the Lambda function.

modules/print-notification/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module "plumbing" {
2727

2828
sns_topic_arn = var.sns_topic_arn
2929
lambda_function_arn = aws_lambda_function.print.arn
30+
result_types = var.result_types
3031
fifo_queue_name = var.fifo_queue_name
3132
batch_size = var.batch_size
3233
enabled = var.enabled

modules/print-notification/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ variable "sns_topic_arn" {
33
type = string
44
}
55

6+
variable "result_types" {
7+
description = "Result types to subscribe to; empty means all."
8+
type = list(string)
9+
default = []
10+
}
11+
612
variable "fifo_queue_name" {
713
description = "Name of the FIFO SQS queue (must end with .fifo)."
814
type = string

modules/scheduled-lambda/README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
# Scheduled Lambda Module
22

3-
Provision a container-based Lambda function that runs on an EventBridge schedule and publishes to SNS topics.
3+
Provision a container-based Lambda function that runs on an EventBridge schedule and publishes to a shared SNS topic.
44

55
## Usage
66

77
```hcl
8-
resource "aws_sns_topic" "example_topic" {
9-
name = "example-topic"
8+
resource "aws_sns_topic" "results" {
9+
name = "cloud-cron-results"
1010
}
1111
1212
module "scheduled_lambda" {
1313
source = "./modules/scheduled-lambda"
1414
1515
lambda_image_uri = "123456789012.dkr.ecr.us-west-2.amazonaws.com/my-lambda:latest"
1616
schedule_expression = "rate(5 minutes)"
17-
sns_topic_arns = {
18-
task = aws_sns_topic.example_topic.arn
19-
}
17+
sns_topic_arn = aws_sns_topic.results.arn
2018
2119
lambda_env = {
2220
LOG_LEVEL = "info"
@@ -28,7 +26,7 @@ module "scheduled_lambda" {
2826

2927
- `lambda_image_uri` (string): URI of the Lambda container image.
3028
- `schedule_expression` (string): EventBridge schedule expression.
31-
- `sns_topic_arns` (map(string)): Logical topic keys to SNS topic ARN mapping.
29+
- `sns_topic_arn` (string): SNS topic ARN for publishing results.
3230
- `lambda_env` (map(string)): Additional environment variables for the Lambda.
3331
- `timeout` (number): Lambda timeout in seconds.
3432
- `memory_size` (number): Lambda memory size in MB.

0 commit comments

Comments
 (0)