Terraform
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 value
const 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 executable
const 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 executable
const bucket = new aws.S3Bucket(this, 'bucket', {
bucketPrefix: `learn-cdktf-${name}`,
})
// Upload Lambda zip file to newly created S3 bucket
const 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 role
const 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 logs
new aws.IamRolePolicyAttachment(this, 'lambda-managed-policy', {
policyArn:
'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
role: role.name,
})
// Create Lambda function
const 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 gateway
const 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 install
added 305 packages, and audited 357 packages in 5s
35 packages are looking for funding
run `npm fund` for details
found 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" random
Checking whether pre-built provider exists for the following constraints:
provider: aws
version : ~>4.0
language: typescript
cdktf : 0.15.0
Found pre-built provider.
Adding package @cdktf/provider-aws @ 9.0.0
Installing 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.0
Found pre-built provider.
Adding package @cdktf/provider-random @ 2.0.0
Installing 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 list
Stack name Path
lambda-hello-world cdktf.out/stacks/lambda-hello-world
lambda-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-world
lambda-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 directory
lambda-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-name
lambda-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 directory
lambda-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-world
lambda-hello-world Initializing the backend...
lambda-hello-world Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
lambda-hello-world - Reusing previous version of hashicorp/random from the dependency lock file
lambda-hello-world - Using previously-installed hashicorp/aws v4.23.0
lambda-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 0s
lambda-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 0s
lambda-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-name
lambda-hello-name Initializing the backend...
lambda-hello-name Initializing provider plugins...
- Reusing previous version of hashicorp/random from the dependency lock file
lambda-hello-name - Reusing previous version of hashicorp/aws from the dependency lock file
lambda-hello-name - Using previously-installed hashicorp/random v3.3.2
lambda-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 1s
lambda-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 0s
lambda-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.