You've successfully subscribed to Nuvotex Blog
Great! Next, complete checkout for full access to Nuvotex Blog
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.

Object attribute defaults on terraform - hello simplicity

Using terraform's object attributes defaults empowers you to create complex input values that still apply default values on a highly granular level. This post shows an example how to use this.

Daniel Nachtrub
Daniel Nachtrub

Until now i've mostly been using the "configurable defaults" pattern to expose complex configurations to uses of my terraform code (https://blog.nuvotex.de/default-values-on-terraform-objects/).

At some point in time I felt that this is still too much code that also has some downsides under certain situations (an example is deepmerge and the "null value" problen that unsets nested default objects).

Object attribute defaults to the rescue!

A rather simple yet powerful way is to make use of optional attribute defaults (terraform 1.3+). An example for such a variable is shown here:

variable "system_management" {
  type = object({
    configure = bool
    certificates = optional(object({
      ca = optional(object({
        name     = optional(string, "system-pki-ca")
        lifetime = optional(number, 3650)
        keysize  = optional(string, "secp384r1")
      }), {})
      services = optional(object({
        name     = optional(string, "system-tls-cert")
        lifetime = optional(number, 3650)
        keysize  = optional(string, "secp384r1")
      }), {})
    }), {})
    services = optional(object({
      telnet = optional(object({
        port              = optional(number, 23)
        protocol          = optional(string, "tcp")
        enabled           = optional(bool, false)
        allowed_addresses = optional(list(string), ["127.0.0.0/8"])
      }), {})
      ssh = optional(object({
        port              = optional(number, 22)
        protocol          = optional(string, "tcp")
        enabled           = optional(bool, true)
        allowed_addresses = optional(list(string), ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"])
      }), {})
      www = optional(object({
        port              = optional(number, 80)
        protocol          = optional(string, "tcp")
        enabled           = optional(bool, false)
        allowed_addresses = optional(list(string), ["127.0.0.0/8"])
      }), {})
      www-ssl = optional(object({
        port              = optional(number, 443)
        protocol          = optional(string, "tcp")
        enabled           = optional(bool, true)
        allowed_addresses = optional(list(string), ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"])
        use_service_cert  = optional(bool)
        tls_version       = optional(string, "only-1.2")
      }), {})
      }), {
      telnet  = {}
      ssh     = {}
      www     = {}
      www-ssl = {}
    })
  })
  default = {
    configure = false
  }
  nullable = false
}

object defaults

What is happening here?

For every object / value we declare the value as optional but still specify a fallback or default. This results in the behavior that terraform will set the default value for us if the attribute is not specified explicitly.

To apply nested defaults easily, we set the default of every nested object to be {} and terraform will kick in and apply the defaults of this object now in turn for us.

Isn't that somewhat awesome? Easy, well defined - stuff that everyone loves :-)

IaCTerraformCloud

Daniel Nachtrub

Kind of likes computers. Linux foundation certified: LFCS / CKA / CKAD / CKS. Microsoft certified: Cybersecurity Architect Expert & Azure Solutions Architect Expert.