Try function for Boolean values

Hi There,

I’m working in the following Terraform definitions and making use try() for setting default values.

Everything works as expected for any variable set as string and/or list when, in cases of no values are declared, the variable assumes the try() default value (e.g. visibility_level = try(each.value.visibility_level, "private")

The problem lies on Boolean, which the true/false seems to be ignored by try(). If we explicitly declare the variable/value (e.g. allow_merge_on_skipped_pipeline = false), it works. However, when we comment out or remove the variable, try() does not seem to set the default value set (e.g. try(each.value.allow_merge_on_skipped_pipeline, false).

Now I’m wondering… is try() applicable for Boolean values?

Thanks in advance for any clue on that.

projects.auto.tfvars

gitlab_projects = {
  "contoso-server" = {
    name                  = "contoso-server"
    description           = "contoso-server repo"
    namespace             = "contoso"
    default Branch        = "main"
    mr_approvals_required = 0
...
    # allow_merge_on_skipped_pipeline       = false            # optional
    # remove_source_branch_after_merge      = true             # optional

variables.tf

variable "gitlab_projects" {
  type = map(object({
    name                                  = string
    description                           = string
    namespace                             = string
    default_branch                        = string
    mr_approvals_required                 = number
...
    allow_merge_on_skipped_pipeline       = optional(bool)
    container_registry_enabled            = optional(bool)
...

  }))
  description = "List of GitLab Projects to be provisioned"
}

main.tf

resource "gitlab_project" "this" {
  for_each     = var.gitlab_projects
  name         = each.value.name
  description  = each.value.description
  namespace_id = data.gitlab_group.this[each.key].id
  path         = try(each.value.path, each.value.name)
  visibility_level       = try(each.value.visibility_level, "private")
  default Branch         = each.value.default_branch

  allow_merge_on_skipped_pipeline       = try(each.value.allow_merge_on_skipped_pipeline, false)
  container_registry_enabled            = try(each.value.container_registry_enabled, true)
...

}

Hi @hutger,

The try call you showed can never select the fallback value because it is never an error to evaluate each.value.allow_merge_on_skipped_pipeline as long as that attribute exists in your object type.

When you use optional as you showed here, the attribute can have three possible values: true, false, or null. The last of those is what value the attribute will have if the caller doesn’t set it.

If you want to use a different value when the caller doesn’t set this argument, you can specify a second argument to optional like this:

remove_source_branch_after_merge = optional(bool, true)

With the declaration above, remove_source_branch_after_merge is guaranteed to always be either true or false, and will default to true of not set. You therefore don’t need any special functions at the point where you will use it, because Terraform will automatically handle inserting the default value before your module receives this object.

HI @apparentlymart , thanks for giving me some enlightment on this. Makes total sense.

In that case, the approach would be removing the try() function from the Boolean attributes and set the fallback via variables (optional(bool, true/false)).

Is that right?

Hi @hutger,

Yes, I believe so.

The general idea I want to draw attention to here is that if you write out an exact object type constraint then Terraform guarantees that all attributes you declared will be present in the resulting value, so you should never need to use techniques like try to interact with those attributes.

The try function is for situations where you can’t be sure of the shape of the data structure, such as when you are working with the result of a function like jsondecode where the result type is chosen dynamically based on the input. You shouldn’t ever need to use try when working with a statically-typed data structure like what results from an input variable with an exact type constraint.

An attribute being “optional” means that the caller may omit it, but Terraform still guarantees that it will be present in the object you receive inside the module. Terraform achieves that either by inserting the default value you specified or, if you don’t specify a default value, using null as the default default.

1 Like

Awesome @apparentlymart. Based on that, I’ll reconsider the use of try() on other parts of the code as well.

Thanks for the clarifications.