From 161c52721565508648f13098d1b6e8e60bc93149 Mon Sep 17 00:00:00 2001 From: archiev4 Date: Mon, 9 Mar 2026 11:17:22 +0530 Subject: [PATCH 1/4] New serverless pattern - eventbridge-fanout-pattern --- eventbridge-fanout-pattern/README.md | 167 +++++++++ .../architecture/architecture.png | Bin 0 -> 50326 bytes .../example-pattern.json | 58 ++++ eventbridge-fanout-pattern/main.tf | 326 ++++++++++++++++++ eventbridge-fanout-pattern/processor.zip | Bin 0 -> 990 bytes 5 files changed, 551 insertions(+) create mode 100644 eventbridge-fanout-pattern/README.md create mode 100644 eventbridge-fanout-pattern/architecture/architecture.png create mode 100644 eventbridge-fanout-pattern/example-pattern.json create mode 100644 eventbridge-fanout-pattern/main.tf create mode 100644 eventbridge-fanout-pattern/processor.zip diff --git a/eventbridge-fanout-pattern/README.md b/eventbridge-fanout-pattern/README.md new file mode 100644 index 0000000000..6092a1ecf3 --- /dev/null +++ b/eventbridge-fanout-pattern/README.md @@ -0,0 +1,167 @@ +# EventBridge fan-out pattern in Terraform + +This Terraform pattern demonstrates schedule fan-out using Amazon EventBridge Scheduler. A single schedule puts an event onto a custom EventBridge Event Bus, which routes it to three downstream targets simultaneously which includes an AWS Lambda function for processing, an Amazon SNS topic for notifications, and an Amazon SQS queue for archival. Each target is matched by its own EventBridge Rule on the bus, so new targets can be added without modifying the schedule itself. Every target is fully decoupled and if one fails, the others are unaffected. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-fanout-pattern + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Terraform](https://learn.hashicorp.cxom/tutorials/terraform/install-cli?in=terraform/aws-get-started) installed + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +1. Change directory to the pattern directory: + ``` + cd eventbridge-fanout-pattern + ``` +1. From the command line, initialize terraform to downloads and installs the providers defined in the configuration: + ``` + terraform init + ``` +1. From the command line, apply the configuration in the main.tf file: + ``` + terraform apply -auto-approve + ``` +1. During the prompts: + #var.aws_region + - Enter a value: {enter the region for deployment} + + #var.prefix + - Enter a value: {enter any prefix to associate with resources} + +1. Note the outputs from the Terraform deployment process. These contain the resource names and/or ARNs which are used for testing. + +## How it works + +![architecture](architecture/architecture.png) + +Amazon EventBridge Scheduler runs a single schedule on a five minute rate and targets a custom EventBridge Event Bus which is the core of the fan-out pattern. Because Scheduler only supports one target per schedule, the bus acts as a routing hub. Three EventBridge Rules sit on that bus, each matching the same event pattern and forwarding the event simultaneously to three independent targets namely an AWS Lambda function that processes the payload, an Amazon SNS topic that delivers notifications to subscribers, and an Amazon SQS queue that stores the event for archival or downstream consumption. If one target fails or is removed, the others continue to work without interruption. + +## Testing + +The scheduler fires once every five minutes and puts an event onto a custom EventBridge Event Bus. Three rules on that bus match the event and fan it out simultaneously to a Lambda function, an SNS topic, and an SQS queue. + +To test the full flow without waiting for the next five-minute trigger, you can publish the same event to the bus manually using the AWS CLI put-events command and verify within seconds that Lambda logs the event, a message appears in the SQS queue, and SNS delivers to any confirmed subscribers. + +1. After deploying the stack, create a Subscriber for your Amazon SNS topic (For ex, your email) and confirm the subscription. + https://docs.aws.amazon.com/sns/latest/dg/sns-create-subscribe-endpoint-to-topic.html + +1. Publish the event in the custom bus using the following CLI command: + ``` + PREFIX=$(terraform output -raw prefix) + ``` + + ``` + aws events put-events --entries '[ + { + "Source": "'$PREFIX'.scheduler.fan-out", + "DetailType": "ScheduledTask", + "EventBusName": "'$PREFIX'-scheduled-bus", + "Detail": "{\"taskId\":\"manual-testing\",\"message\":\"manual-test-message-1\"}" + } + ]' + ``` + +1. Now check the CloudWatch logs for the Lambda function to see if the message has been processed + ``` + aws logs tail /aws/lambda/$PREFIX-scheduled-processor --since 2m --format short + ``` + You should see an output like this: + ``` + { + "statusCode": 200, + "taskId": "manual-test", + "result": { + "echo": { + "taskId": "manual-testing", + "message": "manual-test-message-1" + } + }, + "processedAt": "2026-03-09TXXXXX", + "requestId": "a730a9b9-fbXXXXXX" + } + ``` + This means that the message has been processed by the Lambda function + +1. Check the SQS queue to see if the message has been received + ``` + aws sqs receive-message --queue-url $(terraform output -raw sqs_queue_url) --wait-time-seconds 5 + ``` + You should see an output in this format: + ``` + { + "Messages": [ + { + "MessageId": "e6be124b-XXXX", + "ReceiptHandle": "AQEBXXXX", + "MD5OfBody": "dff96eXXXXX", + "Body": { + "version": "0", + "id": "417a39aXXXXX", + "detail-type": "ScheduledTask", + "source": ".scheduler.fan-out", + "account": "123456789012", + "time": "2025-01-15TXXXX", + "region": "", + "resources": [], + "detail": { + "taskId": "manual-testing", + "message": "manual-test-message-1" + } + } + } + ]} + ``` + +1. The SNS topic will send the message to the confirmed subscriber so check the subscriber (for ex, email) to see the message. + ``` + { + "version": "0", + "id": "df58af75-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "detail-type": "ScheduledTask", + "source": ".scheduler.fan-out", + "account": "123456789012", + "time": "2025-01-15T10:30:00Z", + "region": "", + "resources": [], + "detail": { + "taskId": "manual-testing", + "message": "manual-test-message-1" + } + } + ``` + +## Cleanup + +1. Change directory to the pattern directory: + ``` + cd serverless-patterns/eventbridge-fanout-pattern + ``` + +1. Delete all created resources + ``` + terraform destroy -auto-approve + ``` + +1. During the prompts: + ``` + Enter all details as entered during creation. + ``` + +1. Confirm all created resources has been deleted + ``` + terraform show + ``` +---- +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 \ No newline at end of file diff --git a/eventbridge-fanout-pattern/architecture/architecture.png b/eventbridge-fanout-pattern/architecture/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..f0a35a0eb191b569ff5eac4b6da71ac69e92b0fa GIT binary patch literal 50326 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 literal 0 HcmV?d00001 diff --git a/eventbridge-fanout-pattern/example-pattern.json b/eventbridge-fanout-pattern/example-pattern.json new file mode 100644 index 0000000000..d35886ad2f --- /dev/null +++ b/eventbridge-fanout-pattern/example-pattern.json @@ -0,0 +1,58 @@ +{ + "title": "EventBridge Scheduler fan-out pattern", + "description": "Create an Amazon EventBridge Scheduler that fans out to multiple targets via a custom Event Bus", + "language": "Python", + "level": "200", + "framework": "Terraform", + "introBox": { + "headline": "How it works", + "text": [ + "This sample project demonstrates how to use Amazon EventBridge Scheduler to trigger multiple downstream targets from a single schedule. Since Scheduler supports only one target per schedule, this pattern uses a custom EventBridge Event Bus as the fan-out hub." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-fanout-pattern", + "templateURL": "serverless-patterns/eventbridge-fanout-pattern", + "projectFolder": "eventbridge-fanout-pattern", + "templateFile": "main.tf" + } + }, + "resources": { + "bullets": [ + { + "text": "Creating Amazon EventBridge event patterns", + "link": "https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html" + }, + { + "text": "Event buses in Amazon EventBridge", + "link": "https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-bus.html" + } + ] + }, + "deploy": { + "text": [ + "terraform init", + "terraform apply" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "terraform destroy", + "terraform show" + ] + }, + "authors": [ + { + "name": "Archana V", + "image": "https://media.licdn.com/dms/image/v2/D5603AQGhkVtEhllFEw/profile-displayphoto-shrink_400_400/B56ZZH3LL6H0Ag-/0/1744962369913?e=1774483200&v=beta&t=wAhvwq-jEIWnyQwDfIkK_-Sq16Z4RvpjWtYmDkc3eg4", + "bio": "Solutions Architect at AWS", + "linkedin": "archanavenkat" + } + ] +} diff --git a/eventbridge-fanout-pattern/main.tf b/eventbridge-fanout-pattern/main.tf new file mode 100644 index 0000000000..a0680cc7c3 --- /dev/null +++ b/eventbridge-fanout-pattern/main.tf @@ -0,0 +1,326 @@ +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +############################################################ +# Variables +############################################################ + +variable "aws_region" { + description = "AWS region for resources (e.g. us-east-1, us-west-2)" + type = string + + validation { + condition = can(regex("^[a-z]{2}-[a-z]+-[0-9]+$", var.aws_region)) + error_message = "Must be a valid AWS region (e.g. us-east-1, eu-west-2)." + } +} + +variable "prefix" { + description = "Unique prefix for all resource names" + type = string + + validation { + condition = can(regex("^[a-z0-9][a-z0-9\\-]{1,20}$", var.prefix)) + error_message = "Prefix must be 2-21 lowercase alphanumeric characters or hyphens." + } +} + +variable "log_retention_days" { + description = "CloudWatch log retention in days (0 = never expire)" + type = number + default = 14 +} + +############################################################ +# Data Sources +############################################################ + +data "aws_caller_identity" "current" {} + +############################################################ +# 1. CUSTOM EVENT BUS (the fan-out hub) +############################################################ + +resource "aws_cloudwatch_event_bus" "scheduled_bus" { + name = "${var.prefix}-scheduled-bus" +} + +############################################################ +# 2. TARGETS — Lambda (Python), SNS, SQS +############################################################ + +# --- Lambda --- + +resource "aws_iam_role" "lambda_role" { + name = "${var.prefix}-processor-lambda-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { Service = "lambda.amazonaws.com" } + }] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_basic" { + role = aws_iam_role.lambda_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + +# Create log group BEFORE the Lambda so Terraform owns it +resource "aws_cloudwatch_log_group" "processor_logs" { + name = "/aws/lambda/${var.prefix}-scheduled-processor" + retention_in_days = var.log_retention_days +} + +resource "aws_lambda_function" "processor" { + function_name = "${var.prefix}-scheduled-processor" + role = aws_iam_role.lambda_role.arn + handler = "processor.lambda_handler" + runtime = "python3.12" + timeout = 30 + memory_size = 128 + filename = "processor.zip" + + environment { + variables = { + PREFIX = var.prefix + ENV = "production" + LOG_LEVEL = "INFO" + } + } + + depends_on = [ + aws_cloudwatch_log_group.processor_logs, + aws_iam_role_policy_attachment.lambda_basic, + ] +} + +# --- SNS --- + +resource "aws_sns_topic" "alerts" { + name = "${var.prefix}-scheduled-alerts" +} + +# --- SQS --- + +resource "aws_sqs_queue" "archive" { + name = "${var.prefix}-scheduled-archive" +} + +############################################################ +# 3. EVENT RULES (fan-out from bus to targets) +############################################################ + +locals { + event_pattern = jsonencode({ + source = ["${var.prefix}.scheduler.fan-out"] + detail-type = ["ScheduledTask"] + }) +} + +# --- Rule #1 → Lambda --- + +resource "aws_cloudwatch_event_rule" "process_rule" { + name = "${var.prefix}-process-scheduled-event" + event_bus_name = aws_cloudwatch_event_bus.scheduled_bus.name + event_pattern = local.event_pattern +} + +resource "aws_cloudwatch_event_target" "lambda_target" { + rule = aws_cloudwatch_event_rule.process_rule.name + event_bus_name = aws_cloudwatch_event_bus.scheduled_bus.name + target_id = "${var.prefix}-send-to-lambda" + arn = aws_lambda_function.processor.arn +} + +resource "aws_lambda_permission" "allow_eventbridge" { + statement_id = "AllowEventBridgeInvoke" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.processor.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.process_rule.arn +} + +# --- Rule #2 → SNS --- + +resource "aws_cloudwatch_event_rule" "notify_rule" { + name = "${var.prefix}-notify-scheduled-event" + event_bus_name = aws_cloudwatch_event_bus.scheduled_bus.name + event_pattern = local.event_pattern +} + +resource "aws_cloudwatch_event_target" "sns_target" { + rule = aws_cloudwatch_event_rule.notify_rule.name + event_bus_name = aws_cloudwatch_event_bus.scheduled_bus.name + target_id = "${var.prefix}-send-to-sns" + arn = aws_sns_topic.alerts.arn +} + +resource "aws_sns_topic_policy" "allow_eventbridge" { + arn = aws_sns_topic.alerts.arn + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "AllowEventBridgePublish" + Effect = "Allow" + Principal = { Service = "events.amazonaws.com" } + Action = "sns:Publish" + Resource = aws_sns_topic.alerts.arn + Condition = { + ArnEquals = { + "aws:SourceArn" = aws_cloudwatch_event_rule.notify_rule.arn + } + } + }] + }) +} + +# --- Rule #3 → SQS --- + +resource "aws_cloudwatch_event_rule" "archive_rule" { + name = "${var.prefix}-archive-scheduled-event" + event_bus_name = aws_cloudwatch_event_bus.scheduled_bus.name + event_pattern = local.event_pattern +} + +resource "aws_cloudwatch_event_target" "sqs_target" { + rule = aws_cloudwatch_event_rule.archive_rule.name + event_bus_name = aws_cloudwatch_event_bus.scheduled_bus.name + target_id = "${var.prefix}-send-to-sqs" + arn = aws_sqs_queue.archive.arn +} + +resource "aws_sqs_queue_policy" "allow_eventbridge" { + queue_url = aws_sqs_queue.archive.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "AllowEventBridgeSend" + Effect = "Allow" + Principal = { Service = "events.amazonaws.com" } + Action = "sqs:SendMessage" + Resource = aws_sqs_queue.archive.arn + Condition = { + ArnEquals = { + "aws:SourceArn" = aws_cloudwatch_event_rule.archive_rule.arn + } + } + }] + }) +} + +############################################################ +# 4. SCHEDULER → EVENT BUS (the single schedule) +############################################################ + +resource "aws_iam_role" "scheduler_role" { + name = "${var.prefix}-scheduler-eventbridge-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { Service = "scheduler.amazonaws.com" } + }] + }) +} + +resource "aws_iam_role_policy" "scheduler_put_events" { + name = "${var.prefix}-put-events-policy" + role = aws_iam_role.scheduler_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "events:PutEvents" + Resource = aws_cloudwatch_event_bus.scheduled_bus.arn + }] + }) +} + +# Log group for scheduler (delivery logs) +resource "aws_cloudwatch_log_group" "scheduler_logs" { + name = "/aws/scheduler/${var.prefix}-fan-out" + retention_in_days = var.log_retention_days +} + +resource "aws_scheduler_schedule" "fan_out" { + name = "${var.prefix}-fan-out" + schedule_expression = "rate(5 minutes)" + + flexible_time_window { + mode = "OFF" + } + + target { + arn = aws_cloudwatch_event_bus.scheduled_bus.arn + role_arn = aws_iam_role.scheduler_role.arn + + eventbridge_parameters { + source = "${var.prefix}.scheduler.fan-out" + detail_type = "ScheduledTask" + } + + input = jsonencode({ + taskId = "5-minute-job" + prefix = var.prefix + message = "Triggered by EventBridge Scheduler" + }) + } + + depends_on = [aws_cloudwatch_log_group.scheduler_logs] +} + +############################################################ +# 5. OUTPUTS +############################################################ + +output "prefix" { + value = var.prefix +} + +output "event_bus_name" { + value = aws_cloudwatch_event_bus.scheduled_bus.name +} + +output "event_bus_arn" { + value = aws_cloudwatch_event_bus.scheduled_bus.arn +} + +output "schedule_name" { + value = aws_scheduler_schedule.fan_out.name +} + +output "schedule_arn" { + value = aws_scheduler_schedule.fan_out.arn +} + +output "lambda_function_name" { + value = aws_lambda_function.processor.function_name +} + +output "sns_topic_arn" { + value = aws_sns_topic.alerts.arn +} + +output "sqs_queue_url" { + value = aws_sqs_queue.archive.url +} \ No newline at end of file diff --git a/eventbridge-fanout-pattern/processor.zip b/eventbridge-fanout-pattern/processor.zip new file mode 100644 index 0000000000000000000000000000000000000000..5e3b1dacb8388f1c2949d76373d2ac16b0aa393b GIT binary patch literal 990 zcmWIWW@Zs#U|`^2c@--wdC{8AD>=kOSxXWXL|Mf zlQ~>rjh6zCEKu3LDfDVmnR>nuTid5B7cNa6o0%s49CnJD;!AxVd1|wy@dUCg-lWqV z{CtJO_Nk5;-ruj=ZESMVTDD&^vfR5akCS-tZO)=2_CjMCY}WM)^q zI`QW1Z+_`1-mC|f@#;Q!r7U&ay?#l}tJQz`?@I|^$VzjVeSO2;qIXAj3BOTDG>B&W z!>FGZkSyt3s4ckn{7$Z`7Hac;bJv!h>@GcHfAy=aZKLINH{C@qex+1dzSURx)?3w( z+Z^~h?)$9c@51;Gt-ZBn_FKW4JuU2F(p5PeQg5V}STE0&b(`p1<+}65vh&HNfmno#L~BWD|h~tY|{^qOKJ8xXW^`$ z|0(a=m!^f$ce<(-(H-DxbUY&hUwK~ zrf1LZ6r|ftzxVFaj?1b2J}KD^5-*L`rfRUBdQf%J^=s-JcKyx?%ZktZzFGDuVUDQw z?#VGKc86_sN`zM2SbnYei`Mu3)sCIhCBh#VU5Pv@x$}*$mAd!y9k<>Gv#jb5x%qWU z!m-j{brD+@F1xyXQsdp-?d=?|-8Qx6CfmkW>RFilbW4dyZ@hJm!F~3+UJ)s_-RF6a zEO;fcn(yqw`Jdjs+IB{cY1ZF<-R#SIJ^Xs4!~Zv(37E5_+C6Qp=B1wONUzY~$xEzR z>$X`;=5tyw^`g5i_j6{K)wz2Qdn*>yr~R{vWK-TzYB;xFTzDQ=29ITNd>MC;uflyj z9xu Date: Mon, 9 Mar 2026 11:25:56 +0530 Subject: [PATCH 2/4] Changed runtime to py3.14 --- eventbridge-fanout-pattern/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventbridge-fanout-pattern/main.tf b/eventbridge-fanout-pattern/main.tf index a0680cc7c3..d9d6343269 100644 --- a/eventbridge-fanout-pattern/main.tf +++ b/eventbridge-fanout-pattern/main.tf @@ -90,7 +90,7 @@ resource "aws_lambda_function" "processor" { function_name = "${var.prefix}-scheduled-processor" role = aws_iam_role.lambda_role.arn handler = "processor.lambda_handler" - runtime = "python3.12" + runtime = "python3.14" timeout = 30 memory_size = 128 filename = "processor.zip" From 1c0b95335dedbf1590ab3be96233ea5e1dc5b105 Mon Sep 17 00:00:00 2001 From: archiev4 Date: Mon, 9 Mar 2026 11:36:41 +0530 Subject: [PATCH 3/4] Updated version --- eventbridge-fanout-pattern/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eventbridge-fanout-pattern/main.tf b/eventbridge-fanout-pattern/main.tf index d9d6343269..9984b64f42 100644 --- a/eventbridge-fanout-pattern/main.tf +++ b/eventbridge-fanout-pattern/main.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.5" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.0" + version = "~> 6.32.1" } } } From 66efad46a21b519c22faa32ae182482282602a6e Mon Sep 17 00:00:00 2001 From: archiev4 Date: Sat, 14 Mar 2026 11:35:08 +0530 Subject: [PATCH 4/4] Fixed link --- eventbridge-fanout-pattern/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventbridge-fanout-pattern/README.md b/eventbridge-fanout-pattern/README.md index 6092a1ecf3..10f6368d75 100644 --- a/eventbridge-fanout-pattern/README.md +++ b/eventbridge-fanout-pattern/README.md @@ -11,7 +11,7 @@ Important: this application uses various AWS services and there are costs associ * [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured * [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -* [Terraform](https://learn.hashicorp.cxom/tutorials/terraform/install-cli?in=terraform/aws-get-started) installed +* [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/aws-get-started) installed ## Deployment Instructions