Setting up AWS EventBridge Rules with Terraform

Learn how to create and manage AWS EventBridge rules using Terraform, including event patterns, schedules, and targets

Setting up AWS EventBridge Rules with Terraform

AWS EventBridge (formerly CloudWatch Events) is a serverless event bus service. This guide demonstrates how to set up and manage EventBridge rules using Terraform.

Video Tutorial

Learn more about managing AWS EventBridge with Terraform in this comprehensive video tutorial:

Prerequisites

  • AWS CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Basic understanding of event-driven architectures
  • Familiarity with JSON/YAML

Project Structure

terraform-eventbridge/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── eventbridge/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── patterns/
    └── events.json

EventBridge Configuration

Create modules/eventbridge/main.tf:

# Event Bus
resource "aws_cloudwatch_event_bus" "main" {
  name = "${var.project_name}-bus"

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-bus"
    }
  )
}

# Event Bus Policy
resource "aws_cloudwatch_event_bus_policy" "main" {
  event_bus_name = aws_cloudwatch_event_bus.main.name

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "AllowOtherAccountsPutEvents"
        Effect    = "Allow"
        Principal = {
          AWS = var.allowed_account_ids
        }
        Action   = "events:PutEvents"
        Resource = aws_cloudwatch_event_bus.main.arn
      }
    ]
  })
}

# Schedule Rule
resource "aws_cloudwatch_event_rule" "schedule" {
  name                = "${var.project_name}-schedule"
  description         = "Schedule-based rule"
  event_bus_name      = aws_cloudwatch_event_bus.main.name
  schedule_expression = "rate(5 minutes)"

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-schedule"
    }
  )
}

# Pattern-based Rule
resource "aws_cloudwatch_event_rule" "pattern" {
  name           = "${var.project_name}-pattern"
  description    = "Pattern-based rule"
  event_bus_name = aws_cloudwatch_event_bus.main.name

  event_pattern = jsonencode({
    source      = ["aws.ec2"]
    detail-type = ["EC2 Instance State-change Notification"]
    detail = {
      state = ["running", "stopped"]
    }
  })

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-pattern"
    }
  )
}

# Lambda Target
resource "aws_cloudwatch_event_target" "lambda" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "SendToLambda"
  arn            = aws_lambda_function.handler.arn

  retry_policy {
    maximum_event_age_in_seconds = 3600
    maximum_retry_attempts       = 3
  }

  dead_letter_config {
    arn = aws_sqs_queue.dlq.arn
  }
}

# SQS Target
resource "aws_cloudwatch_event_target" "sqs" {
  rule           = aws_cloudwatch_event_rule.schedule.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "SendToSQS"
  arn            = aws_sqs_queue.main.arn

  input_transformer {
    input_paths = {
      time = "$.time"
      id   = "$.id"
    }
    input_template = "{\\"timestamp\\": <time>, \\"event_id\\": <id>}"
  }
}

# SNS Target
resource "aws_cloudwatch_event_target" "sns" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "SendToSNS"
  arn            = aws_sns_topic.notifications.arn

  input_path = "$.detail"
}

# Step Functions Target
resource "aws_cloudwatch_event_target" "step_functions" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "SendToStepFunctions"
  arn            = aws_sfn_state_machine.main.arn
  role_arn       = aws_iam_role.events.arn
}

# Kinesis Target
resource "aws_cloudwatch_event_target" "kinesis" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "SendToKinesis"
  arn            = aws_kinesis_stream.main.arn
  role_arn       = aws_iam_role.events.arn

  kinesis_target {
    partition_key_path = "$.id"
  }
}

# API Destination Target
resource "aws_cloudwatch_event_api_destination" "webhook" {
  name                             = "${var.project_name}-webhook"
  description                      = "Webhook destination"
  invocation_endpoint             = "https://api.example.com/webhook"
  http_method                     = "POST"
  invocation_rate_limit_per_second = 10
  connection_arn                  = aws_cloudwatch_event_connection.webhook.arn
}

resource "aws_cloudwatch_event_connection" "webhook" {
  name               = "${var.project_name}-webhook-connection"
  description        = "Webhook connection"
  authorization_type = "API_KEY"

  auth_parameters {
    api_key {
      key   = "Authorization"
      value = var.webhook_api_key
    }
  }
}

resource "aws_cloudwatch_event_target" "api_destination" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "SendToWebhook"
  arn            = aws_cloudwatch_event_api_destination.webhook.arn
  role_arn       = aws_iam_role.events.arn
}

Event Patterns

  1. EC2 State Changes
resource "aws_cloudwatch_event_rule" "ec2_state" {
  name           = "${var.project_name}-ec2-state"
  description    = "Track EC2 state changes"
  event_bus_name = aws_cloudwatch_event_bus.main.name

  event_pattern = jsonencode({
    source      = ["aws.ec2"]
    detail-type = ["EC2 Instance State-change Notification"]
    detail = {
      state = ["running", "stopped", "terminated"]
    }
  })
}
  1. S3 Events
resource "aws_cloudwatch_event_rule" "s3_events" {
  name           = "${var.project_name}-s3-events"
  description    = "Track S3 object events"
  event_bus_name = aws_cloudwatch_event_bus.main.name

  event_pattern = jsonencode({
    source      = ["aws.s3"]
    detail-type = ["AWS API Call via CloudTrail"]
    detail = {
      eventSource = ["s3.amazonaws.com"]
      eventName   = ["PutObject", "DeleteObject"]
    }
  })
}
  1. Custom Events
resource "aws_cloudwatch_event_rule" "custom" {
  name           = "${var.project_name}-custom"
  description    = "Handle custom application events"
  event_bus_name = aws_cloudwatch_event_bus.main.name

  event_pattern = jsonencode({
    source      = ["custom.myapp"]
    detail-type = ["UserSignup", "OrderPlaced"]
    detail = {
      status = ["success", "failure"]
    }
  })
}

Schedule Patterns

  1. Cron Expression
resource "aws_cloudwatch_event_rule" "cron" {
  name                = "${var.project_name}-cron"
  description         = "Run on specific schedule"
  event_bus_name      = aws_cloudwatch_event_bus.main.name
  schedule_expression = "cron(0 12 * * ? *)"
}
  1. Rate Expression
resource "aws_cloudwatch_event_rule" "rate" {
  name                = "${var.project_name}-rate"
  description         = "Run at regular intervals"
  event_bus_name      = aws_cloudwatch_event_bus.main.name
  schedule_expression = "rate(5 minutes)"
}

Input Transformation

  1. JSON Path
resource "aws_cloudwatch_event_target" "transform" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "TransformInput"
  arn            = aws_lambda_function.handler.arn

  input_transformer {
    input_paths = {
      instance = "$.detail.instance-id"
      state    = "$.detail.state"
      time     = "$.time"
    }
    input_template = <<EOF
{
  "instanceId": <instance>,
  "currentState": <state>,
  "timestamp": <time>
}
EOF
  }
}

Monitoring and Alerts

  1. CloudWatch Metrics
resource "aws_cloudwatch_metric_alarm" "failed_invocations" {
  alarm_name          = "${var.project_name}-failed-invocations"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name        = "FailedInvocations"
  namespace          = "AWS/Events"
  period             = "300"
  statistic          = "Sum"
  threshold          = "0"
  alarm_description  = "EventBridge rule invocation failed"
  alarm_actions      = [aws_sns_topic.alerts.arn]

  dimensions = {
    RuleName = aws_cloudwatch_event_rule.pattern.name
  }
}
  1. Dead Letter Queue
resource "aws_sqs_queue" "dlq" {
  name = "${var.project_name}-dlq"

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-dlq"
    }
  )
}

resource "aws_cloudwatch_event_target" "with_dlq" {
  rule           = aws_cloudwatch_event_rule.pattern.name
  event_bus_name = aws_cloudwatch_event_bus.main.name
  target_id      = "WithDLQ"
  arn            = aws_lambda_function.handler.arn

  dead_letter_config {
    arn = aws_sqs_queue.dlq.arn
  }
}

Best Practices

  1. Event Bus Design

    • Use custom event buses
    • Implement proper permissions
    • Define clear event patterns
    • Use meaningful names
  2. Target Configuration

    • Implement retries
    • Configure DLQ
    • Transform inputs
    • Monitor failures
  3. Security

    • Use IAM roles
    • Implement encryption
    • Control access
    • Monitor usage
  4. Performance

    • Optimize event patterns
    • Configure batch size
    • Monitor latency
    • Handle failures

Conclusion

You’ve learned how to set up and manage AWS EventBridge using Terraform. This setup provides:

  • Event routing
  • Schedule-based execution
  • Target integration
  • Monitoring capabilities

Remember to:

  • Design clear event patterns
  • Implement proper error handling
  • Monitor rule execution
  • Optimize performance