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.