Search code examples
amazon-web-servicesaws-lambdaterraformamazon-cloudwatch

Terraform multiple cloudwatch events trigger same lambda function


I have setup the following in Terraform. So two event rules, start_event at 8am and stop_event at 6pm.

# Create cloudwatch event rules
resource "aws_cloudwatch_event_rule" "stop_ec2_event_rule" {
  name        = "stop-ec2-event-rule"
  description = "Stop EC2 instance at a specified time each day"
  schedule_expression = var.cloudwatch_schedule_stop
}

resource "aws_cloudwatch_event_rule" "start_ec2_event_rule" {
  name        = "start-ec2-event-rule"
  description = "Start EC2 instance at a specified time each day"
  schedule_expression = var.cloudwatch_schedule_start
}

Each event passes an action to the lambda

resource "aws_cloudwatch_event_target" "stop_ec2_event_rule_target" {
  rule      = aws_cloudwatch_event_rule.stop_ec2_event_rule.name
  target_id = "TriggerLambdaFunction"
  arn       = aws_lambda_function.lambda_rscheduler.arn
  input     = "{\"environment\":\"${var.environment}\", \"action\":\"stop\"}"
}

resource "aws_cloudwatch_event_target" "start_ec2_event_rule_target" {
  rule      = aws_cloudwatch_event_rule.start_ec2_event_rule.name
  target_id = "TriggerLambdaFunction"
  arn       = aws_lambda_function.lambda_rscheduler.arn
  input     = "{\"environment\":\"${var.environment}\", \"action\":\"start\"}"
}

This works


resource "aws_lambda_permission" "allow_cloudwatch" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_rscheduler.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.stop_ec2_event_rule.arn

This issue I'm facing is that I cannot get Terraform to associate the start_event with the lambda function. I go into the AWS console and I can manually add the CloudWatch start_event trigger to the lambda function.

If I have the start_event resources

resource "aws_lambda_permission" "allow_cloudwatch" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_rscheduler.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.start_ec2_event_rule.arn

It will complain that the statement_id is duplicated.

I needed something like the terraform aws_lambda_event_source_mapping but that only allows Lambda functions to get events from Kinesis, DynamoDB and SQS; and not a CloudWatch event.

How can I tell terraform to associate multiple cloudwatch events to the same lambda function; when I can manually do it from the AWS console?


Solution

  • statement_id is not compulsory, so you can safely omit it from your aws_lambda_permission and terraform will unique generate id for you automatically. You can also use count or for_each to save you some typing for aws_lambda_permission.

    For example, using for_each you could define aws_lambda_permission to be:

    resource "aws_lambda_permission" "allow_cloudwatch" {
      
      for_each      = {for idx, v in [
          aws_cloudwatch_event_rule.stop_ec2_event_rule,
          aws_cloudwatch_event_rule.start_ec2_event_rule
      ]: idx => v}
      
      action        = "lambda:InvokeFunction"
      function_name = aws_lambda_function.lambda_rscheduler.function_name
      principal     = "events.amazonaws.com"
      source_arn    = each.value.arn
    }
    

    Analogical versions could be written for aws_cloudwatch_event_rule and aws_cloudwatch_event_target so that your code is based on for_each or count without the copy-and-paste repetitions.