DEV Community

drewmullen
drewmullen

Posted on • Edited on

17 2

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!

Billboard image

Deploy and scale your apps on AWS and GCP with a world class developer experience

Coherence makes it easy to set up and maintain cloud infrastructure. Harness the extensibility, compliance and cost efficiency of the cloud.

Learn more

Top comments (4)

Collapse
 
entscheidungsproblem profile image

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

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →