AWS Serverless Application Model : Guide to writing your first AWS SAM Application

AWS Serverless Application Model : Guide to writing your first AWS SAM Application

Today we are going to see how one can write a serverless application using Serverless Application Model (SAM). First, let's understand what Serverless Application Model is.

What is Serverless Application Model?

What do you do when you want to create a resource on AWS, for example, Lambda function? You log in to the AWS console and manually go to the Lambda service to create a function. Now say you want to create 10 such functions you have to do the same process 10 times. This can be error-prone and time-consuming. With SAM you can just write once and deploy as many times you want flawlessly. It is similar to Cloud Formation. You can say SAM is the superset of Cloudformation.

The AWS Serverless Application Model (SAM) is an open-source framework for building serverless applications. It provides shorthand syntax to express functions, APIs, databases, and event source mappings. With just a few lines of code, you can define the application you want and model it using YAML. During deployment, SAM transforms and expands the SAM syntax into AWS CloudFormation syntax, enabling you to build serverless applications faster.

With the use of Environment variables and parameters, you can dynamically name all your resources and create different environments like DEV, TEST, PROD with the same set of resources.

Getting started with SAM

To use SAM you will need SAM-CLI installed. SAM CLI provides a Lambda-like execution environment that lets you locally build, test, and debug applications defined by SAM templates. You can also use the SAM CLI to deploy your applications to AWS. You can follow the guide here to install SAM CLI. Before we proceed make sure your user has all the permissions to create resources and configured on the machine. To configure AWS account, install AWS CLI and run aws configure command to set up credentials. I am using the admin user that has all the permissions.

Now we are ready to download sample application and examine its contents. Run the following command on terminal

sam init

It will ask you to make several choices. Note: you need to have your runtime already installed.

ubuntu@ubuntu-VirtualBox:~/MyWorkspace/BlogPosts/SAM$ 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.al2
    13 - java8
    14 - dotnetcore2.1
Runtime: 2

Project name [sam-app]: SAM application

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
    5 - Elastic File System Sample App
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: SAM application
Runtime: python3.8
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./SAM application/README.md

Go to the file system and it would have created following file structure:

SAM application/
   ├── README.md
   ├── events/
   │   └── event.json
   ├── hello_world/
   │   ├── __init__.py
   │   ├── app.py            #Contains your AWS Lambda handler logic.
   │   └── requirements.txt  #Contains any Python dependencies the application requires, used for sam build
   ├── template.yaml         #Contains the AWS SAM template defining your application's AWS resources.
   └── tests/
       └── unit/
           ├── __init__.py
           └── test_handler.py

Open template.yml and let's breakdown the contents:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  SAM application

  Sample SAM Template for SAM application

# 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.lambda_handler
      Runtime: python3.8
      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

AWSTemplateFormatVersion - The first line always stays the same for every template.yml.

Transform - It specifies one or more macros that AWS CloudFormation uses to process your template. This is a required section.

Description is self-explanatory.

Globals - It is unique to the SAM and defines properties shared by the whole template. In this case, the timeout will apply to all functions. Globals don't exist in cloud formation.

Resources - You define all AWS resources under this. This is also a mandatory section. Here we have defined a function with the logical name HelloWorldFunction, it is of type AWS::Serverless::Function with a runtime of python 3.8, CodeUri points to the folder where our handler resides on the filesystem. Handler is the filename and the method. Events would create an API in the API Gateway with method type GET and path /hello.

Outputs- Describes the values that are returned and available for use after the stack is created. For example, in our template, HelloWorldApi output gives the API Url generated by the function. We are also setting Lambda and IAM Role ARN as output. Also note !Sub & GetAtt functions in output, !Sub is used to substitute a variable with a value. Here it will substitute ${AWS::Region} and ${ServerlessRestApi}. GetAtt function returns the value of an attribute from a resource in the template. Here we are asking for ARN of the HelloWorldfunction.

Now examine the app.py file. It is straightforward and returns "hello world" as a response.

import json

# import requests


def lambda_handler(event, context):
    """Sample pure Lambda function

    Parameters
    ----------
    event: dict, required
        API Gateway Lambda Proxy Input Format

        Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format

    context: object, required
        Lambda Context runtime methods and attributes

        Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html

    Returns
    ------
    API Gateway Lambda Proxy Output Format: dict

        Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
    """

    # try:
    #     ip = requests.get("http://checkip.amazonaws.com/")
    # except requests.RequestException as e:
    #     # Send some context about this error to Lambda Logs
    #     print(e)

    #     raise e

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
            # "location": ip.text.replace("\n", "")
        }),
    }

Get back to the terminal and cd into the directory where the template.yml resides, run the command: sam build

ubuntu@ubuntu-VirtualBox:~/MyWorkspace/BlogPosts/SAM/SAM application$ sam build
Building function 'HelloWorldFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

The sam build command builds any dependencies that your application has, and copies your application source code to folders under .aws-sam/build to be zipped and uploaded to Lambda. Check the contents of .aws-sam folder in project directory. It would be something similar to shown below. In HelloWorldFunction folder, there would be many other files along with app.py

.aws_sam/
   └── build/
       ├── HelloWorldFunction/
       └── template.yaml

Now its time to run sam deploy -g. It will ask you couple of configuration information like application name, region name where you want to create the stack, etc, and will wait for your confirmation if you asked for confirmation before deploy.

buntu@ubuntu-VirtualBox:~/MyWorkspace/BlogPosts/SAM/SAM application$ sam deploy -g

Configuring SAM deploy
======================

    Looking for samconfig.toml :  Not found

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sam-app]: my-first-sam-app
    AWS Region [us-east-1]: 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-1xyg1t2j2ws5k
        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
Uploading to my-first-sam-app/9601808ead19b558184dcec8285866a3  538937 / 538937.0  (100.00%)

    Deploying with following values
    ===============================
    Stack name                 : my-first-sam-app
    Region                     : us-east-1
    Confirm changeset          : True
    Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-1xyg1t2j2ws5k
    Capabilities               : ["CAPABILITY_IAM"]
    Parameter overrides        : {}

Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Uploading to my-first-sam-app/362accae02d25f5921348967d73b9d29.template  1115 / 1115.0  (100.00%)

Waiting for changeset to be created..
CloudFormation stack changeset
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                                          LogicalResourceId                                                  ResourceType                                                     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                                              HelloWorldFunctionHelloWorldPermissionProd                         AWS::Lambda::Permission                                          
+ 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:<account #>:changeSet/samcli-deploy1599948737/03d65ab9-a943-494d-8db6-abf6aad17537


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]:

Once you confirm, it will start creating stack. Below is the output after the stack creation completes:

2020-09-12 17:13:45 - 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_IN_PROGRESS                                AWS::Lambda::Function                             HelloWorldFunction                                Resource creation Initiated                     
CREATE_COMPLETE                                   AWS::Lambda::Function                             HelloWorldFunction                                -                                               
CREATE_IN_PROGRESS                                AWS::ApiGateway::RestApi                          ServerlessRestApi                                 -                                               
CREATE_IN_PROGRESS                                AWS::ApiGateway::RestApi                          ServerlessRestApi                                 Resource creation Initiated                     
CREATE_COMPLETE                                   AWS::ApiGateway::RestApi                          ServerlessRestApi                                 -                                               
CREATE_IN_PROGRESS                                AWS::Lambda::Permission                           HelloWorldFunctionHelloWorldPermissionProd        Resource creation Initiated                     
CREATE_IN_PROGRESS                                AWS::ApiGateway::Deployment                       ServerlessRestApiDeployment47fc2d5f9d             -                                               
CREATE_IN_PROGRESS                                AWS::Lambda::Permission                           HelloWorldFunctionHelloWorldPermissionProd        -                                               
CREATE_COMPLETE                                   AWS::ApiGateway::Deployment                       ServerlessRestApiDeployment47fc2d5f9d             -                                               
CREATE_IN_PROGRESS                                AWS::ApiGateway::Deployment                       ServerlessRestApiDeployment47fc2d5f9d             Resource creation Initiated                     
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                           HelloWorldFunctionHelloWorldPermissionProd        -                                               
CREATE_COMPLETE                                   AWS::CloudFormation::Stack                        my-first-sam-app                                  -                                               
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                                                                
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole                                                                                                                                                          
Description         Implicit IAM Role created for Hello World function                                                                                                                                 
Value               arn:aws:iam::<account #>:role/my-first-sam-app-HelloWorldFunctionRole-27OIM6WD99F0                                                                                                

Key                 HelloWorldApi                                                                                                                                                                      
Description         API Gateway endpoint URL for Prod stage for Hello World function                                                                                                                   
Value               https://3kuuurt63m.execute-api.us-east-1.amazonaws.com/Prod/hello/                                                                                                                 

Key                 HelloWorldFunction                                                                                                                                                                 
Description         Hello World Lambda Function ARN                                                                                                                                                    
Value               arn:aws:lambda:us-east-1:<account #>:function:my-first-sam-app-HelloWorldFunction-1U5YD9NICU5LP                                                                                   
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - my-first-sam-app in us-east-1

As you can see in the outputs, the HelloWorldApi URL was generated which you can hit in the browser and verify your API is working fine. You can also see the same information in Cloud Formation's output section on AWS console.

Great Job! You have successfully completed your First SAM App!

What else you can do in SAM?

This is not the end, you can also test the function locally before deploy. The AWS SAM CLI provides the sam local command to run your application using Docker containers that simulate the execution environment of Lambda. I am on linux machine, hence I am going to run command : sudo /home/linuxbrew/.linuxbrew/bin/sam local start-api

SAM CLI now collects telemetry to better understand customer needs.

    You can OPT OUT and disable telemetry collection by setting the
    environment variable SAM_CLI_TELEMETRY=0 in your shell.
    Thanks for your help!

    Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

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-09-12 22:31:06  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

When you hit the URL(127.0.0.1:3000/hello) in the browser, you should see the same response as before "hello world" and on the terminal, it should show something similar to this

Invoking app.lambda_handler (python3.8)
Failed to download a new amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.1.0 image. Invoking with the already downloaded image.
Mounting /home/ubuntu/MyWorkspace/BlogPosts/SAM/SAM application/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: f4945824-0a1a-1742-2f46-4d0bc2bbe515 Version: $LATEST
END RequestId: f4945824-0a1a-1742-2f46-4d0bc2bbe515
REPORT RequestId: f4945824-0a1a-1742-2f46-4d0bc2bbe515    Init Duration: 216.83 ms    Duration: 2.88 ms    Billed Duration: 100 ms    Memory Size: 128 MB    Max Memory Used: 24 MB    
No Content-Type given. Defaulting to 'application/json'.
2020-09-12 22:38:28 127.0.0.1 - - [12/Sep/2020 22:38:28] "GET /hello HTTP/1.1" 200 -

Where to go from here?

This was a very basic SAM application but I hope you got the gist of it. As an exercise, you can add more resources like CloudFront, Route53, DynamoDB, ACM etc, and see how to model it in YAML. Take a look here and here for more complex SAM Templates.

If you like my articles feel free to follow me on Twitter for updates!