DEV Community

drewmullen
drewmullen

Posted on • Edited on

Terraform: Variable validation with samples

Terraform allows you to validate variable input in using validation blocks using custom condition and yielding a custom error_message. Below are some examples:

Note: Please share your common validation rules you've written and I'll update here

Update: Please check out this awesome project by @bschaatsbergen that provides built in functions for assertions: https://github.com/bschaatsbergen/terraform-provider-assert

Strings

String may not contain character

Scenario: String may not contain a /.

variable "string_may_not_contain" { type = string default = "test" validation { error_message = "Value cannot contain a \"/\"." condition = !can(regex("/", var.string_may_not_contain)) } } 
Enter fullscreen mode Exit fullscreen mode

String with valid options

Scenario: Here we have a string and we only allow to values "approved" or "disapproved". I show 2 examples of the same check using different methods:

variable "string_only_valid_options" { type = string default = "approved" # using regex validation { condition = can(regex("^(approved|disapproved)$", var.string_only_valid_options)) error_message = "Invalid input, options: \"approved\", \"disapproved\"." } # using contains() validation { condition = contains(["approved", "disapproved"], var.string_only_valid_options) error_message = "Invalid input, options: \"approved\", \"disapproved\"." } } 
Enter fullscreen mode Exit fullscreen mode

Valid AWS Region Name

Scenario: string must be like AWS region

variable "string_like_aws_region" { type = string default = "us-east-1" validation { condition = can(regex("[a-z][a-z]-[a-z]+-[1-9]", var.string_like_aws_region)) error_message = "Must be valid AWS Region names." } 
Enter fullscreen mode Exit fullscreen mode

Valid IAM Role Name

Scenario: Your string must be a valid IAM role name

variable "string_valid_iam_role_name" { type = string default = "MyCoolRole" # arn example: "arn:aws:iam::123456789012:role/MyCoolRole" validation { condition = can(regex("^[a-zA-Z][a-zA-Z\\-\\_0-9]{1,64}$", var.string_valid_iam_role_name)) error_message = "IAM role name must start with letter, only contain letters, numbers, dashes, or underscores and must be between 1 and 64 characters." } } 
Enter fullscreen mode Exit fullscreen mode

Valid IPv4 CIDR

Scenario: Your string input needs to look like a IPv4 CIDR. Thank you @entscheidungsproblem for reporting and providing a fix for /32.

variable "string_like_valid_ipv4_cidr" { type = string default = "10.0.0.0/16" validation { condition = can(cidrhost(var.string_like_valid_ipv4_cidr, 0)) error_message = "Must be valid IPv4 CIDR." } } 
Enter fullscreen mode Exit fullscreen mode

Semantic Version

variable "semv1" { default = "10.57.123" validation { error_message = "Must be valid semantic version." condition = can(regex("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", var.semv1)) } } 
Enter fullscreen mode Exit fullscreen mode

Maps

Map with optional conflicting keys

Scenario: You have a map variable and 2 keys conflict, in this case, you can only set either cidr or netmask.

variable "only_one_optional_key" { type = object({ name = optional(string) cidrs = optional(list(string)) netmask = optional(number) }) default = { cidr = "10.0.0.0/16" name = "test" } validation { error_message = "Can only specify either \"cidrs\", or \"netmask\"." condition = length(setintersection(keys(var.only_one_optional_key), ["cidrs", "netmask"])) == 1 } } 
Enter fullscreen mode Exit fullscreen mode

Numbers

Number within a range

Scenario: number must be between 1-16.
Thanks: @tlindsay42

variable "num_in_range" { type = number default = 1 validation { condition = var.num_in_range >= 1 && var.num_in_range <= 16 && floor(var.num_in_range) == var.num_in_range error_message = "Accepted values: 1-16." } } 
Enter fullscreen mode Exit fullscreen mode

If you liked this post, please like. If you think it would be helpful in the future as a reference, please bookmark!

Top comments (4)

Collapse
 
entscheidungsproblem profile image
entscheidungsproblem • Edited

This is a great article, thanks!

I've found that the IPV4 method fails for /32 ips:

> cidrhost("0.0.0.0/32",32) ╷ │ Error: Error in function call │ │ on <console-input> line 1: │ (source code not available) │ │ Call to function "cidrhost" failed: prefix of 32 does not accommodate a host numbered 32. ╵ 
Enter fullscreen mode Exit fullscreen mode

But by replacing 32 with 0 as the second argument, it works:

> cidrhost("0.0.0.0/32",0) "0.0.0.0" > cidrhost("0.0.0.0/0",0) "0.0.0.0" 
Enter fullscreen mode Exit fullscreen mode

This also works for IPv6:

> cidrhost("0000:0000:0000:0000:0000:0000:0000:0001/128",0) "::1" 
Enter fullscreen mode Exit fullscreen mode
Collapse
 
drewmullen profile image
drewmullen

oh man that is a great find! thank you for reporting. cited you as well! :cheers:

Collapse
 
sumonmselim profile image
Muhammad Sumon Molla Selim

Valid IAM ARN example throws "illegal escape sequence".

Collapse
 
drewmullen profile image
drewmullen

fixed! thank you