Using serverless Ruby on AWS Lambda to resize images
Tags: ruby, aws, lambda, serverless
Published: 2019-01-04 09:08:54

---
date: "2019-01-04T10:08:54+01:00"
cover_image: https://maartenvanvliet.nl/2019/01/04/ruby_aws_lambda/paulius-dragunas-719039-unsplash.jpg
tags: ["ruby", "aws", "lambda", "serverless"]
title: Using serverless Ruby on AWS Lambda to resize images
published: true
---

Republished from (https://maartenvanvliet.nl/2019/01/04/ruby_aws_lambda/)

At the last AWS ReInvent, it was announced that AWS Lambda would support Ruby as a runtime language.
I was eager to try this out, Ruby's powerful syntax and features are a joy to work with and coupling this with AWS Lambda I figured it could be leveraged for some easy image resizing Lambda.

I started off with the [serverless framework](https://serverless.com/blog/api-ruby-serverless-framework/) as this is an easy way provision Lambda functions. The goal is that when an image is uploaded to an S3 bucket, a Lambda is started, it resizes the image, and then uploads it to another bucket.

The first step is to install `serverless`

```
npm install -g serverless
serverless create -t aws-ruby -p image-resizer
```

This creates the basic boilerplate of a serverless framework ruby Lambda function. It will create two files, a `handler.rb`, the file with the actual function the Lambda will call, and a `serverless.yml` a file that the serverless framework uses to configure and provision the lambda and affiliated services such as AWS S3 and AWS API Gateway.

We will first change the `serverless.yml` configuration. We add a function `handle_resize` that calls the `ImageHandler.process` function in `handler.rb` whenever an object is created in the `your-images` S3 bucket. Furthermore, under the provider key, we grant the function access to S3 and use the `ruby2.5` runtime.

We also add an S3 resource `resized-your-images`, this will be the bucket where we moved the resized images to.

```yml
service: image-resizer 

provider:
  name: aws
  runtime: ruby2.5
  iamRoleStatements:
    - Effect: Allow
      Action:
        - s3:*
      Resource: "*"

functions:
   handle_resize:
     handler: handler.ImageHandler.process
     events:
       - s3:
           bucket: your-images
           event: s3:ObjectCreated:*

resources:
  Resources:
    ResizedImages:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: resized-your-images
```

Now we can implement the `ImageHandler.process` function.

## Writing the Ruby lambda
First we'll add a Gemfile because of two dependencies we need to use. The AWS S3 SDK to retrieve and upload files to S3, and the `mini_magick` gem, a wrapper for imagemagick.

```ruby
#Gemfile
source 'https://rubygems.org'

gem 'aws-sdk-s3', '~> 1.30.0'
gem "mini_magick", '~> 4.9.0'
```

Next we update the `handler.rb` file.

```ruby
#handler.rb
require 'uploaded_file'

class ImageHandler

  def self.process(event:, context:)
    event = event["Records"].first
    bucket_name = event["s3"]["bucket"]["name"]
    object_name = event["s3"]["object"]["key"]

    file = UploadedFile.from_s3(bucket_name, object_name)
    file.resize "100x100"
    file.upload_file("resized-your-images", "resized_" + event["s3"]["object"]["key"] )
  end
end
```

The `ImageHandler.process` is the function called by the lambda and has two arguments, the `event`, in our case an event sent by S3 about newly created files, and the `context`, basically metadata about the function and its environment.

The `process` function retrieves the bucket and key of the newly created object from the event object. Then it calls into the not yet implemented `UploadedFile` class. This class retrieves the s3 object, resizes it and then uploads it to another bucket. This class is independent from the lambda, and in general all business logic should be. This makes it easier to use the code in other contexts and also allows easier testing. E.g. I can test the `UploadedFile.from_s3(bucket_name, object_name)` without having to mock the entire S3 Lambda event. 

Now we'll implement the UploadedFile class.

```ruby
# uploaded_file.rb
require "aws-sdk-s3"
require "mini_magick"

class UploadedFile
    def self.from_s3(bucket_name, object_name)
        s3 = Aws::S3::Resource.new()
        object = s3.bucket(bucket_name).object(object_name)

        tmp_file_name = "/tmp/#{object_name}"
        object.get(response_target: tmp_file_name)
        
        UploadedFile.new(tmp_file_name)
    end

    def initialize(tmp_file)
        @tmp_file = tmp_file
    end

    def resize(params)
        image = MiniMagick::Image.open(@tmp_file)
        image.resize params
        @resized_tmp_file = "/tmp/resized.jpg"
        image.write @resized_tmp_file
    end

    def upload_file(target_bucket, target_object)
        s3 = Aws::S3::Resource.new()
        object = s3.bucket(target_bucket).object(target_object).upload_file(@resized_tmp_file)
    end
end
```
The `uploaded_file.rb` is typical ruby. It retrieves the S3 file, stores it in a tmp-file. We call `mini_magick` to resize the image, and then uploads the file again.


## Deployment
When deploying we need to make sure that the Lambda function has access to the dependencies. Serverless framework will build a zip-file with the code and all dependencies locally and then deploy this to AWS. Therefore it is important that we run the same version of ruby locally as on AWS, so that would be ruby 2.5. If you use `rbenv`, you can use `rbenv install 2.5.0` and `rbenv local 2.5.0` to set the ruby version for this project. 

Next we need to vendorize the dependencies so they are included in the package. Run `bundle install --path vendor/bundle`, now the dependencies are in the `vendor` directory. 

Before we can deploy we still need to set the credentials for AWS. See [Serverless Framework documentation](https://serverless.com/framework/docs/providers/aws/guide/credentials/) for more information. 

Now that is done you can invoke `sls deploy` and the lambda function will be deployed and the S3 buckets will be created. Upload an image to the `your-images` bucket and moments later a resized version will appear in the `resized-your-images` bucket. If anything goes wrong you can follow that in the log files in AWS Cloudwatch. There will be a log stream for the lambda function. You can also call `sls logs -f handle_resize` to tail the logs.

Find the code on [github](https://github.com/maartenvanvliet/serverless_ruby_image_resizer)

Photo by Paulius Dragunas on Unsplash