Chalice
Chalice is the “Python Serverless Microframework for AWS”. It allows quick and simple development of REST APIs, and comes with a a deploy tool that does all the work necessary to deploy your lambda, as well as create policy and integrate with the API Gateway. Let’s start out by creating an example app:
$ chalice new-project example-app
And then we can quickly create a simple app that reads in a couple of parameters and creates a JSON response. Note the use of the decorator to declare the route and method:
from chalice import Chalice
app = Chalice(app_name= 'example-app' )
@app .route( '/customer/{customer_id}/order/{order_id}' , methods=[ 'PUT' ])
def register_order(customer_id, order_id):
return { 'customer' :customer_id ,
"order_id" : order_id }
|
Deploying this as simple as:
$ chalice deploy
Creating role: example-app-dev
Creating deployment package.
Creating lambda function: example-app-dev
Initiating first time deployment.
Deploying to API Gateway stage: api
https://id.execute-api.us-west-2.amazonaws.com/api/
At which point you can access it like this:
$ curl -X PUT https://id.execute-api.us-west-2.amazonaws.com/api/customer/customer01/order/123183123
{"customer": "customer01", "order_id": "123183123"}
See the tutorial, which is quite good, for more information on what you can do inside the apps (such as tying in other AWS services).
Enter Terraform
But what if you want to use Terraform to deploy your infrastructure? The first step is to create a deployment package:
$ chalice package .
Creating deployment package.
$ ls -l deployment.zip
-rw-r--r-- 1 nchazin staff 9022 Oct 18 22:24 deployment.zip
And now we’re ready to code up our terraform. We’ll begin by defining a few variables, which we can store values for in a terraform.tfvars file.
variable
"environment"
{
description =
"AWS account environment environment for the lambda and api gateway)"
}
variable
"region"
{
description =
"AWS region"
default =
"us-west-2"
}
variable
"account_id"
{
description =
"AWS account id of the environment"
}
Next we’ll define the lambda itself, along with its associated role, and a policy which allows us to log and monitor with Cloudwatch:
provider "aws" {
profile = "${var.environment}"
region = "${var.region}"
}
resource "aws_iam_role" "lambda_example_app_role" {
name = "lambda_example_app_role"
assume_role_policy = << EOF
{
"Version" : "2012-10-17" ,
"Statement" : [
{
"Action" : "sts:AssumeRole" ,
"Principal" : {
"Service" : "lambda.amazonaws.com"
},
"Effect" : "Allow" ,
"Sid" : ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "lambda_example_app_role_policy" {
name = "lambda_example_app_role_policy"
role = "${aws_iam_role.lambda_example_app_role.id}"
policy = << EOF
{
"Version" : "2012-10-17" ,
"Statement" : [
{
"Effect" : "Allow" ,
"Action" : [
"cloudwatch:PutMetricData" ,
],
"Resource" : "*"
},
{
"Effect" : "Allow" ,
"Action" : [
"logs:CreateLogGroup" ,
"logs:CreateLogStream" ,
"logs:PutLogEvents"
],
"Resource" : "arn:aws:logs:*:*:*"
}
]
}
EOF
}
resource "aws_lambda_function" "example_app" {
function_name = "example_app"
filename = "deployment.zip"
description = "An example app"
role = "${aws_iam_role.lambda_example_app_role.arn}"
handler = "app.app"
timeout = 300
runtime = "python3.6"
}
|
With our lambda set up, we can create out API Gateway:
resource
"aws_api_gateway_rest_api"
"example_api"
{
name =
"CustomerOrderAPI"
description =
"API Gateway to register customer orders"
}
/*
these four blocks declare the path
for
our api
-------------------------------------------------------------------------
*/
resource
"aws_api_gateway_resource"
"customer"
{
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
parent_id =
"${aws_api_gateway_rest_api.example_api.root_resource_id}"
path_part =
"customer"
}
resource
"aws_api_gateway_resource"
"customer_id"
{
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
parent_id =
"${aws_api_gateway_resource.customer.id}"
path_part =
"{customer_id}"
}
resource
"aws_api_gateway_resource"
"order"
{
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
parent_id =
"${aws_api_gateway_resource.customer_id.id}"
path_part =
"order"
}
resource
"aws_api_gateway_resource"
"order_id"
{
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
parent_id =
"${aws_api_gateway_resource.order.id}"
path_part =
"{order_id}"
}
/*
-------------------------------------------------------------------------
*/
resource
"aws_api_gateway_method"
"example_method"
{
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
resource_id =
"${aws_api_gateway_resource.order_id.id}"
http_method =
"PUT"
authorization =
"NONE"
}
resource
"aws_api_gateway_integration"
"example_api_integration"
{
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
resource_id =
"${aws_api_gateway_resource.order_id.id}"
http_method =
"${aws_api_gateway_method.example_method.http_method}"
integration_http_method =
"POST"
type =
"AWS_PROXY"
uri =
"${aws_lambda_function.example_app.invoke_arn}"
}
resource
"aws_api_gateway_deployment"
"example_deployment_dev"
{
depends_on = [
"aws_api_gateway_method.example_method"
,
"aws_api_gateway_integration.example_api_integration"
,
]
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
stage_name =
"dev"
}
resource
"aws_api_gateway_deployment"
"example_deployment_prod"
{
depends_on = [
"aws_api_gateway_method.example_method"
,
"aws_api_gateway_integration.example_api_integration"
,
]
rest_api_id =
"${aws_api_gateway_rest_api.example_api.id}"
stage_name =
"api"
}
output
"dev_url"
{
}
output
"prod_url"
{
}
The final piece is the permission that allows our API Gateway to access the lambda. Note that you can be more specific and limit acess to the specific API method and path if desired by adding them to the source_arn.
Now we can install our API:
$ terraform apply
...
Apply complete! Resources:
13
added,
0
changed,
0
destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify
and
destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path:
Outputs:
And query it at both the stage and prod endpoints:
{
"customer"
:
"aaa"
,
"order_id"
:
"bbb"
}
{
"customer"
:
"yoyodyne"
,
"order_id"
:
"100"
}