Using CDKTF To Create an AWS Lambda Function –

by Blog Admin
0 comment

Having not done much infrastructure before, writing Terraform seemed a pretty daunting task. Learning HCL and its nuances in a declarative manner and configuring it all for different environments is a bit of a learning curve. Creating the same in code using an imperative style seems a better path for a developer.

Setting Up

This is a simple example of using Terraforms cloud development kit (CDKTF) to create a Lambda function in AWS in Typescript.

To get started, follow their installation setup here.

Create a new project:

mkdir cdktf-lambda

cd cdktf-lambda

cdktf init --template="typescript" --providers="aws@~>4.0"

Follow the cmd prompts, and lastly:

npm i @cdktf/provider-archive@5.0.1

At the time, the dependencies were:

  "dependencies": {     "@cdktf/provider-archive": "^5.0.1",     "@cdktf/provider-aws": "13.0.0",     "cdktf": "^0.15.5",     "constructs": "^10.1.310"   },   "devDependencies": {     "@types/jest": "^29.5.1",     "@types/node": "^20.1.0",     "jest": "^29.5.0",     "ts-jest": "^29.1.0",     "ts-node": "^10.9.1",     "typescript": "^5.0.4"   }

The directory structure will have been created like so:

directory structure

The init command provides some boilerplate code to get up and running with.

The main.ts is the central file from which the code will run, and this is known as a TerraformStack. In this stack is where all the IaC will be placed.

Let’s Go

CDKTF has the concept of providers, which are Terraform wrappers for third-party APIs such as AWS. We need to add one for AWS and one to handle the lambda archive bindings:

class MyStack extends TerraformStack { private prefix = 'dale-test-'; private region = '' private accountId = ''; constructor(scope: Construct, id: string) { super(scope, id); new ArchiveProvider(this, "archiveProvider"); new AwsProvider(this, this.prefix + "aws", { region: this.region, allowedAccountIds: [this.accountId], defaultTags: [ { tags: { name: this.prefix +'lambda-stack', version: "1.0", } } ] }); } }

There should be enough here to do a sanity check run:

cdktf diff

At this stage, it error’d and the tsconfig file requires the following to be added:
"ignoreDeprecations": "5.0"

A successful run should show:

cdktf-lambda  No changes. Your infrastructure matches the configuration. 

Roles

Next, add an IAM role and a policy for the Lambda:

const role = new IamRole(this, this.prefix + "iam_for_lambda", {             assumeRolePolicy: new DataAwsIamPolicyDocument(this, this.prefix + "assume_role", {                 statement: [                     {                         actions: [                             "sts:AssumeRole"                         ],                         effect: "Allow",                         principals: [                             {                                 identifiers: ["lambda.amazonaws.com"],                                 type: "Service",                             },                         ],                     }                 ],             }).json,             name: this.prefix + "iam_for_lambda",         });          new IamRolePolicy(this, this.prefix + "iamPolicy", {             name: this.prefix + `iamPolicy-state`,             role: role.id,             policy: new DataAwsIamPolicyDocument(this, this.prefix + "iamPolicyDoc", {                 version: "2012-10-17",                 statement: [                     {                         effect: "Allow",                         actions: ["logs:CreateLogGroup"],                         resources: [`arn:aws:logs:${this.region}:${this.accountId}:*`]                     },                     {                         effect: "Allow",                         actions: [                             "logs:CreateLogStream",                             "logs:PutLogEvents"                         ],                         resources: [                             `arn:aws:logs:${this.region}:${this.accountId}:log-group:/aws/lambda/dale-test-manual:*`                         ]                     }                 ]             }).json         });

Lambda

We’ll create a simple form and place index.html and index.js into the dist folder:

Hello from AWS Lambda!

CDKTF Lambda Demo

{formResults} {debug} ” data-lang=”text/html” contenteditable=”false”>

                      Hello from AWS Lambda!      
  

CDKTF Lambda Demo

{formResults} {debug}

const fs = require('fs'); let html = fs.readFileSync('index.html', {encoding: 'utf8'}); /** * Returns an HTML page containing an interactive Web-based * tutorial. Visit the function URL to see it and learn how * to build with lambda. */ exports.handler = async (event) => { let modifiedHTML = dynamicForm(html, event.queryStringParameters); modifiedHTML = debug(modifiedHTML, event); const response = { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: modifiedHTML, }; return response; }; function debug(modifiedHTML, event) { return modifiedHTML.replace('{debug}', JSON.stringify(event)); } function dynamicForm(html, queryStringParameters) { let formres = ''; if (queryStringParameters) { Object.values(queryStringParameters).forEach(val => { formres = formres + val + ' '; }); } return html.replace('{formResults}', '

Form Submission: ' + formres + '

'); }

Now, set up the lambda:

The files are archived from the dist folder, packaged up, and set in the LambdaFunction.

A LambdaFunctionUrl is set up so it can be publicly accessed. It is also debugged out to see more details.

 const archiveFile = new DataArchiveFile(this, this.prefix +"lambda", {    outputPath: "lambda_function_payload.zip",    sourceDir: path.resolve(__dirname, "dist"),    type: "zip",  });  const lambda = new LambdaFunction(this, this.prefix +"test_lambda", {   environment: {     variables: {       foo: "bar",     },   },   filename: "lambda_function_payload.zip",   functionName: "dale_test_auto",   handler: "index.handler",   role: role.arn,   runtime: "nodejs16.x",   sourceCodeHash: archiveFile.outputBase64Sha256, });  const url = new LambdaFunctionUrl(this, this.prefix +'lambda-url', {   functionName: lambda.functionName,   authorizationType: 'NONE' });  const debugOutput = new TerraformOutput(this, "lambda-function", {   value: url, });  console.log(debugOutput);

Thats it!

Deploying

Now when running cdktf diff we should see it will add four items:

Plan: 4 to add, 0 to change, 0 to destroy. 

  • # aws_iam_role.dale-test-iam_for_lambda (dale-test-iam_for_lambda) will be created
  • # aws_iam_role_policy.dale-test-iamPolicy (dale-test-iamPolicy) will be created
  • # aws_lambda_function.dale-test-test_lambda (dale-test-test_lambda) will be created
  • # aws_lambda_function.dale-test-test_lambda (dale-test-test_lambda) will be created

Now, deploy it.

cdktf deploy  

Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: lambda lambda-function = { "authorization_type" = "NONE" "cors" = tolist([]) "function_arn" = "arn:aws:lambda:eu-west-2::function:dale_test_auto" "function_name" = "dale_test_auto" "function_url" = "https://.lambda-url.eu-west-2.on.aws/" "id" = "dale_test_auto" "invoke_mode" = "BUFFERED" "qualifier" = "" "timeouts" = null /* object */ "url_id" = "" }

The function_url now is the URL to view the lambda as shown below:

CDKTF Lambda Demo

To teardown the Lambda, simply run:

cdktf destroy   Destroy complete! Resources: 4 destroyed.

The full code can be found here.

Conclusion

The code is relatively simple to create infrastructure using CDKTF. It enables a logical structuring of reusable code. Once the infrastructure grows, managing a maintaining the codebase will require less effort. 

Coming from a developer standpoint, it makes sense to create the using IaC. While being a Java developer, TS was selected due to the fact the TF also writes the core library in TS although it does support other languages too but transpired ultimately back to TS.

Not covered here, but the code can be unit tested to ensure everything is wired up correctly, although this doesn’t necessarily prevent error downstream when planning and applying.

Also not covered here are advanced techniques required when passing resources between stacks and references from newly created resources at run time. All possible using CDKTF, however. That may be the topic for the next post.

I hope you enjoyed the post, and thanks for reading.

Opinions expressed by MaximusDevs contributors are their own.

You may also like