diff --git a/python/api-sqs-lambda/README.md b/python/api-sqs-lambda/README.md new file mode 100644 index 0000000000..b8f80eb200 --- /dev/null +++ b/python/api-sqs-lambda/README.md @@ -0,0 +1,60 @@ + +# API Gateway + SQS + Lambda + +Creates an API Gateway API with a POST Method, a SQS queue, and a Lambda function. Requests to the API are enqueued into the SQS queue, which triggers the Lambda function. + +![Architecture](architecture.png) + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +This project is set up like a standard Python project. The initialization process also creates a virtualenv within this +project, stored under the `.env` directory. To create the virtualenv it assumes that there is a `python3` (or `python` +for Windows) executable in your path with access to the `venv` package. If for any reason the automatic creation of the +virtualenv fails, you can create the virtualenv manually. + +To manually create a virtualenv on MacOS and Linux: + +``` +$ python3 -m venv .env +``` + +After the init process completes and the virtualenv is created, you can use the following +step to activate your virtualenv. + +``` +$ source .env/bin/activate +``` + +If you are a Windows platform, you would activate the virtualenv like this: + +``` +% .env\Scripts\activate.bat +``` + +Once the virtualenv is activated, you can install the required dependencies. + +``` +$ pip install -r requirements.txt +``` + +At this point you can now synthesize the CloudFormation template for this code. + +``` +$ cdk synth +``` + +## Testing the app + +Upon successful deployment, you should see an API Gateway REST API in your account. It can be tested from the console or the CLI: + +``` +$ aws apigateway test-invoke-method --rest-api-id --resource-id --http-method POST --body {"key":"value"} +``` + +This request should complete with a 200 OK. The Lambda function should print the API Gateway request body in its CloudWatch logs. (https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html) + +``` +2020-05-27T11:50:44.755-05:00 Received Message Body from API GW: {key:value} +``` + +This message will also be visible in the SQS Queue metrics in CloudWatch. (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-access-metrics.html) diff --git a/python/api-sqs-lambda/api_sqs_lambda/__init__.py b/python/api-sqs-lambda/api_sqs_lambda/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/api-sqs-lambda/api_sqs_lambda/api_sqs_lambda_stack.py b/python/api-sqs-lambda/api_sqs_lambda/api_sqs_lambda_stack.py new file mode 100644 index 0000000000..6cf0438b89 --- /dev/null +++ b/python/api-sqs-lambda/api_sqs_lambda/api_sqs_lambda_stack.py @@ -0,0 +1,78 @@ +from aws_cdk import ( + aws_sqs as sqs, + aws_iam as iam, + aws_apigateway as apigw, + aws_lambda as _lambda, + aws_lambda_event_sources as lambda_event_source, + core +) + +class ApiSqsLambdaStack(core.Stack): + + def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: + super().__init__(scope, id, **kwargs) + + #Create the SQS queue + queue = sqs.Queue(self, "SQSQueue") + + #Create the API GW service role with permissions to call SQS + rest_api_role = iam.Role( + self, + "RestAPIRole", + assumed_by=iam.ServicePrincipal("apigateway.amazonaws.com"), + managed_policies=[iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSQSFullAccess")] + ) + + #Create an API GW Rest API + base_api = apigw.RestApi(self, 'ApiGW',rest_api_name='TestAPI') + + #Create a resource named "example" on the base API + api_resource = base_api.root.add_resource('example') + + + #Create API Integration Response object: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IntegrationResponse.html + integration_response = apigw.IntegrationResponse( + status_code="200", + response_templates={"application/json": ""}, + + ) + + #Create API Integration Options object: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IntegrationOptions.html + api_integration_options = apigw.IntegrationOptions( + credentials_role=rest_api_role, + integration_responses=[integration_response], + request_templates={"application/json": "Action=SendMessage&MessageBody=$input.body"}, + passthrough_behavior=apigw.PassthroughBehavior.NEVER, + request_parameters={"integration.request.header.Content-Type": "'application/x-www-form-urlencoded'"}, + ) + + #Create AWS Integration Object for SQS: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/AwsIntegration.html + api_resource_sqs_integration = apigw.AwsIntegration( + service="sqs", + integration_http_method="POST", + path="{}/{}".format(core.Aws.ACCOUNT_ID, queue.queue_name), + options=api_integration_options + ) + + #Create a Method Response Object: https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/MethodResponse.html + method_response = apigw.MethodResponse(status_code="200") + + #Add the API GW Integration to the "example" API GW Resource + api_resource.add_method( + "POST", + api_resource_sqs_integration, + method_responses=[method_response] + ) + + #Creating Lambda function that will be triggered by the SQS Queue + sqs_lambda = _lambda.Function(self,'SQSTriggerLambda', + handler='lambda-handler.handler', + runtime=_lambda.Runtime.PYTHON_3_7, + code=_lambda.Code.asset('lambda'), + ) + + #Create an SQS event source for Lambda + sqs_event_source = lambda_event_source.SqsEventSource(queue) + + #Add SQS event source to the Lambda function + sqs_lambda.add_event_source(sqs_event_source) \ No newline at end of file diff --git a/python/api-sqs-lambda/app.py b/python/api-sqs-lambda/app.py new file mode 100644 index 0000000000..414663a23b --- /dev/null +++ b/python/api-sqs-lambda/app.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +from aws_cdk import core + +from api_sqs_lambda.api_sqs_lambda_stack import ApiSqsLambdaStack + + +app = core.App() +ApiSqsLambdaStack(app, "ApiSqsLambdaStack") + +app.synth() diff --git a/python/api-sqs-lambda/architecture.png b/python/api-sqs-lambda/architecture.png new file mode 100644 index 0000000000..eef66e1be4 Binary files /dev/null and b/python/api-sqs-lambda/architecture.png differ diff --git a/python/api-sqs-lambda/cdk.json b/python/api-sqs-lambda/cdk.json new file mode 100644 index 0000000000..39c301c463 --- /dev/null +++ b/python/api-sqs-lambda/cdk.json @@ -0,0 +1,7 @@ +{ + "app": "python3 app.py", + "context": { + "@aws-cdk/core:enableStackNameDuplicates": "true", + "aws-cdk:enableDiffNoFail": "true" + } +} diff --git a/python/api-sqs-lambda/lambda/lambda-handler.py b/python/api-sqs-lambda/lambda/lambda-handler.py new file mode 100644 index 0000000000..b538087ddd --- /dev/null +++ b/python/api-sqs-lambda/lambda/lambda-handler.py @@ -0,0 +1,10 @@ +import json +import boto3 + +def handler(event, context): + response = "Received Message Body from API GW: " + event['Records'][0]['body'] + print(response) + return { + 'statusCode': 200, + 'body': response + } \ No newline at end of file diff --git a/python/api-sqs-lambda/requirements.txt b/python/api-sqs-lambda/requirements.txt new file mode 100644 index 0000000000..bfdcf5fe1f --- /dev/null +++ b/python/api-sqs-lambda/requirements.txt @@ -0,0 +1,7 @@ +aws-cdk.core + +aws-cdk.aws_lambda +aws-cdk.aws_lambda_event_sources +aws-cdk.aws_sqs +aws-cdk.aws_iam +aws-cdk.aws_apigateway diff --git a/python/api-sqs-lambda/setup.py b/python/api-sqs-lambda/setup.py new file mode 100644 index 0000000000..5bcc8279c7 --- /dev/null +++ b/python/api-sqs-lambda/setup.py @@ -0,0 +1,45 @@ +import setuptools + + +with open("README.md") as fp: + long_description = fp.read() + + +setuptools.setup( + name="api_sqs_lambda", + version="0.0.1", + + description="A CDK Python app to create the API GW + SQS + Lambda integration", + long_description=long_description, + long_description_content_type="text/markdown", + + author="Akshit Khanna", + + package_dir={"": "api_sqs_lambda"}, + packages=setuptools.find_packages(where="api_sqs_lambda"), + + install_requires=[ + "aws-cdk.core==1.41.0", + ], + + python_requires=">=3.6", + + classifiers=[ + "Development Status :: 4 - Beta", + + "Intended Audience :: Developers", + + "License :: OSI Approved :: Apache Software License", + + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + + "Typing :: Typed", + ], +)