wdec
June 15, 2020, 12:44pm
1
Is there a way to define as part of an object definition the variable validation.
Eg Say we have the following variable defined
variable "VMSize" {
type = string
default = "Small"
validation {
condition = can(regex("^Small$|^Medium$|^Large$", var.VMSize))
error_message = "Invalid VM Size."
}
}
How can one define it as part of an object?
The following doesn’t work (with v0.13-beta1).
variable "VMCluster" {
type = object({
"VMSize" = {
type = string
default = "Small"
validation {
condition = can(regex("^Small$|^Medium$|^Large$", var.VMSize))
error_message = "Invalid VM Size."
}
}
})}
In general, will HCL be revising the object type definition to allow them to contain normally defined variables rather than the <KEY> = <TYPE> syntax?
The following would be great:
variable "VMCluster" {
type = object({
variable "VMSize" {
type = string
default = "Small"
validation {
condition = can(regex("^Small$|^Medium$|^Large$", var.VMSize))
error_message = "Invalid VM Size."
}
}
})}
Good question! You can define validations over the whole value of an object type. For example, like this:
variable "VMCluster" {
type = object({ VMSize = string })
validation {
condition = can(regex("^Small$|^Medium$|^Large$", var.VMCluster.VMSize))
error_message = "Invalid VM Size."
}
}
(As a side note, it’s not possible to specify default values for single attributes of objects as you write in your last example: only complete values of objects are valid, and they must include key-value pairs for each attribute.)
Two other things that might be relevant here: an alternative way to write this specific validation condition, and using multiple validations.
Depending on how much you enjoy writing regexes, instead of using can(regex(…)), it might be simpler to use contains:
variable "VMCluster" {
type = object({ VMSize = string })
validation {
condition = contains(["Small", "Medium", "Large"], var.VMCluster.VMSize)
error_message = "Invalid VM Size."
}
}
And as your object type gets more complex, you can add multiple validations. For example:
variable "VMCluster" {
type = object({ VMSize = string, Count = number })
validation {
condition = contains(["Small", "Medium", "Large"], var.VMCluster.VMSize)
error_message = "Invalid VM Size."
}
validation {
condition = var.VMCluster.Count > 1
error_message = "Count must be greater than 1."
}
}
I hope this helps! Let me know if anything is unclear.
1 Like
wdec
June 15, 2020, 5:50pm
3
Thanks for the explanation.
Now, from a language coherency perspective this is pretty awful syntax. It would be MUCH better for objects to simply allow regular variable definition syntax. Is that already a planned enhancement? (I’ll open an request if not)
@wdec I don’t think we’ve heard that feedback before, no.
If you leave an enhancement request, it would be very helpful to have examples of what you’re trying to do that the current implementation doesn’t allow. Thanks in advance!
wdec
July 2, 2020, 1:35pm
5
I added my proposal as comment to a somewhat related ongoing topic:
opened 10:37AM - 30 Dec 18 UTC
closed 02:37PM - 29 Aug 22 UTC
enhancement
config
### Current Terraform Version
```
Terraform v0.12.0-alpha4 (2c36829d3265661d8e… dbd5014de8090ea7e2a076)
```
### Proposal
I like the `object` variable type and it would be nice to be able to define optional arguments which can carry `null` value too, to use:
```hcl
variable "network_rules" {
default = null
type = object({
bypass = optional(list(string))
ip_rules = optional(list(string))
virtual_network_subnet_ids = optional(list(string))
})
}
resource "azurerm_storage_account" "sa" {
name = random_string.name.result
location = var.location
resource_group_name = var.resource_group_name
account_replication_type = var.account_replication_type
account_tier = var.account_tier
dynamic "network_rules" {
for_each = var.network_rules == null ? [] : list(var.network_rules)
content {
bypass = network_rules.value.bypass
ip_rules = network_rules.value.ip_rules
virtual_network_subnet_ids = network_rules.value.virtual_network_subnet_ids
}
}
```
instead of:
```hcl
variable "network_rules" {
default = null
type = map(string)
}
resource "azurerm_storage_account" "sa" {
name = random_string.name.result
location = var.location
resource_group_name = var.resource_group_name
account_replication_type = var.account_replication_type
account_tier = var.account_tier
dynamic "network_rules" {
for_each = var.network_rules == null ? [] : list(var.network_rules)
content {
bypass = lookup(network_rules, "bypass", null) == null ? null : split(",", lookup(network_rules, "bypass"))
ip_rules = lookup(network_rules, "ip_rules", null) == null ? null : split(",", lookup(network_rules, "ip_rules"))
virtual_network_subnet_ids = lookup(network_rules, "virtual_network_subnet_ids", null) == null ? null : split(",", lookup(network_rules, "virtual_network_subnet_ids"))
}
}
}
```
We’ll see how it goes. Maybe it should be a separate proposal