Serverless Movie API: Development, Testing, and Security
1. Frontend Lambda Function Implementations
1.1 Filter by Movie Category
To implement a Lambda function that takes a movie category as input and returns a list of movies corresponding to that category, use the following code:
func filter(category string)(events.APIGatewayProxyResponse, error) {
...
filter := expression.Name("category").Equal(expression.Value(category))
projection := expression.NamesList(expression.Name("id"), expression.Name("name"),
expression.Name("description"))
expr, err := expression.NewBuilder().WithFilter(filter).WithProjection(projection).Build()
if err != nil {
return events.APIGatewayProxyResponse {
StatusCode: http.StatusInternalServerError,
Body: "Error while building DynamoDB expression",
}, nil
}
svc := dynamodb.New(cfg)
req := svc.ScanRequest(&dynamodb.ScanInput {
TableName: aws.String(os.Getenv("TABLE_NAME")),
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
FilterExpression: expr.Filter(),
ProjectionExpression: expr.Projection(),
})
...
}
1.2 Filter by Movie Title
To implement a Lambda function that takes a movie’s title as input and returns all movies with the keyword in their title, use the following code:
func filter(keyword string) (events.APIGatewayProxyResponse, error) {
...
filter := expression.Name("name").Contains(keyword)
projection := expression.NamesList(expression.Name("id"), expression.Name("name"),
expression.Name("description"))
expr, err := expression.NewBuilder().WithFilter(filter).WithProjection(projection).Build()
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusInternalServerError,
Body: "Error while building DynamoDB expression",
}, nil
}
svc := dynamodb.New(cfg)
req := svc.ScanRequest(&dynamodb.ScanInput{
TableName: aws.String(os.Getenv("TABLE_NAME")),
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
FilterExpression: expr.Filter(),
ProjectionExpression: expr.Projection(),
})
...
}
1.3 Delete and Edit Movie Buttons
- Delete Button : Update the
MoviesAPIservice to include the following function to implement a delete button on the web application to delete a movie by calling theDeleteMovieLambda function from API Gateway.
delete(id: string){
return this.http
.delete(`${environment.api}/${id}`, {headers: this.getHeaders()})
.map(res => {
return res
})
}
- Edit Button : To implement an edit button on the web application to allow the user to update movie attributes, use the following function.
update(movie: Movie){
return this.http
.put(environment.api, JSON.stringify(movie), {headers: this.getHeaders()})
.map(res => {
return res
})
}
1.4 CI/CD Workflow for API Gateway Documentation
To implement a CI/CD workflow with either CircleCI, Jenkins, or CodePipeline to automate the generation and deployment of the API Gateway documentation, use the following code:
def bucket = 'movies-api-documentation'
def api_id = ''
node('slaves'){
stage('Generate'){
if (env.BRANCH_NAME == 'master') {
sh "aws apigateway get-export --rest-api-id ${api_id} \
--stage-name production \
--export-type swagger swagger.json"
}
else if (env.BRANCH_NAME == 'preprod') {
sh "aws apigateway get-export --rest-api-id ${api_id} \
--stage-name staging \
--export-type swagger swagger.json"
} else {
sh "aws apigateway get-export --rest-api-id ${api_id} \
--stage-name sandbox \
--export-type swagger swagger.json"
}
}
stage('Publish'){
sh "aws s3 cp swagger.json s3://${bucket}"
}
}
2. Testing Serverless Application
2.1 Unit Tests for Lambda Functions
- UpdateMovie Lambda Function :
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-lambda-go/events"
)
func TestUpdate_InvalidPayLoad(t *testing.T) {
input := events.APIGatewayProxyRequest{
Body: "{'name': 'avengers'}",
}
expected := events.APIGatewayProxyResponse{
StatusCode: 400,
Body: "Invalid payload",
}
response, _ := update(input)
assert.Equal(t, expected, response)
}
func TestUpdate_ValidPayload(t *testing.T) {
input := events.APIGatewayProxyRequest{
Body: "{\"id\":\"40\", \"name\":\"Thor\", \"description\":\"Marvel movie\", \"cover\":\"poster url\"}",
}
expected := events.APIGatewayProxyResponse{
Body: "{\"id\":\"40\", \"name\":\"Thor\", \"description\":\"Marvel movie\", \"cover\":\"poster url\"}",
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
response, _ := update(input)
assert.Equal(t, expected, response)
}
- DeleteMovie Lambda Function :
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-lambda-go/events"
)
func TestDelete_InvalidPayLoad(t *testing.T) {
input := events.APIGatewayProxyRequest{
Body: "{'name': 'avengers'}",
}
expected := events.APIGatewayProxyResponse{
StatusCode: 400,
Body: "Invalid payload",
}
response, _ := delete(input)
assert.Equal(t, expected, response)
}
func TestDelete_ValidPayload(t *testing.T) {
input := events.APIGatewayProxyRequest{
Body: "{\"id\":\"40\", \"name\":\"Thor\", \"description\":\"Marvel movie\", \"cover\":\"poster url\"}",
}
expected := events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
response, _ := delete(input)
assert.Equal(t, expected, response)
}
2.2 Modifying Jenkinsfile for Unit Tests
To modify the Jenkinsfile to include the execution of automated unit tests, use the following code:
def bucket = 'movies-api-deployment-packages'
node('slave-golang'){
stage('Checkout'){
checkout scm
}
stage('Test'){
sh 'go get -u github.com/golang/lint/golint'
sh 'go get -t ./...'
sh 'golint -set_exit_status'
sh 'go vet .'
sh 'go test .'
}
stage('Build'){
sh 'GOOS=linux go build -o main main.go'
sh "zip ${commitID()}.zip main"
}
stage('Push'){
sh "aws s3 cp ${commitID()}.zip s3://${bucket}"
}
stage('Deploy'){
sh "aws lambda update-function-code --function-name FindAllMovies \
--s3-bucket ${bucket} \
--s3-key ${commitID()}.zip \
--region us-east-1"
}
}
def commitID() {
sh 'git rev-parse HEAD > .git/commitID'
def commitID = readFile('.git/commitID').trim()
sh 'rm .git/commitID'
commitID
}
2.3 Modifying buildspec.yml for Unit Tests
To modify the buildspec.yml definition file to include the execution of unit tests before pushing the deployment package to S3 using AWS CodeBuild, use the following code:
version: 0.2
env:
variables:
S3_BUCKET: "movies-api-deployment-packages"
PACKAGE: "github.com/mlabouardy/lambda-codepipeline"
phases:
install:
commands:
- mkdir -p "/go/src/$(dirname ${PACKAGE})"
- ln -s "${CODEBUILD_SRC_DIR}" "/go/src/${PACKAGE}"
- go get -u github.com/golang/lint/golint
pre_build:
commands:
- cd "/go/src/${PACKAGE}"
- go get -t ./...
- golint -set_exit_status
- go vet .
- go test .
build:
commands:
- GOOS=linux go build -o main
- zip $CODEBUILD_RESOLVED_SOURCE_VERSION.zip main
- aws s3 cp $CODEBUILD_RESOLVED_SOURCE_VERSION.zip s3://$S3_BUCKET/
post_build:
commands:
- aws lambda update-function-code --function-name FindAllMovies --s3-bucket
$S3_BUCKET --s3-key $CODEBUILD_RESOLVED_SOURCE_VERSION.zip
2.4 SAM Template for Lambda Function
The following is a SAM template file for the FindAllMovies Lambda function:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
StageName:
Type: String
Default: staging
Description: The API Gateway deployment stage
Resources:
FindAllMovies:
Type: AWS::Serverless::Function
Properties:
Handler: main
Runtime: go1.x
Role: !GetAtt FindAllMoviesRole.Arn
CodeUri: ./findall/deployment.zip
Environment:
Variables:
TABLE_NAME: !Ref MoviesTable
Events:
AnyRequest:
Type: Api
Properties:
Path: /movies
Method: GET
RestApiId:
Ref: MoviesAPI
FindAllMoviesRole:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "sts:AssumeRole"
Principal:
Service:
- "lambda.amazonaws.com"
Policies:
-
PolicyName: "PushCloudWatchLogsPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
-
PolicyName: "ScanDynamoDBTablePolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:Scan
Resource: "*"
MoviesTable:
Type: AWS::Serverless::SimpleTable
Properties:
PrimaryKey:
Name: ID
Type: String
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
MoviesAPI:
Type: 'AWS::Serverless::Api'
Properties:
StageName: !Ref StageName
DefinitionBody:
swagger: 2.0
info:
title: !Sub API-${StageName}
paths:
/movies:
x-amazon-apigateway-any-method:
produces:
- application/json
x-amazon-apigateway-integration:
uri:
!Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-
31/functions/${FindAllMovies.Arn}:current/invocations"
passthroughBehavior: when_no_match
httpMethod: POST
type: aws_proxy
The following mermaid flowchart shows the general process of the CI/CD workflow for API Gateway documentation:
graph LR
A[Start] --> B{Branch Name}
B -- master --> C[Generate Swagger for Production]
B -- preprod --> D[Generate Swagger for Staging]
B -- other --> E[Generate Swagger for Sandbox]
C --> F[Publish to S3]
D --> F
E --> F
F --> G[End]
3. Securing Serverless Application
3.1 Integrating Social Logins
- Facebook Login :
- Create Facebook Application : Go to the Facebook Developers portal and create a new application.
- Copy App ID and Secret : After creating the application, copy the App ID and secret.
- Configure in Amazon Cognito : In the Amazon Cognito Console, configure Facebook as a provider.
- Add Login Button : Follow the Facebook Guide to add the Facebook login button to the web application.
- Fetch JWT Token : Once the user is authenticated, a Facebook session token will be returned. Add this token to the Amazon Cognito credentials provider to fetch a JWT token.
- Add to API Request : Finally, add the JWT token to the API Gateway request Authorization header.
- Twitter Login : Amazon Cognito does not support Twitter as an authentication provider out of the box. You will need to use OpenID Connect to extend Amazon Cognito.
- Google Login :
- Create Google Project : Create a new project from the Google Developers Console .
- Enable API and Create Client ID : Enable the Google API under APIs and auth, and then create an OAuth 2.0 client ID.
- Configure in Amazon Cognito : Configure Google in the Amazon Cognito Console.
- Add Sign - in Button : Follow the Google documentation for Web to add the Google sign - in button.
- Retrieve JWT Token : After user authentication, an authentication token will be generated, which can be used to retrieve the JWT token.
3.2 Account Creation and Password Reset
- Account Creation : A Go - based Lambda function can be created to handle the account creation workflow.
package main
import (
"os"
"github.com/aws/aws - lambda - go/lambda"
"github.com/aws/aws - sdk - go - v2/aws"
"github.com/aws/aws - sdk - go - v2/aws/external"
"github.com/aws/aws - sdk - go - v2/service/cognitoidentityprovider"
)
type Account struct {
Username string `json:"username"`
Password string `json:"password"`
}
func signUp(account Account) error {
cfg, err := external.LoadDefaultAWSConfig()
if err != nil {
return err
}
cognito := cognitoidentityprovider.New(cfg)
req := cognito.SignUpRequest(&cognitoidentityprovider.SignUpInput{
ClientId: aws.String(os.Getenv("COGNITO_CLIENT_ID")),
Username: aws.String(account.Username),
Password: aws.String(account.Password),
})
_, err = req.Send()
if err != nil {
return err
}
return nil
}
func main() {
lambda.Start(signUp)
}
- Password Reset : A Go - based Lambda function can be created to reset the user password.
package main
import (
"os"
"github.com/aws/aws - lambda - go/lambda"
"github.com/aws/aws - sdk - go - v2/aws"
"github.com/aws/aws - sdk - go - v2/aws/external"
"github.com/aws/aws - sdk - go - v2/service/cognitoidentityprovider"
)
type Account struct {
Username string `json:"username"`
}
func forgotPassword(account Account) error {
cfg, err := external.LoadDefaultAWSConfig()
if err != nil {
return err
}
cognito := cognitoidentityprovider.New(cfg)
req := cognito.ForgotPasswordRequest(&cognitoidentityprovider.ForgotPasswordInput{
ClientId: aws.String(os.Getenv("COGNITO_CLIENT_ID")),
Username: aws.String(account.Username),
})
_, err = req.Send()
if err != nil {
return err
}
return nil
}
func main() {
lambda.Start(forgotPassword)
}
4. Infrastructure as Code
4.1 Terraform Template for InsertMovie Lambda
# Setup execution role for the Lambda function
resource "aws_iam_role" "role" {
name = "InsertMovieRole"
assume_role_policy = "${file("assume - role - policy.json")}"
}
resource "aws_iam_policy" "cloudwatch_policy" {
name = "PushCloudWatchLogsPolicy"
policy = "${file("cloudwatch - policy.json")}"
}
resource "aws_iam_policy" "dynamodb_policy" {
name = "ScanDynamoDBPolicy"
policy = "${file("dynamodb - policy.json")}"
}
resource "aws_iam_policy_attachment" "cloudwatch - attachment" {
name = "cloudwatch - lambda - attchment"
roles = ["${aws_iam_role.role.name}"]
policy_arn = "${aws_iam_policy.cloudwatch_policy.arn}"
}
resource "aws_iam_policy_attachment" "dynamodb - attachment" {
name = "dynamodb - lambda - attchment"
roles = ["${aws_iam_role.role.name}"]
policy_arn = "${aws_iam_policy.dynamodb_policy.arn}"
}
# Create the Lambda function
resource "aws_lambda_function" "insert" {
function_name = "InsertMovie"
handler = "main"
filename = "function/deployment.zip"
runtime = "go1.x"
role = "${aws_iam_role.role.arn}"
environment {
variables {
TABLE_NAME = "movies"
}
}
}
# Expose a POST method on /movies resources in the REST API
resource "aws_api_gateway_method" "proxy" {
rest_api_id = "${var.rest_api_id}"
resource_id = "${var.resource_id}"
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda" {
rest_api_id = "${var.rest_api_id}"
resource_id = "${var.resource_id}"
http_method = "${aws_api_gateway_method.proxy.http_method}"
integration_http_method = "POST"
type = "AWS_PROXY"
uri = "${aws_lambda_function.insert.invoke_arn}"
}
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.insert.arn}"
principal = "apigateway.amazonaws.com"
source_arn ="${var.execution_arn}/*/*"
}
4.2 Updating CloudFormation Template
Add the following properties to the Resources section of the CloudFormation template to trigger the defined Lambda function with API Gateway in response to incoming HTTP requests.
API:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: API
FailOnWarnings: 'true'
DemoResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
ParentId:
'Fn::GetAtt': [API, RootResourceId]
PathPart: demo
RestApiId:
Ref: API
DisplayMessageMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
HttpMethod: GET
AuthorizationType: NONE
ResourceId:
Ref: DemoResource
RestApiId:
Ref: API
Integration:
Type: AWS
Uri: {'Fn::Join': ["", "- \"arn:aws:apigateway:\"\n-!Ref \"AWS::Region\"\n- \":lambda:path/\"\n- \"/2015 - 03 - 31/functions/\"\n- Fn::GetAtt:\n - HelloWorldFunction\n - Arn\n- \"/invocations\""]}
IntegrationHttpMethod: GET
4.3 SAM File for Serverless API
Resources:
FindAllMovies:
Type: AWS::Serverless::Function
Properties:
Handler: main
Runtime: go1.x
Role:!GetAtt FindAllMoviesRole.Arn
CodeUri:./findall/deployment.zip
Environment:
Variables:
TABLE_NAME:!Ref MoviesTable
Events:
AnyRequest:
Type: Api
Properties:
Path: /movies
Method: GET
RestApiId:
Ref: MoviesAPI
InsertMovie:
Type: AWS::Serverless::Function
Properties:
Handler: main
Runtime: go1.x
Role:!GetAtt InsertMovieRole.Arn
CodeUri:./insert/deployment.zip
Environment:
Variables:
TABLE_NAME:!Ref MoviesTable
Events:
AnyRequest:
Type: Api
Properties:
Path: /movies
Method: POST
RestApiId:
Ref: MoviesAPI
DeleteMovie:
Type: AWS::Serverless::Function
Properties:
Handler: main
Runtime: go1.x
Role:!GetAtt DeleteMovieRole.Arn
CodeUri:./delete/deployment.zip
Environment:
Variables:
TABLE_NAME:!Ref MoviesTable
Events:
AnyRequest:
Type: Api
Properties:
Path: /movies
Method: DELETE
RestApiId:
Ref: MoviesAPI
UpdateMovie:
Type: AWS::Serverless::Function
Properties:
Handler: main
Runtime: go1.x
Role:!GetAtt UpdateMovieRole.Arn
CodeUri:./update/deployment.zip
Environment:
Variables:
TABLE_NAME:!Ref MoviesTable
Events:
AnyRequest:
Type: Api
Properties:
Path: /movies
Method: PUT
RestApiId:
Ref: MoviesAPI
4.4 Configuring Terraform Remote State
- Create S3 Bucket : Use the following AWS CLI command to create an S3 bucket.
aws s3 mb s3://terraform - state - files --region us - east - 1
- Enable Server - Side Encryption :
aws s3api put - bucket - encryption --bucket terraform - state - files \
--server - side - encryption - configuration file://config.json
The config.json file should have the following content:
{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
}
- Configure Terraform :
terraform {
backend "s3" {
bucket = "terraform - state - files"
key = "KEY_NAME"
region = "us - east - 1"
}
}
4.5 CloudFormation Template for Serverless API
AWSTemplateFormatVersion: "2010 - 09 - 09"
Description: "Simple Lambda Function"
Parameters:
BucketName:
Description: "S3 Bucket name"
Type: "String"
TableName:
Description: "DynamoDB Table Name"
Type: "String"
Default: "movies"
Resources:
FindAllMoviesRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012 - 10 - 17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
-
PolicyName: "PushCloudWatchLogsPolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
-
PolicyName: "ScanDynamoDBTablePolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- dynamodb:Scan
Resource: "*"
FindAllMovies:
Type: "AWS::Lambda::Function"
Properties:
Code:
S3Bucket:!Ref BucketName
S3Key: findall - deployment.zip
FunctionName: "FindAllMovies"
Handler: "main"
Runtime: "go1.x"
Role:!GetAtt FindAllMoviesRole.Arn
Environment:
Variables:
TABLE_NAME:!Ref TableName
InsertMovieRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012 - 10 - 17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
-
PolicyName: "PushCloudWatchLogsPolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
-
PolicyName: "PutItemDynamoDBTablePolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- dynamodb:PutItem
Resource: "*"
InsertMovie:
Type: "AWS::Lambda::Function"
Properties:
Code:
S3Bucket:!Ref BucketName
S3Key: insert - deployment.zip
FunctionName: "InsertMovie"
Handler: "main"
Runtime: "go1.x"
Role:!GetAtt InsertMovieRole.Arn
Environment:
Variables:
TABLE_NAME:!Ref TableName
UpdateMovieRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012 - 10 - 17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
-
PolicyName: "PushCloudWatchLogsPolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
-
PolicyName: "PutItemDynamoDBTablePolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- dynamodb:PutItem
Resource: "*"
UpdateMovie:
Type: "AWS::Lambda::Function"
Properties:
Code:
S3Bucket:!Ref BucketName
S3Key: update - deployment.zip
FunctionName: "UpdateMovie"
Handler: "main"
Runtime: "go1.x"
Role:!GetAtt UpdateMovieRole.Arn
Environment:
Variables:
TABLE_NAME:!Ref TableName
DeleteMovieRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012 - 10 - 17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
-
PolicyName: "PushCloudWatchLogsPolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
-
PolicyName: "DeleteItemDynamoDBTablePolicy"
PolicyDocument:
Version: "2012 - 10 - 17"
Statement:
- Effect: Allow
Action:
- dynamodb:DeleteItem
Resource: "*"
DeleteMovie:
Type: "AWS::Lambda::Function"
Properties:
Code:
S3Bucket:!Ref BucketName
S3Key: update - deployment.zip
FunctionName: "DeleteMovie"
Handler: "main"
Runtime: "go1.x"
Role:!GetAtt DeleteMovieRole.Arn
Environment:
Variables:
TABLE_NAME:!Ref TableName
MoviesApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "MoviesApi"
FailOnWarnings: "true"
MoviesResource:
Type: "AWS::ApiGateway::Resource"
Properties:
ParentId:
Fn::GetAtt:
- "MoviesApi"
- "RootResourceId"
PathPart: "movies"
RestApiId:
Ref: MoviesApi
CreateMovieMethod:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "POST"
AuthorizationType: "NONE"
ResourceId:
Ref: MoviesResource
RestApiId:
Ref: MoviesApi
Integration:
Type: "AWS"
Uri:
Fn::Join:
- ""
- - "arn:aws:apigateway:"
-!Ref "AWS::Region"
- ":lambda:path/"
- "/2015 - 03 - 31/functions/"
- Fn::GetAtt:
- InsertMovie
- Arn
- "/invocations"
IntegrationHttpMethod: "POST"
DeleteMovieMethod:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "DELETE"
AuthorizationType: "NONE"
ResourceId:
Ref: MoviesResource
RestApiId:
Ref: MoviesApi
Integration:
Type: "AWS"
Uri:
Fn::Join:
- ""
- - "arn:aws:apigateway:"
-!Ref "AWS::Region"
- ":lambda:path/"
- "/2015 - 03 - 31/functions/"
- Fn::GetAtt:
- DeleteMovie
- Arn
- "/invocations"
IntegrationHttpMethod: "DELETE"
UpdateMovieMethod:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "PUT"
AuthorizationType: "NONE"
ResourceId:
Ref: MoviesResource
RestApiId:
Ref: MoviesApi
Integration:
Type: "AWS"
Uri:
Fn::Join:
- ""
- - "arn:aws:apigateway:"
-!Ref "AWS::Region"
- ":lambda:path/"
- "/2015 - 03 - 31/functions/"
- Fn::GetAtt:
- UpdateMovie
- Arn
- "/invocations"
IntegrationHttpMethod: "PUT"
ListMoviesMethod:
Type: "AWS::ApiGateway::Method"
Properties:
HttpMethod: "GET"
AuthorizationType: "NONE"
ResourceId:
Ref: MoviesResource
RestApiId:
Ref: MoviesApi
Integration:
Type: "AWS"
Uri:
Fn::Join:
- ""
- - "arn:aws:apigateway:"
-!Ref "AWS::Region"
- ":lambda:path/"
- "/2015 - 03 - 31/functions/"
- Fn::GetAtt:
- FindAllMovies
- Arn
- "/invocations"
IntegrationHttpMethod: "GET"
DynamoDBTable:
Type: "AWS::DynamoDB::Table"
Properties:
TableName:!Ref TableName
AttributeDefinitions:
-
AttributeName: "ID"
AttributeType: "S"
KeySchema:
-
AttributeName: "ID"
KeyType: "HASH"
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
4.6 Terraform Template for Serverless API
resource "aws_iam_role" "roles" {
count = "${length(var.functions)}"
name = "${element(var.functions, count.index)}Role"
assume_role_policy = "${file("policies/assume - role - policy.json")}"
}
resource "aws_iam_policy" "policies" {
count = "${length(var.functions)}"
name = "${element(var.functions, count.index)}Policy"
policy = "${file("policies/${element(var.functions, count.index)}-policy.json")}"
}
resource "aws_iam_policy_attachment" "policy - attachments" {
count = "${length(var.functions)}"
name = "${element(var.functions, count.index)}Attachment"
roles = ["${element(aws_iam_role.roles.*.name, count.index)}"]
policy_arn = "${element(aws_iam_policy.policies.*.arn, count.index)}"
}
resource "aws_lambda_function" "functions" {
count = "${length(var.functions)}"
function_name = "${element(var.functions, count.index)}"
handler = "main"
filename = "functions/${element(var.functions, count.index)}.zip"
runtime = "go1.x"
role = "${element(aws_iam_role.roles.*.arn, count.index)}"
environment {
variables {
TABLE_NAME = "${var.table_name}"
}
}
}
resource "aws_api_gateway_rest_api" "api" {
name = "MoviesAPI"
}
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
parent_id = "${aws_api_gateway_rest_api.api.root_resource_id}"
path_part = "movies"
}
resource "aws_api_gateway_deployment" "staging" {
depends_on = ["aws_api_gateway_integration.integrations"]
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
stage_name = "staging"
}
resource "aws_api_gateway_method" "proxies" {
count = "${length(var.functions)}"
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${aws_api_gateway_resource.proxy.id}"
http_method = "${lookup(var.methods, element(var.functions, count.index))}"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "integrations" {
count = "${length(var.functions)}"
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${element(aws_api_gateway_method.proxies.*.resource_id,
count.index)}"
http_method = "${element(aws_api_gateway_method.proxies.*.http_method,
count.index)}"
integration_http_method = "POST"
type = "AWS_PROXY"
uri = "${element(aws_lambda_function.functions.*.invoke_arn, count.index)}"
}
resource "aws_lambda_permission" "permissions" {
count = "${length(var.functions)}"
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = "${element(aws_lambda_function.functions.*.arn, count.index)}"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_deployment.staging.execution_arn}/*/*"
}
The following mermaid flowchart shows the general process of the Serverless API deployment using Terraform:
graph LR
A[Start] --> B[Create IAM Roles]
B --> C[Create IAM Policies]
C --> D[Attach Policies to Roles]
D --> E[Create Lambda Functions]
E --> F[Create API Gateway]
F --> G[Create API Resources and Methods]
G --> H[Create API Integrations]
H --> I[Set Lambda Permissions]
I --> J[Deploy API]
J --> K[End]
In conclusion, building a serverless movie API involves multiple aspects such as frontend development, testing, security, and infrastructure management. By following the steps and code snippets provided in this blog, you can create a robust and secure serverless application.
超级会员免费看
2191

被折叠的 条评论
为什么被折叠?



