AWS Developer Tools Blog
AWS Chalice Now Supports YAML Templates
Version 1.15.0 of AWS Chalice, a framework for writing serverless applications in Python, adds support for YAML when generating and merging AWS Serverless Application Model (SAM) templates. This allows you to add additional AWS resources to your Chalice application.
As part of deploying a Chalice application, you can generate a SAM template that represents your app. In previous versions of Chalice, this generated a JSON file. If you wanted to create additional resources you could provide a JSON file that would get merged with the SAM template that Chalice creates. However, there are several benefits to writing a SAM template in YAML. YAML is designed to be human readable, it’s more succinct than JSON, and you can use abbreviated tag-based syntax for certain AWS CloudFormation functions such as !GetAtt
, !Ref
, and !Sub
.
We’ll look at an example of using this new functionality by creating a REST API that’s backed by an Amazon DynamoDB table. We’ll create our DynamoDB table by writing a SAM template that’s merged with our Chalice application.
Initial setup
First, we’ll create a virtual environment and install Chalice. We’ll be using Python 3.7 in this example.
$ python3 --version
Python 3.7.3
$ python3 -m venv venv37
$ . venv37/bin/activate
(venv37) $ pip install chalice
Collecting chalice
...
Successfully installed chalice-1.15.0
We should now have Chalice version 1.15.0 installed.
(venv37) $ chalice --version
chalice 1.15.0, python 3.7.3, darwin 18.7.0
Creating an application
Next we’ll create a new project using the chalice new-project
command and cd
into this new directory.
(venv37) $ chalice new-project yamlmerge
(venv37) $ cd yamlmerge/
The sample application generated by the chalice new-project
command will create a new REST API with Amazon API Gateway that’s backed by an AWS Lambda function. To add a DynamoDB table to our application we’ll specify a SAM template that we want to be included in our Chalice application.
Create a resources.yaml
file in the application directory with this content:
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Environment:
Variables:
TABLE_NAME:
Ref: DemoTable
Resources:
DemoTable:
Type: AWS::Serverless::SimpleTable
DynamoDBAccess:
Type: AWS::IAM::Policy
Properties:
PolicyName: AddDDBAccess
Roles:
- Ref: DefaultRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- dynamodb:PutItem
- dynamodb:GetItem
Effect: Allow
Resource:
Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${DemoTable}
In order to integrate a DynamoDB table into our Chalice app there are three things we must specify in our resources.yaml
file. First we have to specify the Dynamodb table resource. In our case we’re using the AWS::Serverless::SimpleTable
resource from SAM. Next, we have to map the name of the DynamoDB table into the environment variables of our Lambda function. To do this, we’re using the Globals
section which will add the TABLE_NAME
environment variable into all the Lambda functions we create in our Chalice app. Finally, we need to add a policy to our IAM role associated with our Lambda function that gives this role access to the DynamoDB table we’ve created.
Now we can write our application code. The name of the DynamoDB table we created in our resources.yaml
file is available through the TABLE_NAME
environment variable in our application.
import os
import boto3
from chalice import Chalice, NotFoundError
app = Chalice(app_name='yamlmerge')
app.debug = True
_TABLE = None
def get_table_resource():
global _TABLE
if _TABLE is None:
table_name = os.environ.get('TABLE_NAME', '')
_TABLE = boto3.resource('dynamodb').Table(table_name)
return _TABLE
@app.route('/item/{id}', methods=['PUT'])
def create_item(id):
record = {'id': id, **app.current_request.json_body}
table = get_table_resource()
table.put_item(Item=record)
@app.route('/item/{id}', methods=['GET'])
def get_item(id):
table = get_table_resource()
response = table.get_item(Key={'id': id}).get('Item')
if response is not None:
return response
else:
raise NotFoundError(id)
The application code creates an /item/{id}
endpoint where you can send HTTP PUT and GET requests. When we receive a PUT request we’ll store the JSON body in our DynamoDB table. When we receive a GET request we’ll query the DynamoDB table for a matching record and return it back to the user as JSON.
Now we’re ready to deploy our application. We’ll use AWS CloudFormation to deploy our app. The first thing we need to do is package our Chalice application as a SAM template using the chalice package
command. In the latest version of Chalice, 1.15.0, we’ve added a new --template-format
option along with the ability for --merge-template
to accept a YAML template file to merge. If you provide a file name that ends with .yaml
/.yml
to the --merge-template
option Chalice will automatically switch to generating a YAML template for you.
Deploying our application
(venv37) $ chalice package --merge-template resources.yaml out/
When we cd
into this directory, we’ll see that instead of the normal sam.json
file, we’ll now have a sam.yaml
file which also includes the contents of our resources.yaml
file.
(venv37) $ cd out
(venv37) $ tree
.
├── deployment.zip
└── sam.yaml
To deploy our application we’ll use the AWS CLI v2. If you don’t have the AWS CLI v2 installed, see the installation docs in the user guide.
(venv37) $ # Note the Amazon S3 bucket name below needs to be unique.
(venv37) $ aws s3 mb s3://myapp-bucket-location
(venv37) $ aws cloudformation package --template-file sam.yaml \
--s3-bucket myapp-bucket-location --output-template-file packaged.yaml
(venv37) aws cloudformation deploy --template-file packaged.yaml \
--stack-name MyChaliceApp --capabilities CAPABILITY_IAM
Once this finishes deploying we can query the stack’s outputs to get the URL of our REST API.
(venv37) $ aws cloudformation describe-stacks --stack-name MyChaliceApp \
--query "Stacks[0].Outputs[?OutputKey=='EndpointURL'].OutputValue | [0]"
"https://abcd.execute-api.us-west-2.amazonaws.com/api/"
Testing our application
We can test our application by sending HTTP PUT and GET requests to that endpoint URL. We’ll use the httpie package.
(venv37) $ pip install httpie
Collecting httpie
...
Successfully installed httpie-2.1.0
First we’ll create a record by sending a PUT request to the /item/1
URL.
$ echo '{"name": "james"}' | http PUT https://abcd.execute-api.us-west-2.amazonaws.com/api/item/1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 4
Content-Type: application/json
...
null
Now we’ll verify that we can successfully retrieve this record by sending a GET request to the /item/1
URL.
$ http GET https://abcd.execute-api.us-west-2.amazonaws.com/api/item/1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 25
Content-Type: application/json
...
{
"id": "1",
"name": "james"
}
Next steps
Whenever we make a change to our application we rerun the same packaging and deploy commands to redeploy our application. We can combine these steps into a single script to simplify our deployment process. Create a deploy.sh
file with the following contents:
(venv37) $ cat > deploy.sh
#!/bin/bash
S3_BUCKET="myapp-bucket-location"
chalice package --merge-template resources.yaml out/
cd out/
aws cloudformation package --template-file sam.yaml --s3-bucket $S3_BUCKET \
--output-template-file packaged.yaml
aws cloudformation deploy --template-file packaged.yaml \
--stack-name MyChaliceApp --capabilities CAPABILITY_IAM
(venv37) $ chmod +x deploy.sh
Now whenever we make a change to our application code or our resources.yaml
template we can just run the ./deploy.sh
script and our application will be updated.
If we want to delete our application we can run the delete-stack
command.
(venv37) $ aws cloudformation delete-stack --stack-name MyChaliceApp
Wrapping up
Try out the new release of Chalice today and let us know what you think. You can share feedback with us on our GitHub repo.