The Benefits of Moving from Terraform to Pulumi

Christopher Lenard
4 min readApr 21, 2021

--

Terraform is wonderful…until it’s not. The trusty infrastructure as code provider is starting to show some gray hairs.

One alternative to Terraform is Pulumi. It’s conceptually similar to Terraform. Terraform Plans => Pulumi Projects. Terraform Workspaces => Pulumi Stacks. Pulumi’s website has a formal comparison here.

In this article, I’ll dive into the unwritten “between-the-lines” benefits of Pulumi over Terraform.

1. Language Options

From @molly_struve

Terraform uses a domain-specific-language (DSL) called HashiCorp Configuration Language (HCL), which enforces a strict schema and supports a variety of cloud providers. HCL’s strict nature is nice because it forces consistent code structure.

On the flip side, that strict nature is also Terraform’s downfall. It poorly implements basic control flow like for_each loops and switch statements. (for_each is getting additional support with newer versions of Terraform, but it is still woefully inhibited).

Let’s compare for loops creating Azure resource groups in Terraform and Pulumi with Golang.

Terraform
Pulumi & Go

The Terraform implementation breaks from the traditional looping setup we see in other languages like Go. Additionally, I’ll explore how Terraform’s for loops fail during dynamic resource creation.

Pulumi supports many languages. Node.js, Python, .NET Core, Go. Read more here. Pick your poison.

Writing your infrastructure as code in a familiar, widely used language is better than using HCL. More developers have experience using one of the above languages versus HCL, so they can jump right in. Having fully featured control flow only sweetens the deal.

2. Dynamic Resource Creation at Runtime

If you use an external configuration database for your infrastructure, Terraform’s recommendation is to write a python script to read your external datasource. The problem with this is when you want to create an unknown number of resources.

As an example, let’s say you wrote a Terraform plan to create a Kubernetes cluster and n node pools. In your development environment, you only need 2 node pools. In prod, just 3. You define the node count per environment in a configuration database.

This scenario will get pretty hairy with Terraform because it requires a known value for count and for_each at runtime.

The count value must be known before Terraform performs any remote resource actions. This means count can't refer to any resource attributes that aren't known until after a configuration is applied.

As a workaround, you could write a python script that runs cli commands to create the node pools. Then, have Terraform run that script. However, you won’t be able to track resource state and error handling is shoddy. If that’s a solution, then why are we using Terraform?

Pulumi does not have this limitation. pulumi preview shows you what your configuration database defines. Resource state is tracked, errors are handled, and the code is readable.

In this Pulumi/Go example, an unknown number of virtual network peerings will be created. getRegionPeerings() reads the configuration database. You’ve got to love that for loop.

3. Limited Utility Functions and Types

Using standard utility functions in most languages is easy. In Terraform, strap yourself in for a StackOverflow adventure. Oftentimes, the solution ends up being unintuitive.

As an example, imagine you want to know if a string contains a substring and return a boolean result. Look at the Terraform solution.

count = replace(var.vnet_name, “/foo/”, “”) == local.bar ? “1” : “0”

What does that look like in Pulumi/Go?

count := strings.Contains(vnetName, "foo")

Way more readable.

4. More than Two Conditionals

from Ben Porter

In Terraform, have you ever needed to test more than 2 conditions? For example, Azure has 4 different clouds (public, usgovernment, china, and germany). In Terraform, that could look like this:

public_cloud = var.cloud == “public” ? true : false
us_government_cloud = var.cloud == “usgovernment” ? true : false
china_cloud = var.cloud == “china” ? true : false
germany_cloud = var.cloud == “germany” ? true : false

Then, you have to write more code on top of this to handle differences between clouds. You could also define logic in Terraform using a map , but it would still be ridiculous.

In Pulumi/Go, you use if/else or switch-case. Done.

Overall

Pulumi is better than Terraform. Hands down. It has all the features of Terraform, but written with the simplicity and power of a fully featured programming language.

--

--