Creating, updating, and invoking AWS Lambda Functions in Haskell

For this part of the code, we will first work with a simple Lambda Function created in NodeJS. In the next section, we will replace the NodeJS part with a Lambda Function written completely in Haskell.

Important note about credentials

A slightly confusing aspect about working with Lambda Functions is that there are two IAM roles involved in the process:

The first IAM role is involved in managing the Lambda Function (i.e. creating it, or updating its code).

  • This role is related to the user who is making the API calls to create/update the Lambda Function. With respect to this tutorial, it is related to the getAwsEnv function given below
  • The role usually requires the AWSLambdaFullAccess “permission policy” (as it is called in AWS parlance). If you’re doing a lot of critical things within AWS Lambda, then it might not be a a better idea to restrict these permissions even further.
The easiest way to is it set the AWS_KEY and AWS_SECRET environment variable while running this code (or set-up your ~/.aws file). Make sure the relevant user has AwsLambdaFullAccess

The second IAM role is involed when the Lambda Function is invoked/executed.

  • AWS needs to know which AWS services are available to the Lambda Function at the time of execution of the Lambda Function.
  • The required permissions completely depend upon what your Lambda Function is doing. If your Lambda Function is not accessing any S3 bucket, this role need not have any S3 related permission. Similarly, if your Lambda Function needs to trigger some deployment via Code Deploy, then it will need the relevant persmission.
  • At the minimum, this role will need the AWSLambdaBasicExecutionRole “permission policy”
Note: Once you create this role (with at least the AWSLambdaBasicExecutionRole permission), please modify fnRole given below before attempting to run this code.


Creating an AWS.Env

Please make sure you read the section on credentials before attempting to run this code

All AWS API calls triggered via amazonka libraries, need to be in the AWST monad. You can even define your own monad that implements the HasEnv type-class, if you need to do something really exotic, but we’ll keep things simple and use the AWST monad in this tutorial.

An action wrapped in the AWST monad can be run via the runAWS function, which requires an env :: Env argument. The env argument holds a bunch of stuff required to make AWS API calls. For example:

  • the authentication credentials
  • the AWS region to use
  • the HTTP Manager with which to make HTTP calls
  • whether to raise HTTP exceptions for non-200 responses, or to return the underlyint response as-is
  • and a few other things

The most important thing in Env are the AWS Auth credentials. There are multiple ways of creating these credentials, but the easiest is to use the Discover mechanism provided by the library.

Listing Lambda Functions

Listing Lambda Functions is straightforward and can be achieved with the following code snippet:

Let’s write a wrapper on top of listFunctions, that prints a well-formatted listing of Lambda Functions on the console.

Creating a Lambda Function

There are two steps to creating a Lambda Function:

  1. Create a ZIP file containing the Lambda Function’s code, along with all other dependencies (like custom libraries, executables, etc)
  2. Upload the ZIP file using the createFunction API call while providing a bunch of other meta-data required by AWS Lambda.

Creating a ZIP file containing the Lambda Function’s code

You may choose to create a ZIP file using shell commands or other utilities, but in the spirit of reducing the number of moving parts, we will be creating even the ZIP file using the zip Haskell library.

Note: This function will silently overwrite the existing /tmp/ file

Uploading the ZIP file

Please make sure you read the section on credentials first

Next, we will create a Lambda Function from a simple JS file.

Another important aspect is that, while a Lambda Function refers to a single function, the ZIP file is permitted to have multiple function definitions. Which function to use, is determined by the handler that you configure while creating the Lambda Function.

So, let’s call our function myNodeFunction which will correspdond to the myHandler function defined in the index.js file.

Finally, here is the code to create the Lambda Function using the node10.x runtime

Note: You will need to use the unofficial fork of the library for this to work. More details here

Invoking a Lambda Function “directly”

As I had mentioned earlier, it is not necessary to use API Gateway to invoke your Lambda Function. Use an API Gateway or CloudFormation or suchlike ONLY IF it is really required.

In the spirit of reducing moving parts, here’s how to invoke the Lambda Function “directly”:

Updating a Lambda Function’s code

createFunction throws an error if you call it with an existing function name. We have to use updateFunctionCode and updateFunctionConfiguration to update an existing function. Here’s how to update an existing Lambda Function…

“Upsert” a Lambda Function’s code

… and here’s a unified function to “upsert” (create OR insert) a Lambda Function: