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!