Geht wählen! Am 26. September 2021 ist Bundestagswahl in Deutschland, wählt Annalena Baerbock für zukunftssichere Klima-, Digital- und Sozialpolitik.. Mehr infos zum Wahlprogramm auf gruene.de 🗳💚🌻

Validating variables in terraform

Finally, terraform can validate input variables!

Written by Kevin Gimbel on , 🍿 6 min. read

With the 0.13 release terraform introduces input validation, and with the 1.0.0 releases it was stabilized! Before there was no way of validating variables other than checking their length or doing basic checks inside the code, for example in the locals.tf file, and errors with variables mostly came to light during deployments. There was also no way of telling the user what what wrong with a variable.

Excited about this feature I took a deep-dive at work and implemented validation for all variables in our new infrastructure modules. This blog post will go over the possibilities of validations and how to implement them and how to combine multiple asserts. All code examples are practical and no foo-bar-baz made-up code!

Knowledge of terraform is not required, however you should be somewhat familiar with HCL, the configuration language of terraform.

All examples are available in a repo at github.com/KevinGimbel/blog-terraform-validation-example. At times this post says "To test this, run..."; All of these can be run against the code inside the example repo. To use the repo, follow these steps:

git clone https://github.com/KevinGimbel/blog-terraform-validation-example.git
cd blog-terraform-validation-example
terraform init

Syntax of a validation

All validations happen in the variables.tf file where variables are defined. Inside the block defining a variable a new validation block is placed:

variable "my_var" {
type = number
default = 0

validation {
condition = var.my_var >= 0
error_message = "Please set my_var to a positive number or 0."
}
}

Terraform requires the sentence to start with an uppercase letter and end with a dot.

Validating the length of a string

The following defines a variable named hetzner_api_token that represents a Hetzner Cloud API token.

# file: variables.tf
variable "hetzner_api_token" {
type = string
description = "Hetzner API token"
}

If somebody would specify hetzner_api_token = "" the variable would be defined as far as terraform is concerned and terraform would start deploying until it reaches a point where the missing token causes an issue; To prevent this we can apply validations like so

# file: variables.tf
variable "hetzner_api_token" {
type = string
description = "Hetzner API token"

validation {
condition = length(var.hetzner_api_token) == 64
error_message = "Must be a 64 character long Hetzner API token."
}
}

This validates that the variable is set AND is exactly 64 characters long, which is the length of Hetzner Cloud API tokens.

To test this, run

# Shows no error and logs the configured token
terraform apply -target=null_resource.validate_hetzner_api_token
# Invalid, shows error message
terraform apply -target=null_resource.validate_hetzner_api_token -var "hetzner_api_token=xxx"

Advanced validation with regex

Checking the length might be helpful for simple assertions but sometimes more advanced checks are needed. This is where regex comes into play. To use regex terraform provides the can function.

variable "server_role_arn" {
type = string
description = "AWS arn of the role to assign to servers"

validation {
condition = can(regex("^arn:aws:iam::[[:digit:]]{12}:role/.+", var.server_role_arn))
error_message = "Must be a valid AWS IAM role ARN."
}
}

This validation fails if the provides string doesn't match the format of an AWS IAM role ARN. This means it will also fail if an ARN is provided that matches a user, like arn:aws:iam::012345678912:user/kevin! The [[:digit:]]{12} checks for exactly 12 digits, it's equal to \d{12} in other regex engines. .+ at the end matches any character one or more times.

To test this, run

terraform apply
# Invalid, shows error message
terraform apply -target=null_resource.validate_server_role_arn
terraform apply -target=null_resource.validate_server_role_arn -var "server_role_arn=arn:aws:iam::012345678912:user/kevin"
│ Error: Invalid value for variable
│ 
│   on variables.tf line 1:
│    1: variable "server_role_arn" {
│ 
│ Must be a valid AWS IAM role ARN.
│ 
│ This was checked by the validation rule at variables.tf:5,3-13.

Multiple conditions

Validations can have more than one condition. Conditions are grouped together with the alltrue and anytrue functions, which take a list of conditions and returns true or false.

variable "env" {
type = string
description = "Environment to deploy, can be production, staging, development, or test."

validation {
condition = anytrue([
var.env == "production",
var.env == "staging",
var.env == "development",
var.env == "test"
])
error_message = "Must be a valid env, can be production, staging, development, or test."
}
}

To test this, run

# valid 
terraform apply -target=null_resource.validate_env
# Invalid
terraform apply -target=null_resource.validate_env -var "env=prod"

Multiple, nested conditions

alltrue and anytrue can be nested! The next example validates the variable is a legit Hetzner Server type identifier, without requiring any regex magic! 🥸

variable "hetzner_server_type" {
type = string
description = "Server type to use, see https://www.hetzner.com/cloud#pricing"
default = "cx21"

validation {
condition = alltrue([
anytrue([
can(regex("cx*", var.hetzner_server_type)),
can(regex("cpx*", var.hetzner_server_type)),
can(regex("ccx*", var.hetzner_server_type))
]),
anytrue([
length(var.hetzner_server_type) == 5,
length(var.hetzner_server_type) == 4
])
])
error_message = "Must be a valid Hetzner server type, e.g. cx21, cx11, ccx22 see https://www.hetzner.com/cloud#pricing for available options."
}
}

It checks that both of these are true:

  • The variable starts with cx, cpx, or ccx
  • The variable is 4 or 5 characters long

To test this, run

# valid 
terraform apply -target=null_resource.validate_hetzner_server_type
# invalid, shows validation error
terraform apply -target=null_resource.validate_hetzner_server_type -var "hetzner_server_type=nope"

Validating elements in a list

Lastly, using a for loop entries inside of a list can be validated.

variable "hetzner_datacenters" {
type = list(string)
description = "List of datacenter identifier into which the server can be launched, chosen at random"
default = ["nbg1", "fsn1"]

validation {
condition = alltrue([
for dc in var.hetzner_datacenters : contains(["nbg1", "fsn1", "hel1"], dc)
])
error_message = "Must one or more of: 'nbg1', 'fsn1', or 'hel1' - no other value is allowed."
}
}

Right now Hetzner has three datacenter locations, two in Germany (Nürnberg (nbg1) and Falkenstein (fsn1)) and one in Finland (Helsinki (hel1)), so validating them is doable; for AWS, GPC, or Azure this would be a lot harder and require regex instead.

To test this, run

# valid 
terraform apply -target=null_resource.validate_hetzner_datacenters
# invalid, shows validation error
terraform apply -target=null_resource.validate_hetzner_datacenters -var 'hetzner_datacenters=["fsn1", "lax1"]'

Conclusion

Using the shown functions like regex, length and loops we can validate a variable has a certain value or follows a certain format.

With anytrue and alltrue we can group together multiple validations so that either all or one of multiple conditions must match.

Further reading

Hi, I'm Kevin!

I'm a DevOps Engineer with a focus on automation and security. Before shifting into DevOps and cloud computing I worked as Front-End Developer, which is still a hobby and field of interest for me.

Hand-made vector avatar of Kevin Gimbel

I'm very passionated about a variety of games - digital, boardgames, and pen & paper; and also interested in Sci-Fi, Cyberpunk, and dystopian books. You can find out more on the about page.