Terraform 13, Validate variable base on the value of another

Viewed 6591

Is there a way of implementing the below logic

variable "environment" {
  description = "The environment this will be run in can only be set to [preprod|test|prod]"
  type        = string
  default     = "test"
  validation {
    condition     = can(regex("^(prod|preprod|test)$", var.environment))
    error_message = "The environment variable can only be set to [prod|preprod|test]."
  }
}

variable "fet_code" {
  description = "Set the feature code"
  type        = string
  default     = ""
  validation {
    condition     = var.environment == "test" && length(var.fet_code) != 3
    error_message = "The environment has been set to 'test' but the fet_code has not be defined."
  }
}

At the moment i get the following error:

Error: Invalid reference in variable validation

  on variable.tf line 17, in variable "fet_code":
  17:     condition     = var.environment == "fet" && length(var.fet_code) == 3

The condition for variable "fet_code" can only refer to the variable itself,
using var.fet_code.

I understand what the problem is with the code, I am just wondering if there is a way round the restriction?

5 Answers

While there's a Github issue for implementing this as a feature, the only way to validate against multiple variables is by using locals to throw an error at runtime:

variable "environment" {
  description = "The environment this will be run in can only be set to [preprod|test|prod]"
  type        = string
  default     = "test"
  validation {
    condition     = can(regex("^(prod|preprod|test)$", var.environment))
    error_message = "The environment variable can only be set to [prod|preprod|test]."
  }
}

variable "fet_code" {
  description = "Set the feature code"
  type        = string
  default     = ""
}

locals {
  validate_fet_code_cnd = var.environment == "test" && length(var.fet_code) != 3
  validate_fet_code_msg = "The environment has been set to 'test' but the fet_code has not been defined."
  validate_fet_code_chk = regex(
      "^${local.validate_fet_code_msg}$",
      ( !local.validate_fet_code_cnd
        ? local.validate_fet_code_msg
        : "" ) )
}

It's a messy, sketchy hack, but it should prevent invalid values from getting applied.

The best solution I found for a similar problem for myself was to put the check in a module. Said module is then only included if and when the environment requires the variable to be set.

module input_validation {
    source   = "./modules/input_validation"
    count    = var.environment == "test" ? 1 : 0
    fet_code = var.fet_code
}

And then in modules/input_validation/input_validation.tf:

variable "fet_code" {
  default = ""
  type    = string
  validation {
    condition     = length(var.fet_code) != 3
    error_message = "The environment has been set to 'test' but the fet_code has not be defined."
  }
}

It seems to me that the two variables are close related. I would change them to one object in order to achieve what you want.

variable "environment" {
  type = object({
    environment = string
    fet_code = string
  })
  default = {
    environment = "test"
    fet_code = ""
  }

  validation {
    condition     = can(regex("^(prod|preprod|test)$", var.environment.environment))
    error_message = "The environment variable can only be set to [prod|preprod|test]."
  }

  validation {
    condition     = var.environment.environment == "test" && length(var.environment.fet_code) != 3
    error_message = "The environment has been set to 'test' but the fet_code has not be defined."
  }
}

You can then pass the variables like this:

environment = {
  environment = "test"
  fet_code = "1234"
}

Thank you for your comment @Torge, as you mention working with a local source module, is possible to validate multiple variables or locals

As example in source code define some locals that can take hardcoded values or from variables input. Validation of clauses are resolved by boolean local consolidation

locals {
  node_type = "cache.t1.micro"
  snapshot_retention_limit = 0
  consolidation = local.node_type == "cache.t1.micro" && local.snapshot_retention_limit > 0 ? false : true
}


module "validation" {
  source = "./module"
  
  node_type     = local.node_type
  consolidation = local.consolidation
}

After that inside module folder, you can evaluate individual variables like variable node_type or more complex validation like variable consolidation

variable "consolidation" {
    type = bool
    description = "consolidation of locals `node_type` & `snapshot_retention_limit` "
    validation {
        condition     = var.consolidation == true
        error_message = "Module parameter `consolidation` can not be equals `false`."
    }
}


variable "node_type" {
  description = "Required AWS values"
  validation {
    condition     = can(regex("^cache\\..*\\..*", var.node_type))
    error_message = "Only nodes starting with cache. are allowed."
  }
}

as you cannot refer to other variables outside of the specific variable, you can do it in a different way with using it as a list:

variable "fet_code" {
  description = "Set the feature code"
  type        = list
  default     = ["test", ""]
  validation {
    condition     = var.fet_code[0] == "test" && length(var.fet_code[1]) != 3
    error_message = "The environment has been set to 'test' but the fet_code has not be defined."
  }
}
Related