Deploy Lambda functions with TypeScript and CDK for Terraform
You can use the Cloud Development Kit for Terraform (CDKTF) to define advanced deployment configurations in a familiar programming language such as TypeScript, Python, or Go.
CDKTF stacks let you manage multiple Terraform configurations in the same CDKTF application. They can save you from writing repetitive CDKTF code, while allowing you to independently manage the resources in each stack. You can also use the outputs of one stack as inputs for another.
Each stack generates its own Terraform workspace so that you can manage each stack independently. You can use stacks to connect multiple deployments across your application, simplifying your infrastructure workflow. In CDKTF v0.4+, asset constructs let you manage assets for resources that need them, such as template_file
, S3 bucket objects, or Lambda function archive files.
In this tutorial, you will deploy a CDKTF application made up of two stacks, each containing a simple AWS Lambda function written in TypeScript. In the process, you will use TerraformAsset
to package your Lambda deployment artifact and define stacks to manage the Lambda deployments independently.
Prerequisites
The tutorial assumes that you are familiar with the CDK for Terraform workflow. If you are new to CDK for Terraform itself, refer first to the Get Started with CDK for Terraform tutorials.
For this tutorial, you will need:
- Terraform v1.2+
- CDK for Terraform v0.15+
- an AWS account
- AWS Credentials configured for use with Terraform
Terraform and CDKTF will use credentials set in your environment or through other means as described in the Terraform documentation.
Note
Some of the infrastructure in this tutorial does not qualify for the AWS free tier. Destroy the infrastructure at the end of the guide to avoid unnecessary charges. We are not responsible for any charges that you incur.
Explore CDKTF application
In your terminal, clone the sample repository.
$ git clone https://github.com/hashicorp-education/learn-cdktf-assets-stacks-lambda
Navigate to the cloned repository.
$ cd learn-cdktf-assets-stacks-lambda
This directory contains the three subdirectories.
- The
lambda-hello-world
directory hosts a Lambda handler written in TypeScript. Every Lamdba function must have a handler. When users invoke the Lambda function, it runs the handler code. This function returns "Hello world!". - The
lambda-hello-name
directory also hosts a Lambda handler written in TypeScript. This function returns "Hello NAME!", whereNAME
is the value for thename
query parameter. IfNAME
is undefined, the handler will return "Hello there!". - The
cdktf
directory hosts the CDKTF application written in TypeScript. You will use this directory to deploy thelambda-hello-world
andlambda-hello-name
Lambda functions.
Notice that both Lambda functions contain pre-compiled dist
directories, so you do not have to compile them in this tutorial. In addition, notice that the directories containing the Lambda handlers are not in the cdktf
directory. This is best practice — you should not have application code in your infrastructure directory.
Navigate to the cdktf
directory.
$ cd cdktf
Open main.ts
, which contains the main CDKTF application. This application defines the LambdaStack
, a CDKTF stack you will use to deploy the lambda-hello-world
and lambda-hello-name
functions.
This file uses the preconfigured AWS provider (@cdktf/provider-aws
). Importing
the library allows you to use your code editor's autocomplete functionality to
help you write the CDK application code.
In the next sections, you will review the LambdaStack
code and use it to
deploy two different Lambda functions.
Examine the code
In this example, the LambdaStack
groups all the resources necessary to deploy
a Lambda function.
First the code creates a random value to ensure that the resource names for your Lambda function and IAM role are unique.
main.ts
// Create random valueconst pet = new random.Pet(this, 'random-name', { length: 2,})
The example code configures the AWS provider to deploy your functions to the
us-west-2
region.
main.ts
new aws.AwsProvider(this, 'provider', { region: 'us-west-2',})
The TerraformAsset
construct
helps you manage assets and expose them to your CDK resources. This code uses
the TerraformAsset
to package the compiled handler code into an archive and
assigns it to a variable named asset
.
main.ts
// Create Lambda executableconst asset = new TerraformAsset(this, 'lambda-asset', { path: path.resolve(__dirname, config.path), type: AssetType.ARCHIVE, // if left empty it infers directory and file})
Tip
This file imports TerraformAsset
and AssetType
from the cdktf
library.
Next the code creates an S3 bucket to host the archive containing your Lambda function and uploads it to the bucket.
main.ts
// Create unique S3 bucket that hosts Lambda executableconst bucket = new aws.S3Bucket(this, 'bucket', { bucketPrefix: `learn-cdktf-${name}`,})// Upload Lambda zip file to newly created S3 bucketconst lambdaArchive = new aws.S3BucketObject(this, 'lambda-archive', { bucket: bucket.bucket, key: `${config.version}/${asset.fileName}`, source: asset.path, // returns a posix path})
Next the example code creates an IAM role for your Lambda function and the
function itself. Notice that the IAM role references the lambdaRolePolicy
object defined at the beginning of the file. The stack also grants the Lambda
role access to write to CloudWatch.
main.ts
// Create Lambda roleconst role = new aws.IamRole(this, 'lambda-exec', { name: `learn-cdktf-${name}-${pet.id}`, assumeRolePolicy: JSON.stringify(lambdaRolePolicy),})// Add execution role for lambda to write to CloudWatch logsnew aws.IamRolePolicyAttachment(this, 'lambda-managed-policy', { policyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', role: role.name,})// Create Lambda functionconst lambdaFunc = new aws.LambdaFunction(this, 'learn-cdktf-lambda', { functionName: `learn-cdktf-${name}-${pet.id}`, s3Bucket: bucket.bucket, s3Key: lambdaArchive.key, handler: config.handler, runtime: config.runtime, role: role.arn,})
Then the code creates an API gateway that targets your Lambda function. This
will make your function accessible via an HTTPS endpoint. The LambdaPermission
gives your API gateway endpoint permission to invoke your Lambda function.
main.ts
// Create and configure API gatewayconst api = new aws.Apigatewayv2Api(this, 'api-gw', { name: name, protocolType: 'HTTP', target: lambdaFunc.arn,})new aws.LambdaPermission(this, 'apigw-lambda', { functionName: lambdaFunc.functionName, action: 'lambda:InvokeFunction', principal: 'apigateway.amazonaws.com', sourceArn: `${api.executionArn}/*/*`,})
Inspect function definitions
Toward the bottom of the main.ts
file, you will find definitions for the lambda-hello-world
and lambda-hello-name
functions using LambdaStack
.
main.ts
new LambdaStack(app, 'lambda-hello-world', { path: '../lambda-hello-world/dist', handler: 'index.handler', runtime: 'nodejs10.x', stageName: 'hello-world', version: 'v0.0.1',})new LambdaStack(app, 'lambda-hello-name', { path: '../lambda-hello-name/dist', handler: 'index.handler', runtime: 'nodejs10.x', stageName: 'hello-name', version: 'v0.0.1',})
Notice how each definition maps to the respective function's properties: the lambda-hello-world
's path
points to ../lambda-hello-world/dist
and its stageName
points to hello-world
. Once you have created the LambdaStack
class, you can use it to deploy as many similarly configured Lambda functions as you want.
View application stacks
Install the dependencies for the CDKTF application.
$ npm installadded 305 packages, and audited 357 packages in 5s35 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities
When you create a new CDKTF application from scratch, the cdktf init
command
will install these dependencies. The application from the sample repository is
already initialized, so you can use npm install
to install the application's
dependencies.
Now, run cdktf provider
to install the
random
and
aws
providers. This configuration uses the random
provider to ensure the IAM role
name is unique.
$ cdktf provider add "aws@~>4.0" randomChecking whether pre-built provider exists for the following constraints: provider: aws version : ~>4.0 language: typescript cdktf : 0.15.0Found pre-built provider.Adding package @cdktf/provider-aws @ 9.0.0Installing package @cdktf/provider-aws @ 9.0.0 using npm.Package installed.Checking whether pre-built provider exists for the following constraints: provider: random version : latest language: typescript cdktf : 0.15.0Found pre-built provider.Adding package @cdktf/provider-random @ 2.0.0Installing package @cdktf/provider-random @ 2.0.0 using npm.Package installed.
List all the stacks defined in your CDKTF application. Notice these map to the new LambdaStack
definitions declared in main.ts
.
$ cdktf listStack name Pathlambda-hello-world cdktf.out/stacks/lambda-hello-worldlambda-hello-name cdktf.out/stacks/lambda-hello-name
Deploy Hello World function
Deploy the lambda-hello-world
stack. Remember to confirm the deploy by
choosing Approve
.
$ cdktf deploy lambda-hello-worldlambda-hello-world Initializing the backend...lambda-hello-world Successfully configured the backend "local"! Terraform will automatically use this backend unless the backend configuration changes.lambda-hello-world Initializing provider plugins...lambda-hello-world - Finding hashicorp/aws versions matching "4.23.0"...lambda-hello-world - Finding hashicorp/random versions matching "3.3.2"...lambda-hello-world - Using hashicorp/aws v4.23.0 from the shared cache directorylambda-hello-world - Installing hashicorp/random v3.3.2...lambda-hello-world - Installed hashicorp/random v3.3.2 (signed by HashiCorp)##... Plan: 8 to add, 0 to change, 0 to destroy. Changes to Outputs: + url = (known after apply) ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan To perform exactly these actions, run the following command to apply: terraform apply "plan"Please review the diff output above for lambda-hello-world❯ Approve Applies the changes outlined in the plan. Dismiss Stop##... Apply complete! Resources: 8 added, 0 changed, 0 destroyed. Outputs:lambda-hello-world url = "https://bur6dfzia5.execute-api.us-west-2.amazonaws.com" lambda-hello-world url = https://bur6dfzia5.execute-api.us-west-2.amazonaws.com
Open the url
output in your web browser to confirm the API gateway returns "Hello world!".
Deploy Hello Name function
Deploy the lambda-hello-name
stack. Remember to confirm the deploy with Approve
.
$ cdktf deploy lambda-hello-namelambda-hello-name Initializing the backend...lambda-hello-name Successfully configured the backend "local"! Terraform will automatically use this backend unless the backend configuration changes.lambda-hello-name Initializing provider plugins...lambda-hello-name - Finding hashicorp/aws versions matching "4.23.0"...lambda-hello-name - Finding hashicorp/random versions matching "3.3.2"...lambda-hello-name - Using hashicorp/aws v4.23.0 from the shared cache directorylambda-hello-name - Using hashicorp/random v3.3.2 from the shared cache directory##... Plan: 8 to add, 0 to change, 0 to destroy. Changes to Outputs: + url = (known after apply) ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan To perform exactly these actions, run the following command to apply: terraform apply "plan"Please review the diff output above for lambda-hello-name❯ Approve Applies the changes outlined in the plan. Dismiss Stop##... Apply complete! Resources: 8 added, 0 changed, 0 destroyed. Outputs:lambda-hello-name url = "https://oogzjki3cb.execute-api.us-west-2.amazonaws.com" lambda-hello-name url = https://oogzjki3cb.execute-api.us-west-2.amazonaws.com
Open the url
output in your web browser to confirm the API gateway returns "Hello there!".
Append ?name=Terry
to the URL. The API gateway will respond with "Hello Terry!".
Inspect synthesized stacks
CDKTF stacks have their own configuration and state files.
In your cdktf
directory, find the cdktf.out
directory. CDKTF generates this directory when it synthesizes the Terraform configuration from the application code when you run cdktf synth
or cdktf deploy
.
Notice how there are two directories in cdktf.out/stacks
. Each directory corresponds with the stack defined in the application code and contains the generated Terraform configuration, plan file, and assets.
cdktf.out/├── manifest.json└── stacks ├── lambda-hello-name │ ├── assets │ │ └── lambda-asset │ │ └── ACDAAB9A40AD1E0EC3B40C4B53527E85 │ │ └── archive.zip │ ├── cdk.tf.json │ └── plan └── lambda-hello-world ├── assets │ └── lambda-asset │ └── BD42BA06A24B006B4907A4F399734ACC │ └── archive.zip ├── cdk.tf.json └── plan
In addition, you will find terraform.lambda-hello-name.tfstate
and
terraform.lambda-hello-name.tfstate
in your cdktf
directory. The cdktf
deploy STACK_NAME
generates a state file named terraform.STACK_NAME.tfstate
where STACK_NAME
is the stack name. Like any Terraform state file, you should
not commit these files into source control.
Clean up resources
Destroy the infrastructure you created in this tutorial.
First, destroy the lambda-hello-world
stack. Remember to confirm the destroy with Approve
.
$ cdktf destroy lambda-hello-worldlambda-hello-world Initializing the backend...lambda-hello-world Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock filelambda-hello-world - Reusing previous version of hashicorp/random from the dependency lock filelambda-hello-world - Using previously-installed hashicorp/aws v4.23.0lambda-hello-world - Using previously-installed hashicorp/random v3.3.2##... Plan: 0 to add, 0 to change, 8 to destroy. Changes to Outputs: - url = "https://bur6dfzia5.execute-api.us-west-2.amazonaws.com" -> null ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan To perform exactly these actions, run the following command to apply: terraform apply "plan"Please review the diff output above for lambda-hello-world❯ Approve Applies the changes outlined in the plan. Dismiss Stop##..lambda-hello-world aws_s3_object.lambda-archive (lambda-archive): Destruction complete after 0slambda-hello-world aws_s3_bucket.bucket (bucket): Destroying... [id=learn-cdktf-lambda-hello-world20220727201212716200000001]lambda-hello-world aws_s3_bucket.bucket (bucket): Destruction complete after 0slambda-hello-world Destroy complete! Resources: 8 destroyed.
Now, destroy the lambda-hello-name
stack. Remember to confirm the destroy with Approve
.
$ cdktf destroy lambda-hello-namelambda-hello-name Initializing the backend...lambda-hello-name Initializing provider plugins... - Reusing previous version of hashicorp/random from the dependency lock filelambda-hello-name - Reusing previous version of hashicorp/aws from the dependency lock filelambda-hello-name - Using previously-installed hashicorp/random v3.3.2lambda-hello-name - Using previously-installed hashicorp/aws v4.23.0##.. Plan: 0 to add, 0 to change, 8 to destroy. Changes to Outputs: - url = "https://oogzjki3cb.execute-api.us-west-2.amazonaws.com" -> null ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan To perform exactly these actions, run the following command to apply: terraform apply "plan"Please review the diff output above for lambda-hello-world❯ Approve Applies the changes outlined in the plan. Dismiss Stop##...lambda-hello-name aws_s3_object.lambda-archive (lambda-archive): Destruction complete after 1slambda-hello-name aws_s3_bucket.bucket (bucket): Destroying... [id=learn-cdktf-lambda-hello-name20220727201429435300000001]lambda-hello-name aws_s3_bucket.bucket (bucket): Destruction complete after 0slambda-hello-name Destroy complete! Resources: 8 destroyed.
Next steps
In this tutorial, you deployed and destroyed two different Lambda functions using CDKTF. In the process, you learned how to use TerraformAsset
to package your Lambda deployment artifact and stacks to independently manage the Lambda deployments.
You can also connect multiple stacks together to define multiple deployments in a single CDKTF application.
For more information on topics covered in this tutorial, check out the following resources.
- Inspect the code for a fully integrated approach in
fully-integrated
branch. This approach usesyarn
based monorepo setup and introduces aNodejsFunction
Construct which bundles the code transparently viaesbuild
. Visit this PR includes additional context and instructions. - Read more about constructs in the CDKTF Constructs documentation.
- Read more about assets in the CDKTF Assets documentation.
- Read more about CDKTF releases.