Variable Validation of maps

I’m using Terraform v0.12.29 and have the following within variables.tf:

    variable "cors_rule" {
  type        = any
  default = [
    {
      allowed_methods = ["PUT", "POST"]
      allowed_origins = ["https://test1.google.com", "https://test2.google.com"]
      allowed_headers = ["*"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3600
    },
    {
      allowed_methods = ["HEAD", "GET"]
      allowed_origins = ["https://test1.google.com"]
      allowed_headers = ["*"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3000
    }
  ]
  
  // allowed_origins  
  validation {
    condition = can(tolist([
      for origin in lookup(var.cors_rule, "allowed_origins", []) : regex("^.*google.com", origin)
    ]))
    error_message = "Attribute 'allowed_origins' of 'cors_rule' variable url must end with yahoo.com."
  }

  // max_age_seconds
  validation {
    condition     = tonumber(lookup(var.cors_rule, "max_age_seconds", 0)) =< 3600
    
    error_message = "Attribute 'max_age_seconds' of 'cors_rule' must be less or equal to 3600 seconds."
  }  
  
}

Yet the validation condition is failing for me with the following output. How do I handle maps within validation condition?

Error: Invalid value for variable

  on variables.tf line 161:
 161: variable "cors_rule" {

Attribute 'allowed_origins' of 'cors_rule' variable url must end with google.com.

This was checked by the validation rule at variables.tf:198,3-13.


Error: Invalid function argument

  on variables.tf line 207, in variable "cors_rule":
 207:     condition     = tonumber(lookup(var.cors_rule, "max_age_seconds", 0)) >= 3600
    |----------------
    | var.cors_rule is tuple with 2 elements

Invalid value for "inputMap" parameter: lookup() requires a map as the first
argument.

Hi @xvillanu!

The general idea here is to write an experssion which returns false rather than raising an error if any of the necessary conditions aren’t true. In your case then, we need a way to ask Terraform if the given value is compatible with the lookup operation.

I was initially going to show a way to get this done with the can function, but since you are already using lookup here it’s interesting to observe that lookup is really just a specialization of can's relative try. The following two expressions are functionally equivalent as long as var.cors_rule is a map or object:

lookup(var.cors_rule, "max_age_seconds", 0)
try(var.cors_rule.max_age_seconds, 0)

However, the try version also moves on to the fallback if var.cors_rule is not of a type where .max_age_seconds makes sense, and so it neatly avoids the error message you encountered by checking both that var.cors_rule is of a suitable type and whether it has a max_age_seconds attribute at the same time.

Putting that into your larger condition expression, we can get this:

try(var.cos_rule.max_age_seconds, 0) <= 3600

Assuming that you want it to be valid to not specify max_age_seconds at all, I think this would get the result you wanted because zero is less than 3600.

Since your input is a sequence of objects rather than just a single object we also need some extra machinery to apply the check to every element of the sequence. You are already doing something like this with can(tolist( in your other variable. For more recent Terraform versions I would use alltrue here but your solution of wrapping the whole for expression in a can is a reasonable workaround for your earlier Terraform version. (It does mean that it’ll mask various other sorts of possible errors, which may or may not be an advantage depending on whether the situation was one you wanted to allow or not, but it’s a pragmatic compromise until you’re on a new enough version of Terraform to use alltrue.)