Merge pull request #31210 from hashicorp/update-optional-type-attributes

Edit type constraints docs for style and flow
This commit is contained in:
Alisdair McDiarmid 2022-06-17 15:47:57 -04:00 committed by GitHub
commit 479c71f93d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 23 additions and 122 deletions

View File

@ -6,7 +6,6 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/experiments"
"github.com/hashicorp/terraform/version"
"github.com/zclconf/go-cty/cty"
)
// When developing UI for experimental features, you can temporarily disable
@ -196,51 +195,5 @@ func checkModuleExperiments(m *Module) hcl.Diagnostics {
}
*/
if !m.ActiveExperiments.Has(experiments.ModuleVariableOptionalAttrs) {
for _, v := range m.Variables {
if typeConstraintHasOptionalAttrs(v.ConstraintType) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Optional object type attributes are experimental",
Detail: "This feature is currently an opt-in experiment, subject to change in future releases based on feedback.\n\nActivate the feature for this module by adding module_variable_optional_attrs to the list of active experiments.",
Subject: v.DeclRange.Ptr(),
})
}
}
}
return diags
}
func typeConstraintHasOptionalAttrs(ty cty.Type) bool {
if ty == cty.NilType {
// Weird, but we'll just ignore it to avoid crashing.
return false
}
switch {
case ty.IsPrimitiveType():
return false
case ty.IsCollectionType():
return typeConstraintHasOptionalAttrs(ty.ElementType())
case ty.IsObjectType():
if len(ty.OptionalAttributes()) != 0 {
return true
}
for _, aty := range ty.AttributeTypes() {
if typeConstraintHasOptionalAttrs(aty) {
return true
}
}
return false
case ty.IsTupleType():
for _, ety := range ty.TupleElementTypes() {
if typeConstraintHasOptionalAttrs(ety) {
return true
}
}
return false
default:
return false
}
}

View File

@ -1,6 +0,0 @@
variable "a" {
type = object({
# The optional attributes experiment isn't enabled, so this isn't allowed.
a = optional(string)
})
}

View File

@ -1,9 +1,3 @@
terraform {
experiments = [
module_variable_optional_attrs, # WARNING: Experimental feature "module_variable_optional_attrs" is active
]
}
variable "a" {
type = object({
foo = optional(string)

View File

@ -27,7 +27,7 @@ func init() {
registerConcludedExperiment(SuppressProviderSensitiveAttrs, "Provider-defined sensitive attributes are now redacted by default, without enabling an experiment.")
registerConcludedExperiment(ConfigDrivenMove, "Declarations of moved resource instances using \"moved\" blocks can now be used by default, without enabling an experiment.")
registerConcludedExperiment(PreconditionsPostconditions, "Condition blocks can now be used by default, without enabling an experiment.")
registerCurrentExperiment(ModuleVariableOptionalAttrs)
registerConcludedExperiment(ModuleVariableOptionalAttrs, "Optional object attributes in module variable type constraints can now be used by default, without enabling an experiment.")
}
// GetCurrent takes an experiment name and returns the experiment value
@ -92,6 +92,7 @@ var currentExperiments = make(Set)
// Members of this map are registered in the init function above.
var concludedExperiments = make(map[Experiment]string)
//lint:ignore U1000 No experiments are active
func registerCurrentExperiment(exp Experiment) {
currentExperiments.Add(exp)
}

View File

@ -11978,10 +11978,6 @@ resource "test_resource" "foo" {
func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
terraform {
experiments = [module_variable_optional_attrs]
}
variable "in" {
type = object({
required = string
@ -12054,10 +12050,6 @@ output "out" {
func TestContext2Apply_moduleVariableOptionalAttributesDefault(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
terraform {
experiments = [module_variable_optional_attrs]
}
variable "in" {
type = object({
required = string

View File

@ -260,11 +260,11 @@ value and thus perform no type conversion whatsoever.
## Optional Object Type Attributes
Terraform v1.3 adds support for marking particular attributes as optional in an
object type constraint.
-> **Note:** Optional type attributes are supported in Terraform v1.3 and later.
To mark an attribute as optional, use the additional `optional(...)` modifier
around its type declaration:
Terraform typically returns an error when it does not receive a value for specified object attributes. When you mark an attribute as optional, Terraform instead inserts a default value for the missing attribute. This allows the receiving module to describe an appropriate fallback behavior.
To mark attributes as optional, use the `optional` modifier in the object type constraint. The following example creates optional attribute `b` and optional attribute with a default value `c`.
```hcl
variable "with_optional_attribute" {
@ -276,32 +276,18 @@ variable "with_optional_attribute" {
}
```
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.
- **Type:** (Required) The first argument
specifies the type of the attribute.
- **Default:** (Optional) The second argument defines the default value that Terraform should use if the attribute is not present. This must be compatible with the attribute type. If not specified, Terraform uses a `null` value of the appropriate type as the default.
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.
Terraform applies object attribute defaults top-down in nested variable types. This means that Terraform applies the default value you specify in the `optional` modifier first and then later applies any nested default values to that attribute.
### 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.
The following example defines a variable for storage buckets that host a website. This variable type uses several optional attributes, including `website`, which is itself an optional `object` type that has optional attributes and defaults.
```hcl
terraform {
# Optional attributes are currently experimental.
experiments = [module_variable_optional_attrs]
}
variable "buckets" {
type = list(object({
name = string
@ -315,8 +301,13 @@ variable "buckets" {
}
```
To test this out, we can create a file `terraform.tfvars` to provide an example
value for `var.buckets`:
The following example `terraform.tfvars` file specifies three bucket configurations for `var.buckets`.
- `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
The `production` bucket does not specify the index and error documents, and the `archived` bucket omits the website configuration entirely. Terraform will use the default values specified in the `bucket` type constraint.
```hcl
buckets = [
@ -347,15 +338,11 @@ buckets = [
]
```
The intent here is to specify three bucket configurations:
This configuration produces the following variable values.
- `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:
- For the `production` and `docs` buckets, Terraform sets `enabled` to `true`. Terraform also supplies default values for `website`, and then the values specified in `docs` override those defaults.
- For the `archived` and `docs` buckets, Terraform sets `routing_rules` to a `null` value. When Terraform does not receive optional attributes and there are no specified defaults, Terraform populates those attributes with a `null` value.
- For the `archived` bucket, Terraform populates the `website` attribute with the default values specified in the `buckets` type constraint.
```hcl
tolist([
@ -396,23 +383,3 @@ tolist([
},
])
```
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
`experiments` argument set as follows:
```hcl
terraform {
experiments = [module_variable_optional_attrs]
}
```
Until the experiment is concluded, the behavior of this feature may see
breaking changes even in minor releases. We recommend using this feature
only in prerelease versions of modules as long as it remains experimental.