$ 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:
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:
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.
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:
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.