Variable validate value in specific key in json?

Hello,

I’m looking for a solution that would allow me to validate the content of a json sent to a module, do you think that’s possible?

I send JSON to a module which must create a resource “datadog_dashboard_json”, and I would like to check that the “Title” field contains precise information.

Hi @cvalentin-dkt,

You can use jsondecode as part of a validation condition in order to test against the contents. The resulting expression is unfortunately likely to be quite complicated, but it should be possible.

variable "example_json" {
  type = string

  validation {
    condition = (
       can(jsondecode(var.example_json)) ?
       (jsondecode(var.example_json).Title == "example") :
       false
    )
    error_message = "Must be valid JSON and have Title set to \"example\"."
  }
}

One other thing to consider is that it might be a more convenient interface for your module to accept the data structure directly, rather than in JSON, and then your module can call jsonencode itself once it’s done any necessary processing and validation on the value. That would avoid an awkward situation where your caller would call jsonencode to produce the JSON only for you to then immediately decode it again for further processing.

variable "example" {
  # "any" here will allow any sort of data structure
  # for type-checking purposes, and then we'll constrain
  # it more with the validation rule below.
  type = any

  validation {
    condition = (
       can(var.example.Title) ?
       (var.example.Title == "example") :
       false
    )
    error_message = "Must be an object with attribute Title set to \"example\"."
  }
}

The can(var.example.Title) here is checking two things at once: first that it’s either an object or a map (otherwise attribute syntax wouldn’t be valid at all), and second that there is an attribute or element named “Title”. If those two conditions are true then it further checks the value of the Title attribute.

Elsewhere in your module you can use jsonencode(var.example) to get the JSON form you need to pass into the DataDog dashboard configuration.

Hello,

I cannot use the datadog_dashboard data structure because I am unable to dynamically send a multilevel object with various optional data (see Dynamic blocks with dynamic key? - #10 by cvalentin-dkt)

This is why I go through the json mode which also makes life easier for my users via the export of dashboards in datadog.

When i try this :

variable "example_json" {
  type = string

  validation {
    condition = (
       can(jsondecode(var.dashboard)) ?
       (regex("^[Monitoring]", jsondecode(var.dashboard).title)) :
       false
    )
    error_message = "Must be valid JSON and have Title set to \"example\"."
  }
}

I can’t use regex here :confused:

│   on ../terraform-modules/monitoring-datadog-terraform-dashboard-v2/variables.tf line 56, in variable "dashboard":
│   56:        (regex("^[Monitoring]", jsondecode(var.dashboard).title)) :

Call to function "regex" failed: pattern did not match any part of the given string.

So I try like this, but I have the regex can not find me the necessary fields :

variable "dashboard" {
  type        = string
  description = "The JSON formatted definition of the Dashboard."
  validation {
    condition = can(regex("^[(Test|Test1|Test2)]", jsondecode(var.dashboard).title))
    error_message = "Dashboard Title must be contain prefix business_department, Format : [business_department] NAME."
  }
}

Any idea ?

I would just like to check that the title field of the json does indeed contain a potential list of values in prefix in the format [PREFIX] …

Hi @cvalentin-dkt,

It looks like you’re very close here. The trick is that the expression in the “true” arm of the conditional expression must also return a true or false depending on whether it’s valid, rather than an error, and so I think it would be sufficient to put just that regex call in a can to get that effect:

variable "example_json" {
  type = string

  validation {
    condition = (
       can(jsondecode(var.dashboard)) ?
       can(regex("^[Monitoring]", jsondecode(var.dashboard).title)) :
       false
    )
    error_message = "Must be valid JSON and have Title set to \"example\"."
  }
}

Another way to write it which avoids duplicating the jsonencode call is to use try, which is similar to can except it returns the first argument whose evaluation succeeds rather than returning a boolean to indicate whether a single argument succeeded. We can therefore use try to provide a fallback value to use if the dashboard string isn’t valid JSON:

  condition = can(regex(
    "^[Monitoring]",
    try(jsondecode(var.dashboard).title, "")
  ))

The inner try here says to try to JSON decode the dashboard string and access .title from the result, but if any of that fails just use an empty string instead, which will then fail the regular expression because it doesn’t have one of the expected prefixes, and so it’ll still produce the validation error in that situation.

I think one remaining problem is of regular expression syntax rather than Terraform syntax. The regular expression [Monitoring] means to match a single character that must be one of the characters in the brackets (any of the letters “Monitrg” in this case). I think you intended to use () parentheses to describe the precedence for the | operator:

  condition = can(regex(
    "^(Monitoring|OtherExample)",
    try(jsondecode(var.dashboard).title, "")
  ))

Putting this altogether it requires that the following all be true:

  • The given string is valid JSON (or jsonencode would fail)
  • The data structure described by the JSON is an object with a property named title (or .title would fail)
  • The value of title is a string or convertible to a string (or regex would reject it as being of an unsupported type)
  • That the string representation of title starts with either Monitoring or OtherExample (or regex would reject it as not matching the pattern)

Thx for your help, it’s work with this solution :

variable "dashboard" {
  type        = string
  description = "The JSON formatted definition of the Dashboard."
  # CHECK DASHBOARD TITLE
  validation {
    condition = can(regex("^\\[(monitoring|test)\\] ", try(jsondecode(var.dashboard).title, ""))
    error_message = "Dashboard Title must be contain prefix business_department.\n\nFormat : [business_department] Dashboard Name\n\nAccept prefix :\n- [monitoring]\n- [test]\n\nFix & retry :)."
  }
}

I would have preferred to be able to define the different possible fields in another variable, but a priori it is impossible to call another variable at this level.

Exemple:

variable "business_department_allow" {
  type = string
  default = "monitoring|test"
}
variable "dashboard" {
  type        = string
  description = "The JSON formatted definition of the Dashboard."
  # CHECK DASHBOARD TITLE
  validation {
    condition = can(regex("^\\[(${var.business_department_allow})\\] ", try(jsondecode(var.dashboard).title, "")))
    error_message = "Dashboard Title must be contain prefix business_department"
  }
}