Lambda handler code #
We’ll start with the Terraconstructs Lambda handler code.
- Create a directory
lambda
in the root of your project tree (next tomain.ts
). - TS CDKTF projects created with
cdktf init
ignore all.js
files by default. To track these files with git, add!lambda/*.js
to your.gitignore
file. This ensures that your Lambda assets are properly checked into source control. - Add a file called
lambda/hello.js
with the following contents:
exports.handler = async function(event) {
console.log("request:", JSON.stringify(event, undefined, 2));
return {
statusCode: 200,
headers: { "Content-Type": "text/plain" },
body: `Hello, CDKTF! You've hit ${event.path}\n`
};
};
This is a simple Lambda function which returns the text “Hello, CDKTF! You’ve hit [url path]”. The function’s output also includes the HTTP status code and HTTP headers. These are used by API Gateway to formulate the HTTP response to the user.
This lambda is provided in Javascript. For more information on writing lambda functions in your language of choice, please refer to the AWS Lambda documentation here.
Install the AWS Lambda construct library #
The TerraConstructs library is an extensive library of constructs. The construct library
is divided into modules, one for each Cloud and Service type it supports. For example,
if you want to define an AWS Lambda function, we will need to use the aws/compute
module.
A few words about copying & pasting in this workshop #
In this workshop, we highly recommended to type CDK code instead of copying & pasting (there’s usually not much to type). This way, you’ll be able to fully experience what it’s like to use the CDK. It’s especially cool to see your IDE help you with auto-complete, inline documentation and type safety.
Add an AWS Lambda Function to your stack #
Add import
statements at the beginning of main.ts
, and a LambdaFunction
to your stack.
import { App } from "cdktf";
import { Construct } from "constructs";
import { AwsStack, AwsStackProps } from "terraconstructs/lib/aws";
import { Code, LambdaFunction, Runtime } from "terraconstructs/lib/aws/compute";
class MyStack extends AwsStack {
constructor(scope: Construct, id: string, props: AwsStackProps) {
super(scope, id, props);
// defines an AWS Lambda resource
new LambdaFunction(this, "HelloHandler", {
runtime: Runtime.NODEJS_22_X,
code: Code.fromAsset("lambda"),
handler: "hello.handler",
});
}
}
const app = new App();
new MyStack(app, "cdk-workshop", {
environmentName: "dev",
gridUUID: "cdk-workshop-dev",
providerConfig: {
region: "us-east-1",
},
});
app.synth();
A few things to notice:
- Our function uses the NodeJS (
NODEJS_22_X
) runtime - The handler code is loaded from the
lambda
directory which we created earlier. Path is relative to where you executecdktf
from, which is the project’s root directory - The name of the handler function is
hello.handler
(“hello” is the name of the file and “handler” is the exported function name)
A word about constructs and constructors #
As you can see, the class constructors of both MyStack
and
LambdaFunction
(and many other classes in the CDK) have the signature
(scope, id, props)
. This is because all of these classes are constructs.
Constructs are the basic building block of CDK apps. They represent abstract
“cloud components” which can be composed together into higher level abstractions
via scopes. Scopes can include constructs, which in turn can include other
constructs, etc.
Constructs are always created in the scope of another construct and must always have an identifier which must be unique within the scope it’s created. Therefore, construct initializers (constructors) will always have the following signature:
scope
: the first argument is always the scope in which this construct is created. In almost all cases, you’ll be defining constructs within the scope of current construct, which means you’ll usually just want to passthis
for the first argument. Make a habit out of it.id
: the second argument is the local identity of the construct. It’s an ID that has to be unique amongst construct within the same scope. The CDKTF uses this identity to calculate the Terraform Logical ID for each resource defined within this scope. To read more about IDs in the CDK, see the CDKTF user manual.props
: the last (sometimes optional) argument is always a set of initialization properties. Those are specific to each construct. For example, theLambdaFunction
construct accepts properties likeruntime
,code
andhandler
. You can explore the various options using your IDE’s auto-complete
Diff #
Save your code, and let’s take a quick look at the diff before we deploy:
cdktf diff
Output would look like this:
cdk-workshop Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
cdk-workshop # aws_cloudwatch_log_group.HelloHandler_LogGroup_49850324 (HelloHandler/LogGroup) will be created
+ resource "aws_cloudwatch_log_group" "HelloHandler_LogGroup_49850324" {
+ arn = (known after apply)
+ id = (known after apply)
+ log_group_class = (known after apply)
+ name = "/aws/lambda/cdk-workshop-dev-cdorkshopHelloHandler"
+ name_prefix = (known after apply)
+ retention_in_days = 7
+ skip_destroy = false
+ tags = {
+ "Name" = "dev-HelloHandler"
+ "grid:EnvironmentName" = "dev"
+ "grid:UUID" = "cdk-workshop-dev"
}
+ tags_all = {
+ "Name" = "dev-HelloHandler"
+ "grid:EnvironmentName" = "dev"
+ "grid:UUID" = "cdk-workshop-dev"
}
}
...
# aws_s3_object.FileAsset_S3 (FileAsset_S3) will be created
+ resource "aws_s3_object" "FileAsset_S3" {
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "cdk-workshop-dev-694710432912-us-east-1"
+ bucket_key_enabled = (known after apply)
+ checksum_crc32 = (known after apply)
+ checksum_crc32c = (known after apply)
+ checksum_crc64nvme = (known after apply)
+ checksum_sha1 = (known after apply)
+ checksum_sha256 = (known after apply)
+ content_type = (known after apply)
+ etag = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ key = "de63b575ee4558f96da9ebc022e06ff896758c0b7ddaab0ed4ab0e17318c9daa.zip"
+ kms_key_id = (known after apply)
+ server_side_encryption = (known after apply)
+ source = "assets/FileAsset/de63b575ee4558f96da9ebc022e06ff896758c0b7ddaab0ed4ab0e17318c9daa/archive.zip"
+ source_hash = "de63b575ee4558f96da9ebc022e06ff896758c0b7ddaab0ed4ab0e17318c9daa"
+ storage_class = (known after apply)
+ tags_all = (known after apply)
+ version_id = (known after apply)
}
Plan: 6 to add, 0 to change, 0 to destroy.
As you can see, this code synthesizes an aws_lambda_function resource. It also synthesized a couple of AWS S3 Asset objects that are used by TerraConstructs to propagate the location of the handler code.
Deploy #
Let’s deploy:
cdktf deploy
You’ll notice that cdktf deploy
not only deployed your Terraform configuration, but
also archived and uploaded the lambda
directory from your disk to the asset bucket.
Testing our function #
Let’s go to the AWS Lambda Console and test our function.
-
Open the AWS Lambda Console (make sure you are in the correct region).
You should see our function:
-
Click on the function name to go to the console.
-
Click on the Test tab tab under the Function overview:
-
Enter
test
under Event name. -
Select API Gateway AWS Proxy from the Template list.
-
Hit Test and wait for the execution to complete.
-
Expand Details in the Execution result pane and you should see our expected output: