hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.58k stars 9.54k forks source link

Validation rules as first class resources? #31712

Open tspearconquest opened 2 years ago

tspearconquest commented 2 years ago

Terraform Version

Terraform v1.2.0
on darwin_amd64
+ provider registry.terraform.io/hashicorp/azurerm v2.99.0

Use Cases

Variable validations are incredibly useful, but the rules can quickly get complex and it detracts from the readability of the code having the validations in the variable definition itself.

It would be great if we could have validations become a first class resource in terraform; a virtual resource which behaves similar to the null resource but will cause an error if the validation rules fail.

I also think that it be great to be able to specify multiple different variables for validation against one another, and the current design outright denies users to create any validation rules which leverage another variable to complete the validation, whereas that need not need to be the case with dedicated a validation resource.

Attempted Solutions

Feature Request

Proposal

No response

References

No response

apparentlymart commented 2 years ago

Hi @tspearconquest! Thanks for sharing this feedback.

The intent of placing validation rules directly inside the definitions of the variables they apply to is so that readers can easily see which rules apply to each variable, since variable validations intended for localized concerns such as making sure a string has correct syntax, or making sure a number is within an expected range.

It sounds like the checks you have in mind have a broader scope than that, potentially taking into account various different values from the module scope to decide whether the resulting combination is valid.

For that sort of need we added Preconditions and Postconditions in Terraform v1.2, which essentially allows expressing checks from a different perspective: instead of blocking values entering a module as a whole, you can instead state what must be true in order for a particular resource to be configurable (preconditions) or describe what needs to be true about a resource itself after it's been created or updated (postconditions).

In particular, due to being declared in the details of a module rather than at its entry point both preconditions and postconditions can refer to anything else that's in scope inside that module, including any number of different variables and also to other resources as long as evaluation is possible without any dependency cycles.

Could you review the documentation for those features and let us know if they could help to meet your goal, or if there's still something missing that prevents you from writing the checks you want to write? Thanks!

tspearconquest commented 2 years ago

Thanks I will look and get back to you.

tspearconquest commented 2 years ago

Thanks for the info. It's readability that prompted me to post this.

As an example, there's no way I'm aware of to break up a regex onto multiple lines, so validations of different lengths makes it harder to read through my variables.

variable "remote_subnet_id" {
  description = "Resource ID of the remote subnet where the endpoint will be located"
  type        = string
  nullable    = false

  validation {
    condition = (
      var.remote_subnet_id == ""
      || length(regexall("^[/]subscriptions[/](?:(?:12345678-1234-5678-1234-567812345678)|(?:87654321-8765-4321-8765-432187654321)|(?:12345678-8765-4321-1234-567887654321))[/]resourceGroups[/][^/]+[/]providers[/]Microsoft[.]Network[/]virtualNetworks[/][^/]+[/]subnets[/].+$", var.remote_subnet_id)) > 0
    )
    error_message = "Remote subnet ID, if defined, must be a valid subnet resource ID."
  }
}

variable "vpn_pub_ip" {
  description = "VPN public IP"
  type        = string
  nullable    = false
  default     = ""

  validation {
    condition     = var.vpn_pub_ip == "" || length(regexall("^12[.]34[.]56[.]78[/]32$", var.vpn_pub_ip)) > 0
    error_message = "VPN public IP, if defined, must be the outbound Public IP address of the VPN."
  }
}

I'll give preconditions a shot. I tend to look at the variables as separate from the code, so having them be easier to read by putting pre-conditions on the resources instead of validations on the variables may very well be what we need to do. Thanks again! I will update you when I have a chance to test this.