Skip to content

Commit

Permalink
website: Update documentation for optional attrs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alisdair committed May 31, 2022
1 parent f378c7f commit f92fcc4
Showing 1 changed file with 132 additions and 10 deletions.
142 changes: 132 additions & 10 deletions website/docs/language/expressions/type-constraints.mdx
Expand Up @@ -258,28 +258,150 @@ 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:

```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
Expand Down

0 comments on commit f92fcc4

Please sign in to comment.