Tfgen: configure your Terraform stacks using plain Go

surminus1 pts0 comments

On this page

Until recently, we extensively used HashiCorp's CDK for Terraform. Then they announced the end of its development. We didn't want to spend months migrating to a new ecosystem, but we needed a replacement.<br>In the Infrastructure Team at Ably, we like Go. I wondered if there was an opportunity for something simpler.<br>Provisioning infrastructure at Ably<br>We provision all our infrastructure for the core Pub/Sub platform where possible using Infrastructure as Code, initially using Terraform and then making the switch to OpenTofu (but for simplicity I will refer to "Terraform" throughout, even though we use tofu). Every production change to our infrastructure has to go through peer review and is deployed using Scalr.<br>This includes our networking stack, which is a set of Amazon VPCs provisioned across multiple regions for both our non-production and production environments, in accounts separate from our application workloads.<br>Since each VPC must be peered with every other VPC along with resources such as security group rules and NAT gateways, configuring this entirely in HCL became unwieldy very quickly.<br>It was in this context that we adopted the "terraform-cdk", also known as "CDKTF". This tool was a natural way to get out of HCL's limitations, where engineers were able to write code in a variety of languages which produced Terraform-compatible JSON. We adopted this using TypeScript.<br>We had a lot of success using this tool, and because of this adopted it for some of our functionality that had a lot of repeated logic, but required us to provision resources in separate workspaces, in separate regions, and in separate accounts. Using CDKTF made this extremely simple, and we provisioned new resources using human-friendly configuration files.<br>The resulting generated JSON was committed to our repository, and deployed using our standard processes. We could see who made what changes to a configuration file, and see it clearly expressed in the more complex JSON output.<br>We maintained roughly 150 production workspaces using this mechanism.<br>Goodbye CDKTF<br>Unfortunately, on December 10, 2025, HashiCorp decided to sunset CDKTF.<br>This put us in a bit of a bind: yes, the tool was working fine, but there will be no new features, and any open bugs will never be fixed.<br>We did some research on a potential replacement: we wanted to keep the process we have that works really well for us, and we didn't want to commit to a lengthy migration that touches all of our critical production resources, which increases risk and takes a lot of engineering effort with relatively low value.<br>We considered various other tools: Terragrunt, Pulumi, Jsonnet and Cue.<br>Terragrunt is widely used and allows you to manage many projects at scale, which would work for us, but to be able to fit into this model, we would have needed to change our workflows, which probably would have been the bulk of the migration.<br>Given we wanted to continue using our centralised configuration files, we would have needed to write something custom anyway to keep them, or migrate to something else. Basically, it would have been a huge amount of work.<br>Pulumi suffered the same problem: it's a great tool, but would have meant a migration to a completely new ecosystem.<br>The templating languages of Jsonnet and Cue were probably closer to what we needed, but it would have been a great deal of work to ensure we had byte-identical output to what we already had committed to the repository.<br>Essentially, all these tools suffered the same issue: a huge amount of work that we were not prepared to undertake. In the end we settled on writing something ourselves, using Go.<br>Writing a replacement<br>We already primarily use Go for managing our infrastructure, so it was the natural choice for a homespun replacement.<br>As well as writing a drop-in replacement, which used our existing configuration and wrote identical output to existing paths, I also wanted to ease the constraints that we had previously bumped into with CDKTF.<br>CDKTF required us to "get" specific provider versions, which had generated strong types. This made writing TypeScript a little easier with LSPs, but also involved a build step to fetch these definitions. In practice, I didn't think the stronger types were worth the cost of the extra build step. I wanted something more flexible, that produced code that was validated by Terraform tooling itself.<br>My idea was to produce a simple public interface that matched Terraform concepts, where each component was a type:<br>// Resource represents a resource block in a Terraform configuration.<br>type Resource struct {<br>Type string // resource type, e.g. "aws_s3_bucket"<br>Name string // local name, unique per type within the stack<br>Config Config // the resource's arguments

The Config type here is what makes it flexible. Rather than having to generate a large amount of types for each different provider and handle the problems of how they change between versions, instead the user is responsible for...

using terraform infrastructure cdktf type replacement

Related Articles