diff --git a/Makefile b/Makefile index 0ee0ee76fbd..9801baa7eb4 100644 --- a/Makefile +++ b/Makefile @@ -90,3 +90,14 @@ changelog: mypy: poetry run mypy --pretty aws_lambda_powertools + +format-examples: + poetry run isort docs/shared + poetry run black docs/shared/*.py + poetry run isort docs/examples + poetry run black docs/examples/*/*.py + +lint-examples: + poetry run python3 -m py_compile docs/shared/*.py + poetry run python3 -m py_compile docs/examples/*/*.py + cfn-lint docs/examples/*/*.yml diff --git a/docs/examples/index/debug_mode.py b/docs/examples/index/debug_mode.py new file mode 100644 index 00000000000..d063da98645 --- /dev/null +++ b/docs/examples/index/debug_mode.py @@ -0,0 +1,3 @@ +from aws_lambda_powertools.logging.logger import set_package_logger + +set_package_logger() # (1) diff --git a/docs/examples/index/lambda_layer_cdk_app.py b/docs/examples/index/lambda_layer_cdk_app.py new file mode 100644 index 00000000000..e13332aa235 --- /dev/null +++ b/docs/examples/index/lambda_layer_cdk_app.py @@ -0,0 +1,19 @@ +from aws_cdk import aws_lambda, core + + +class SampleApp(core.Construct): + def __init__(self, scope: core.Construct, id_: str, env: core.Environment) -> None: + super().__init__(scope, id_) + + powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn( + self, + id="lambda-powertools", + layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPython:20", + ) + aws_lambda.Function( + self, + "sample-app-lambda", + runtime=aws_lambda.Runtime.PYTHON_3_9, + layers=[powertools_layer] + # other props... + ) diff --git a/docs/examples/index/lambda_layer_main.tf b/docs/examples/index/lambda_layer_main.tf new file mode 100644 index 00000000000..0c4b44e5e08 --- /dev/null +++ b/docs/examples/index/lambda_layer_main.tf @@ -0,0 +1,38 @@ +terraform { + required_version = "~> 1.1.7" + required_providers { + aws = "~> 4.4.0" + } +} + +provider "aws" { + region = "{region}" +} + +resource "aws_iam_role" "iam_for_lambda" { + name = "iam_for_lambda" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Principal = { + Service = "lambda.amazonaws.com" + }, + Effect = "Allow" + } + ] + }) +} + +resource "aws_lambda_function" "test_lambda" { + filename = "lambda_function_payload.zip" + function_name = "lambda_function_name" + role = aws_iam_role.iam_for_lambda.arn + handler = "index.test" + runtime = "python3.9" + layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:20"] + + source_code_hash = filebase64sha256("lambda_function_payload.zip") +} diff --git a/docs/examples/index/lambda_layer_template.yml b/docs/examples/index/lambda_layer_template.yml new file mode 100644 index 00000000000..ac81b62e54f --- /dev/null +++ b/docs/examples/index/lambda_layer_template.yml @@ -0,0 +1,11 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: src + Handler: app.lambda_handler + Runtime: python3.9 + Layers: + - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:20 diff --git a/docs/examples/index/least_priviledged_template.yml b/docs/examples/index/least_priviledged_template.yml new file mode 100644 index 00000000000..bb33810fca7 --- /dev/null +++ b/docs/examples/index/least_priviledged_template.yml @@ -0,0 +1,54 @@ +AWSTemplateFormatVersion: "2010-09-09" +Resources: + PowertoolsLayerIamRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: + Service: + - "cloudformation.amazonaws.com" + Action: + - "sts:AssumeRole" + Path: "/" + PowertoolsLayerIamPolicy: + Type: "AWS::IAM::Policy" + Properties: + PolicyName: PowertoolsLambdaLayerPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: CloudFormationTransform + Effect: Allow + Action: cloudformation:CreateChangeSet + Resource: + - arn:aws:cloudformation:us-east-1:aws:transform/Serverless-2016-10-31 + - Sid: GetCfnTemplate + Effect: Allow + Action: + - serverlessrepo:CreateCloudFormationTemplate + - serverlessrepo:GetCloudFormationTemplate + Resource: + # this is arn of the powertools SAR app + - arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer + - Sid: S3AccessLayer + Effect: Allow + Action: + - s3:GetObject + Resource: + # AWS publishes to an external S3 bucket locked down to your account ID + # The below example is us publishing lambda powertools + # Bucket: awsserverlessrepo-changesets-plntc6bfnfj + # Key: *****/arn:aws:serverlessrepo:eu-west-1:057560766410:applications-aws-lambda-powertools-python-layer-versions-1.10.2/aeeccf50-****-****-****-********* + - arn:aws:s3:::awsserverlessrepo-changesets-*/* + - Sid: GetLayerVersion + Effect: Allow + Action: + - lambda:PublishLayerVersion + - lambda:GetLayerVersion + Resource: + - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:aws-lambda-powertools-python-layer* + Roles: + - Ref: "PowertoolsLayerIamRole" diff --git a/docs/examples/index/sar_cdk_app.py b/docs/examples/index/sar_cdk_app.py new file mode 100644 index 00000000000..6c6068a9104 --- /dev/null +++ b/docs/examples/index/sar_cdk_app.py @@ -0,0 +1,35 @@ +from aws_cdk import aws_lambda +from aws_cdk import aws_sam as sam +from aws_cdk import core + +POWERTOOLS_BASE_NAME = "AWSLambdaPowertools" +# Find latest from github.com/awslabs/aws-lambda-powertools-python/releases +POWERTOOLS_VER = "1.26.1" +POWERTOOLS_ARN = "arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer" + + +class SampleApp(core.Construct): + def __init__(self, scope: core.Construct, id_: str) -> None: + super().__init__(scope, id_) + + # Launches SAR App as CloudFormation nested stack and return Lambda Layer + powertools_app = sam.CfnApplication( + self, + f"{POWERTOOLS_BASE_NAME}Application", + location={"applicationId": POWERTOOLS_ARN, "semanticVersion": POWERTOOLS_VER}, + ) + + powertools_layer_arn = powertools_app.get_att("Outputs.LayerVersionArn").to_string() + powertools_layer_version = aws_lambda.LayerVersion.from_layer_version_arn( + self, f"{POWERTOOLS_BASE_NAME}", powertools_layer_arn + ) + + aws_lambda.Function( + self, + "sample-app-lambda", + runtime=aws_lambda.Runtime.PYTHON_3_8, + function_name="sample-lambda", + code=aws_lambda.Code.asset("./src"), + handler="app.handler", + layers=[powertools_layer_version], + ) diff --git a/docs/examples/index/sar_main.tf b/docs/examples/index/sar_main.tf new file mode 100644 index 00000000000..53625ceb641 --- /dev/null +++ b/docs/examples/index/sar_main.tf @@ -0,0 +1,41 @@ +terraform { + required_version = "~> 0.13" + required_providers { + aws = "~> 3.50.0" + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_serverlessapplicationrepository_cloudformation_stack" "deploy_sar_stack" { + name = "aws-lambda-powertools-python-layer" + + application_id = data.aws_serverlessapplicationrepository_application.sar_app.application_id + semantic_version = data.aws_serverlessapplicationrepository_application.sar_app.semantic_version + capabilities = [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM" + ] +} + +data "aws_serverlessapplicationrepository_application" "sar_app" { + application_id = "arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer" + semantic_version = var.aws_powertools_version +} + +variable "aws_powertools_version" { + type = string + default = "1.26.1" + description = "The AWS Powertools release version" +} + +output "deployed_powertools_sar_version" { + value = data.aws_serverlessapplicationrepository_application.sar_app.semantic_version +} + +# Fetch Lambda Powertools Layer ARN from deployed SAR App +output "aws_lambda_powertools_layer_arn" { + value = aws_serverlessapplicationrepository_cloudformation_stack.deploy_sar_stack.outputs.LayerVersionArn +} diff --git a/docs/examples/index/sar_template.yml b/docs/examples/index/sar_template.yml new file mode 100644 index 00000000000..a031bcdbbad --- /dev/null +++ b/docs/examples/index/sar_template.yml @@ -0,0 +1,19 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + AwsLambdaPowertoolsPythonLayer: + Type: AWS::Serverless::Application + Properties: + Location: + ApplicationId: arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer + SemanticVersion: 1.26.1 # change to latest semantic version available in SAR + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: src + Handler: app.lambda_handler + Runtime: python3.9 + Layers: + # fetch Layer ARN from SAR App stack output + - !GetAtt AwsLambdaPowertoolsPythonLayer.Outputs.LayerVersionArn diff --git a/docs/index.md b/docs/index.md index 83d841de153..886586024b7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,6 @@ A suite of utilities for AWS Lambda functions to ease adopting best practices su Check out [this detailed blog post](https://aws.amazon.com/blogs/opensource/simplifying-serverless-best-practices-with-lambda-powertools/) with a practical example. - ## Install Powertools is available in the following formats: @@ -22,7 +21,6 @@ Powertools is available in the following formats: When using Layers, you can add Lambda Powertools as a dev dependency (or as part of your virtual env) to not impact the development process. - ### Lambda Layer [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html){target="_blank"} is a .zip file archive that can contain additional code, pre-packaged dependencies, data, or configuration files. Layers promote code sharing and separation of responsibilities so that you can iterate faster on writing business logic. @@ -58,12 +56,8 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: === "SAM" - ```yaml hl_lines="5" - MyLambdaFunction: - Type: AWS::Serverless::Function - Properties: - Layers: - - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:20 + ```yaml hl_lines="11" + --8<-- "docs/examples/index/lambda_layer_template.yml" ``` === "Serverless framework" @@ -79,71 +73,13 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: === "CDK" ```python hl_lines="11 16" - from aws_cdk import core, aws_lambda - - class SampleApp(core.Construct): - - def __init__(self, scope: core.Construct, id_: str, env: core.Environment) -> None: - super().__init__(scope, id_) - - powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn( - self, - id="lambda-powertools", - layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPython:20" - ) - aws_lambda.Function(self, - 'sample-app-lambda', - runtime=aws_lambda.Runtime.PYTHON_3_9, - layers=[powertools_layer] - # other props... - ) + --8<-- "docs/examples/index/lambda_layer_cdk_app.py" ``` === "Terraform" - ```terraform hl_lines="9 38" - terraform { - required_version = "~> 1.0.5" - required_providers { - aws = "~> 3.50.0" - } - } - - provider "aws" { - region = "{region}" - } - - resource "aws_iam_role" "iam_for_lambda" { - name = "iam_for_lambda" - - assume_role_policy = < None: - super().__init__(scope, id_) - - # Launches SAR App as CloudFormation nested stack and return Lambda Layer - powertools_app = sam.CfnApplication(self, - f'{POWERTOOLS_BASE_NAME}Application', - location={ - 'applicationId': POWERTOOLS_ARN, - 'semanticVersion': POWERTOOLS_VER - }, - ) - - powertools_layer_arn = powertools_app.get_att("Outputs.LayerVersionArn").to_string() - powertools_layer_version = aws_lambda.LayerVersion.from_layer_version_arn(self, f'{POWERTOOLS_BASE_NAME}', powertools_layer_arn) - - aws_lambda.Function(self, - 'sample-app-lambda', - runtime=aws_lambda.Runtime.PYTHON_3_8, - function_name='sample-lambda', - code=aws_lambda.Code.asset('./src'), - handler='app.handler', - layers: [powertools_layer_version] - ) + ```python hl_lines="19 22-25 34" + --8<-- "docs/examples/index/sar_cdk_app.py" ``` === "Terraform" @@ -289,47 +180,7 @@ If using SAM, you can include this SAR App as part of your shared Layers stack, > Credits to [Dani Comnea](https://github.com/DanyC97) for providing the Terraform equivalent. ```terraform hl_lines="12-13 15-20 23-25 40" - terraform { - required_version = "~> 0.13" - required_providers { - aws = "~> 3.50.0" - } - } - - provider "aws" { - region = "us-east-1" - } - - resource "aws_serverlessapplicationrepository_cloudformation_stack" "deploy_sar_stack" { - name = "aws-lambda-powertools-python-layer" - - application_id = data.aws_serverlessapplicationrepository_application.sar_app.application_id - semantic_version = data.aws_serverlessapplicationrepository_application.sar_app.semantic_version - capabilities = [ - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ] - } - - data "aws_serverlessapplicationrepository_application" "sar_app" { - application_id = "arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer" - semantic_version = var.aws_powertools_version - } - - variable "aws_powertools_version" { - type = string - default = "1.20.2" - description = "The AWS Powertools release version" - } - - output "deployed_powertools_sar_version" { - value = data.aws_serverlessapplicationrepository_application.sar_app.semantic_version - } - - # Fetch Lambda Powertools Layer ARN from deployed SAR App - output "aws_lambda_powertools_layer_arn" { - value = aws_serverlessapplicationrepository_cloudformation_stack.deploy_sar_stack.outputs.LayerVersionArn - } + --8<-- "docs/examples/index/sar_main.tf" ``` ??? example "Example: Least-privileged IAM permissions to deploy Layer" @@ -341,60 +192,7 @@ If using SAM, you can include this SAR App as part of your shared Layers stack, === "template.yml" ```yaml hl_lines="21-52" - AWSTemplateFormatVersion: "2010-09-09" - Resources: - PowertoolsLayerIamRole: - Type: "AWS::IAM::Role" - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: - - "cloudformation.amazonaws.com" - Action: - - "sts:AssumeRole" - Path: "/" - PowertoolsLayerIamPolicy: - Type: "AWS::IAM::Policy" - Properties: - PolicyName: PowertoolsLambdaLayerPolicy - PolicyDocument: - Version: "2012-10-17" - Statement: - - Sid: CloudFormationTransform - Effect: Allow - Action: cloudformation:CreateChangeSet - Resource: - - arn:aws:cloudformation:us-east-1:aws:transform/Serverless-2016-10-31 - - Sid: GetCfnTemplate - Effect: Allow - Action: - - serverlessrepo:CreateCloudFormationTemplate - - serverlessrepo:GetCloudFormationTemplate - Resource: - # this is arn of the powertools SAR app - - arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer - - Sid: S3AccessLayer - Effect: Allow - Action: - - s3:GetObject - Resource: - # AWS publishes to an external S3 bucket locked down to your account ID - # The below example is us publishing lambda powertools - # Bucket: awsserverlessrepo-changesets-plntc6bfnfj - # Key: *****/arn:aws:serverlessrepo:eu-west-1:057560766410:applications-aws-lambda-powertools-python-layer-versions-1.10.2/aeeccf50-****-****-****-********* - - arn:aws:s3:::awsserverlessrepo-changesets-*/* - - Sid: GetLayerVersion - Effect: Allow - Action: - - lambda:PublishLayerVersion - - lambda:GetLayerVersion - Resource: - - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:aws-lambda-powertools-python-layer* - Roles: - - Ref: "PowertoolsLayerIamRole" + --8<-- "docs/examples/index/least_priviledged_template.yml" ``` You can fetch available versions via SAR ListApplicationVersions API: @@ -455,9 +253,7 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai As a best practice, AWS Lambda Powertools module logging statements are suppressed. If necessary, you can enable debugging using `set_package_logger` for additional information on every internal operation: ```python title="Powertools debug mode example" -from aws_lambda_powertools.logging.logger import set_package_logger - -set_package_logger() # (1) +--8<-- "docs/examples/index/debug_mode.py" ``` 1. :information_source: this will configure our `aws_lambda_powertools` logger with debug.