2020-07-09 17:27:30 -07:00
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Purpose
Shows how to use the AWS SDK for Python (Boto3) to use Amazon API Gateway to
create a REST API backed by a Lambda function.
Instead of using the low-level Boto3 client APIs shown in this example, you can use
AWS Chalice to more easily create a REST API.
For a working code example, see the `lambda/chalice_examples/lambda_rest` example
in this GitHub repo.
2020-07-24 16:04:24 -07:00
For more information about AWS Chalice, see https://github.com/aws/chalice.
2020-07-09 17:27:30 -07:00
"""
import calendar
import datetime
import json
import logging
2023-01-26 05:07:51 -08:00
import sys
2020-07-09 17:27:30 -07:00
import time
import boto3
from botocore . exceptions import ClientError
import requests
2023-01-26 05:07:51 -08:00
from lambda_basics import LambdaWrapper
# Add relative path to include demo_tools in this code example without need for setup.
2023-10-18 10:35:05 -07:00
sys . path . append ( " ../.. " )
2023-01-26 05:07:51 -08:00
from demo_tools . retries import wait
2020-07-09 17:27:30 -07:00
logger = logging . getLogger ( __name__ )
def create_rest_api (
2023-10-18 10:35:05 -07:00
apigateway_client ,
api_name ,
api_base_path ,
api_stage ,
account_id ,
lambda_client ,
lambda_function_arn ,
) :
2020-07-09 17:27:30 -07:00
"""
Creates a REST API in Amazon API Gateway. The REST API is backed by the specified
AWS Lambda function.
2020-07-24 16:04:24 -07:00
The following is how the function puts the pieces together, in order:
1. Creates a REST API in Amazon API Gateway.
2020-07-09 17:27:30 -07:00
2. Creates a ' /demoapi ' resource in the REST API.
3. Creates a method that accepts all HTTP actions and passes them through to
the specified AWS Lambda function.
4. Deploys the REST API to Amazon API Gateway.
5. Adds a resource policy to the AWS Lambda function that grants permission
to let Amazon API Gateway call the AWS Lambda function.
:param apigateway_client: The Boto3 Amazon API Gateway client object.
:param api_name: The name of the REST API.
:param api_base_path: The base path part of the REST API URL.
:param api_stage: The deployment stage of the REST API.
:param account_id: The ID of the owning AWS account.
:param lambda_client: The Boto3 AWS Lambda client object.
:param lambda_function_arn: The Amazon Resource Name (ARN) of the AWS Lambda
function that is called by Amazon API Gateway to
handle REST requests.
:return: The ID of the REST API. This ID is required by most Amazon API Gateway
methods.
"""
try :
response = apigateway_client . create_rest_api ( name = api_name )
2023-10-18 10:35:05 -07:00
api_id = response [ " id " ]
2020-07-09 17:27:30 -07:00
logger . info ( " Create REST API %s with ID %s . " , api_name , api_id )
except ClientError :
logger . exception ( " Couldn ' t create REST API %s . " , api_name )
raise
try :
response = apigateway_client . get_resources ( restApiId = api_id )
2023-10-18 10:35:05 -07:00
root_id = next ( item [ " id " ] for item in response [ " items " ] if item [ " path " ] == " / " )
2020-07-09 17:27:30 -07:00
logger . info ( " Found root resource of the REST API with ID %s . " , root_id )
except ClientError :
logger . exception ( " Couldn ' t get the ID of the root resource of the REST API. " )
raise
try :
response = apigateway_client . create_resource (
2023-10-18 10:35:05 -07:00
restApiId = api_id , parentId = root_id , pathPart = api_base_path
)
base_id = response [ " id " ]
2020-07-09 17:27:30 -07:00
logger . info ( " Created base path %s with ID %s . " , api_base_path , base_id )
except ClientError :
logger . exception ( " Couldn ' t create a base path for %s . " , api_base_path )
raise
try :
apigateway_client . put_method (
2023-10-18 10:35:05 -07:00
restApiId = api_id ,
resourceId = base_id ,
httpMethod = " ANY " ,
authorizationType = " NONE " ,
)
logger . info (
" Created a method that accepts all HTTP verbs for the base " " resource. "
)
2020-07-09 17:27:30 -07:00
except ClientError :
logger . exception ( " Couldn ' t create a method for the base resource. " )
raise
2023-10-18 10:35:05 -07:00
lambda_uri = (
f " arn:aws:apigateway: { apigateway_client . meta . region_name } : "
f " lambda:path/2015-03-31/functions/ { lambda_function_arn } /invocations "
)
2020-07-09 17:27:30 -07:00
try :
# NOTE: You must specify 'POST' for integrationHttpMethod or this will not work.
apigateway_client . put_integration (
2023-10-18 10:35:05 -07:00
restApiId = api_id ,
resourceId = base_id ,
httpMethod = " ANY " ,
type = " AWS_PROXY " ,
integrationHttpMethod = " POST " ,
uri = lambda_uri ,
)
2020-07-09 17:27:30 -07:00
logger . info (
" Set function %s as integration destination for the base resource. " ,
2023-10-18 10:35:05 -07:00
lambda_function_arn ,
)
2020-07-09 17:27:30 -07:00
except ClientError :
logger . exception (
2023-10-18 10:35:05 -07:00
" Couldn ' t set function %s as integration destination. " , lambda_function_arn
)
2020-07-09 17:27:30 -07:00
raise
try :
apigateway_client . create_deployment ( restApiId = api_id , stageName = api_stage )
logger . info ( " Deployed REST API %s . " , api_id )
except ClientError :
logger . exception ( " Couldn ' t deploy REST API %s . " , api_id )
raise
2023-10-18 10:35:05 -07:00
source_arn = (
f " arn:aws:execute-api: { apigateway_client . meta . region_name } : "
f " { account_id } : { api_id } /*/*/ { api_base_path } "
)
2020-07-09 17:27:30 -07:00
try :
lambda_client . add_permission (
2023-10-18 10:35:05 -07:00
FunctionName = lambda_function_arn ,
StatementId = f " demo-invoke " ,
Action = " lambda:InvokeFunction " ,
Principal = " apigateway.amazonaws.com " ,
SourceArn = source_arn ,
)
logger . info (
" Granted permission to let Amazon API Gateway invoke function %s "
" from %s . " ,
lambda_function_arn ,
source_arn ,
)
2020-07-09 17:27:30 -07:00
except ClientError :
2023-10-18 10:35:05 -07:00
logger . exception (
" Couldn ' t add permission to let Amazon API Gateway invoke %s . " ,
lambda_function_arn ,
)
2020-07-09 17:27:30 -07:00
raise
return api_id
def construct_api_url ( api_id , region , api_stage , api_base_path ) :
"""
Constructs the URL of the REST API.
:param api_id: The ID of the REST API.
2020-07-24 16:04:24 -07:00
:param region: The AWS Region where the REST API was created.
2020-07-09 17:27:30 -07:00
:param api_stage: The deployment stage of the REST API.
:param api_base_path: The base path part of the REST API.
:return: The full URL of the REST API.
"""
2023-10-18 10:35:05 -07:00
api_url = (
f " https:// { api_id } .execute-api. { region } .amazonaws.com/ "
f " { api_stage } / { api_base_path } "
)
2020-07-09 17:27:30 -07:00
logger . info ( " Constructed REST API base URL: %s . " , api_url )
return api_url
def delete_rest_api ( apigateway_client , api_id ) :
"""
Deletes a REST API and all of its resources from Amazon API Gateway.
:param apigateway_client: The Boto3 Amazon API Gateway client.
:param api_id: The ID of the REST API.
"""
try :
apigateway_client . delete_rest_api ( restApiId = api_id )
logger . info ( " Deleted REST API %s . " , api_id )
except ClientError :
logger . exception ( " Couldn ' t delete REST API %s . " , api_id )
raise
def usage_demo ( ) :
"""
Shows how to deploy an AWS Lambda function, create a REST API, call the REST API
in various ways, and remove all of the resources after the demo completes.
"""
2023-10-18 10:35:05 -07:00
logging . basicConfig ( level = logging . INFO , format = " %(levelname)s : %(message)s " )
print ( " - " * 88 )
2020-07-09 17:27:30 -07:00
print ( " Welcome to the AWS Lambda and Amazon API Gateway REST API creation demo. " )
2023-10-18 10:35:05 -07:00
print ( " - " * 88 )
2020-07-09 17:27:30 -07:00
2023-10-18 10:35:05 -07:00
lambda_filename = " lambda_handler_rest.py "
lambda_handler_name = " lambda_handler_rest.lambda_handler "
lambda_role_name = " demo-lambda-role "
lambda_function_name = " demo-lambda-rest "
api_name = " demo-lambda-rest-api "
2020-07-09 17:27:30 -07:00
2023-10-18 10:35:05 -07:00
iam_resource = boto3 . resource ( " iam " )
lambda_client = boto3 . client ( " lambda " )
2023-01-26 05:07:51 -08:00
wrapper = LambdaWrapper ( lambda_client , iam_resource )
2023-10-18 10:35:05 -07:00
apig_client = boto3 . client ( " apigateway " )
2020-07-09 17:27:30 -07:00
2023-01-26 05:07:51 -08:00
print ( " Checking for IAM role for Lambda... " )
iam_role , should_wait = wrapper . create_iam_role_for_lambda ( lambda_role_name )
if should_wait :
logger . info ( " Giving AWS time to create resources... " )
wait ( 10 )
2023-10-18 10:35:05 -07:00
print (
f " Creating AWS Lambda function { lambda_function_name } from "
f " { lambda_handler_name } ... "
)
2023-01-26 05:07:51 -08:00
deployment_package = wrapper . create_deployment_package (
2023-10-18 10:35:05 -07:00
lambda_filename , lambda_filename
)
2023-01-26 05:07:51 -08:00
lambda_function_arn = wrapper . create_function (
2023-10-18 10:35:05 -07:00
lambda_function_name , lambda_handler_name , iam_role , deployment_package
)
2020-07-09 17:27:30 -07:00
print ( f " Creating Amazon API Gateway REST API { api_name } ... " )
2023-10-18 10:35:05 -07:00
account_id = boto3 . client ( " sts " ) . get_caller_identity ( ) [ " Account " ]
api_base_path = " demoapi "
api_stage = " test "
2020-07-09 17:27:30 -07:00
api_id = create_rest_api (
2023-10-18 10:35:05 -07:00
apig_client ,
api_name ,
api_base_path ,
api_stage ,
account_id ,
lambda_client ,
lambda_function_arn ,
)
2020-07-09 17:27:30 -07:00
api_url = construct_api_url (
2023-10-18 10:35:05 -07:00
api_id , apig_client . meta . region_name , api_stage , api_base_path
)
2020-07-09 17:27:30 -07:00
print ( f " REST API created, URL is : \n \t { api_url } " )
print ( f " Sleeping for a couple seconds to give AWS time to prepare... " )
time . sleep ( 2 )
print ( f " Sending some requests to { api_url } ... " )
https_response = requests . get ( api_url )
2023-10-18 10:35:05 -07:00
print (
f " REST API returned status { https_response . status_code } \n "
f " Message: { json . loads ( https_response . text ) [ ' message ' ] } "
)
2020-07-09 17:27:30 -07:00
https_response = requests . get (
api_url ,
2023-10-18 10:35:05 -07:00
params = { " name " : " Martha " } ,
headers = { " day " : calendar . day_name [ datetime . date . today ( ) . weekday ( ) ] } ,
)
print (
f " REST API returned status { https_response . status_code } \n "
f " Message: { json . loads ( https_response . text ) [ ' message ' ] } "
)
2020-07-09 17:27:30 -07:00
https_response = requests . post (
api_url ,
2023-10-18 10:35:05 -07:00
params = { " name " : " Martha " } ,
headers = { " day " : calendar . day_name [ datetime . date . today ( ) . weekday ( ) ] } ,
json = { " adjective " : " fabulous " } ,
)
print (
f " REST API returned status { https_response . status_code } \n "
f " Message: { json . loads ( https_response . text ) [ ' message ' ] } "
2020-07-09 17:27:30 -07:00
)
2023-10-18 10:35:05 -07:00
https_response = requests . delete ( api_url , params = { " name " : " Martha " } )
print (
f " REST API returned status { https_response . status_code } \n "
f " Message: { json . loads ( https_response . text ) [ ' message ' ] } "
)
2020-07-09 17:27:30 -07:00
print ( " Deleting the REST API, AWS Lambda function, and security role... " )
time . sleep ( 5 ) # Short sleep avoids TooManyRequestsException.
2023-01-26 05:07:51 -08:00
wrapper . delete_function ( lambda_function_name )
2020-07-09 17:27:30 -07:00
for pol in iam_role . attached_policies . all ( ) :
pol . detach_role ( RoleName = iam_role . name )
iam_role . delete ( )
print ( f " Deleted role { iam_role . name } . " )
delete_rest_api ( apig_client , api_id )
print ( " Thanks for watching! " )
2023-10-18 10:35:05 -07:00
if __name__ == " __main__ " :
2020-07-09 17:27:30 -07:00
usage_demo ( )