Terraform Validate vs. Terraform Console: Different results: why?

Hi there,
I’m using a Terraform module to create some resources.
The resources themselves have mandatory and optional attributes and some of the attributes are validated (and this is the problem).

My problem statement is:

I want to validate the content of optional keys in a map.
Keys are either present and the content must get validated,
OR the keys are not present, which is also okay.

Here’s some example of the resource-values I use while developing with Terraform console:

locals {
  my_teams = {
    "team1" = {
      name          = "Some Tea Team"
      short_name    = "STT"
      contact_email = "team-stt@example.com"
    }
    "team2" = {
      name                  = "Something with Network"
      short_name            = "NET"
      contact_email         = "network@example.com"
      billing_contact_email = "network-billing@example.com"
    }
    "team3" = {
      name                  = "Some Utilities Creators"
      short_name            = "SUSI"
      contact_email         = "team-susi@example.com"
      billing_contact_email = "team-susi-billing@example.com"
    }
  }
}

The contact_email is mandatory, the billing_contact_email is optional.
I want to do regex-validation on both attributes.

I used terraform console with only this snippet (above) to come up with this condition for my validation:

alltrue([for key, team in local.my_teams : (lookup(team, "billing_contact_email", "<NONE>") == "<NONE>" ? true : (can(regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", lookup(team, "billing_contact_email", "ERROR!")))))]

In pseudo-code:

lookup the key “billing_contact_email”
if there’s no key → output = “<NONE>” → return “true” to the validate-function
if there is the key → apply regex to the value of the key → return “true” or “false” as a result

The alltrue() function can get removed for better debugging. → this snippet works nicely (and yes, I restarted terraform console after chaning my code-snippet :slight_smile: )

Code in the terraform module:

variable "my_teams" {
  type = map(object({
    name                  = string
    short_name            = string
    contact_email         = string
    billing_contact_email = optional(string)
    tags                  = optional(map(string), {})
  }))

  description = "My Teams"

  validation {
    condition     = alltrue([for key, team in var.my_teams : can(regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", lookup(team, "contact_email", "<NONE>")))])
    error_message = "Attribute 'contact_email' not set or it's value is not a valid email address."
  }

  validation {
    condition = alltrue(
      [for key, team in var.my_teams : (lookup(team, "billing_contact_email", "<NONE>") == "<NONE>" ? true : (can(regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", lookup(team, "billing_contact_email", "ERROR!")))))]
    )
    error_message = "Attribute 'billing_contact_email' has not a valid email address as a value."
  }
}

How the module is called:

module "team_hierarchy" {
  source          = "../modules/team-creation"
  org_unit        = "My Organization"
  my_teams = {
    "team1" = {
      name          = "Some Tea Team"
      short_name    = "STT"
      contact_email = "team-stt@example.com"
    }
    "team2" = {
      name                  = "Something with Network"
      short_name            = "NET"
      contact_email         = "network@example.com"
      billing_contact_email = "network-billing@example.com"
    }
    "team3" = {
      name                  = "Some Utilities Creators"
      short_name            = "SUSI"
      contact_email         = "team-susi@example.com"
      billing_contact_email = "team-susi-billing@example.com"
    }
  }
}

The code is identically, only the variable is named differently.

How come the results are so different?
There is no dynamic data involved, every change is applied, and I only want to add the validation.

How can I solve my validation-problem and why do I get different results?

Similar issue: Different result during terraform plan & console

My above assumption was, that lookup() is the perfect tool for the job, because I deal with a map.

It turns out that try() is working great:

[for key, team in var.my_teams : try(team.billing_contact_email, null) == null ? true : (can(regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", lookup(team, "billing_contact_email", "ERROR!"))))]

So the first step is to use try() instead of lookup to evaluate if the key is present.
Works for me.

References:

The question “Why do I get different results?” is still open. :slight_smile:

Hi @paul.puschmann,

Can you share more details about exactly what you were running here and what result you got? From what you’ve described I’m unsure exactly what difference in behavior you are referring to.

I needed to create some nice condition for the validation of my variable.
Just hitting terraform validate didn’t solve it for me and I wanted to take a look at the data structures I generated / worked on.
So the basic idea was to leverage terraform console to work interactively on the (similar) dataset and get better and more transparent results.


I created this file as main.tf in an empty directory:

locals {
  my_teams = {
    "team1" = {
      name          = "Some Tea Team"
      short_name    = "STT"
      contact_email = "team-stt@example.com"
    }
    "team2" = {
      name                  = "Something with Network"
      short_name            = "NET"
      contact_email         = "network@example.com"
      billing_contact_email = "network-billing@example.com"
    }
    "team3" = {
      name                  = "Some Utilities Creators"
      short_name            = "SUSI"
      contact_email         = "team-susi@example.com"
      billing_contact_email = "team-susi-billing@example.com"
    }
  }
}

In this directory I executed terraform console and entered this command:

[for key, team in local.my_teams : (lookup(team, "billing_contact_email", "<NONE>") == "<NONE>" ? true : (can(regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", lookup(team, "billing_contact_email", "ERROR!")))))])

which created this output:

[
  true,
  true,
  true,
]

and additional alltrue() wrapped around would just create true as a result.
^ this should be the same code as in the variable validation condition in my Terraform module.

For the “other code”: I just executed terraform validate and expected the validation to work.

→ with try() instead of lookup() the validation condition is fine in the Terraform module

Hi @paul.puschmann,

I think what’s going on here is that the input variable value isn’t considered when testing that the configuration is statically valid as terraform validate does, because static validation checks whether the module is valid for any possible input, not for one specific input.

However, I’m not 100% sure of that explanation, since it does seem like the data in question here could be known during static validation in principle… it depends on how you’ve wired things together.