Spacelift + ServiceNow = Self-Service IaC Without the Learning Curve

➡️ Register Now

Terraform

Terraform Variable Validation: Terraform 1.9 & Earlier Versions

terraform variable validation

🚀 Level Up Your Infrastructure Skills

You focus on building. We’ll keep you updated. Get curated infrastructure insights that help you make smarter decisions.

Variables are a key feature of Terraform. They allow users to write flexible, reusable, and scalable configurations by abstracting values that may change between environments or deployments. Instead of hardcoding values into the Terraform code, variables allow users to pass in dynamic inputs, ensuring configurations are adaptable and maintainable across different scenarios. 

One of the main issues that might come up when using variables is the possibility of misconfigurations or errors caused by assigning values not expected by the configuration block or module. 

To overcome this issue, Terraform enables validations for input variables, which can prevent problems before they occur, ensuring your infrastructure’s integrity, security, and reliability. 

What we will cover:

  1. What is variable validation?
  2. How to validate variables in Terraform 1.8 and earlier
  3. Cross-object referencing for input variable validations in Terraform 1.9
  4. Common mistakes and best practices for Terraform variable validation
  5. What is the difference between precondition and validation in Terraform?

What is variable validation?

Variable validation in Terraform is a mechanism to enforce custom rules on input variable values during plan or apply operations. It helps ensure variables meet specific constraints, improving reliability and preventing misconfiguration.

With a simple variable definition in Terraform, we can easily assign and pass values around our configuration. Still, without validations, these variables can receive any value, which can cause configuration issues. This lack of validation can result in errors, unexpected behavior, or incorrect resource configurations. 

By adding validation rules, we ensure that only valid inputs are accepted, preventing misconfigurations. Validation provides a way to enforce constraints on variable values, such as data type, format, or range, which helps improve the stability and reliability of the infrastructure as code.

For example, let’s say you have the following variable for setting the CIDR of a VPC:

variable "cidr" {}

Then, if you are using it in a module, you’d do something like this:

module "vpc" {
  source  = "..."
  cidr = "10.0.2.0/24"
}

But, given you don’t have validations, you can assign any value, for example:

module "vpc" {
  source  = "..."
  cidr = 123456
}

This is an invalid CIDR, but you’ll only receive an error when running your configuration.

This was a simple example, but you can use validations to increase compliance by only allowing defined values in your modules.

How to validate variables in Terraform 1.8 and earlier

Terraform offers three types of variable validations:

  • Type validation – when assigning the expected type of a variable 
  • Required validation – default variable requirement when a default value is not specified.
  • Custom validation – custom validation rules using the validation block

1. Type validations

To validate variables with type validations in Terraform, you define the type argument within a variable block. Type validation ensures input values conform to expected data types such as string, number, bool, list, map, object, or complex nested types.

These are the data types:

  • string –  Used for text values, like names or identifiers
  • bool – Either true or false; used in conditional logic
  • number – Represents numeric values, such as integer and fractional values
  • list – A collection of ordered elements of the same type, helpful in specifying multiple values in sequence 
  • map – A collection of key-value pairs; each key is unique and associated with a value
  • object – A structured data type with multiple attributes, often used for more complex configurations
  • set – Similar to a list, but contains unique elements without any specific order
  • tuple – An ordered collection of elements of different types, allowing diverse data structures

Example: Terraform variable type validation

Let’s start by demonstrating type validations with the cidr variable we defined before:

variable "cidr" {}

We want it always to be a string, so we can then add the type specification to it with:

variable "cidr" {
  type = string
}

Now, when you run terraform plan, you’ll get the following error stating that the type is not a string:

terraform plan

│ Error: Invalid value for input variable
│   on main.tf line 3, in module "name":
3:   cidr   = [123]
│ The given value is not suitable for module.name.var.cidr declared at module\variables.tf:1,1-16: string required.

2. Required validation

This is a default validation enabled in Terraform that makes sure that all variables have an assigned value. If you don’t set a default value for your variable, you will see a Missing required argument.

variable "cidr" {
  type     = string
}

If you run your terraform plan, you should get the following error message in your console:

terraform plan

│ Error: Missing required argument
│   on main.tf line 1, in module "name":
1: module "name" {
│ The argument "cidr" is required, but no definition was found.

If you are using it in a module, then your IDE/Text Editor should show you an error, such as the example below in Visual Studio Code:

terraform required validation example

And it should have the following message once you hover over it:

Required attribute "cidr" not specified: An attribute named "cidr" is required here
string

But if you give this variable a default value, your error should disappear because the variable will always have a value.

variable "cidr" {
  type     = string
  default  = "10.0.0.1/8"
}

You will no longer see an error in your module:

variable validation in terraform example

3. Custom validations

To validate variables in Terraform 1.8 and earlier, you can use the validation block within variable declarations to enforce custom rules. Custom validations (or custom conditions) are helpful when validating a specific scenario that goes beyond being required or the type. 

For example, you want to ensure that the CIDR used is within a particular range (8, 16, 32, …), that your configurations use the expected authorized AMIs, that a JSON policy being passed doesn’t contain a specific action, and many other custom scenarios.

A custom validation is enabled by adding the validation block in the corresponding variable, and this validation block takes two properties:

  • condition – The condition for this variable to be valid, what you want to validate
  • error_message – The error message to be output if the condition is not met

Example: Using the Terraform variable validation block

We can use the validation block like this:

variable "resource_count" {
  type = number
  validation {
    condition     = var.resource_count < 5
    error_message = "Count should be less than 5"
  }
}

If you pass a resource_count that is bigger than five, you will get the error_message:

terraform plan
│ Error: Invalid value for variable
│   on main.tf line 4, in module "name":
4:   resource_count = 6
│     ├────────────────
│     │ var.resource_count is 6
│ Resource count should be less than 5
│ This was checked by the validation rule at module\variables.tf:8,3-13.

It is also possible to use boolean operators to use more than one boolean expression, for example:

variable "resource_count" {
  type = number
  validation {
    condition     = var.resource_count < 5 && var.resource_count > 0
    error_message = "Resource count should be between 0 and 5"
  }
}

This will output the error:

terraform plan


│ Error: Invalid value for variable
│   on main.tf line 4, in module "name":
4:   resource_count = -1
│     ├────────────────
│     │ var.resource_count is -1
│ Resource count should be between 0 and 5
│ This was checked by the validation rule at module\variables.tf:18,3-13.

Using a validation block with Terraform native functions

You can leverage Terraform’s native functions to validate the input variables. For example, if we wanted to check for allowed runtimes for a Lambda function:

variable "runtime" {
  type = string
  validation {
    condition     = contains(["nodejs20.x", "nodejs22.x"], var.runtime)
    error_message = "Invalid runtime provided. Must be either nodejs20.x or nodejs22.x"
  }
}

We use the contains function to validate the variable against the allowed values. And when you pass a wrong value:

terraform plan
│ Error: Invalid value for variable
│   on main.tf line 5, in module "name":
5:   runtime        = "go"
│     ├────────────────
│     │ var.runtime is "go"
│ Invalid runtime provided. Must be either nodejs20.x or nodejs22.x
│ This was checked by the validation rule at module\variables.tf:19,3-13.

Using a validation block with RegEx expressions

You can run some complex variable validations with RegEx. For example:

variable "cidr" {
  type     = string
  validation {
    condition     = can(regex("^[1-2]?\\d{1,2}\\.[1-2]?\\d{1,2}\\.[1-2]?\\d{1,2}\\.[1-2]?\\d{1,2}\\/[1-3]\\d?$", var.cidr))
    error_message = "Invalid CIDR structure"
  }
}

This is a simple example of a RegEx to validate the basic structure of a CIDR.

Note that we need to use the can function here. The regex function either returns a result with the matches or an error, so the can function will return a boolean value that is true if no error was returned and false if an error was returned.

Now, if you try to pass an invalid CIDR, for example, 1234.123.123.123/28, it should return an error:

terraform plan
│ Error: Invalid value for variable
│   on main.tf line 3, in module "name":
3:   cidr           = "1234.123.123.123/28"
│     ├────────────────
│     │ var.cidr is "1234.123.123.123/28"
│ Invalid CIDR structure
│ This was checked by the validation rule at module\variables.tf:4,3-13.

You can also just replace this regex function with the cidrsubnet native function:

variable "cidr" {
  type     = string
  validation {
    condition     = can(cidrsubnet(var.cidr, 0, 0))
    error_message = "Invalid CIDR structure"
  }
}

You can find out more about the available Terraform functions here.

Using multiple validation blocks for a single variable

Terraform allows you to add multiple validation blocks in a single variable. Each validation block is evaluated independently, and all must pass for the variable to be considered valid.

For example:

variable "resource_count" {
  type = number
  validation {
    condition     = var.resource_count < 5
    error_message = "Resource count should be less than 5"
  }
  validation {
    condition     = var.resource_count % 2 == 0
    error_message = "Resource count should be greater than 0"
  }
}

So, if we pass 7 for this variable, we’ll get the following errors:

terraform plan
│ Error: Invalid value for variable
│   on main.tf line 4, in module "name":
4:   resource_count  = 7
│     ├────────────────
│     │ var.resource_count is 7
│ Resource count should be less than 5
│ This was checked by the validation rule at module\variables.tf:27,3-13.
│ Error: Invalid value for variable
│   on main.tf line 4, in module "name":
4:   resource_count  = 7
│     ├────────────────
│     │ var.resource_count is 7
│ Resource count should be even
│ This was checked by the validation rule at module\variables.tf:31,3-13.

Cross-object referencing for input variable validations in Terraform 1.9

Before Terraform 1.9, you could create validation rules only for the variable that adds the validation block. With Terraform 1.9, HashiCorp introduced the potential to reference other objects in your validation:

  • Variables
  • Locals
  • Data sources
  • Resources

Referencing other variables

You can reference other variables in the same way you would in other blocks, by using the keyword var and then the name of the variable, var.my_variable:

variable "create_sns" {
  description = "Whether to create a new SNS topic."
  type        = bool
  default     = false
}

variable "sns_arn" {
  description = "ARN of the existing SNS to use."
  type        = string
  default     = ""

  validation {
    condition     = var.create_sns == false ? length(var.sns_arn) > 0 : true
    error_message = "SNS ARN is required if usecreate_sns_sns is false."
  }
}

Running terraform plan without use_sns or with it set to false will output the error:

terraform plan

Planning failed. Terraform encountered an error while generating this plan.

│ Error: Invalid value for variable
│   on  line 0:
│   (source code not available)
│ SNS ARN is required if usecreate_sns_sns is false.
│ This was checked by the validation rule at module\variables.tf:48,3-13.

Referencing local values

Local values are typically used to abstract reusable values, convert data between different formats, combine data into various data structures, or calculate other values within a configuration scope. You can also reference it in a variable validation.

In the example below, we add two arguments and the expected result of a multiplication:

variable "first_argument" {
  type = number
}


variable "second_argument" {
  type = number
}

locals {
  result = var.first_argument * var.second_argument
}

variable "expected_result" {
  type = number
  validation {
    condition     = local.result == var.expected_result
    error_message = "The two arguments didn't give the expected result"
  }
}

Note: This is valid from Terraform 1.9.0. Before that, you can’t reference locals inside your variables.

Running terraform plan with invalid values will give you something similar to:

terraform plan

Planning failed. Terraform encountered an error while generating this plan.

│ Error: Invalid value for variable
│   on main.tf line 9, in module "name":
9:   expected_result = 5
│     ├────────────────
│     │ local.result is 4
│     │ var.expected_result is 5
│ The two arguments didn't give the expected result
│ This was checked by the validation rule at module\variables.tf:68,3-13.

Referencing data sources

Sometimes, you might need to validate a variable against an existing resource in the cloud. For example, you should validate if the AWS Region provided is valid in AWS. 

For these cases, we can leverage referencing data sources in our variable validations. Terraform will automatically retrieve the data from the cloud, assign it to the data source, and then run your variable validation against it.

The example below validates a region variable:

data "aws_regions" "available" {}

variable "aws_region" {
  description = "AWS region where resources will be created"
  type        = string

  validation {
    condition     = contains(data.aws_regions.available.names, var.aws_region)
    error_message = "Invalid AWS region. Choose a valid AWS region from the available list."
  }
}

Now, if you assign an invalid region, like some-region, for example, you should get the following output:

terraform plan

module.name.data.aws_regions.available: Reading...
module.name.data.aws_regions.available: Read complete after 0s [id=aws]

Planning failed. Terraform encountered an error while generating this plan.

│ Error: Invalid value for variable
│   on main.tf line 10, in module "name":
10:   aws_region      = "some-region"
│     ├────────────────
│     │ data.aws_regions.available.names is set of string with 17 elements
│     │ var.aws_region is "some-region"
│ Invalid AWS region. Choose a valid AWS region from the available list.
│ This was checked by the validation rule at module\variables.tf:93,3-13.

Referencing resources

Terraform 1.9 also allows us to reference resources in our variable validation. However, this feature is trickier because property values are known during the plan phase, and property values are only known after the referenced resource is created during the apply phase. 

You need to know which property you are trying to use in your validation to know when the validation will stop the run, whether in the plan or the apply phase, after it creates some resources. 

If your workflow allows your configuration to apply some resources and then stop the apply phase due to a validation error, then it is acceptable to reference this property. If your workflow doesn’t allow it, you should always reference only properties available during the plan.

It is very important to understand that Terraform won’t revert any changes or destroy any resources if the apply run is aborted.

Let’s see the example below, where we reference the max_message_size of an aws_sqs_queue resource, which is available during the plan:

resource "aws_sqs_queue" "queue" {
 name             = "my-queue"
 max_message_size = 2048
}

variable "timeout" {
 type = number
 validation {
   condition     = aws_sqs_queue.queue.max_message_size >= 2048 ? var.timeout >= 30 : var.timeout < 30
   error_message = "Invalid timeout value"
 }
}

resource "aws_lambda_function" "name" {
 function_name = "test-function"
 role          = "role::arn"
 runtime       = "nodejs22.x"
 handler       = "index.handler"
 filename      = "${path.module}/payload.zip"
 timeout       = var.timeout
}

Here, we are defining a variable timeout for the lambda. And when the max_batch_size of the SQS queue is more prominent than 2048, the timeout must be greater than 30 seconds.

Now, if we terraform plan, we can see the output:

terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform planned the following actions, but then encountered a problem:

  # module.name.aws_sqs_queue.queue will be created
  + resource "aws_sqs_queue" "queue" {
      + arn                               = (known after apply)
      + content_based_deduplication       = false
      + deduplication_scope               = (known after apply)
      + delay_seconds                     = 0
      + fifo_queue                        = false
      + fifo_throughput_limit             = (known after apply)
      + id                                = (known after apply)
      + kms_data_key_reuse_period_seconds = (known after apply)
      + max_message_size                  = 2048
      + message_retention_seconds         = 345600
      + name                              = "my-queue"
      + name_prefix                       = (known after apply)
      + policy                            = (known after apply)
      + receive_wait_time_seconds         = 0
      + redrive_allow_policy              = (known after apply)
      + redrive_policy                    = (known after apply)
      + sqs_managed_sse_enabled           = (known after apply)
      + tags_all                          = (known after apply)
      + url                               = (known after apply)
      + visibility_timeout_seconds        = 30
    }

Plan: 1 to add, 0 to change, 0 to destroy.
│ Error: Invalid value for variable
│   on main.tf line 9, in module "name":
9:   timeout = 20
│     ├────────────────
│     │ aws_sqs_queue.queue.max_message_size is 2048
│     │ var.timeout is 20
│ Invalid timeout value
│ This was checked by the validation rule at module/main.tf:8,3-13.

Notice that Terraform generated the plan for the SQS queue and then ran the validation for the timeout. Once it failed, it aborted the plan and didn’t create a plan for the lambda function.

Let’s use a simple example to demonstrate how it works with a property in the apply phase. Here, we are referencing the arn of the aws_sqs_queue:

resource "aws_sqs_queue" "queue" {
 name             = "my-queue"
 max_message_size = 2048
}

variable "timeout" {
 type = number
 validation {
   condition     = strcontains(aws_sqs_queue.queue.arn, "invalid_string") ? var.timeout >= 30 : var.timeout < 30
   error_message = "Invalid timeout value"
 }
}

resource "aws_lambda_function" "name" {
 function_name = "test-function"
 role          = "arn:aws:iam::0000000000000:role/transcribe-lambda-role"
 runtime       = "nodejs22.x"
 handler       = "index.handler"
 filename      = "${path.module}/payload.js"
 timeout       = var.timeout
}

If you run terraform apply, you should get the following output:

terraform apply  

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.name.aws_lambda_function.name will be created
  + resource "aws_lambda_function" "name" {
      + architectures                  = (known after apply)
      + arn                            = (known after apply)
      + code_sha256                    = (known after apply)
      + filename                       = "module/payload.zip"
      + function_name                  = "test-function"
      + handler                        = "index.handler"
      + id                             = (known after apply)
      + invoke_arn                     = (known after apply)
      + last_modified                  = (known after apply)
      + memory_size                    = 128
      + package_type                   = "Zip"
      + publish                        = false
      + qualified_arn                  = (known after apply)
      + qualified_invoke_arn           = (known after apply)
      + reserved_concurrent_executions = -1
      + role                           = "arn:aws:iam::0000000000000:role/lambda-role"
      + runtime                        = "nodejs22.x"
      + signing_job_arn                = (known after apply)
      + signing_profile_version_arn    = (known after apply)
      + skip_destroy                   = false
      + source_code_hash               = (known after apply)
      + source_code_size               = (known after apply)
      + tags_all                       = (known after apply)
      + timeout                        = 40
      + version                        = (known after apply)

      + ephemeral_storage (known after apply)

      + logging_config (known after apply)

      + tracing_config (known after apply)
    }

  # module.name.aws_sqs_queue.queue will be created
  + resource "aws_sqs_queue" "queue" {
      + arn                               = (known after apply)
      + content_based_deduplication       = false
      + deduplication_scope               = (known after apply)
      + delay_seconds                     = 0
      + fifo_queue                        = false
      + fifo_throughput_limit             = (known after apply)
      + id                                = (known after apply)
      + kms_data_key_reuse_period_seconds = (known after apply)
      + max_message_size                  = 2048
      + message_retention_seconds         = 345600
      + name                              = "my-queue"
      + name_prefix                       = (known after apply)
      + policy                            = (known after apply)
      + receive_wait_time_seconds         = 0
      + redrive_allow_policy              = (known after apply)
      + redrive_policy                    = (known after apply)
      + sqs_managed_sse_enabled           = (known after apply)
      + tags_all                          = (known after apply)
      + url                               = (known after apply)
      + visibility_timeout_seconds        = 30
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.


  Enter a value: yes


module.name.aws_sqs_queue.queue: Creating...
module.name.aws_sqs_queue.queue: Still creating... [10s elapsed]
module.name.aws_sqs_queue.queue: Still creating... [20s elapsed]
module.name.aws_sqs_queue.queue: Still creating... [30s elapsed]
module.name.aws_sqs_queue.queue: Creation complete after 31s [id=https://sqs.eu-central-1.amazonaws.com/0000000000000/my-queue]
│ Error: Invalid value for variable
│   on main.tf line 9, in module "name":
9:   timeout = 40
│     ├────────────────
│     │ aws_sqs_queue.queue.arn is "arn:aws:sqs:eu-central-1:0000000000000:my-queue"
│     │ var.timeout is 40
│ Invalid timeout value
│ This was checked by the validation rule at module/main.tf:8,3-13.

Notice how Terraform generates the plan for the SQS queue and the Lambda function. Still, the SQS queue was created when the configuration was applied, and the timeout variable validation was run. As it was invalid, it aborted the apply.

Common mistakes and best practices for Terraform variable validation

Below are some of the best practices you should follow when implementing variable validation in Terraform.

1. Write descriptive error messages

Generic error messages like Invalid field don’t tell us the expected value for that variable, which can lead to longer than necessary debugging sessions. 

variable "resource_count" {
  type = number
  validation {
    condition     = var.resource_count < 5
    error_message = "Invalid resource_count"
  }
}

To solve this,  write an error message that gives you context of what is expected.

variable "resource_count" {
  type = number
  validation {
    condition     = var.resource_count < 5
    error_message = "Resource count must be less than 5"
  }
}

2. Keep conditions simple and readable

In cases such as RegExes, you might need more complex validation. This can lead to bulkier code and make errors more challenging to understand and debug. 

For example, we have a regex that validates a CIDR structure, and it can only accept 16 or 24 subnets.

variable "cidr" {
  type     = string
  validation {
    condition     = can(regex("^[1-2]?\\d{1,2}\\.[1-2]?\\d{1,2}\\.[1-2]?\\d{1,2}\\.[1-2]?\\d{1,2}\\/(16|24)$", var.cidr))
    error_message = "Invalid CIDR structure"
  }
}

Leverage all Terraform native functions to simplify your validations. 

Below, we break the validation into two pieces, one validating the CIDR structure using the cidrsubnet function. Another is to validate the net mask with the cidrnetmask function.

variable "cidr" {
  type     = string
  validation {
    condition     = can(cidrsubnet(var.cidr, 0, 0))
    error_message = "Invalid CIDR structure"
  }

  validation {
    condition     = contains(["255.255.0.0", "255.255.255.0"], cidrnetmask(var.cidr))
    error_message = "Invalid subnet prefix provided. Must be 16 or 24"
  }
}

3. Break down long validations

As a general best practice, keep your conditions simple and readable.

In the example below, a complex and long condition is written in a validation block:

variable "my_variable" {
  type = number

  validation {
    condition = var.my_variable > 0 && var.my_variable % 2 == 0 && var.my_variable < 20
    error_message = "Variable must be bigger than 0, smaller than 20, and an even value"
  }
}

Break the condition into multiple validation blocks to make your code easier to understand and simplify the error messages.

variable "my_variable" {
  type = number

  validation {
    condition = var.my_variable > 0
    error_message = "Variable must be bigger than 0"
  }

  validation {
    condition = var.my_variable % 2 == 0
    error_message = "Variable must be an even value"
  }

  validation {
    condition = var.my_variable < 20
    error_message = "Variable must be smaller than 20"
  }
}

4. Validate against live data

Before Terraform 1.9, if you wanted to validate a variable against a value in the cloud, you would have to hardcode it. This can lead to writing and validating against the wrong values.

variable "aws_region" {
  type = string

  validation {
    condition     = contains(["us-east-1", "us-west-1"], var.aws_region)
    error_message = "Invalid AWS region."
  }
}

Since Terraform 1.9, it has been possible to leverage data sources referencing in variables to validate against existing values.

data "aws_regions" "available" {}

variable "aws_region" {
  type = string

  validation {
    condition     = contains(data.aws_regions.available.names, var.aws_region)
    error_message = "Invalid AWS region. Use a valid region from AWS."
  }
}

5. Understand which step your validation will run

This is more relevant to resource referencing. To avoid unexpected behaviours, you must always understand when your validation will run (plan or apply phase).

For example:

  • aws_sqs_queue.name will be validated during the plan phase
  • aws_sqs_queue.arn will be validated during the apply phase

Suppose your workflow allows a validation to stop your configuration creation during the process. It is then acceptable to validate against properties that are only known during the apply. If not, you must always avoid these validations.

What is the difference between precondition and validation in Terraform?

Preconditions and postconditions serve a similar purpose of validation. They validate data in your Terraform configuration.

The main difference is that validation is done directly in the input variable, and its purpose is to validate inputs.  Here is an example of validating an ami_id variable

variable "ami_id" {
  type = string
  validation {
    condition     = startswith(var.ami_id, "ami-")
    error_message = "AMI ID must start with ami-"
  }
}

Precondition and postcondition are done directly in the resource block (resource, data, output), and their purpose is to ensure state correctness. Here is an example of pre- and postcondition application to validate an EC2 instance:

resource "aws_instance" "example" {
  instance_type = "t3.micro"
  ami           = data.aws_ami.example.id

  lifecycle {
    # Checks before planning/creating instance
    precondition {
      condition     = data.aws_ami.example.architecture == "x86_64"
      error_message = "The selected AMI must be for the x86_64 architecture."
    }

    # Checks after creating instance
    postcondition {
      condition     = self.instance_state == "running"
      error_message = "EC2 instance must be running."
    }
  }
}

Here is a table showing the main differences between them:

Feature Validation Preconditions Postconditions
Scope Input variables (configuration/modules) Resources, data sources, and outputs Resources, data sources, and outputs
Purpose Validate user input Ensure state correctness Ensure state correctness
Restrict allowed values for an input Check if a resource meets the required conditions before planning or creating it Check if a resource meets the required conditions after planning or creating
When it stops during apply Before implementing planned actions for the associated resource Before implementing planned actions for the associated resource After implementing planned actions for the associated resource
Example use case Prevent configuration from being run on region us-west-2 Prevent a resource from being created with the wrong tags Prevent run from continuing if an EC2 instance state after creation

How to manage Terraform resources with Spacelift

Terraform is really powerful, but to achieve an end-to-end secure Gitops approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:

  • Policies (based on Open Policy Agent) – You can control how many approvals you need for runs, what kind of resources you can create, and what kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
  • Multi-IaC workflows – Combine Terraform with Kubernetes, Ansible, and other infrastructure-as-code (IaC) tools such as OpenTofu, Pulumi, and CloudFormation,  create dependencies among them, and share outputs
  • Build self-service infrastructure – You can use Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
  • Integrations with any third-party tools – You can integrate with your favorite third-party tools and even build policies for them. For example, see how to integrate security tools in your workflows using Custom Inputs.

Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. Read the documentation for more information on configuring private workers.

You can check it out for free by creating a trial account or booking a demo with one of our engineers.

Key points

This article explains variable validations, a key feature of Terraform. They allow us to fail quickly and prevent input variables from assigning undesired values. We also learned about the types of validations in Terraform: type, required, and custom validations.

We discussed how using custom validation is as simple as adding the validation block to a variable and then assigning a condition, which is the validation rule, and an error_message, which is output when the condition fails.

We learned how to create custom validations, how to use multiple validation blocks, and how to reference external blocks like other variables, local values, data sources, and resources. We also learned best practices when using variable validations.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Terraform management made easy

Spacelift effectively manages Terraform state, more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and includes many more features.

Learn more

Thu, May 15, 2025 @ 11:00am EDT

The First Community-Driven
IaC Conference

Register now