Cloudinary Integration with Amazon Serverless Model (SAM)

It is now possible to provide backend Cloudinary functionality that is dependent on secret credentials via the web using Lambdas configured by SAM.

What is AWS SAM?

AWS provides the Serverless Application Model as a means toward specifying configurations for the deployment of Lambda functions that are accessible by the API Gateway. Without SAM the process of attaching the API Gateway to your lambda is manual. Without the API Gateway you can't access your function using HTTP.
We'll look at how to create, deploy and use functions created with a SAM template.

Getting Started

In order to bootstrap the creating of the SAM template and the custom (lambda) function, you should install Docker and the AWS SAM CLI. You'll see that there are SAM CLI commands that allow you to invoke your function locally for testing if you have Docket installed. See AWS Documentation for more information on Getting Started with SAM
Before looking at Cloudinary integration, let's master the steps of creating , testing and deploying a node.js HelloWorld template.
  1. 1.
    Install Docker.
  2. 2.
    Install the AWS CLI and the SAM CLI.
  3. 3.
    Create an S3 bucket to hold your function, for example aws s3 mb s3://my-bucket --region us-east-1
  4. 4.
    Create a stack name to manage the collection of resources in this application, for example aws cloudformation delete-stack --stack-name my-stack
  5. 5.
    Use sam init to create a HelloWorld application
% sam init Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location Choice: 1
Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - ruby2.5
12 - java8
13 - dotnetcore2.1
Runtime: 1
Project name [sam-app]: first-sam-app
Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git
AWS quick start application templates:
1 - Hello World Example
2 - Step Functions Sample App (Stock Trader)
3 - Quick Start: From Scratch
4 - Quick Start: Scheduled Events
5 - Quick Start: S3
6 - Quick Start: SNS
7 - Quick Start: SQS
8 - Quick Start: Web Backend Template
selection: 1
Generating application:
Name: first-sam-app
Runtime:
nodejs12.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./first-sam-app/README.md Super-powers are granted randomly so please submit an issue if you're not happy with yours.
The default Hello World function is a GET request that returns a stringified object {message:'hello world'}.
The steps we'll follow to understand testing and deployment are:
  1. 1.
    build a package from the template
  2. 2.
    build the package and create a .toml file
  3. 3.
    invoke the function locally (test uses Docker)
  4. 4.
    start a web server locally to test the function in browser or postman
  5. 5.
    deploy the function using information in the .toml file

1. Build a package from the Template

Look at YAML Template
Start by looking at the content of the .yaml file to become familiar with it. The function we're creating here will be called HelloWorldFunction. When accessing it with HTTP, the CodeUri tell us that we can find the code for the function in a subdirectory named hello-world. Under the Events|HellowWorld|Properties keys, we can see that we'll add the path /hello to call the function and we're using the get method. The Handler tells us that the is a file called app that exports a function named lambdaHandler. These names can be changed as you develop custom functions with custom paths.
You can have multiple functions defined in a single template. The Globals section is where you can declare config values that apply to all functions. We'll use this section later to add environment variables.
The Outputs section defines the how the api will be made accessible to the outside world. When deployment is complete, we get the URL that allows us to call the function using HTTP.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
first-sam-app
Sample SAM Template for first-sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
Look at app.js
Locate hello-world/app.js.
We're using node.js to export a function named lambdaHandler. The event will contain information about the event that triggered the function call. The context context information about invocation, function, and execution environment.
let response;
exports.lambdaHandler = async (event, context) => {
try {
response = {
'statusCode': 200,
'body': JSON.stringify({
message: 'hello world',
})
}
} catch (err) {
console.log(err);
return err;
}
return response
};
Let's build a package from the yaml template. Note: you can use yml or yaml as a file extension for you config files. The sam package command creates a zip that uploads to the s3 bucket. It returns information about the location of artifacts in AWS. You'll see the CodeUri prefixed with the bucket where it will be deployed.
sam package \
--template-file template.yaml \
--output-template-file package.yaml \
--s3-bucket my-bucket

2. Build the app and create .toml config for deployment

You can use the sam deploy --guided command to step through options. The first time you run it, you'll answer some questions and at the end, you be asked if you want to create a .toml file. If you create a .toml file you can read and modify the configuration for deployment there, so say Y .
By answering yes to Confirm changes before deploy, we will always have a chance to view the proposed deployment config before it is pushed out to AWS. This also means that future deployments will wait for this confirmation.
Confirm changes before deploy [y/N]: y
sam deploy --guided
Configuring SAM deploy
======================
Looking for samconfig.toml : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: first-sam-app
AWS Region [us-east-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
Save arguments to samconfig.toml [Y/n]: Y
Looking for resources needed for deployment: Found!
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1uf8fvmooev6z
A different default S3 bucket can be set in samconfig.toml
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Deploying with following values
===============================
Stack name : first-sam-app
Region : us-east-1
Confirm changeset : True
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1uf8fvmooev6z
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Initiating deployment
=====================
Error: Unable to upload artifact HelloWorldFunction referenced by CodeUri parameter of HelloWorldFunction resource.
S3 Bucket does not exist.
Note that we aren't setting up authorization and we haven't specified the bucket, so we message about that. We're not setting up authorizing so we can ignore that for now. Also, notice that the Stack name was created, but since we already created a stack, we want to use that name when we deploy. SAM is built on CloudFormation and you can find the stacks created listed there. We can edit the first-sam-app/samconfig.toml file to add the stack name and bucket name we created earlier. Later we'll add information about the environment variables to this config.
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "my-stack"
s3_bucket = "my-bucket"
s3_prefix = "my-stack"
region = "us-east-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
After you update the s3_bucket with your bucket name and stack-name and s3-prefix with your stack name you are ready to deploy.

3.Invoke the function locally

Specify the function name as it was configured under Resources.
sam local invoke HelloWorldFunction
This will execute the function using Docker and return the response to the command line.
Invoking app.lambdaHandler (nodejs12.x)
Fetching lambci/lambda:nodejs12.x Docker container image......
Mounting /Users/localuser/projects/aws2/first-sam-app/hello-world as /var/task:ro,delegated inside runtime container
START RequestId: 7ba0d6f6-8e06-1c1f-b6fc-ccc592b0d6b6 Version: $LATEST
END RequestId: 7ba0d6f6-8e06-1c1f-b6fc-ccc592b0d6b6
REPORT RequestId: 7ba0d6f6-8e06-1c1f-b6fc-ccc592b0d6b6 Init Duration: 148.81 ms Duration: 5.90 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 39 MB
{"statusCode":200,"body":"{\"message\":\"hello world\"}"}

4. Start a web server locally to test the function in browser or postman

sam local start-api
You'll get information on the command line about the server.
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions.
You do not need to restart/reload SAM CLI while working on your functions,
changes will be reflected instantly/automatically.
You only need to restart SAM CLI if you update your AWS SAM template
2020-06-12 12:52:28 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
You can use postman or, because this is a GET action, the browser to request the function call using HTTP.
Hello World Message from local lambda

5. Deploy the function using information in the .toml file

Execute the deploy command.
sam deploy
When this function is complete, you'll be able to review the output similar to what you see below.
sam deploy
Deploying with following values
===============================
Stack name : my-stack
Region : us-east-1
Confirm changeset : True
Deployment s3 bucket : my-bucket
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Initiating deployment
=====================
Uploading to my-stack/528a966c4e60e52b92e90d49bc736b77 127537 / 127537.0 (100.00%)
HelloWorldFunction may not have authorization defined.
Uploading to my-stack/928a4085d7664deafc969bea59e16436.template 1054 / 1054.0 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType
---------------------------------------------------------------------------------------------------------------------------
+ Add HelloWorldFunctionHelloWorldPermissionP AWS::Lambda::Permission
rod
+ Add HelloWorldFunctionRole AWS::IAM::Role
+ Add HelloWorldFunction AWS::Lambda::Function
+ Add ServerlessRestApiDeployment47fc2d5f9d AWS::ApiGateway::Deployment
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage
+ Add ServerlessRestApi AWS::ApiGateway::RestApi
---------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:us-east-1:697239927605:changeSet/samcli-deploy1591994501/42ca72d2-75b5-472b-8c71-9780b5c23b34
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2020-06-12 13:43:08 - Waiting for stack create/update to complete
CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated
CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction -
CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorld -
PermissionProd
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorld Resource creation Initiated
PermissionProd
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment4 -
7fc2d5f9d
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment4 -
7fc2d5f9d
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment4 Resource creation Initiated
7fc2d5f9d
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionHelloWorld -
PermissionProd
CREATE_COMPLETE AWS::CloudFormation::Stack first-sam-app -
-------------------------------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------------
Key HelloWorldFunctionIamRole
Description Implicit IAM Role created for Hello World function
Value arn:aws:iam::697239927605:role/cld-stack-HelloWorldFunctionRole-1ESSBB2JLUIS5
Key HelloWorldApi
Description API Gateway endpoint URL for Prod stage for Hello World function
Value https://whd3754g79.execute-api.us-east-1.amazonaws.com/Prod/hello/
Key HelloWorldFunction
Description Hello World Lambda Function ARN
Value arn:aws:lambda:us-east-1:697239927605:function:cld-stack-HelloWorldFunction-WLHH8OU16U1R
---------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - cld-stack in us-east-1
The output shows the creation of the resources. Future re-deployements of this package will show modifications and deletions.
At the bottom of the deployment report you'll find the URL that you can use to request that the function be executed under HellowWorldApi . In this example the URL is
https://whd3754g79.execute-api.us-east-1.amazonaws.com/Prod/hello/
Use the URL in form postman, cURL or the browser to test.

Next Steps

Now that we are familiar with the artifacts and processes needed to create and deploy a function using SAM, let look at how to integrate Cloudinary by using the node.js SDK in a SAM function.