Writing Terraform Code in TypeScript, Python, Go and More

Ming Yang
Contino Engineering
8 min readJan 30, 2023

--

This article is co-authored by Ming Yang and Paul Martins.

Summary

The Cloud Development Kit for Terraform (CDKTF) is an infrastructure-as-code (IaC) tool that provisions and manages local or cloud infrastructure via Terraform using popular programming languages. Here in this article, we will describe when and how to apply CDKTF in your CI/CD cycle.

Adopting the Cloud Development Kit for Terraform (CDKTF)

DevOps is a set of processes and techniques whose goal is to make software delivery more efficient. A key practice to implement DevOps processes is to adopt Infrastructure as Code (IaC). Since its release by HashiCorp in 2012, Terraform has gradually become the go-to infrastructure automation and management tool across global organisations. In a declarative format, the HashiCorp Configuration Language (HCL) makes it possible for Terraform users to describe the desired state of their infrastructure in code format and then deploy the infrastructure or apply changes to the environment as needed.

Instead of using HCL, CDKTF allows developers across your organisation to use popular programming languages such as Python, Go, Java, TypeScript and C# to generate Terraform configuration and create infrastructure using Terraform. As of v0.14, CDKTF has pre-built plugins so that its users can provision resources from popular providers including AWS, Google Cloud Platform, Azure, Kubernetes, Docker and Github.

A Quick Example

Before we dive deeper into the capabilities of CDKTF, let’s use an example and have a look at how CDKTF can deploy a local web server via Docker. Here, we choose TypeScript as our programming language to deploy a local NGINX server on port 80. We’ll need Node.js (v16+) and use npm (v9.1.2 as the latest version at the time of writing) to install CDKTF:

npm install - global cdktf-cli@latest

After installing CDKTF, we can initialise a CDKTF project by using the cdktf init command.

cdktf init - template=typescript - local

Then we install the plugin for Docker:

cdktf provider add kreuzwerker/docker

Put the following content to the main.tf file in the project folder:

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { DockerProvider } from "@cdktf/provider-docker/lib/provider";
import { Image } from "@cdktf/provider-docker/lib/image";
import { Container } from "@cdktf/provider-docker/lib/container";

class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);

new DockerProvider(this, "docker", {});

const dockerImage = new Image(this, "nginxImage", {
name: "nginx:latest",
keepLocally: false,
});

new Container(this, "nginxContainer", {
name: "tutorial",
image: dockerImage.latest,
ports: [
{
internal: 80,
external: 8000,
},
],
});
}
}

With all the configuration written, we can now deploy this CDKTF application (that is, the CDKTF project) by running:

cdktf deploy  # This is similar to the `terraform apply` command.

And by entering localhost:80 in your web browser, you’ll be able to see the NGINX server set up.

When finished playing, destroy the container:

cdktf destroy  # This is equivalent to the `terraform destroy` command.

For developers who have experience in Terraform, the configuration in the application above is equivalent to the following written in HCL:

resource "docker_image" "nginx" {
name = "nginx:latest"
keep_locally = false
}

resource "docker_container" "nginx" {
image = docker_image.nginx.latest
name = "tutorial"
ports {
internal = 80
external = 8000
}
}

As shown in the diagram below, the CDKTF commands executed in our quick example are part of the workflow in which a CDKTF application provisions the desired infrastructure. All CDKTF operations like deploy and destroy communicate with Terraform for execution.

To provide flexibility, CDKTF applications are interoperable with Terraform projects written in HCL, meaning that:

  1. CDKTF can generate modules that HCL Terraform projects can use in their configurations. CDKTF applications store the synthesised HCL code in the cdkt.out/stacks folder within the project, so that Terraform engineers can always review the configuration in HCL, even before running the cdktf deploy command (by running cdktf synth).
  2. CDKTF can also translate existing code written in HCL into a preferred programming language by using the cdktf convert command.

The history and the architecture of CDKTF

CDKTF shares the core concepts (namely the Construct Programming Model, CPM) and components with the Amazon Web Services Cloud Development Kit (AWS CDK) that went public in 2017. AWS CDK is an open-source software development framework for defining cloud IaC with popular programming languages and deploying it through AWS CloudFormation. It provides a set of language-native frameworks for defining infrastructure, and adapters that let underlying provisioning tools use those definitions.

The first release of CDKTF arrived to the public in mid-2020, and at the time of writing, v0.14 is publicly available as the latest version, offering significant performance improvements compared to previous releases.

CDKTF leverages existing libraries and tools to help convert the definitions you write in your preferred programming language to Terraform configuration files. CDKTF applications are structured as a tree of constructs, which, according to AWS, are classes that “encode configuration detail, boilerplate, and glue logic” for using one or multiple infrastructure services.

Within a CDKTF application, the essential classes are the App, the Stack and the Resource class. The infrastructure configurations are containerised in one or more App class instances, in which one or more Stacks are present.

A Stack represents a collection of infrastructure resources, which is similar to a Terraform working directory. In the previous example where we deployed a local NGINX server, we had a single stack. CDKTF users can actually deploy infrastructure to multiple environments (e.g develop, test, in the diagram above) by specifying more than one stack in their CDKTF applications. Last but not least, a Resource defines infrastructure objects and has various attributes depending on its nature and provider — something any Terraform user should be very familiar with.

Custom constructs

As mentioned above, every building block in a CDKTF application is a construct. With CDKTF, developers can import and create custom construct classes, increasing code reusability and enforcing best practices. Developers can even create custom constructs in more than one programming language.

When building a custom construct, it is highly recommended to make the construct configurable and extensible and provide good defaults whenever possible. Once the construct is created, the cdktf-tf-module-stack package, available in various programming languages, is handy to transform the code into a Terraform Module that can be shared across CDKTF applications and published to the public.

Deployment methods and best practices

Once a CDKTF application is complete, developers can deploy the infrastructure defined in the application in two ways: using the cdktf deploy command, which acts like the terraform apply command in Terraform, or the cdktf synth command to synthesise the application and using Terraform thereafter. The latter can be more popular in organisations where Terraform is integrated with existing CI pipelines, whereas the former is recommended in applications where complex dependencies exist. Unlike Terraform, CDKTF can interpret the inter-dependencies among the stacks, so that the required objects in different stacks can be deployed sequentially. If Terraform Cloud is used in your organisation, both the methods can be adopted.

When designing the architecture of a CDKTF application, it is recommended to create multiple stacks based on:

  • Stages, such as develop, test and production
  • Business units, such as finance, operations
  • Types of objects, such as databases, compute engines
  • Regions, if high availability is required

To achieve this, the custom constructs that describe the stacks should be as reusable as possible, as described in the section above.

If you have sensitive data stored in and read from environment variables, it is noteworthy that the synthesised CDKTF code generated from the cdktf synth command will display those data in plain text. CDKTF therefore recommends using the TerraformVariable construct to access sensitive data.

When to use CDKTF?

While many DevOps practitioners feel comfortable managing their infrastructure via the declarative way using HCL, you should consider using CDKTF when your team have a strong preference or need to use a procedural language to define and manage the infrastructure so that they can apply programming idioms including parameters, loops, conditionals, composition, and inheritance to model your system design using building blocks from cloud providers.

Examples:

You are part of a team that doesn’t have people with HCL skills, then a frontend or backend developer with cloud knowledge could easily use TFCDK to deploy the required infrastructure following IaC best practices.

You currently manage your infrastructure with TF and you have a lot of dynamic resource blocks, for_each and count statements, or a lot of duplicated resource types. These could be code smells hinting that you’d like to do more with HCL but are limited by its declarative nature.

CDKTF could work well for you if your team needs to create abstractions to help manage complexity. CDKTF allows developers to build applications with high-level constructs that automatically provide sensible, secure defaults and reusable code for your cloud resources, defining more infrastructure composed of multiple resources with less code.

Example:

An infosec policy has recently been updated: it is now required that all your AWS S3 buckets are encrypted with their own custom key and have server access logs enabled. With TF this new requirement will need to be captured for every new “aws_s3_bucket” resource. With TFCDK, you can simply define/update a single custom construct and re-use it across all your stacks. Additionally, you can write a unit test with your favourite testing framework to check if any unencrypted or unmonitored S3 bucket is found in your app. This will ensure that whenever the infrastructure code gets updated, the policy will always be respected and the infosec team remains happy.

From a code management point of view, adopting CDKTF allows you to put your infrastructure, application code and configuration all in one place, ensuring that at every milestone you have a complete, cloud-deployable system. You can also easily share infrastructure design patterns among teams within your organisation or even with the public.

Despite the benefits and flexibility CDKTF offers, the tool may not be the most efficient choice for every project in every team. For example, some teams are very comfortable with using Terraform and have created numerous HCL modules, providers, etc. Additionally, if you like to write code for every piece of infrastructure to deploy, then TFCDK is probably not for you either since it can “hide” complexity through construct inheritance or default variables. It is also worth noting that being a newly released tool, it can lag a bit behind on documentation or log verbosity, but we’d expect this to improve over time.

Tags: Terraform, CDK, IaC, CDKTF, DevOp, TypeScript

--

--