July 16, 2020·6 min read·engineeringterraformcdktfinfrastructure

CDK for Terraform adds programming languages before the plan

The CDK for Terraform community preview generates Terraform JSON from TypeScript and Python. Its synthesis boundary preserves Terraform planning while introducing another program to understand.

A programming language can describe how to construct an infrastructure configuration without becoming the engine that provisions it. That separation is the interesting design choice in CDK for Terraform. The application runs first and emits a configuration. Terraform then interprets that configuration through its existing lifecycle.

HashiCorp and AWS introduce the community preview on July 16, with TypeScript and Python support. The launch-era README labels the project experimental, in alpha testing and unready for production use. This is an additional way to author Terraform configuration, with a deliberately early stability claim.

I find the proposal interesting because Terraform's reusable abstraction over infrastructure APIs is its strongest property. Supporting another authoring language can preserve that property. The important question is how clearly the generated configuration retains the relationships the author intended.

Synthesis introduces a separate execution stage

The README divides the project into the cdktf library and cdktf-cli. Within that division, the library provides programming constructs for infrastructure resources. The CLI creates projects and generates configuration. The application uses AWS CDK constructs, but the generated output is Terraform configuration in JSON.

That output is an explicit boundary. The synthesis guide documents cdktf synth, which writes configuration to cdktf.out. It then shows ordinary Terraform commands operating on that directory.

cdktf synth
cd cdktf.out
terraform plan

This sequence gives a useful way to reason about failures. A TypeScript compilation error belongs to authoring and synthesis. An error during Terraform planning concerns the configuration Terraform receives. Failure in the first stage may prevent a configuration from being produced at all, while a failure in the second can occur after synthesis succeeded.

The distinction also limits what language-level checking establishes. A program can compile while generating a configuration that Terraform or its providers reject. The compiler can check the generated binding's types, but it does not turn every infrastructure constraint into a TypeScript type rule.

The reverse inspection is valuable too. Reading the JSON reveals the configuration Terraform will consume. That makes the output useful evidence when the source program uses helper functions or loops whose combined effect is difficult to infer directly from a short code review.

Generated bindings connect provider schemas to source code

The TypeScript getting-started guide demonstrates importing provider and resource classes from a generated directory. The CLI documentation says cdktf get downloads the configured providers and modules and generates CDK constructs for them.

A project can describe its provider requirements in cdktf.json. The launch-era CLI guide gives the following shape.

{
  "language": "typescript",
  "app": "node main.js",
  "terraformProviders": ["aws@~> 2.0"]
}

Here, the application command identifies the program that synthesis executes. The provider entry identifies the provider dependency used for generated constructs. Those settings play different roles, even though they sit in the same project file.

This introduces a maintenance relationship between the selected provider schema and generated source bindings. A resource argument has a representation in the authoring language and another in Terraform's configuration. Understanding that relationship matters when an editor reports that a property does not exist, or when a provider update adds an argument the current generated classes do not expose.

The TypeScript guide illustrates the mapping with an instance whose source uses instanceType and whose generated JSON uses instance_type. That small naming difference makes the translation concrete. Debugging at the JSON boundary requires recognizing Terraform's argument names, even when the source follows the host language's naming conventions.

The application describes objects that do not yet exist

The token documentation addresses values that cannot be ordinary concrete source-language values during application execution. Module outputs and resource attributes may need to become Terraform references in the synthesized configuration.

A module output can therefore supply an argument to another module without the application knowing the eventual identifier. The generated JSON retains an interpolation referring to that output. Terraform can interpret the relationship later when it plans and applies the configuration.

This is a different operation from asking the TypeScript application to create a resource and return its real identifier immediately. Constructing an object in the application builds part of a configuration. Provisioning occurs through Terraform after synthesis. Treating the two moments as identical would give a misleading account of when values become available.

The documentation illustrates list-valued output conversion with Token.asList. A placeholder initially represents the value in the host program, then synthesis replaces it with the Terraform expression. The example passes a VPC module's subnet output to another module expecting a list of subnet identifiers.

That mechanism is necessary because the host language needs some representation of the value before infrastructure execution. The representation preserves the intended reference across stages. It does not make an apply-time value available for arbitrary concrete calculations during the earlier program execution.

I consider this the part of the preview that most deserves careful explanation. Familiar syntax can make deferred infrastructure values look like ordinary application data. The token model is the evidence that two evaluation stages still exist underneath that syntax.

Overrides expose the Terraform representation directly

The override documentation describes addOverride for configuration that generated constructs do not express directly. It supports additions at the stack level and changes to resource configuration.

The guide includes remote backend configuration and resource meta-arguments as uses. A resource override refers to the Terraform JSON argument spelling. Its example overwrites a topic's display_name, even though the initial TypeScript property is displayName.

That facility gives the early library a way to express settings beyond its direct bindings. It also changes the kind of checking available at that point. A string naming a Terraform property requires the author to understand the target configuration format, so the generated classes do not eliminate the need for Terraform knowledge.

Empty override arguments or objects are another documented boundary. The guide warns that synthesis removes them. An override intended to represent an empty configuration block therefore needs more thought than merely creating an empty JavaScript object and assuming it will appear unchanged.

The broader tradeoff is straightforward. A programming interface can provide convenient classes for common cases and still require direct access to the generated representation for gaps. That is especially relevant in an alpha preview whose bindings and supported operations are still developing.

The CLI can retain the Terraform workflow

The CLI reference describes cdktf diff as running Terraform plan. Its deployment command applies the application, and the TypeScript guide states that deployment runs Terraform apply in the background. The tools retain Terraform's operational responsibilities while adding a source-language preparation step.

The getting-started guide also documents local state mode. Selecting --local when initializing a project stores the stack state in a local terraform.tfstate file in the project root. That is a state-management decision, separate from choosing TypeScript as the authoring language.

Those choices make the preview applicable to different existing Terraform arrangements. An engineer can inspect synthesized configuration and use the Terraform CLI, or use the CDK CLI's presentation of the workflow. Neither route removes the need to know where state lives or which configuration a plan consumed.

The source application also becomes part of the explanation for a change. Reviewing generated JSON can establish what Terraform sees, while reviewing the source establishes how that configuration was computed. Both matter when reusable code changes the resources emitted for several callers.

What the preview adds to Terraform's abstraction

The useful comparison with HCL concerns where abstraction runs. CDK lets supported programming languages construct configuration before Terraform evaluates it. Its tokens preserve deferred references, and its overrides allow direct additions to the resulting Terraform JSON.

That combination offers another way to package repeated infrastructure patterns. It also introduces generated bindings and an application execution step that maintainers must understand. The preview is most convincing when those stages remain visible, because visibility lets an engineer trace a resource argument from source property to Terraform configuration.

The July release is an alpha experiment in that authoring model. Its concrete output remains inspectable Terraform JSON, with an ordinary plan available after synthesis. That boundary supplies a practical place to examine whether a programming abstraction produced the intended infrastructure declaration.