AWS Serverless Application Model (SAM) is an open-source framework that allows you to simplify the development of Lambda functions for your serverless applications in AWS Cloud, where you need fine-grained control of your cloud infrastructure components.
SAM concept is very simple: it helps you to build and test your Lambda Functions, generate a CloudFormation template for your AWS infrastructure, and deploy everything. The central point of everything is an API Gateway, which is usually called your Lambda Functions in response to its queries.
In this article, you’ll see how to use SAM to do the same thing we did in the previous article, but with the help of a serverless framework.
Table of contents
Install AWS SAM
The installation process for all platforms is covered in official AWS documentation. As a Mac user, I’ll note the basic steps here:
brew tap aws/tap
brew install aws-sam-cli
Alternatively, you can install AWS SAM to your Python development environment:
pip install aws-sam-cli
Create Python Lambda Function
Project init setup is very simple and straightforward:
sam init --name my-sam-app --runtime python3.7 --app-template hello-world
SAM will ask additional interactive questions if you do not specify any of those parameters.
For Python lovers, there’re three template projects available:
- Hello World Example.
- EventBridge Hello World.
- EventBridge App from scratch (100+ Event Schemas).
We’ll use the first one for the beginning.
Let’s take a look at what’s inside our project:
cd my-sam-app
tree
.
├── README.md
├── events
│ └── event.json
├── hello_world
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── template.yaml
└── tests
└── unit
├── __init__.py
└── test_handler.py
So, we have here:
- The basic template of the README.md file.
events
folder contains a sample of JSON requests for testing the hello_world Lambda function.hello_world
folder contains the actual Lambda function code.tests
– no commentstemplate.yaml
– is similar to the CloudFormation template, which supports higher-level entities, like AWS::Serverless::Function, AWS::Serverless::LayerVersion, or AWS::Serverless::HttpApi, for example.
Let’s use the following simple Lambda function code:
import json
import logging
import time
import traceback
import requests
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)
def lambda_handler(event, context):
try:
LOGGER.info('Event structure: %s', event)
response = requests.get('https://ifconfig.co/json')
return {
'statusCode': 200,
'body': "{}".format(
response.text
)
}
except Exception as e:
traceback.print_exc()
response_data = {
'statusCode': 500,
'error': str(e)
}
return response_data
This function will send a request to https://ifconfig.co/json using requests (external Python library), get your IP address, and return it to you in JSON structure.
So, as requests is an external library, let’s put it to requirements.txt:
echo "requests" >> requirements.txt
Note: don’t forget to unit test the Lambda function code.
Build and test the Lambda function
To build our Lambda function, just run:
sam build
Testing Lambda function
To test our Lambda function locally, let’s run
sam local invoke HelloWorldFunction \
--event events/event.json
Testing API Gateway integration
To test Lambda <-> API Gateway integration locally, you need to run to start local API Gateway:
sam local start-api
Now you may use curl to test your API call:
curl http://127.0.0.1:3000/hello
Deploy Lambda function
As soon as the Lambda function is tested, you may deploy it. To do that, you need to have an S3 bucket created, where your Lambda function distribution will be uploaded.
Service S3 bucket
Let’s create such a bucket (we’ll use Python one-liner to make the bucket name random):
aws s3 mb "s3://my-sam-app-$(python -c "import random, string; print(''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(5)))")"
make_bucket: my-sam-app-6rz4m
For more information about managing Amazon S3 using AWS CLI, check out our How to use AWS CLI to manage Amazon S3 article.
Package project
Now we need to package all SAM project artifacts and compile the final CloudFormation template (my-sam-app-compiled-template.yaml)
sam package --output-template-file my-sam-app-compiled-template.yaml --s3-bucket my-sam-app-6rz4m
To this point in time, you’ll have a randomly named lambda function distribution file in your S3 bucket:
aws s3 ls s3://my-sam-app-6rz4m
2020-03-19 20:26:29 532327 73d2d8baa2978842e922f95f3ca1dce0
And generated my-sam-app-compiled-template.yaml file:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'my-sam-app
Sample SAM Template for my-sam-app
'
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://my-sam-app-6rz4m/73d2d8baa2978842e922f95f3ca1dce0
Handler: app.lambda_handler
Runtime: python3.7
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Description: API Gateway endpoint URL for Prod stage for Hello World function
Value:
Fn::Sub: https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/
HelloWorldFunction:
Description: Hello World Lambda Function ARN
Value:
Fn::GetAtt:
- HelloWorldFunction
- Arn
HelloWorldFunctionIamRole:
Description: Implicit IAM Role created for Hello World function
Value:
Fn::GetAtt:
- HelloWorldFunctionRole
- Arn
Deployment
Let’s deploy our function:
sam deploy --template-file my-sam-app-compiled-template.yaml \
--stack-name my-sam-app-stack \
--capabilities CAPABILITY_IAM
This will deploy:
- Lambda function role.
- API Gateway.
- Lambda Function.
Test deployment
Let’s query our lambda function using API Gateway:
aws cloudformation \
describe-stacks \
--stack-name my-sam-app-stack \
--query "Stacks[0].Outputs[?OutputKey=='HelloWorldApi'].OutputValue" \
--output text
https://0rppnkrtgg.execute-api.us-east-2.amazonaws.com/Prod/hello/
curl https://0rppnkrtgg.execute-api.us-east-2.amazonaws.com/Prod/hello/
Update Project
Imagine we changed our mind, and instead of getting our IP address, we want to send our users randomly generated passwords. Let’s use our python one-liner as an example and change our Lambda function.
Here’s the new content of our app.py file:
import json
import logging
import random
import string
import traceback
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)
def lambda_handler(event, context):
try:
LOGGER.info('Event structure: %s', event)
random_password = ''.join(
random.SystemRandom().choice(
string.ascii_lowercase + string.digits
) for _ in range(20)
)
return {
'statusCode': 200,
'body': json.dumps({
'random_password': f"{random_password}"
})
}
except Exception as e:
traceback.print_exc()
response_data = {
'statusCode': 500,
'error': str(e)
}
return response_data
Deploy Update
To deploy an update, all you need to do is to build (if your Lambda functions have been changed) and package the project:
sam build
sam package \
--output-template-file my-sam-app-compiled-template.yaml \
--s3-bucket my-sam-app-6rz4m
And finally, deploy the update:
sam deploy --template-file my-sam-app-compiled-template.yaml \
--stack-name my-sam-app-stack \
--capabilities CAPABILITY_IAM
And we may get our random password by curl-ing our API Gateway:
curl https://0rppnkrtgg.execute-api.us-east-2.amazonaws.com/Prod/hello/
Cleaning up
As soon as you’re done, you may clean up everything using the following command to delete the CloudFormation stack:
aws cloudformation delete-stack --stack-name my-sam-app-stack
And the following command to delete your bucket and artifacts:
aws s3 rb s3://my-sam-app-6rz4m --force
Summary
After looking through this article, I hope you better understand the development workflow where SAM is involved.
If you found this article useful, please, help us spread it to the world.
Stay tuned!