🐶
Terraform

Terraform Optional Nested Objects: A How-To Guide

By Filip on 11/12/2024

Learn how to define and use optional nested object variables in Terraform to create flexible and reusable infrastructure configurations.

Terraform Optional Nested Objects: A How-To Guide

Table of Contents

Introduction

In Terraform, defining optional nested objects with default values ensures flexibility and avoids configuration errors. This approach allows you to specify default values for nested attributes, which are applied if the user doesn't provide them. This guide outlines the steps to define optional nested objects with defaults in Terraform, enabling you to create adaptable and robust infrastructure configurations.

Step-by-Step Guide

To define optional nested objects with defaults in Terraform, follow these steps:

  1. Define the variable with its type as an object:

    variable "example" {
      type = object({
        nested_field = object({
          optional_value = string
        })
      })
    }
  2. Set default values, including for nested attributes:

    variable "example" {
      type = object({
        nested_field = object({
          optional_value = string
        })
      })
      default = {
        nested_field = {
          optional_value = "default value"
        }
      }
    }
  3. Use the optional() function for conditional defaults within nested objects:

    variable "example" {
      type = object({
        nested_field = optional(object({
          optional_value = string
        }), {
          optional_value = "default value"
        })
      })
    }
  4. Access nested values using dot notation:

    resource "example_resource" "example" {
      # ... other configurations ...
      optional_value = var.example.nested_field.optional_value
    }

Important Considerations:

  • Terraform applies defaults top-down, so nested defaults are applied after parent defaults.
  • Explicitly setting a value to null overrides any default values.
  • When using optional(), the entire nested object will be set to the default if not provided.
  • For complex scenarios, consider using modules to organize and reuse variable definitions.

Code Example

This code defines a Terraform variable "example" with an optional nested object "nested_field". The nested object has default values for its fields "optional_value" and "another_optional_value". The main.tf file uses a null_resource to demonstrate accessing and printing these values. Users can provide the "example" variable with custom values, partially or entirely, or rely on the defined defaults. This allows for flexible configuration with optional nested objects and default values in Terraform.

This example demonstrates how to define and use optional nested objects with default values in Terraform.

variables.tf

variable "example" {
  type = object({
    nested_field = optional(object({
      optional_value = string
      another_optional_value = number
    }), {
      optional_value = "default string value"
      another_optional_value = 123
    })
  })
}

main.tf

resource "null_resource" "example" {
  provisioner "local-exec" {
    command = <<EOT
    echo "Optional Value: ${var.example.nested_field.optional_value}"
    echo "Another Optional Value: ${var.example.nested_field.another_optional_value}"
    EOT
  }
}

Explanation:

  1. variables.tf:

    • We define a variable named example with a nested object structure.
    • The nested_field is declared as optional using the optional() function.
    • Inside optional(), we define the object structure for nested_field and provide default values for optional_value and another_optional_value.
  2. main.tf:

    • We use a null_resource with a local-exec provisioner for demonstration purposes.
    • The provisioner prints the values of optional_value and another_optional_value from the example variable.

Usage:

  • Without providing any input: The default values defined in variables.tf will be used.

  • Providing partial input: You can provide only specific nested values, and the remaining ones will use their defaults. For example:

    example = {
      nested_field = {
        optional_value = "custom value"
      }
    }
  • Overriding default values: You can explicitly set a value to null to override the default.

Output:

When running Terraform, the output will display the values used for optional_value and another_optional_value, either the defaults or any custom values provided.

This example demonstrates how to effectively manage optional nested objects with default values in Terraform, allowing for flexible and reusable configurations.

Additional Notes

Best Practices:

  • Clarity and Readability: While nesting is powerful, strive for clarity. For deeply nested structures, consider breaking them down into smaller, reusable modules.
  • Documentation: Clearly document your variable definitions, especially the purpose of nested objects and their default values. This helps users understand the expected structure and make informed decisions.
  • Testing: Test your configurations thoroughly, including scenarios where nested objects are provided with default values, partially provided, and completely overridden.

Common Pitfalls:

  • Overusing optional(): While convenient, overusing optional() can make it harder to track which values are truly optional and which are expected. Use it judiciously.
  • Mutable Defaults: Avoid using mutable data structures (like lists or maps) as default values for optional objects. This can lead to unexpected behavior when the default value is modified elsewhere in the configuration.

Alternatives to Nested Objects:

  • Maps with Dynamic Keys: For scenarios where the structure of the nested object is not fixed, consider using maps with dynamically generated keys.
  • Modules with Input Variables: For complex configurations, encapsulate logic and data structures within modules and expose relevant options as input variables.

Additional Tips:

  • Validation: Use Terraform's validation features (like custom validation rules) to enforce constraints on the values provided for optional nested objects.
  • Version Control: Like all infrastructure code, version control your Terraform configurations to track changes and collaborate effectively.

By understanding these concepts and following best practices, you can leverage the power of optional nested objects with defaults to create flexible, maintainable, and robust Terraform configurations.

Summary

This table summarizes how to define and use optional nested objects with default values in Terraform:

Step Description Code Example
1. Define Variable Type Define the variable and its type as an object, including nested objects. ```terraform
variable "example" {
 type = object({
   nested_field = object({
     optional_value = string
   })
 })

}

| 2. Set Default Values | Set default values for the variable, including nested attributes. | ```terraform
variable "example" {
  // ... type definition ...
  default = {
    nested_field = {
      optional_value = "default value"
    }
  }
}
``` |
| 3. Use `optional()` Function | Use the `optional()` function for conditional defaults within nested objects. This sets the entire nested object to the default if not provided. | ```terraform
variable "example" {
  type = object({
    nested_field = optional(object({
      optional_value = string
    }), {
      optional_value = "default value"
    })
  })
}
``` |
| 4. Access Nested Values | Access nested values within resources using dot notation. | ```terraform
resource "example_resource" "example" {
  // ... other configurations ...
  optional_value = var.example.nested_field.optional_value
}
``` |

**Key Considerations:**

* **Default Application:** Terraform applies defaults top-down (parent defaults first).
* **Null Values:** Explicitly setting a value to `null` overrides any default values.
* **Complex Scenarios:** Use modules to organize and reuse variable definitions for complex scenarios. 

## Conclusion

By combining these techniques, you can create adaptable and well-structured Terraform configurations. Remember to prioritize clarity, document your code thoroughly, and test your configurations to ensure they behave as expected. This approach empowers you to leverage the flexibility of optional nested objects while maintaining predictable and manageable infrastructure deployments. 

## References

* ![Default (optional) values in nested null object cause it to be non-null ...](https://opengraph.githubassets.com/a7f1a2746b496de3e4fffd16292a9d0e114743f7f653d6661d3833ae6fd52ec6/hashicorp/terraform/issues/32160) [Default (optional) values in nested null object cause it to be non-null ...](https://github.com/hashicorp/terraform/issues/32160) | Terraform Version Terraform v1.3.4 Terraform Configuration Files module "defaulting_module" { source = "./modules/default-module" request = {} } output "output" { value = module.defaulting_module }...
* ![Type Constraints - Configuration Language | Terraform | HashiCorp ...](https://developer.hashicorp.com/og-image/terraform.jpg) [Type Constraints - Configuration Language | Terraform | HashiCorp ...](https://developer.hashicorp.com/terraform/language/expressions/type-constraints) | Terraform applies object attribute defaults top-down in nested variable types. ... optional modifier first and then later applies any nested default values ...
* ![[Request] module_variable_optional_attrs: Optional nested maps ...](https://opengraph.githubassets.com/b9787c01700cefb15938a2b4efd10f36c65b37f9db1db1b66b6e9702ebc09925/hashicorp/terraform/issues/27613) [[Request] module_variable_optional_attrs: Optional nested maps ...](https://github.com/hashicorp/terraform/issues/27613) | Current Terraform Version 0.14.6 Use-cases I have a complex module. I would like to allow my end-users to be able to group like-variables together — specifically as a nested object/map. I'm looking...
* ![Required attributes in nested object - Terraform - HashiCorp Discuss](https://global.discourse-cdn.com/hashicorp/original/1X/154331ed085112b536f7a0c4c5355c17a57e53c1.png) [Required attributes in nested object - Terraform - HashiCorp Discuss](https://discuss.hashicorp.com/t/required-attributes-in-nested-object/23854) | Hi,  I’m currently using the Terraform 0.13.5 and fairly new to the product.  // variables.tf in a module variable "foo" = {   description = "Demo"   type = object({      foo1 = string      foo2 = object({          foo2_1 = string                                 // Optional          foo2_2 = bool                                   // Optional      })      foo3 = object({          foo3_1 = number                             // Required value          foo3_2 = string                                ...
* ![Terraform “dynamic” inside “dynamic” blocks — an implementation ...](https://miro.medium.com/v2/resize:fit:1200/1*sCQP6zeuPgyi_Kke5rs0Vw.png) [Terraform “dynamic” inside “dynamic” blocks — an implementation ...](https://medium.com/@adrianarba/terraform-dynamic-inside-dynamic-blocks-an-implementation-example-c129f62135bb) | Sometimes, to tackle seemingly easy tasks, one needs to venture into uncharted territory to find a solution.
* ![Request for Feedback: Optional object type attributes with defaults in ...](https://global.discourse-cdn.com/hashicorp/original/1X/154331ed085112b536f7a0c4c5355c17a57e53c1.png) [Request for Feedback: Optional object type attributes with defaults in ...](https://discuss.hashicorp.com/t/request-for-feedback-optional-object-type-attributes-with-defaults-in-v1-3-alpha/40550?page=3) | An important distinction to be made here is that an explicitly set null value can only be determined at the syntax level, not within the language in general. This means when assigning nested objects to types with defaults, nested unset attributes will inherently have a null value and therefore we need to decide whether those will get a default or not. The choice is somewhat arbitrary, but a choice needs to be made nonetheless, and it’s consistent with the definition of the Terraform language to ...
* ![Terraform — Optional Values. A quick article discussing the options ...](https://miro.medium.com/v2/resize:fit:678/0*bcpwamyNyz160CR3.png) [Terraform — Optional Values. A quick article discussing the options ...](https://danielrandell93.medium.com/terraform-optional-values-73407f1d5ce5) | A quick article discussing the options for having optional values in your variables in Terraform.
* ![Nested optional() vars question - Terraform - HashiCorp Discuss](https://global.discourse-cdn.com/hashicorp/original/1X/154331ed085112b536f7a0c4c5355c17a57e53c1.png) [Nested optional() vars question - Terraform - HashiCorp Discuss](https://discuss.hashicorp.com/t/nested-optional-vars-question/51877) | Greetings,  I have this  variable "druid_cluster" {   description = "Druid cluster"   type        = map(map(object({     instance_type = string     private_ip = string     root_block_device = optional(list(map(object({       encrypted = bool       volume_type = string       volume_size = number       tags  = list(string)     }))), [{       encrypted   = true       volume_type = "gp2"       volume_size = 512       tags = { some = "tag"}     }])   })))   default = {} }  I’m trying to get it work. ...
* ![Best practices for setting default values for objects : r/Terraform](https://share.redd.it/preview/post/vkkitq) [Best practices for setting default values for objects : r/Terraform](https://www.reddit.com/r/Terraform/comments/vkkitq/best_practices_for_setting_default_values_for/) | Posted by u/greenlakejohnny - 15 votes and 26 comments

Were You Able to Follow the Instructions?

😍Love it!
😊Yes
😐Meh-gical
😞No
🤮Clickbait