Resizing Images with Lambda

This sample application is going to be much more complex than the last one. We are going to perform several actions, each of which require policies. Without these policies Intrinsic will prevent the actions from occurring.

Resizer Flow

With this application, we want users to upload images to an Amazon S3 bucket [1]. Within Amazon we have configured S3 to trigger our Lambda when an upload occurs [2]. When an upload happens, Amazon will give us information about the newly uploaded file, such as what S3 bucket it is in and the path to the file. We will then make use of the AWS SDK to access and download the image file [3] to a temporary directory [4]. Once we have the file we will pass it to GraphicsMagick to be resized [5]. GraphicsMagick then reads and writes the file [6] but that is outside the realm of our application / Intrinsic. Once the operation has completed Node will then read the resized image from disk [7] and do something with the data.

Intrinsic works at the boundary of your application; that is to say, how it interacts with the world around it. With this in mind the following operations need to be permitted via policies:

  • Read AWS SDK JSON configuration from disk
  • Communicate with the Amazon S3 service / access files
  • Write to /tmp/
  • Execute the GraphicsMagick binary
  • Read from /tmp/

Policies

Now we're going to walk through several different policies as they apply to our resizing application.

Communicating with Amazon S3

There are two URI patterns which our application will need to communicate with when retrieving data from S3. The first one is a generic S3 address. It is used internally by the AWS SDK module. The second is specifically a path to our applications S3 bucket. In this case we only want to read JPEG files which have been uploaded to the uploads/ directory inside of our upload-app bucket. The S3 upload trigger has likewise been configured to only trigger our Lambda Function when files have been uploaded to that directory. The policies to satisfy this requirement look like the following:

policy.outboundHttp.allowGet(
  'https://s3.us-west-1.amazonaws.com/com.intrinsic.sample.upload-app/uploads/*.jpg'
);

Writing to /tmp

Once we've retrieved the file we then want to write it somewhere to disk. Of course, Lambda only permits writing files to /tmp/; trying to write files anywhere else will fail.

We add a policy to allow our function to write JPEG files to /tmp:

policy.fs.allowWrite('/tmp/*.jpg');

Using GraphicsMagick

The GraphicsMagick tool is a binary which can be used to perform many different types of image processing. It is a generic binary unrelated to Node.js and can be called from any type of application. Luckily for us there is a Node.js module which makes working with GraphicsMagick easier. The module we'll be using in this sample app is gm. When working with Lambda functions we cannot use the OS package manager to install our dependencies like we would in a normal OS. Instead we need to specially compile and distribute these binaries within our Lambda Zip file.

In this case our GraphicsMagick binary is located at /var/task/graphicsmagick/bin/gm, so we add the following policy:

policy.childProcess.allowSpawningPath('/var/task/graphicsmagick/bin/gm');

Reading from /tmp

Once the image has been resized we want to be able to read the image content. This policy lets us read any JPEG file from /tmp:

policy.fs.allowRead('/tmp/*.jpg');

Putting it all together

Now that we've taken a look at individual policies, let's look at how we can actually require the necessary Intrinsic module and apply the policies to our Lambda Function. In this application we normally have a file named handler.js which exports a single method called myfunc. Therefore this handler is referred to as handler.myfunc. It is the entry point into our application and is what Lambda will call.

What we can do to enable Intrinsic for Lambda in an unobtrusive manner in this application is to create a new file called intrinsic.js which also exports a single myfunc method. We can then tell Lambda that our handler is named intrinsic.myfunc. The Intrinsic entry point will load our normal application code.

The following is what our intrinsic.js file will look like:

const IntrinsicLambda = require('@intrinsic/lambda');

module.exports = new IntrinsicLambda()
  .configurePolicies(policy => {
    policy.outboundHttp.allowGet(
      'https://s3.us-west-1.amazonaws.com/com.intrinsic.sample.upload-app/uploads/*.jpg'
    );

    policy.fs.allowWrite('/tmp/*.jpg');
    policy.fs.allowRead('/tmp/*.jpg');

    policy.childProcess.allowSpawningPath('/var/task/graphicsmagick/bin/gm');
  })
  .setHandlerName('myfunc')
  .setHandlerFile(`${__dirname}/handler.js`)
  .run();