mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #31210 from hashicorp/update-optional-type-attributes
Edit type constraints docs for style and flow
This commit is contained in:
commit
479c71f93d
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
variable "a" {
|
||||
type = object({
|
||||
# The optional attributes experiment isn't enabled, so this isn't allowed.
|
||||
a = optional(string)
|
||||
})
|
||||
}
|
@ -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)
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user