I assume what you want to achieve here is to apply that condition to each of the objects in the list. In which case, the main building block of the answer is to use for expressions to evaluate the condition once for each element of the list.
In Terraform v0.13 you can write this as a for expression with an if clause where you test the length of the result to see if the condition was valid:
validation {
condition = length([
for o in var.rules : true
if contains(["Allow", "Deny"], o.access)
]) == length(var.rules)
error_message = "All rules must have access of either Allow or Deny."
}
The above works by filtering the input to only include the items that are valid and then checking whether the resulting list still has the same amount of elements. It would have fewer elements if any of the items were invalid.
The forthcoming Terraform 0.14.0 (expected in the next week or so) will include a new function alltrue which aims to simplify the above pattern by allowing you to rewrite it as a for expression whose result is a list of boolean values that must all be true for the condition to hold:
validation {
condition = alltrue([
for o in var.rules : contains(["Allow", "Deny"], o.access)
])
error_message = "All rules must have access of either Allow or Deny."
}
Once you’re able to use Terraform v0.14 I would recommend adopting this second pattern because I think (subjectively) it’d be easier for a future maintainer to read and understand what it means and how it works.
Thanks a lot @apparentlymart
This helped me save a lot of time in figuring out how to use such validations. I believe it should be added to the official documentation.
I’m trying to set up a validation based on your suggestion and it doesn’t seem to work for list(string) vars. I’m getting the below validation error. It works perfectly fine when I comment the validation paragraph out.
variable “project” {
type = map(object({
mongodb_project_name = string
role_names = optional(list(string))
}))
validation {
condition = alltrue([ for x in var.project : contains([“GROUP_OWNER”], x.role_names)])
error_message = “The role name must be one of the following values: ["GROUP_OWNER", "GROUP_READ_ONLY".”
}
Error: Invalid value for variable
│
│ on variables.tf line 9:
│ 9: variable “project” {
│ ├────────────────
│ │ var.project is map of object with 1 element
│
│ The role name must be one of the following values: [“GROUP_OWNER”].
You are on the right track, the only thing that is missing here is that you are passing in a list to the second argument of contains function, this expects a value. You can change your validation function to below and it will work:
variable “project” {
type = map(object({
mongodb_project_name = string
role_names = optional(list(string))
}))
validation {
condition = alltrue( flatten( [ for x in var.project : [for role in x.role_names : contains([“GROUP_OWNER”], role)] ] ) )
error_message = “The role name must be one of the following values: ["GROUP_OWNER", "GROUP_READ_ONLY".”
}
│ Error: Unsupported attribute
│
│ on variables.tf line 20, in variable “projects”:
│ 20: condition = alltrue( flatten( [ for x in var.projects : [for role in x.role_names : contains([ “GROUP_OWNER”, “GROUP_READ_ONLY”, “GROUP_DATA_ACCESS_ADMIN”, “GROUP_DATA_ACCESS_READ_ONLY”, “GROUP_CLUSTER_MANAGER” ], role)] ] ) )
│
│ This object does not have an attribute named “role_names”.
One question: Why does the first snippet include true if before the contains? Is that a Terraform <0.13 constraint? Doesn’t contains(["Allow", "Deny"], o.access) output true anyway like in example snippet two?
My question is not related to the alltrue or == length handling. It’s more of a both versions work but I don’t understand why true if was used and I think it can be ommited.