Automating S3 Events with AWS Lambda

Introduction

Amazon S3 events let you automatically respond to changes in your storage, like when files are uploaded, deleted, or modified. By linking S3 with AWS Lambda, you can create workflows that instantly react to these events without needing to manage servers. This makes it easy to automate tasks like processing uploads, resizing images, or updating records in real-time. In this post, we’ll dive into how you can handle S3 events with AWS Lambda in a simple and efficient way.

Implementing S3 Event Handling with AWS Lambda

To understand what exactly happens when an S3 event is triggered, this post will guide you through the implementation of the architecture below. By examining a practical example, we’ll explore how S3 events are processed and handled using AWS Lambda, providing you with a clear, hands-on understanding of the entire workflow.

The idea is to upload an image to an S3 bucket named original-images. This upload will trigger an S3 event that is intercepted by a Lambda function written in Node.js. The Lambda function processes the incoming event, retrieves the uploaded image, and generates a thumbnail version. This automated process efficiently handles image resizing and stores the resulting thumbnails in a designated thumbnails bucket.

Provision resources using CloudFormation

AWS CloudFormation is a service that helps you automate the setup and management of AWS resources using code. Instead of manually configuring individual services, you define them in a JSON or YAML template. CloudFormation then provisions, updates, or deletes your resources in a consistent and repeatable way, making infrastructure management more efficient and reliable.

Create the CloudFormation template

For me, I prefer to use YAML, but feel free to use JSON if you want. Let's start by creating a file called deployment.yaml.

1#deployment.yaml 2AWSTemplateFormatVersion: "2010-09-09" 3Description: "CloudFormation template to provision resources"

These two lines in the template specify the version of the template format being used and provide a description of the template's purpose.

Now, let's add a Resources section to the template and start by creating the logical resource for the first S3 bucket. This bucket will store the original images and trigger the file creation event.

Note: Resources inside the CloudFormation template are called logical resources. When you create a CloudFormation stack based on the template, a physical resource is created for each logical resource.

1Resources: 2 # First S3 Bucket 3 OriginalImagesBucket: 4 Type: "AWS::S3::Bucket" 5 Properties: 6 BucketName: "mz-original-images-07" 7 NotificationConfiguration: 8 LambdaConfigurations: 9 - Event: "s3:ObjectCreated:*" 10 Function: !GetAtt MzFunction.Arn

This snippet defines the mz-original-images-07 in the template:

  • OriginalImagesBucket: This is the logical name for the S3 bucket resource.
  • Type: Specifies the AWS resource type, which is AWS::S3::Bucket in this case.
  • Properties: Contains the configuration details for the S3 bucket.
  • BucketName: The name of the bucket, which is mz-original-images-07.
  • NotificationConfiguration: Configures the bucket to send notifications.
  • LambdaConfigurations: Specifies the Lambda function to be triggered when an object is created in the bucket.
  • Event: The type of event that triggers the Lambda function, in this case, s3:ObjectCreated:*.
  • Function: Refers to the ARN of the Lambda function, which is specified as !GetAtt MzFunction.Arn. The Lambda function is named MzFunction.
Defines the mz-thumbnails-07 bucket
1# Second S3 Bucket 2ThumbnailsBucket: 3 Type: "AWS::S3::Bucket" 4 Properties: 5 BucketName: "mz-thumbnails-07"

Note: S3 bucket names need to be globally unique, which is why I added mz and 07 to the names.

Defines the MzFunction

Before defining the Mz Lambda function itself, we first need to define the execution role that the function will use in order to react to the creation event, process the file, and then store it in the other bucket. The Lambda function needs the proper permissions to perform all of these actions.

1# IAM Role for Lambda 2LambdaExecutionRole: 3 Type: "AWS::IAM::Role" 4 Properties: 5 RoleName: "LambdaExecutionRole" 6 AssumeRolePolicyDocument: 7 Version: "2012-10-17" 8 Statement: 9 - Effect: "Allow" 10 Principal: 11 Service: "lambda.amazonaws.com" 12 Action: "sts:AssumeRole" 13 ManagedPolicyArns: 14 - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 15 - "arn:aws:iam::aws:policy/AmazonS3FullAccess"

This defines an IAM role called LambdaExecutionRole that allows AWS Lambda to assume the role and execute functions. It grants the Lambda function basic execution permissions and full access to S3 through managed policies.

Now, let's jump into defining our MZFunction itself

1# Lambda Function 2MzFunction: 3 Type: "AWS::Lambda::Function" 4 Properties: 5 FunctionName: "mz-function" 6 Handler: "index.handler" 7 Role: !GetAtt LambdaExecutionRole.Arn 8 Runtime: "nodejs18.x" 9 Timeout: 30 10 MemorySize: 128 11 Code: 12 ZipFile: | 13 exports.handler = async (event) => { 14 console.log("Empty Lambda", event); 15 return true; 16 }

This AWS CloudFormation code above creates our Lambda function "mz-function" using Node.js 18.x. It runs the index.handler function with a 30-secondtimeout, 128 MB of memory. About the function code, I've added a simple console.log() for now, and we'll update it later.

The last thing we have to do is to give the S3 for our originals image the permission to triggered the Lambda function.

1# S3 Bucket Notification for Lambda Trigger 2LambdaInvokePermission: 3 Type: "AWS::Lambda::Permission" 4 Properties: 5 FunctionName: !GetAtt MzFunction.Arn 6 Action: "lambda:InvokeFunction" 7 Principal: "s3.amazonaws.com" 8 SourceArn: "arn:aws:s3:::mz-original-images-07"

The above AWS CloudFormation code grants S3 permission to invoke the Lambda function MzFunction when an event occurs. It allows the S3 bucket mz-original-images-07 to trigger the function. The Principal is set to S3, specifying the source of the event.

Deploy the CloudFormation Template

To deploy a CloudFormation template via the AWS Management Console, follow these steps:

StepAction
1Sign in to AWS Console: Open the AWS Management Console and navigate to the CloudFormation service.
2Create Stack: Click on "Create stack" and select "With new resources (standard)."
3Upload Template: Choose "Upload a template file" and select our deployment.yaml template.
4Configure Stack: Enter a stack name, configure stack options, and set parameters as required.
5Review and Create: Review your settings, and click "Create stack" to deploy the template. AWS will start provisioning the resources defined in your template.

To visualize the template's logical resources, simply click the View in Application Composer button.

After a few minutes, our resources will be created successfully

Test if the Lambda function is triggered when an image is uploaded

To view the console.log output of our Lambda function, follow these steps:

StepAction
1Upload an image to the S3 Originals Images bucket.
2Go to the Lambda service.
3Select our Lambda function: Choose the Lambda function whose logs we want to view.
4Go to the Monitoring tab:
  • Click on the Monitoring tab for the selected Lambda function.
  • Under the Logs section, click on View logs in CloudWatch. This will open the CloudWatch Logs console.
5View logs in CloudWatch:
  • In the CloudWatch Logs console, look for the log group with the name /aws/lambda/mz-function.
  • Click on the log group to view the log streams.
  • Select the most recent log stream to see the output of our console.log statements.
mz-function console.log output
mz-function configs

Add the lambda function code

To add the Lambda code, follow these steps in the AWS Management Console:

  1. Navigate to the Code tab for your Lambda function.
  2. Enter the following code:
1const s3 = new (require("aws-sdk").S3)(); 2 3// Define the source and destination buckets 4const SOURCE_BUCKET = "mz-original-images-07"; // Original images bucket 5const THUMBNAILS_BUCKET = "mz-thumbnails-07"; // Thumbnails bucket 6 7exports.handler = async (event) => { 8 // Extract S3 object information from the event 9 const record = event.Records[0]; 10 const bucket = record.s3.bucket.name; 11 const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, " ")); 12 13 // Check if the bucket is the source bucket 14 if (bucket !== SOURCE_BUCKET) { 15 console.log("Not an image upload event for the source bucket."); 16 return; 17 } 18 19 try { 20 // Get the image from S3 21 const params = { 22 Bucket: bucket, 23 Key: key, 24 }; 25 const { Body } = await s3.getObject(params).promise(); 26 27 // Process the image (Here, we just re-upload the original image for demonstration) 28 // To resize, you would typically need a library, but we'll skip resizing for simplicity 29 30 // Define the destination key 31 const thumbnailKey = `thumbnails/${key}`; 32 33 // Upload the image to the thumbnails bucket 34 const uploadParams = { 35 Bucket: THUMBNAILS_BUCKET, 36 Key: thumbnailKey, 37 Body: Body, 38 ContentType: "image/jpeg", 39 }; 40 await s3.putObject(uploadParams).promise(); 41 42 console.log(`Image uploaded to ${THUMBNAILS_BUCKET}/${thumbnailKey}`); 43 } catch (error) { 44 console.error("Error processing the image:", error); 45 throw error; 46 } 47};

the code is about extracting the image from an S3 bucket, checks if it's from the source bucket, and then re-uploads it to a different S3 bucket.

Note 1 : The image is simply copied without resizing for demonstration purposes.

Note 2 : If you encounter the error "Runtime.ImportModuleError: Error: Cannot find module 'aws-sdk'" whenever your Lambda function is triggered, try downgrading your Node.js runtime from the AWS Management Console. This issue is known to occur with the Node.js 18 runtime, and switching to an earlier version may resolve it.

Finally : click the Deploy button to apply the changes to our Lambda code.

Conclusion

In this blog, we demonstrated how to deploy a CloudFormation template and configure an AWS Lambda function to respond to S3 events. The Lambda function is triggered when an image is uploaded to an S3 bucket, allowing for automated processing or handling of the image. This setup illustrates the basics of integrating S3 with Lambda to streamline workflows in a serverless environment.