diff --git a/alb-lambda-rest-api-sam-py/Pipfile b/alb-lambda-rest-api-sam-py/Pipfile new file mode 100644 index 000000000..6d68c0ac0 --- /dev/null +++ b/alb-lambda-rest-api-sam-py/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +aws-lambda-powertools = {version = "*"} + +[dev-packages] +aws-lambda-powertools = {extra = ["all"], version = "*"} + +[requires] +python_version = "3.9" \ No newline at end of file diff --git a/alb-lambda-rest-api-sam-py/Pipfile.lock b/alb-lambda-rest-api-sam-py/Pipfile.lock new file mode 100644 index 000000000..77497b110 --- /dev/null +++ b/alb-lambda-rest-api-sam-py/Pipfile.lock @@ -0,0 +1,54 @@ +{ + "_meta": { + "hash": { + "sha256": "95a86ddb5eb2be624c7a545ce418fa3780adfb09d35348ac4c436ffac1573263" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aws-lambda-powertools": { + "hashes": [ + "sha256:082762c0939caa670d69b8988142710029bfac050dbd428af0d12570b8a6c7be", + "sha256:d628e32919379fde4cd238578bc47fcc60a43f742bd8daa68533bc5202eae378" + ], + "markers": "python_full_version >= '3.7.4' and python_full_version < '4.0.0'", + "version": "==2.31.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", + "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + ], + "markers": "python_version >= '3.8'", + "version": "==4.9.0" + } + }, + "develop": { + "aws-lambda-powertools": { + "hashes": [ + "sha256:082762c0939caa670d69b8988142710029bfac050dbd428af0d12570b8a6c7be", + "sha256:d628e32919379fde4cd238578bc47fcc60a43f742bd8daa68533bc5202eae378" + ], + "markers": "python_full_version >= '3.7.4' and python_full_version < '4.0.0'", + "version": "==2.31.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", + "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + ], + "markers": "python_version >= '3.8'", + "version": "==4.9.0" + } + } +} diff --git a/alb-lambda-rest-api-sam-py/README.md b/alb-lambda-rest-api-sam-py/README.md new file mode 100644 index 000000000..03fd47b1e --- /dev/null +++ b/alb-lambda-rest-api-sam-py/README.md @@ -0,0 +1,155 @@ +# AWS Lambda RESTful API with Amazon ALB and Path Based Listener Rules + +This configuration pattern creates an Application Load +Balancer with path-based listener rules, paired with an AWS Lambda RESTful API function as the target. It uses Python 3.9 and the Serverless Application Model (SAM) CLI. + +Learn more about this pattern +at [Serverless Land Patterns](https://serverlessland.com/patterns/alb-lambda-rest-api-sam-py). + +Important: this application uses various AWS services and there are costs associated with these services after the Free +Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any +AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already + have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls + and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) ( + AWS SAM) installed +* [Python 3 installed](https://www.python.org/downloads/) +* [AWS Lambda Powertools for Python](https://docs.powertools.aws.dev/lambda/python/latest/) +* [Docker](https://www.docker.com/products/docker-desktop/) + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ``` + cd alb-lambda-rest-api-sam-py + ``` +3. Install dependencies: + ``` + pipenv install + pipenv requirements > requirements.txt + ``` +4. From the command line, use AWS SAM to validate, build and deploy the AWS resources for the pattern as specified in the template.yml file: + ``` + sam validate && sam build && sam deploy --guided + ``` +5. During the prompts: + * Enter a stack name + * Enter the desired AWS Region + * Enter VPC ID + * Enter comma seperated Subnet IDs for e.g, subnet-1,subnet-2 + * Allow SAM CLI to create IAM roles with the required permissions. + +6. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for + testing. + +## How it works + +This setup orchestrates the deployment of an Application Load Balancer, configures path-based routes directing traffic +to a Python-based AWS Lambda function, and leverages +the [AWS Lambda Powertools for Python](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#application-load-balancer) +library. The Lambda function, serving as the target, records details of the incoming ALB event, along with the API and +context objects, logging them to an Amazon CloudWatch Logs log group and Amazon X-Ray. + +Note: ALB has no authentication or authorization and should only be used for demo purposes. + +## Testing + +### SAM CLI for Local API Testing + +The SAM CLI seamlessly extends the capabilities of the AWS CLI, introducing essential features for constructing and validating Lambda applications. Leveraging the power of Docker, it orchestrates the execution of functions within an Amazon Linux environment aligned with the Lambda runtime specifications, all sourced from [sam/build-python3.9](https://gallery.ecr.aws/sam/build-python3.9). +This emulation replicates the Lambda environment. + +To simulate an ALB (Application Load Balancer) event, the CLI relies on the configuration stored in [event.json](./events/event.json). This file corresponds to an ALB event, and it is generated by the SAM command: `sam local generate-event alb request`. + +Now, to build and execute your function, execute the following command: + + +```commandline +pipenv install +pipenv requirements > requirements.txt + +sam build +sam local invoke -e events/event.json +``` + +### Deploy and Test API in AWS + +Once the application is deployed, retrieve the Application Load Balancer endpoint value from CloudFormation Outputs. + +```commandline +curl --request GET --header "Client-Correlation-Id:bb245" --url http://{ALB_ID}.{REGION}.elb.amazonaws.com/hello' +``` + +Alternatively, you can use [xh utility](https://github.com/ducaale/xh), too as: + +```commandline +xh http://{ALB_ID}.{REGION}.elb.amazonaws.com/hello Client-Correlation-Id:bb245 +``` + +Response: + +```commandline +HTTP/1.1 200 OK +Connection: keep-alive +Content-Length: 37 +Content-Type: application/json +Server: awselb/2.0 + +{ + "message": "Hi from API behind ALB" +} + +``` + +When an invalid resource (/nf-test) is used as: + +```commandline +curl --request GET --header "Client-Correlation-Id:not-found-test-1" --url http://{ALB_ID}.{REGION}.elb.amazonaws.com/nf-test' +``` +_OR_ + +with [xh utility](https://github.com/ducaale/xh) as: + +```commandline +xh http://{ALB_ID}.{REGION}.elb.amazonaws.com/nf-test Client-Correlation-Id:'not-found-test-1' +``` + +Response: + +```commandline +HTTP/1.1 404 Not Found +Connection: keep-alive +Content-Length: 27 +Content-Type: text/plain; charset=utf-8 +Date: Fri, 12 Jan 2024 18:44:09 GMT +Server: awselb/2.0 + +404 Error!!! Page Not Found + +``` + +## Cleanup + +1. Delete the stack + ```commandline + sam delete + ``` +2. Confirm the stack has been deleted + ```commandline + aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus" + ``` + +---- +Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/alb-lambda-rest-api-sam-py/alb-lambda-rest-api-sam-py.json b/alb-lambda-rest-api-sam-py/alb-lambda-rest-api-sam-py.json new file mode 100644 index 000000000..51668843f --- /dev/null +++ b/alb-lambda-rest-api-sam-py/alb-lambda-rest-api-sam-py.json @@ -0,0 +1,95 @@ +{ + "title": "ALB with route-based listener rules to AWS Lambda", + "description": "This pattern creates an Application Load Balancer with route-based listener rules, paired with an AWS Lambda function as the target.", + "language": "Python", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This setup orchestrates the deployment of an Application Load Balancer, configures path-based routes directing traffic to a Python-based AWS Lambda function, and leverages the AWS Lambda Powertools for Python library. The Lambda function, serving as the target, records details of the incoming ALB event, along with the API and context objects, logging them to an Amazon CloudWatch Logs log group and Amazon X-Ray." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/alb-lambda-rest-api-sam-py", + "templateURL": "serverless-patterns/alb-lambda-rest-api-sam-py", + "projectFolder": "alb-lambda-rest-api-sam-py", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "AWS CLI", + "link": "https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html" + }, + { + "text": "SAM CLI", + "link": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" + }, + { + "text": "Python 3", + "link": "https://www.python.org/downloads/" + }, + { + "text": "AWS SAM build image for Python 3.9", + "link": "https://gallery.ecr.aws/sam/build-python3.9" + }, + { + "text": "Docker Desktop", + "link": "https://www.docker.com/products/docker-desktop/" + }, + { + "text": "Using AWS Lambda with an Application Load Balancer", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html" + }, + { + "text": "Powertools for AWS Lambda (Python)", + "link": "https://docs.powertools.aws.dev/lambda/python/latest/" + } + ] + }, + "deploy": { + "text": [ + "sam validate && sam build && sam deploy --guided" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "sam delete" + ] + }, + "authors": [ + { + "name": "Balasubrahmanya Balakrishna", + "image": "https://github.com/bb245/balabala/blob/main/balabala.png", + "bio": "Senior Lead Software Engineer, Capital One Services, LLC.", + "linkedin": "bala~bala" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "alb", + "label": "Application Load Balancer" + }, + "icon2": { + "x": 80, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "line1": { + "from": "icon1", + "to": "icon2", + "label": "route-based listener" + } + } +} diff --git a/alb-lambda-rest-api-sam-py/docs/alb_lambda_api_sam_py.png b/alb-lambda-rest-api-sam-py/docs/alb_lambda_api_sam_py.png new file mode 100644 index 000000000..be2c0f82f Binary files /dev/null and b/alb-lambda-rest-api-sam-py/docs/alb_lambda_api_sam_py.png differ diff --git a/alb-lambda-rest-api-sam-py/events/event.json b/alb-lambda-rest-api-sam-py/events/event.json new file mode 100644 index 000000000..7fe6f0bd2 --- /dev/null +++ b/alb-lambda-rest-api-sam-py/events/event.json @@ -0,0 +1,61 @@ +{ + "body": "null", + "resource": "/{proxy+}", + "path": "/hello", + "httpMethod": "GET", + "isBase64Encoded": true, + "queryStringParameters": null, + "pathParameters": { + "proxy": "/hello" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "application/json;v=1", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https", + "Client-Correlation-Id": "bb245" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "hello", + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} \ No newline at end of file diff --git a/alb-lambda-rest-api-sam-py/example-pattern.json b/alb-lambda-rest-api-sam-py/example-pattern.json new file mode 100644 index 000000000..6b4dd4206 --- /dev/null +++ b/alb-lambda-rest-api-sam-py/example-pattern.json @@ -0,0 +1,76 @@ +{ + "title": "AWS Lambda REST API and Amazon ALB with Listener Rules", + "description": "This pattern creates an Application Load Balancer with route-based listener rules, paired with an AWS Lambda function as the target.", + "language": "Python", + "level": "200", + "framework": "SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This setup orchestrates the deployment of an Application Load Balancer, configures path-based routes directing traffic to a Python-based AWS Lambda function, and leverages the [AWS Lambda Powertools for Python](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/) library. The Lambda function, serving as the target, records details of the incoming ALB event, along with the API and context objects, logging them to an Amazon CloudWatch Logs log group and Amazon X-Ray." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/alb-lambda-rest-api-sam-py", + "templateURL": "serverless-patterns/alb-lambda-rest-api-sam-py", + "projectFolder": "alb-lambda-rest-api-sam-py", + "templateFile": "template.yaml" + } + }, + "resources": { + "bullets": [ + { + "text": "AWS CLI", + "link": "https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html" + }, + { + "text": "SAM CLI", + "Link": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" + }, + { + "text": "Python 3", + "Link": "https://www.python.org/downloads/" + }, + { + "text": "AWS SAM build image for Python 3.9", + "Link": "https://gallery.ecr.aws/sam/build-python3.9" + }, + { + "text": "Docker Desktop", + "Link": "https://www.docker.com/products/docker-desktop/" + }, + { + "text": "Using AWS Lambda with an Application Load Balancer", + "Link": "https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html" + }, + { + "text": "Powertools for AWS Lambda (Python)", + "Link": "https://docs.powertools.aws.dev/lambda/python/latest/" + } + ] + }, + "deploy": { + "text": [ + "sam validate && sam build && sam deploy --guided" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: sam delete." + ] + }, + "authors": [ + { + "name": "Balasubrahmanya Balakrishna", + "image": "https://github.com/bb245/balabala/blob/main/balabala.png", + "bio": "Senior Lead Software Engineer, Capital One Services, LLC.", + "linkedin": "bala~bala" + } + ] +} diff --git a/alb-lambda-rest-api-sam-py/requirements.txt b/alb-lambda-rest-api-sam-py/requirements.txt new file mode 100644 index 000000000..0aa6fe00f --- /dev/null +++ b/alb-lambda-rest-api-sam-py/requirements.txt @@ -0,0 +1,3 @@ +-i https://pypi.org/simple +aws-lambda-powertools==2.31.0; python_full_version >= '3.7.4' and python_full_version < '4.0.0' +typing-extensions==4.9.0; python_version >= '3.8' diff --git a/alb-lambda-rest-api-sam-py/src/app.py b/alb-lambda-rest-api-sam-py/src/app.py new file mode 100644 index 000000000..5a5fb390d --- /dev/null +++ b/alb-lambda-rest-api-sam-py/src/app.py @@ -0,0 +1,28 @@ +import json +from http import HTTPStatus + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler import ALBResolver, Response, content_types +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.logging import correlation_paths + +log = Logger() +app = ALBResolver() + + +@app.get("/hello") +def sample_get() -> Response: + client_correlation_id = app.current_event.get_header_value(name="Client-Correlation-Id") + log.info(f"client_correlation_id: {client_correlation_id}") + response = { + "message": "Hi from API behind ALB" + } + return Response(status_code=int(HTTPStatus.OK), body=json.dumps(response), + content_type=content_types.APPLICATION_JSON) + + +@log.inject_lambda_context(correlation_id_path=correlation_paths.APPLICATION_LOAD_BALANCER, log_event=True, + clear_state=True) +def lambda_handler(event: dict, context: LambdaContext) -> dict: + log.debug(event) + return app.resolve(event, context) \ No newline at end of file diff --git a/alb-lambda-rest-api-sam-py/template.yaml b/alb-lambda-rest-api-sam-py/template.yaml new file mode 100644 index 000000000..3cd4a7349 --- /dev/null +++ b/alb-lambda-rest-api-sam-py/template.yaml @@ -0,0 +1,145 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: AWS Lambda REST API and Amazon ALB with Listener Rules +Parameters: + VPCId: + Type: String + Default: VPC_ID + Subnets: + Type: String + Default: subnet-1,subnet-2,subnet-3 + AppName: + Type: String + Default: alb-lambda-rest-api-sam-py + AlbLambdaRestApiSamPyFunctionRulePath: + Type: String + Default: /hello + AlbLambdaRestApiSamPyFunctionPriority: + Type: Number + Default: 10 +Globals: + Function: + Timeout: 3 + MemorySize: 128 + Tracing: Active + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: alb-lambda-rest-api-sam-py + POWERTOOLS_LOG_LEVEL: INFO + Api: + TracingEnabled: true +Resources: + AlbLambdaRestApiSamPyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: src.app.lambda_handler + Runtime: python3.9 + Architectures: + - x86_64 + Policies: + - AWSLambdaBasicExecutionRole + + AlbLambdaRestApiSamPyFunctionPermission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !GetAtt AlbLambdaRestApiSamPyFunction.Arn + Action: lambda:InvokeFunction + Principal: elasticloadbalancing.amazonaws.com + + AlbLambdaRestApiSamPyFunctionLoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Name: !Sub ${AppName}-alb + Type: application + Scheme: internet-facing + Subnets: + Fn::Split: + - "," + - !Ref Subnets + SecurityGroups: [ !Ref AlbLambdaRestApiSamPyFunctionSecurityGroup ] + Tags: + - Key: name + Value: !Ref AWS::StackName + + AlbLambdaRestApiSamPyFunctionTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + DependsOn: AlbLambdaRestApiSamPyFunctionPermission + Properties: + Name: !Sub ${AppName}-tg + HealthCheckEnabled: false + TargetType: lambda + Targets: + - Id: !GetAtt AlbLambdaRestApiSamPyFunction.Arn + Tags: + - Key: name + Value: !Ref AWS::StackName + + HttpListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - Type: fixed-response + FixedResponseConfig: + ContentType: text/plain + MessageBody: 404 Error!!! Page Not Found + StatusCode: 404 + LoadBalancerArn: !Ref AlbLambdaRestApiSamPyFunctionLoadBalancer + Port: 80 + Protocol: HTTP + + AlbLambdaRestApiSamPyFunctionHttpListenerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + Actions: + - TargetGroupArn: !Ref AlbLambdaRestApiSamPyFunctionTargetGroup + Type: forward + Conditions: + - Field: path-pattern + Values: + - !Ref AlbLambdaRestApiSamPyFunctionRulePath + ListenerArn: !Ref HttpListener + Priority: !Ref AlbLambdaRestApiSamPyFunctionPriority + + AlbLambdaRestApiSamPyFunctionSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: !Ref AWS::StackName + GroupDescription: Allow http on port 80 + VpcId: !Ref VPCId + SecurityGroupEgress: + - IpProtocol: "-1" + CidrIp: 0.0.0.0/0 + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + Tags: + - Key: name + Value: !Ref AWS::StackName + + ApplicationResourceGroup: + Type: AWS::ResourceGroups::Group + Properties: + Name: + Fn::Sub: ApplicationInsights-SAM-${AWS::StackName} + ResourceQuery: + Type: CLOUDFORMATION_STACK_1_0 + + ApplicationInsightsMonitoring: + Type: AWS::ApplicationInsights::Application + Properties: + ResourceGroupName: + Ref: ApplicationResourceGroup + AutoConfigurationEnabled: 'true' + +Outputs: + AlbLambdaRestApiSamPyFunction: + Description: ALB Lambda SAM Python Sample Lambda Function ARN + Value: !GetAtt AlbLambdaRestApiSamPyFunction.Arn + AlbLambdaRestApiSamPyFunctionIamRole: + Description: Implicit IAM Role created for ALB Lambda SAM Python Sample Lambda function + Value: !GetAtt AlbLambdaRestApiSamPyFunctionRole.Arn + DNSName: + Value: !GetAtt AlbLambdaRestApiSamPyFunctionLoadBalancer.DNSName \ No newline at end of file