How to schedule your Lambda with Terraform?
Published: 2022-02-20 16:59:12

There are many cases where you might need to run your Lambda regularly without having it triggered by a user action, such as trying to send an email to your customers on a monthly/weekly basis or running a process against your data.

![Image by [Jeshoots](https://jeshoots.com/) via [pexels](https://www.pexels.com/@jeshoots-com-147458)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aczigconb3zge8ewvsak.jpeg)

The execution of a Lambda function can be done on a regular basis through [AWS Event Bridge](https://aws.amazon.com/eventbridge/), where you create a rule that runs on a schedule and triggers your Lambda.

The Event Bridge is a serverless event bus connecting your applications and services by passing data (or events) from a source to a destination. In this post, we will set up Event Bridge and Lambda and configure a rule that schedules the execution of a Lambda function.

## Setup Infrastructure & Lambda function

I've wanted to learn Terraform for a while, so I thought this post would be an excellent opportunity to put this into action. As I'm a newbie here, there are probably better ways to write the Terraform file, and I would appreciate any feedback.

First, let us create a simple JS code to upload to our Lambda function. Create an `index.js` file with the following code:

```
exports.handler  = async (event) => {
  const payload = {
    date: new Date(),
    message: "awesome lambda function",
  };
  return JSON.stringify(payload);
};
```

Second, we want to install Terraform - this could be done using brew on mac: `brew tap hashicorp/tap` then `brew install hashicorp/tap/terraform`. For other Operating Systems, check the [docs](https://learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/aws-get-started).

Now let's dig into each piece of the infrastructure bit by bit. Create a `main.tf` file, which will contain all of our Terraform configuration (This could be split into multiple files and modules, but for this post, I'll keep all of the configs in a single file).

Define AWS as the cloud provider, set the profile name and the region where you'd like to provision your resources:

```
provider "aws" {
  profile = "default"
  region  = "ap-southeast-2"
}
```

Create the lambda function and the execution role (this is an IAM role that grants the function permission to access AWS services and resources - Lambda assumes this role when the function is invoked and could be used to write logs or perform other actions):

```
resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"

  # Terraform's "jsonencode" function converts a
  # Terraform expression result to valid JSON syntax.
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      },
    ]
  })
}


resource "aws_lambda_function" "test_lambda" {
  filename      = "schedule-lambda.zip"
  function_name = "test_lambda"
  role          = aws_iam_role.iam_for_lambda.arn
  handler       = "index.handler"

  source_code_hash = filebase64sha256("schedule-lambda.zip")

  runtime = "nodejs12.x"
}
```

In this previous snippet, we defined two resources; that's 2 Terraform `resource` blocks, the first one in the IAM role, and the second is the Lambda function. Before each configuration block, there are two strings: the `resource type` and the `resource name`. Together, the resource type and resource name form a unique ID that Terraform can use to identify the resource when updating it.

We also identified the handler function, pointed to the role name and added the file name - that's the name of the zip file packaging our lambda code. So we need to package our code for this infrastructure config to work. Run the following command: `zip -r ./schedule-lambda.zip index.js`.

Now we will prepare the config for the event bridge rule and add the Lambda function as a target of this rule:

```
resource "aws_cloudwatch_event_rule" "every_5_minutes" {
  name        = "every_5_minutes_rule"
  description = "trigger lambda every 5 minute"

  schedule_expression = "rate(5 minutes)"
}

resource "aws_cloudwatch_event_target" "lambda_target" {
  rule      = aws_cloudwatch_event_rule.every_5_minutes.name
  target_id = "SendToLambda"
  arn       = aws_lambda_function.test_lambda.arn
}
```

One last step is left where we give the event bridge rule the permission to invoke our Lambda function; this is done using `aws_lambda_permission`:

```
resource "aws_lambda_permission" "allow_eventbridge" {
  statement_id  = "AllowExecutionFromEventBridge"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.test_lambda.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.every_5_minutes.arn
}
```

Notice how we specify the InvokeFunction in the "action", then define the function name and the source (that's the event bridge rule)

## Deploy our Infrastructure

Run the following commands to validate and deploy the infrastructure:

- `terraform init` to prepare your working directory
- `terraform validate` to check whether the configuration is valid
- `terraform plan` to show changes required by the current configuration
- `terraform apply` to create or update infrastructure

To make sure it's working, log in to the AWS console, select your Lambda function and click the "Monitor" tab. Before the execution, it looks as follows: 
![Before Lambda Scheduled Execution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81y27bakjxbiwop65lci.png)

Then after a few minutes, notice the duration, the error and success rate and the concurrent executions - this shows as a straight line from 7:00 until 7:15. In reality, these are 3 connected dots(events) received every 5 minutes: 
![After Lambda Scheduled Execution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xaecfh3r7szkikm6aqy2.png)

I hope this was helpful. How would you schedule the execution of your Lambda function?