From e2a304202531a514e48c46348172bdbbebdec703 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Mon, 25 Apr 2022 14:10:39 -0400 Subject: [PATCH] website: Update documentation for optional attrs Extend the documentation on type constraints to include the new default functionality, including a detailed example of a nested structure with multiple levels of defaults. --- .../language/expressions/type-constraints.mdx | 142 ++++++++++++++++-- 1 file changed, 132 insertions(+), 10 deletions(-) diff --git a/website/docs/language/expressions/type-constraints.mdx b/website/docs/language/expressions/type-constraints.mdx index 9ff803b81bb3..5925b88472de 100644 --- a/website/docs/language/expressions/type-constraints.mdx +++ b/website/docs/language/expressions/type-constraints.mdx @@ -258,10 +258,10 @@ variable "no_type_constraint" { In this case, Terraform will replace `any` with the exact type of the given value and thus perform no type conversion whatsoever. -## Experimental: Optional Object Type Attributes +## Optional Object Type Attributes -From Terraform v0.14 there is _experimental_ support for marking particular -attributes as optional in an object type constraint. +Terraform v1.3 adds support for marking particular attributes as optional in an +object type constraint. To mark an attribute as optional, use the additional `optional(...)` modifier around its type declaration: @@ -269,17 +269,139 @@ around its type declaration: ```hcl variable "with_optional_attribute" { type = object({ - a = string # a required attribute - b = optional(string) # an optional attribute + a = string # a required attribute + b = optional(string) # an optional attribute + c = optional(number, 127) # an optional attribute with default value }) } ``` -By default, for required attributes, Terraform will return an error if the -source value has no matching attribute. Marking an attribute as optional -changes the behavior in that situation: Terraform will instead just silently -insert `null` as the value of the attribute, allowing the receiving module -to describe an appropriate fallback behavior. +When evaluating variable values, Terraform will return an error if an object +attribute specified in the variable type is not present in the given value. +Marking an attribute as optional changes the behavior in that situation: +Terraform will instead insert a default value for the missing attribute, +allowing the receiving module to describe an appropriate fallback behavior. + +The `optional` modifier takes one or two arguments. The first argument +specifies the type of the attribute, and (if given) the second attribute +defines the default value to use if the attribute is not present. The default +must be compatible with the attribute type. If no default is specified, a +`null` value of the appropriate type will be used as the default. + +During evaluation, object attribute defaults are applied top-down in nested +variable types. This means that a given attribute's default value will also +have any nested default values applied to it later. + +### Example: Nested Structures with Optional Attributes and Defaults + +The following configuration defines a variable which describes a number of storage buckets, each of which is used to host a website. This variable type uses several optional attributes, one of which is itself an `object` type with optional attributes and defaults. + +```hcl +terraform { + # Optional attributes are currently experimental. + experiments = [module_variable_optional_attrs] +} + +variable "buckets" { + type = list(object({ + name = string + enabled = optional(bool, true) + website = optional(object({ + index_document = optional(string, "index.html") + error_document = optional(string, "error.html") + routing_rules = optional(string) + }), {}) + })) +} +``` + +To test this out, we can create a file `terraform.tfvars` to provide an example +value for `var.buckets`: + +```hcl +buckets = [ + { + name = "production" + website = { + routing_rules = <<-EOT + [ + { + "Condition" = { "KeyPrefixEquals": "img/" }, + "Redirect" = { "ReplaceKeyPrefixWith": "images/" } + } + ] + EOT + } + }, + { + name = "archived" + enabled = false + }, + { + name = "docs" + website = { + index_document = "index.txt" + error_document = "error.txt" + } + }, +] +``` + +The intent here is to specify three bucket configurations: + +- `production` sets the routing rules to add a redirect; +- `archived` uses default configuration but is disabled; +- `docs` overrides the index and error documents to use text files. + +Note that `production` does not specify the index and error documents, and `archived` omits the website configuration altogether. Because our type specifies a default value for the `website` attribute as an empty object `{}`, Terraform fills in the defaults specified in the nested type. + +The resulting variable value is: + +```hcl +tolist([ + { + "enabled" = true + "name" = "production" + "website" = { + "error_document" = "error.html" + "index_document" = "index.html" + "routing_rules" = <<-EOT + [ + { + "Condition" = { "KeyPrefixEquals": "img/" }, + "Redirect" = { "ReplaceKeyPrefixWith": "images/" } + } + ] + + EOT + } + }, + { + "enabled" = false + "name" = "archived" + "website" = { + "error_document" = "error.html" + "index_document" = "index.html" + "routing_rules" = tostring(null) + } + }, + { + "enabled" = true + "name" = "docs" + "website" = { + "error_document" = "error.txt" + "index_document" = "index.txt" + "routing_rules" = tostring(null) + } + }, +]) +``` + +Here we can see that for `production` and `docs`, the `enabled` attribute has been filled in as `true`. The default values for the `website` attribute have also been filled in, with the values specified by `docs` overriding the defaults. For `archived`, the entire default `website` value is populated. + +One important point is that the `website` attribute for the `archived` and `docs` buckets contains a `null` value for `routing_rules`. When declaring a type constraint with an optional object attributes without a default, a value which omits that attribute will be populated with a `null` value, rather than continuing to omit the attribute in the final result. + +### Experimental Status Because this feature is currently experimental, it requires an explicit opt-in on a per-module basis. To use it, write a `terraform` block with the