Terraform v0.13.1
With the release of Terraform v0.13.1 we started using a lot Custom Validation Rules to check whether an input value for a variable has the expected value/format.
We've noticed that with Custom Validation Rules there is no way(from what I've seen) to validate that all items of a list variable have an expected value or format.
I have tried the below solutions to validate that each item of a list holding CIDR blocks conforms to the CIDR block format of x.x.x.x/x.
The below does not work as the variable var.private_subnets is a list and it would require to loop over each list item. But i can't find a way to do that in a validation block. If, however, I specifically specify the index of an item like var.private_subnets[0] in the condition attribute, it works fine but obviously that will check only one of the items. I could of course specify the validation block multiple times, one time for each list item, but that is not dynamic at all.
variable "private_subnets" {
type = list
default = ["10.10.108.0/25", "10.10.108.128/26", "10.10.108.192/26"]
validation {
condition = can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$", var.private_subnets))
error_message = "Each item of the 'private_subnets' List must be in a CIDR block format. Example: [\"10.106.108.0/25\"]."
}
}
Is there currently a way to achieve what I am trying to do?
If the above is not possible currently, how about adding support and make it possible to have a validation block be able to check somehow all list items?
One way I thought this could be done is to make a validation block be dynamic so we can work then with the for_each blocks. An idea would be something like the below, which does not work when I tried it(throws errors about unsupported blocks):
variable "private_subnets" {
type = list
default = ["10.10.108.0/25", "10.10.108.128/26", "10.10.108.192/26"]
dynamic "validation" {
for_each = var.private_subnets
content {
condition = can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$", private_subnets.value))
error_message = "Each item of the 'private_subnets' List must be in a CIDR block format. Example: [\"10.106.108.0/25\"]."
}
}
}
Thank you!
Hi @piersf! Thanks for sharing this use-case.
A way to get this done with current Terraform is to write a for expression in the condition argument, like this:
condition = can([for s in var.private_subnets : regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$", s)]
Because the for expression as a whole will fail if any of its repeated evaluations fail, can will still return false in that case.
What this does not do is allow the error message to give details about which of the elements is invalid, which is non-ideal. Perhaps something like what you proposed here would allow Terraform to give better feedback about which element was invalid, though it would still be limited only to reporting errors in the first level of the variable's data structure, rather than at arbitrary points in the data structure, so we may wish to investigate a more complete solution if we can find one that doesn't result in an explosion of complexity.
@apparentlymart thank you for the example! It works quite well.
For the time being, we are good if the error message does not say which element is invalid. That is a minor thing.
I am a +1 for this as well. My use case is very similar to @piersf:
variable "security_groups" {
type = list(string)
description = "(optional) list of security groups to apply to spun up nodes"
default = []
# Ideally, each string in the list would match the pattern "sg-[alphnumeric]". Since we don't know if AWS will
# every increase the number of digits in an SG-id, using length() may not age well. Regex() is likely to be good enough
# if the expression contains a minimum length requirement...
}
I think that the for inside of can() with regex() doing the heavy lifting will work for my simple use-case but I have a few other variables that take in a list of objects where each object is expected to have a few fields:
dynamic "some_block" {
for_each = var.some_list_of_maps
content {
property_1 = lookup(some_block.value, "property_1", null)
<...etc...>
}
}
We have decent automated documentation that gives the user a clue as to which keys need to go in the map and what the value should be (int, string...etc) but there's no way to require that each map in the list have only the keys property_1 and property_2 and property_3. Ideally, for each map object in the list: property_1 would map to a string and property_2 would map to a positive integer and property_3 would be a positive integer at least 2x bigger than property_2.
Most helpful comment
Hi @piersf! Thanks for sharing this use-case.
A way to get this done with current Terraform is to write a
forexpression in theconditionargument, like this:Because the
forexpression as a whole will fail if any of its repeated evaluations fail,canwill still returnfalsein that case.What this does not do is allow the error message to give details about which of the elements is invalid, which is non-ideal. Perhaps something like what you proposed here would allow Terraform to give better feedback about which element was invalid, though it would still be limited only to reporting errors in the first level of the variable's data structure, rather than at arbitrary points in the data structure, so we may wish to investigate a more complete solution if we can find one that doesn't result in an explosion of complexity.