-
Notifications
You must be signed in to change notification settings - Fork 434
docs(middleware-factory): snippets split, improved, and lint #1451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
597f2cf
ec90c5d
5cdf6c6
42f03c9
4c2a1e7
b2e1945
54a0600
fbe67c7
79b7b63
78e7bd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Transform: AWS::Serverless-2016-10-31 | ||
Description: Middleware-powertools-utilities example | ||
|
||
Globals: | ||
Function: | ||
Timeout: 5 | ||
Runtime: python3.9 | ||
Tracing: Active | ||
Architectures: | ||
- x86_64 | ||
Environment: | ||
Variables: | ||
LOG_LEVEL: DEBUG | ||
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 | ||
POWERTOOLS_LOGGER_LOG_EVENT: true | ||
POWERTOOLS_SERVICE_NAME: middleware | ||
|
||
Resources: | ||
MiddlewareFunction: | ||
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction | ||
Properties: | ||
CodeUri: middleware/ | ||
Handler: app.lambda_handler | ||
Description: Middleware function | ||
Policies: | ||
- AWSLambdaBasicExecutionRole # Managed Policy | ||
- Version: '2012-10-17' # Policy Document | ||
Statement: | ||
- Effect: Allow | ||
Action: | ||
- dynamodb:PutItem | ||
Resource: !GetAtt HistoryTable.Arn | ||
- Effect: Allow | ||
Action: # https://docs.aws.amazon.com/appconfig/latest/userguide/getting-started-with-appconfig-permissions.html | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if I'm comfortable with this, but from what I can see it's a real example in an official AWS documentation. |
||
- ssm:GetDocument | ||
- ssm:ListDocuments | ||
- appconfig:GetLatestConfiguration | ||
- appconfig:StartConfigurationSession | ||
- appconfig:ListApplications | ||
- appconfig:GetApplication | ||
- appconfig:ListEnvironments | ||
- appconfig:GetEnvironment | ||
- appconfig:ListConfigurationProfiles | ||
- appconfig:GetConfigurationProfile | ||
- appconfig:ListDeploymentStrategies | ||
- appconfig:GetDeploymentStrategy | ||
- appconfig:GetConfiguration | ||
- appconfig:ListDeployments | ||
- appconfig:GetDeployment | ||
Resource: "*" | ||
Events: | ||
GetComments: | ||
Type: Api | ||
Properties: | ||
Path: /comments | ||
Method: GET | ||
GetCommentsById: | ||
Type: Api | ||
Properties: | ||
Path: /comments/{comment_id} | ||
Method: GET | ||
|
||
# DYNAMODB TABLE FOR HISTORIC | ||
leandrodamascena marked this conversation as resolved.
Show resolved
Hide resolved
|
||
HistoryTable: | ||
Type: AWS::DynamoDB::Table | ||
Properties: | ||
TableName: "HistoryTable" | ||
AttributeDefinitions: | ||
- AttributeName: customer_id | ||
AttributeType: S | ||
- AttributeName: request_id | ||
AttributeType: S | ||
KeySchema: | ||
- AttributeName: customer_id | ||
KeyType: HASH | ||
- AttributeName: request_id | ||
KeyType: "RANGE" | ||
BillingMode: PAY_PER_REQUEST | ||
|
||
# APPCONFIG FOR FEATURE FLAGS | ||
leandrodamascena marked this conversation as resolved.
Show resolved
Hide resolved
|
||
FeatureCommentApp: | ||
Type: AWS::AppConfig::Application | ||
Properties: | ||
Description: "Comments Application for feature toggles" | ||
Name: comments | ||
|
||
FeatureCommentDevEnv: | ||
Type: AWS::AppConfig::Environment | ||
Properties: | ||
ApplicationId: !Ref FeatureCommentApp | ||
Description: "Development Environment for the App Config Comments" | ||
Name: dev | ||
|
||
FeatureCommentConfigProfile: | ||
Type: AWS::AppConfig::ConfigurationProfile | ||
Properties: | ||
ApplicationId: !Ref FeatureCommentApp | ||
Name: features | ||
LocationUri: "hosted" | ||
|
||
HostedConfigVersion: | ||
Type: AWS::AppConfig::HostedConfigurationVersion | ||
Properties: | ||
ApplicationId: !Ref FeatureCommentApp | ||
ConfigurationProfileId: !Ref FeatureCommentConfigProfile | ||
Description: 'A sample hosted configuration version' | ||
Content: | | ||
{ | ||
"save_history": { | ||
"default": true | ||
} | ||
} | ||
ContentType: 'application/json' | ||
|
||
# this is just an example | ||
# change this values according your deployment strategy | ||
BasicDeploymentStrategy: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the deployment strategy to avoid the default baking time.. The default time (10 minutes) is too long to someone that wants to test this template. |
||
Type: AWS::AppConfig::DeploymentStrategy | ||
Properties: | ||
Name: "Deployment" | ||
Description: "Deployment strategy for comments app." | ||
DeploymentDurationInMinutes: 1 | ||
FinalBakeTimeInMinutes: 1 | ||
GrowthFactor: 100 | ||
GrowthType: LINEAR | ||
ReplicateTo: NONE | ||
|
||
ConfigDeployment: | ||
Type: AWS::AppConfig::Deployment | ||
Properties: | ||
ApplicationId: !Ref FeatureCommentApp | ||
ConfigurationProfileId: !Ref FeatureCommentConfigProfile | ||
ConfigurationVersion: !Ref HostedConfigVersion | ||
DeploymentStrategyId: !Ref BasicDeploymentStrategy | ||
EnvironmentId: !Ref FeatureCommentDevEnv |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
{ | ||
"body":"None", | ||
"headers":{ | ||
"Accept":"*/*", | ||
"Accept-Encoding":"gzip, deflate, br", | ||
"Connection":"keep-alive", | ||
"Host":"127.0.0.1:3001", | ||
"Postman-Token":"a9d49365-ebe1-4bb0-8627-d5e37cdce86d", | ||
"User-Agent":"PostmanRuntime/7.29.0", | ||
"X-Customer-Id":"1", | ||
"X-Forwarded-Port":"3001", | ||
"X-Forwarded-Proto":"http" | ||
}, | ||
"httpMethod":"GET", | ||
"isBase64Encoded":false, | ||
"multiValueHeaders":{ | ||
"Accept":[ | ||
"*/*" | ||
], | ||
"Accept-Encoding":[ | ||
"gzip, deflate, br" | ||
], | ||
"Connection":[ | ||
"keep-alive" | ||
], | ||
"Host":[ | ||
"127.0.0.1:3001" | ||
], | ||
"Postman-Token":[ | ||
"a9d49365-ebe1-4bb0-8627-d5e37cdce86d" | ||
], | ||
"User-Agent":[ | ||
"PostmanRuntime/7.29.0" | ||
], | ||
"X-Customer-Id":[ | ||
"1" | ||
], | ||
"X-Forwarded-Port":[ | ||
"3001" | ||
], | ||
"X-Forwarded-Proto":[ | ||
"http" | ||
] | ||
}, | ||
"multiValueQueryStringParameters":"None", | ||
"path":"/comments", | ||
"pathParameters":"None", | ||
"queryStringParameters":"None", | ||
"requestContext":{ | ||
"accountId":"123456789012", | ||
"apiId":"1234567890", | ||
"domainName":"127.0.0.1:3001", | ||
"extendedRequestId":"None", | ||
"httpMethod":"GET", | ||
"identity":{ | ||
"accountId":"None", | ||
"apiKey":"None", | ||
"caller":"None", | ||
"cognitoAuthenticationProvider":"None", | ||
"cognitoAuthenticationType":"None", | ||
"cognitoIdentityPoolId":"None", | ||
"sourceIp":"127.0.0.1", | ||
"user":"None", | ||
"userAgent":"Custom User Agent String", | ||
"userArn":"None" | ||
}, | ||
"path":"/comments", | ||
"protocol":"HTTP/1.1", | ||
"requestId":"56d1a102-6d9d-4f13-b4f7-26751c10a131", | ||
"requestTime":"20/Aug/2022:18:18:58 +0000", | ||
"requestTimeEpoch":1661019538, | ||
"resourceId":"123456", | ||
"resourcePath":"/comments", | ||
"stage":"Prod" | ||
}, | ||
"resource":"/comments", | ||
"stageVariables":"None", | ||
"version":"1.0" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import json | ||
from typing import Callable | ||
|
||
import boto3 | ||
import combining_powertools_utilities_schema as schemas | ||
import requests | ||
|
||
from aws_lambda_powertools import Logger, Tracer | ||
from aws_lambda_powertools.event_handler import APIGatewayRestResolver | ||
from aws_lambda_powertools.event_handler.exceptions import InternalServerError | ||
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator | ||
from aws_lambda_powertools.shared.types import JSONType | ||
from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags | ||
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope | ||
from aws_lambda_powertools.utilities.typing import LambdaContext | ||
from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate | ||
|
||
app = APIGatewayRestResolver() | ||
tracer = Tracer() | ||
logger = Logger() | ||
|
||
table_historic = boto3.resource("dynamodb").Table("HistoricTable") | ||
|
||
app_config = AppConfigStore(environment="dev", application="comments", name="features") | ||
feature_flags = FeatureFlags(store=app_config) | ||
|
||
|
||
@lambda_handler_decorator(trace_execution=True) | ||
def middleware_custom(handler: Callable, event: dict, context: LambdaContext): | ||
|
||
# validating the INPUT with the given schema | ||
# X-Customer-Id header must be informed in all requests | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Early return? I think I need other opinions here. |
||
try: | ||
validate(event=event, schema=schemas.INPUT) | ||
except SchemaValidationError as e: | ||
return { | ||
"statusCode": 400, | ||
"body": json.dumps(str(e)), | ||
} | ||
|
||
# extracting headers and requestContext from event | ||
headers = extract_data_from_envelope(data=event, envelope="headers") | ||
request_context = extract_data_from_envelope(data=event, envelope="requestContext") | ||
|
||
logger.debug(f"X-Customer-Id => {headers.get('X-Customer-Id')}") | ||
tracer.put_annotation(key="CustomerId", value=headers.get("X-Customer-Id")) | ||
|
||
response = handler(event, context) | ||
|
||
# automatically adding security headers to all responses | ||
# see: https://securityheaders.com/ | ||
logger.info("Injecting security headers") | ||
response["headers"]["Referrer-Policy"] = "no-referrer" | ||
response["headers"]["Strict-Transport-Security"] = "max-age=15552000; includeSubDomains; preload" | ||
response["headers"]["X-DNS-Prefetch-Control"] = "off" | ||
response["headers"]["X-Content-Type-Options"] = "nosniff" | ||
response["headers"]["X-Permitted-Cross-Domain-Policies"] = "none" | ||
response["headers"]["X-Download-Options"] = "noopen" | ||
leandrodamascena marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
logger.info("Saving api call in history table") | ||
save_api_execution_history(str(event.get("path")), headers, request_context) | ||
|
||
# return lambda execution | ||
return response | ||
|
||
|
||
@tracer.capture_method | ||
def save_api_execution_history(path: str, headers: dict, request_context: dict) -> None: | ||
|
||
try: | ||
# using the feature flags utility to check if the new feature "save api call to history" is enabled by default | ||
# see: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/feature_flags/#static-flags | ||
save_history: JSONType = feature_flags.evaluate(name="save_history", default=False) | ||
if save_history: | ||
# saving history in dynamodb table | ||
tracer.put_metadata(key="execution detail", value=request_context) | ||
table_historic.put_item( | ||
Item={ | ||
"customer_id": headers.get("X-Customer-Id"), | ||
"request_id": request_context.get("requestId"), | ||
"path": path, | ||
"request_time": request_context.get("requestTime"), | ||
"source_ip": request_context.get("identity", {}).get("sourceIp"), | ||
"http_method": request_context.get("httpMethod"), | ||
} | ||
) | ||
|
||
return None | ||
except Exception: | ||
# you can add more logic here to handle exceptions or even save this to a DLQ | ||
# but not to make this example too long, we just return None since the Lambda has been successfully executed | ||
return None | ||
|
||
|
||
@app.get("/comments") | ||
@tracer.capture_method | ||
def get_comments(): | ||
try: | ||
comments: requests.Response = requests.get("https://jsonplaceholder.typicode.com/comments") | ||
comments.raise_for_status() | ||
|
||
return {"comments": comments.json()[:10]} | ||
except Exception as exc: | ||
raise InternalServerError(str(exc)) | ||
|
||
|
||
@app.get("/comments/<comment_id>") | ||
@tracer.capture_method | ||
def get_comments_by_id(comment_id: str): | ||
try: | ||
comments: requests.Response = requests.get(f"https://jsonplaceholder.typicode.com/comments/{comment_id}") | ||
comments.raise_for_status() | ||
|
||
return {"comments": comments.json()} | ||
except Exception as exc: | ||
raise InternalServerError(str(exc)) | ||
|
||
|
||
@middleware_custom | ||
def lambda_handler(event: dict, context: LambdaContext) -> dict: | ||
return app.resolve(event, context) |
Uh oh!
There was an error while loading. Please reload this page.