DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Data Migration With AWS DMS and Terraform IaC
  • Building Scalable Data Lake Using AWS
  • Building a Scalable ML Pipeline and API in AWS
  • Breaking AWS Lambda: Chaos Engineering for Serverless Devs

Trending

  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  • Proactive Security in Distributed Systems: A Developer’s Approach
  • Tired of Spring Overhead? Try Dropwizard for Your Next Java Microservice
  • How to Use AWS Aurora Database for a Retail Point of Sale (POS) Transaction System
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Using CDKTF To Create an AWS Lambda Function

Using CDKTF To Create an AWS Lambda Function

Writing Infrastructure as Code (IaC). This post shows an example of creating a simple HTML form and serving it up in a Lambda function that can be accessed from a URL.

By 
Dale Waterworth user avatar
Dale Waterworth
·
May. 10, 23 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
5.5K Views

Join the DZone community and get the full member experience.

Join For Free

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:

JSON
 
  "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:

TypeScript
 
class MyStack extends TerraformStack {
  private prefix = 'dale-test-';
  private region = '<your-aws-region>'
  private accountId = '<your-aws-account>';
  
  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:

TypeScript
 
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:

HTML
 
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta data-fr-http-equiv="x-ua-compatible" content="ie=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title>Hello from AWS Lambda!</title>
    <style type="text/css">
        @font-face {
            font-family: "sans-serif";
        }

        body {
            margin: 0;
            font-family: "Amazon Ember", Helvetica, Arial, sans-serif;
        }

        h1 {
            background-color: #232f3e;
            color: white;
            font-size: 3rem;
            font-weight: 300;
            margin: 0;
            padding: 1rem;
            text-align: center;
        }


        input[type="text"] {
            font-family: "Amazon Ember", Helvetica, Arial, sans-serif;
            flex-grow: 1;
            border: 1px solid #aab7b8;
            border-radius: 2px;
            color: #16191f;
        }

        input[type="text"]:focus {
            border: 1px solid #00a1c9;
            box-shadow: 0 0 0 1px #00a1c9;
            outline: 2px dotted transparent;
        }

        form {
            display: flex;
            flex-direction: row;
            gap: 1rem;
            padding-right: 3rem;
        }

        input[type="submit"] {
            background-color: white;
            border: 1px solid #545b64;
            border-radius: 2px;
            color: #545b64;
            cursor: pointer;
            font-weight: 700;
            padding: .4rem 2rem;
        }

        input[type="submit"]:hover {
            background-color: #f2f3f3;
            border: 1px solid #16191f;
            color: #16191f;
        }

    </style>
</head>
<body>
<h1>CDKTF Lambda Demo</h1>

<form action="/" method="GET">
    <input name="name"  placeholder="name" label="name">
    <input name="location" placeholder="location" label="location">
    <input type="submit" value="submit">
</form>
{formResults}
{debug}

</body>
</html>


JavaScript
 
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}',
      '<h4>Form Submission: ' + formres + '</h4>');
}


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.

TypeScript
 
 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  

Plain Text
 
        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:<id>:function:dale_test_auto"
          "function_name" = "dale_test_auto"
          "function_url" = "https://<random-url>.lambda-url.eu-west-2.on.aws/"
          "id" = "dale_test_auto"
          "invoke_mode" = "BUFFERED"
          "qualifier" = ""
          "timeouts" = null /* object */
          "url_id" = "<random>"
        }


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

CDKTF Lambda Demo

To teardown the Lambda, simply run:

Plain Text
 
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.

AWS AWS Lambda Terraform (software)

Opinions expressed by DZone contributors are their own.

Related

  • Data Migration With AWS DMS and Terraform IaC
  • Building Scalable Data Lake Using AWS
  • Building a Scalable ML Pipeline and API in AWS
  • Breaking AWS Lambda: Chaos Engineering for Serverless Devs

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!