The Benefits of Moving from Terraform to Pulumi
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
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.
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 meanscount
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
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.