- Published on
Odd Things with terraform
- Authors
- Name
- Jordan Stewart
I've been using terraform for a bit, and in general it's good... but there is some things that I find very rough around the edges.
These are:
- control structure
for_each
andcount
, missing (missingif
) - providers not allowed to use
for_each
- backend state not being parameterised
- looping over lists has to be a set
- lack of tfvar (HCL) support in other languages
- templating is weird
for_each
and count
, missing (missing if
)
Control structure Sometimes it's just nice having code like:
if flag {
# do stuff
}
It's really nice, and in terraform you have to do:
module "servers" {
count = var.flag ? 1 : 0
source = "./server"
servers = 5
}
This just isn't as nice.
Count is also nasty. Let's say I have this:
module "database" {
count = 5
source = "./database"
size = "large"
}
And I want to remove the 2nd database, but keep the 5th. It's hard to do (you have to use imports). So I try to always use for_each
.
for_each
providers not allowed to use This is basically just this github issue open since 2019.
It's basically just isn't allowed:
provider "aws" {
for_each = var.region
And this would be really handy when you need to declare a provider for each AWS region, like when you are using AWS guardduty in multiple regions.
So instead of just having a list of providers you have to have something like:
provider "aws" {
region = "us-east-1"
}
provider "aws" {
region = "us-east-2"
}
provider "aws" {
region = "us-west-1"
}
# and on and on...
backend state not being parameterised
So with terraform you have things like:
resource "aws_instance" "web" {
ami = var.ami
instance_type = var.instance_size
}
And then you suddenly get to backend state, and it looks like:
terraform {
backend "s3" {}
}
... which is confusing.
You would expect this:
# This will NOT work
terraform {
backend "s3" {
bucket = var.state_bucket # This is not allowed!
key = var.state_key # This is not allowed!
}
}
But instead you have to do something like this:
terraform init \
-backend-config=backend-config/sandbox.tfvars.json
And pass in the variables. It's just a completely different way of doing things compared to the rest of the DSL.
looping over lists has to be a set
This one is odd. With the terraform for_each is works on either a set of strings (not a list), or a map.
So this is invalid:
variable "sandboxes" {
type = set(number) # not strings
default = [1, 2, 3]
}
resource "aws_instance" "web" {
for_each = var.sandboxes # fails as not strings
ami = var.ami
instance_type = var.instance_size
}
So is this:
variable "sandboxes" {
type = list(string) # it's a list!
default = [1, 2, 3]
}
resource "aws_instance" "web" {
for_each = var.sandboxes # fails as a set
ami = var.ami
instance_type = var.instance_size
}
But there is also no way to do:
variable "sandboxes" {
type = set(string)
default = ["1", "2", "3"]
}
resource "aws_instance" "web" {
for_each = [for sandbox in var.sandboxes : sandbox if sandbox != "3"] # cannot do if statements here
ami = var.ami
instance_type = var.instance_size
}
But this is fine:
# using a map
variable "sandboxes" {
type = map(object({
index = number
}))
}
resource "aws_instance" "web" {
for_each = {
for name, sandbox in var.sandboxes : name => sandbox
if sandbox.index != 3
} # can do if statements with maps... but not lists... :?
ami = var.ami
instance_type = var.instance_size
}
These above examples, kind of make the DSL look really half-baked.
The map syntax is a bit confusing too, like what name
is in the above example is a bit confusing.
lack of tfvar (HCL) support in other languages
Enough said... tfvars are
Like there is this 250 star python library on github for python: https://github.com/amplify-education/python-hcl2
And this struggling java library: https://github.com/bertramdev/hcl4j
If you compare that to the serialization of json, and yaml, it is incredibly lack luster.
templating is weird
So with Ansible they use the common, and vanilla jinja library, which is really easy to use both in ansible templates, and from python.
Go (or Golang) also has a really easy to use templating library (https://pkg.go.dev/text/template), which is used a lot. But terraform templates decided to have a half/half approach, and be mostly go text templates, but add
So it looks like this:
{
"port": "${addr}:${port}",
}
When it could of been generic go templates as this:
{
"backends": "{{.addr}}:{{.port}}",
}
This has the file extension of .tftpl
, which is a bit odd as well. It's a next of not invented here.
As you can see terraform has some odd characteristics in a few ways.