🎉 I'm releasing 12 products in 12 months! If you love product, checkout my new blog workingoutloud.dev

Back to home

Python Lambda Functions Deployed With The Typescript AWS CDK

This combined is Day 26 of the #100DaysOfPython challenge and Day 8 of the Melbourne lockdown bonus series on the AWS CDK for TypeScript.

This post will demonstrate how we can deploy a sample Python lambda to the AWS Lambda service on LocalStack using the TypeScript AWS CDK.

Source code can be found here

Prerequisites

  1. Basic familiarity with AWS CDK for TypeScript.

Getting started

We will clone the template repo to get started:

$ git clone https://github.com/okeeffed/using-the-aws-cdk-with-localstack-and-aws-cdk-local python-lambda-functions-deployed-with-the-typescript-aws-cdk $ cd python-lambda-functions-deployed-with-the-typescript-aws-cdk $ npm i # if install fails, remove the lockfile and try again # Add the files we will need for the construct $ mkdir lib/construct-python-lambda-fn $ touch lib/construct-python-lambda-fn/index.ts # Add the files we will need for the lambda function $ mkdir -p functions/hello-python-lambda $ touch functions/hello-python-lambda/index.py

At this stage, we will have the app in a basic working state.

In order to create the CDN stack, we will require the following AWS CDK libraries:

@aws-cdk/core @aws-cdk/aws-lambda @aws-cdk/aws-s3-assets

We can install these prior to doing any work:

npm i @aws-cdk/core @aws-cdk/aws-lambda @aws-cdk/aws-s3-assets

The repository that we cloned already has a upgrade script supplied. We can use this to ensure our CDK packages are at parity:

npm run upgrade

We are now at a stage add a pattern construct to a stack.

Adding a pattern construct

We want to create a re-useable construct that will allow us to deploy a basic Python lambda functions.

We are also going to store the function inside of a S3 bucket using the assets library.

Let's get to writing the code. Inside of lib/construct-python-lambda-fn/index.ts, add the following:

import * as lambda from "@aws-cdk/aws-lambda"; import * as cdk from "@aws-cdk/core"; import * as assets from "@aws-cdk/aws-s3-assets"; interface LambdaAssetProps { functionFolderPath: string; } interface ConstructPythonLambdaFnProps extends cdk.StackProps { // We require the path name to the lambda function to be passed in lambdaAssetProps: LambdaAssetProps; // Optional overrides for the lambda function lambdaProps?: lambda.FunctionProps; } export class ConstructPythonLambdaFn extends cdk.Construct { // We invert control over the properties here to use // higher in the stack when needing to add permissions // related the function (not demoed here). public lambdaAsset: assets.Asset; public lambdaFn: lambda.Function; constructor( app: cdk.Construct, id: string, props: ConstructPythonLambdaFnProps ) { super(app, id); // Adds the function to our S3 bucket this.lambdaAsset = new assets.Asset(this, "LambdaAssetsZip", { path: props.lambdaAssetProps.functionFolderPath, }); // We will set base lambda props as a default // but allow them to be overriden const baseLambdaProps = { code: lambda.Code.fromBucket( this.lambdaAsset.bucket, this.lambdaAsset.s3ObjectKey ), timeout: cdk.Duration.seconds(300), runtime: lambda.Runtime.PYTHON_3_8, handler: "index.handler", }; // Override our defaults if a lambdaProps object is supplied const lambdaFnProps = { ...baseLambdaProps, ...props.lambdaProps, }; // Create the lambda function this.lambdaFn = new lambda.Function(this, "LambdaAssetFn", lambdaFnProps); } }

In this construct, we are inverting control over the lambda props be setting healthy defaults but allowing them to be overridden by any props that adhere to the interface.

We also require a path to our function folder that will be passed for the asset.

Making the attributes public for an instance allows us to access required properties in the stack that the constructs are used (which can be for added/distributing permissions to other constructs).

Now we are ready to create a simple function.

Creating the function

Inside of functions/hello-python-lambda/index.py, add the following code:

def handler(event, lambda_context): print('Hello, world!') return 'Success'

This is a super simple function, but the import part is that the file is name index.py and the function is named handler.

This relates to our base construct properties, that have handler: "index.handler" in them.

The index refers to the file name and the handler refers to the function name.

What we need to do now is use the construct in our stack and pass the functions/hello-python-lambda folder.

Adding the construct to our stack

Inside of lib/aws-cdk-with-typescript-foundations-stack.ts, add the following code:

import * as cdk from "@aws-cdk/core"; import { resolve } from "path"; import { ConstructPythonLambdaFn } from "./construct-python-lambda-fn"; export class AwsCdkWithTypescriptFoundationsStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here new ConstructPythonLambdaFn(this, "BasePythonLambda", { lambdaAssetProps: { functionFolderPath: resolve( __dirname, "../functions/hello-python-lambda" ), }, }); } }

Here we are creating a ConstructPythonLambdaFn construct and passing in the functions/hello-python-lambda folder.

That folder itself is an absolute path that is resolved using path.resolve and the available __dirname variable to resolve the folder that the stack is defined in with the relative path to functions/hello-python-lambda.

At this stage, we are ready to deploy our stack to LocalStack and test the function.

Deploying to LocalStack

This will follow on from what was demonstrated in the "Using The AWS CDK With LocalStack And aws-cdk-local" post.

From the project root, we need to synthesize the stack, bootstrap the stack (as we use assets) and deploy the stack:

$ npm run local synth # ... output $ npm run local bootstrap > aws-cdk-with-typescript-foundations@0.1.0 local > cdklocal "bootstrap" ⏳ Bootstrapping environment aws://000000000000/us-east-1... CDKToolkit: creating CloudFormation changeset... ✅ Environment aws://000000000000/us-east-1 bootstrapped. $ npm run local deploy > aws-cdk-with-typescript-foundations@0.1.0 local > cdklocal "deploy" This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening). Please confirm you intend to make the following modifications: IAM Statement Changes ┌───┬────────────────────────┬────────┬────────────────────────┬─────────────────────────┬───────────┐ │ │ Resource │ Effect │ Action │ Principal │ Condition │ ├───┼────────────────────────┼────────┼────────────────────────┼─────────────────────────┼───────────┤ │ + │ ${BasePythonLambda/Lam │ Allow │ sts:AssumeRole │ Service:lambda.amazonaw │ │ │ │ bdaAssetFn/ServiceRole │ │ │ s.com │ │ │ │ .Arn} │ │ │ │ │ └───┴────────────────────────┴────────┴────────────────────────┴─────────────────────────┴───────────┘ IAM Policy Changes ┌───┬───────────────────────────────────────────────┬────────────────────────────────────────────────┐ │ │ Resource │ Managed Policy ARN │ ├───┼───────────────────────────────────────────────┼────────────────────────────────────────────────┤ │ + │ ${BasePythonLambda/LambdaAssetFn/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service- │ │ │ │ role/AWSLambdaBasicExecutionRole │ └───┴───────────────────────────────────────────────┴────────────────────────────────────────────────┘ (NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299) Do you wish to deploy these changes (y/n)? y AwsCdkWithTypescriptFoundationsStack: deploying... [0%] start: Publishing 9536894df9c36ae405d3d040b7007f052dca3506500a07d5d8c117983a81c7d6:current [100%] success: Published 9536894df9c36ae405d3d040b7007f052dca3506500a07d5d8c117983a81c7d6:current AwsCdkWithTypescriptFoundationsStack: creating CloudFormation changeset... ✅ AwsCdkWithTypescriptFoundationsStack Stack ARN: arn:aws:cloudformation:us-east-1:000000000000:stack/AwsCdkWithTypescriptFoundationsStack/6143f661

At this stage, the function has now been deployed to LocalStack and we are ready to run our function.

Invoking the function

# Replace this with your region $ awslocal lambda list-functions --region=us-west-1 { "Functions": [ { "FunctionName": "AwsCdkWithTypescriptFoundationsStack-lambda-b043b2d8", "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:AwsCdkWithTypescriptFoundationsStack-lambda-b043b2d8", "Runtime": "python3.8", "Role": "arn:aws:iam::000000000000:role/cf-AwsCdkWithTypescriptFoundationsStack-BasePythonLambdaLambdaAssetFnServiceRole9C54B057", "Handler": "index.handler", "CodeSize": 267, "Description": "", "Timeout": 300, "LastModified": "2021-08-13T08:21:02.841+0000", "CodeSha256": "4COUngQGx0EBKv1ci9k6oa8wE05ql3L8V/LeTclcjxA=", "Version": "$LATEST", "VpcConfig": {}, "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "a72b9da5-70ea-4cc6-bda2-78a0804b85c0", "State": "Active", "LastUpdateStatus": "Successful", "PackageType": "Zip" } ] } # Use the function name to invoke the function $ awslocal lambda invoke --function-name 'AwsCdkWithTypescriptFoundationsStack-lambda-b043b2d8' --region=us-west-1 lambda_output.txt { "StatusCode": 200, "LogResult": "", "ExecutedVersion": "$LATEST" }

If we now check our LocalStack logs in the terminal where we have Docker running, we can see the following:

localstack_1 | 2021-08-13T08:27:35:INFO:localstack.services.awslambda.lambda_executors: Running lambda cmd: CONTAINER_ID="$(docker create -i -e AWS_REGION="$AWS_REGION" -e DOCKER_LAMBDA_USE_STDIN="$DOCKER_LAMBDA_USE_STDIN" -e LOCALSTACK_HOSTNAME="$LOCALSTACK_HOSTNAME" -e EDGE_PORT="$EDGE_PORT" -e _HANDLER="$_HANDLER" -e AWS_LAMBDA_FUNCTION_TIMEOUT="$AWS_LAMBDA_FUNCTION_TIMEOUT" -e AWS_LAMBDA_FUNCTION_NAME="$AWS_LAMBDA_FUNCTION_NAME" -e AWS_LAMBDA_FUNCTION_VERSION="$AWS_LAMBDA_FUNCTION_VERSION" -e AWS_LAMBDA_FUNCTION_INVOKED_ARN="$AWS_LAMBDA_FUNCTION_INVOKED_ARN" -e AWS_LAMBDA_COGNITO_IDENTITY="$AWS_LAMBDA_COGNITO_IDENTITY" --rm "lambci/lambda:python3.8" "index.handler")";docker cp "/tmp/localstack/zipfile.40b86a72/." "$CONTAINER_ID:/var/task"; docker start -ai "$CONTAINER_ID"; localstack_1 | 2021-08-13T08:27:36:DEBUG:localstack.services.awslambda.lambda_executors: Lambda arn:aws:lambda:us-east-1:000000000000:function:AwsCdkWithTypescriptFoundationsStack-lambda-b043b2d8 result / log output: localstack_1 | "Success" localstack_1 | > START RequestId: 2d4fd5a0-fe6b-11ae-8da9-d3cfb206866b Version: $LATEST localstack_1 | > Hello, world! localstack_1 | > END RequestId: 2d4fd5a0-fe6b-11ae-8da9-d3cfb206866b localstack_1 | > REPORT RequestId: 2d4fd5a0-fe6b-11ae-8da9-d3cfb206866b Init Duration: 171.18 ms Duration: 10.23 ms Billed Duration: 11 ms Memory Size: 1536 MB Max Memory Used: 25 MB

The return value will also be written out into lambda_output.txt:

"Success"

Tear down

Finally, we can tear down the stack:

$ npm run local destroy > aws-cdk-with-typescript-foundations@0.1.0 local > cdklocal "destroy" Are you sure you want to delete: AwsCdkWithTypescriptFoundationsStack (y/n)? y AwsCdkWithTypescriptFoundationsStack: destroying... ✅ AwsCdkWithTypescriptFoundationsStack: destroyed

Summary

Today's post demonstrated how to deploy a basic Python lambda function to LocalStack using the TypeScript AWS CDK.

Moving on from here, we can begin looking at how to add dependencies via Lambda layers in future posts.

Resources and further reading

Photo credit: jdent

Personal image

Dennis O'Keeffe

@dennisokeeffe92
  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on Visibuild.

1,200+ PEOPLE ALREADY JOINED ❤️️

Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.

Python Lambda Functions Deployed With The Typescript AWS CDK

Introduction

Share this post