Trigger Lambda Function When New Image is Uploaded to S3 - (Let's Build 🏗️ Series)
Tags: serverless, aws, terraform, go
Published: 2023-10-24 14:55:14

Let's build a simple architecture where a Lambda function is triggered whenever a user uploads a new image to an S3 bucket.

> 📋 **Note:** This article is preparation for a more advanced one which will be published inside the same series.

**The main parts of this article:**
1- Architecture Overview (Terraform)
2- About AWS Services (Info)
3- Technical Part (Code)
4- Result
5- Conclusion

## Architecture Overview
```hcl
variable "region" {
  default     = "eu-west-1"
  description = "AWS Region to deploy to"
}

terraform {
  required_version = "1.5.1"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.22.0"
    }
  }
}

provider "aws" {
  region = var.region
}

resource "aws_s3_bucket" "image_bucket" {
  bucket = "lets-build-1"
}

resource "aws_iam_role" "lambda_execution_role" {
  name = "lets-build-lambda-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Effect = "Allow",
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_policy_attachment" "lambda_basic_execution" {
  name       = "lets-build-lambda-attachment"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  roles      = [aws_iam_role.lambda_execution_role.name]
}

resource "aws_lambda_function" "image_analysis_lambda" {
  filename         = "./main.zip"
  function_name    = "lets-build-function"
  handler          = "main"
  runtime          = "go1.x"
  role             = aws_iam_role.lambda_execution_role.arn
  memory_size      = "128"
  timeout          = "3"
  source_code_hash = filebase64sha256("./main.zip")
  environment {
    variables = {
      REGION = "${var.region}"
    }
  }
}

resource "aws_lambda_permission" "allow_bucket" {
  statement_id  = "AllowExecutionFromS3Bucket"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.image_analysis_lambda.function_name
  principal     = "s3.amazonaws.com"
  source_arn    = aws_s3_bucket.image_bucket.arn
}

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = aws_s3_bucket.image_bucket.id

  lambda_function {
    lambda_function_arn = aws_lambda_function.image_analysis_lambda.arn
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "images/"
    filter_suffix       = ".png"
  }

  depends_on = [aws_lambda_permission.allow_bucket]
}
```

Hint: always run `terraform fmt` and `terraform validate` to clean the syntax and validate that all is good, after you can run `terraform plan` to see the resources that will be created and finally `terraform apply`

## About AWS Services
1- Amazon S3: To store the image.
2- AWS Lambda: To trigger our function and execute the code.
3- Identity and Access Management (IAM): To give permissions for the S3 and Lambda events.

## Technical Part
Our fancy go code 😜 does simple logging.

```go
package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-lambda-go/lambda"
)

func handler(ctx context.Context) (string, error) {
	fmt.Println("Lambda function executed.")
	return "Lambda function executed successfully.", nil
}

func main() {
	lambda.Start(handler)
}
```

Since we are not using any frameworks, we need to build and create our own .zip file in order to be able to upload the code to our Lambda function. Inside the Terraform configuration file already you can notice that I'm pointing to `main.zip` file which holds our built code.

Build your Go binary for the appropriate target platform:
```
GOARCH=amd64 GOOS=linux go build -o main
```

Create a ZIP file containing your main binary and any other dependencies:
```
zip main.zip main
```

## Result
That's it, let's try to upload a new image to our S3 bucket, and we can see the logs being printed out when we check Amazon CloudWatch.

Here is a simple screenshot from Amazon CloudWatch after I uploaded an image to my S3 Bucket, also remember we are using prefixes for certain folders which in our case it's `/images`, and the file type should be `.png`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/huelcylfszdmm26dv46a.png)

Sweet, now it's your turn to become more creative and build on top of this. And a small reminder that the techniques and skills that are used in this article will be also used for my next one, which will hold more advanced services and skills.

## Conclusion
I wanted to write this article a separate one since there are many use cases in this simple architecture that can be really handy. Hope you guys enjoyed it.

If you'd like more content like this, feel free to connect with me on [LinkedIn](https://www.linkedin.com/in/awedis).