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/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/internal/experiments"
|
"github.com/hashicorp/terraform/internal/experiments"
|
||||||
"github.com/hashicorp/terraform/version"
|
"github.com/hashicorp/terraform/version"
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// When developing UI for experimental features, you can temporarily disable
|
// 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
|
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" {
|
variable "a" {
|
||||||
type = object({
|
type = object({
|
||||||
foo = optional(string)
|
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(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(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.")
|
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
|
// 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.
|
// Members of this map are registered in the init function above.
|
||||||
var concludedExperiments = make(map[Experiment]string)
|
var concludedExperiments = make(map[Experiment]string)
|
||||||
|
|
||||||
|
//lint:ignore U1000 No experiments are active
|
||||||
func registerCurrentExperiment(exp Experiment) {
|
func registerCurrentExperiment(exp Experiment) {
|
||||||
currentExperiments.Add(exp)
|
currentExperiments.Add(exp)
|
||||||
}
|
}
|
||||||
|
@ -11978,10 +11978,6 @@ resource "test_resource" "foo" {
|
|||||||
func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) {
|
func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) {
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
terraform {
|
|
||||||
experiments = [module_variable_optional_attrs]
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "in" {
|
variable "in" {
|
||||||
type = object({
|
type = object({
|
||||||
required = string
|
required = string
|
||||||
@ -12054,10 +12050,6 @@ output "out" {
|
|||||||
func TestContext2Apply_moduleVariableOptionalAttributesDefault(t *testing.T) {
|
func TestContext2Apply_moduleVariableOptionalAttributesDefault(t *testing.T) {
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
terraform {
|
|
||||||
experiments = [module_variable_optional_attrs]
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "in" {
|
variable "in" {
|
||||||
type = object({
|
type = object({
|
||||||
required = string
|
required = string
|
||||||
|
@ -260,11 +260,11 @@ value and thus perform no type conversion whatsoever.
|
|||||||
|
|
||||||
## Optional Object Type Attributes
|
## Optional Object Type Attributes
|
||||||
|
|
||||||
Terraform v1.3 adds support for marking particular attributes as optional in an
|
-> **Note:** Optional type attributes are supported in Terraform v1.3 and later.
|
||||||
object type constraint.
|
|
||||||
|
|
||||||
To mark an attribute as optional, use the additional `optional(...)` modifier
|
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.
|
||||||
around its type declaration:
|
|
||||||
|
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
|
```hcl
|
||||||
variable "with_optional_attribute" {
|
variable "with_optional_attribute" {
|
||||||
@ -276,32 +276,18 @@ variable "with_optional_attribute" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When evaluating variable values, Terraform will return an error if an object
|
The `optional` modifier takes one or two arguments.
|
||||||
attribute specified in the variable type is not present in the given value.
|
- **Type:** (Required) The first argument
|
||||||
Marking an attribute as optional changes the behavior in that situation:
|
specifies the type of the attribute.
|
||||||
Terraform will instead insert a default value for the missing 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.
|
||||||
allowing the receiving module to describe an appropriate fallback behavior.
|
|
||||||
|
|
||||||
The `optional` modifier takes one or two arguments. The first argument
|
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.
|
||||||
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
|
### 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
|
```hcl
|
||||||
terraform {
|
|
||||||
# Optional attributes are currently experimental.
|
|
||||||
experiments = [module_variable_optional_attrs]
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "buckets" {
|
variable "buckets" {
|
||||||
type = list(object({
|
type = list(object({
|
||||||
name = string
|
name = string
|
||||||
@ -315,8 +301,13 @@ variable "buckets" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To test this out, we can create a file `terraform.tfvars` to provide an example
|
The following example `terraform.tfvars` file specifies three bucket configurations for `var.buckets`.
|
||||||
value 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
|
```hcl
|
||||||
buckets = [
|
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;
|
- 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.
|
||||||
- `archived` uses default configuration but is disabled;
|
- 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.
|
||||||
- `docs` overrides the index and error documents to use text files.
|
- For the `archived` bucket, Terraform populates the `website` attribute with the default values specified in the `buckets` type constraint.
|
||||||
|
|
||||||
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
|
```hcl
|
||||||
tolist([
|
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