mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-13 09:32:24 -06:00
Merge pull request #31634 from hashicorp/alisdair/optional-object-attribute-explicit-null
typeexpr: Replace null attr values with defaults
This commit is contained in:
commit
4960e6aeba
@ -91,7 +91,7 @@ func (t *defaultsTransformer) Enter(p cty.Path, v cty.Value) (cty.Value, error)
|
||||
// Apply defaults where attributes are missing, constructing a new
|
||||
// value with the same marks.
|
||||
for attr, defaultValue := range defaults {
|
||||
if _, ok := attrs[attr]; !ok {
|
||||
if attrValue, ok := attrs[attr]; !ok || attrValue.IsNull() {
|
||||
attrs[attr] = defaultValue
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,23 @@ func TestDefaults_Apply(t *testing.T) {
|
||||
"b": cty.StringVal("false"),
|
||||
}),
|
||||
},
|
||||
// Defaults will replace explicit nulls.
|
||||
"object with explicit null for attribute with default": {
|
||||
defaults: &Defaults{
|
||||
Type: simpleObject,
|
||||
DefaultValues: map[string]cty.Value{
|
||||
"b": cty.True,
|
||||
},
|
||||
},
|
||||
value: cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.StringVal("foo"),
|
||||
"b": cty.NullVal(cty.String),
|
||||
}),
|
||||
want: cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.StringVal("foo"),
|
||||
"b": cty.True,
|
||||
}),
|
||||
},
|
||||
// Defaults can be specified at any level of depth and will be applied
|
||||
// so long as there is a parent value to populate.
|
||||
"nested object with defaults applied": {
|
||||
@ -371,7 +388,7 @@ func TestDefaults_Apply(t *testing.T) {
|
||||
// the child type.
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.StringVal("fallback"),
|
||||
"b": cty.NullVal(cty.Bool),
|
||||
"b": cty.False,
|
||||
}),
|
||||
},
|
||||
Children: map[string]*Defaults{
|
||||
@ -410,7 +427,65 @@ func TestDefaults_Apply(t *testing.T) {
|
||||
// default value for "c" includes a non-default value
|
||||
// already.
|
||||
"a": cty.StringVal("fallback"),
|
||||
"b": cty.NullVal(cty.Bool),
|
||||
"b": cty.False,
|
||||
}),
|
||||
"d": cty.NumberIntVal(7),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
"set of nested objects, nulls in default sub-object overridden": {
|
||||
defaults: &Defaults{
|
||||
Type: cty.Set(nestedObject),
|
||||
Children: map[string]*Defaults{
|
||||
"": {
|
||||
Type: nestedObject,
|
||||
DefaultValues: map[string]cty.Value{
|
||||
// The default value for "c" is used to prepopulate
|
||||
// the nested object's value if not specified, but
|
||||
// the null default for its "b" attribute will be
|
||||
// overridden by the default specified in the child
|
||||
// type.
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.StringVal("fallback"),
|
||||
"b": cty.NullVal(cty.Bool),
|
||||
}),
|
||||
},
|
||||
Children: map[string]*Defaults{
|
||||
"c": {
|
||||
Type: simpleObject,
|
||||
DefaultValues: map[string]cty.Value{
|
||||
"b": cty.True,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
value: cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.StringVal("foo"),
|
||||
}),
|
||||
"d": cty.NumberIntVal(5),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"d": cty.NumberIntVal(7),
|
||||
}),
|
||||
}),
|
||||
want: cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.StringVal("foo"),
|
||||
"b": cty.True,
|
||||
}),
|
||||
"d": cty.NumberIntVal(5),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"c": cty.ObjectVal(map[string]cty.Value{
|
||||
// The default value for "b" overrides the explicit
|
||||
// null in the default value for "c".
|
||||
"a": cty.StringVal("fallback"),
|
||||
"b": cty.True,
|
||||
}),
|
||||
"d": cty.NumberIntVal(7),
|
||||
}),
|
||||
|
@ -260,7 +260,7 @@ value and thus perform no type conversion whatsoever.
|
||||
|
||||
## Optional Object Type Attributes
|
||||
|
||||
-> **Note:** Optional type attributes are supported in Terraform v1.3 and later.
|
||||
-> **Note:** Optional object type attributes are supported only in Terraform v1.3 and later.
|
||||
|
||||
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.
|
||||
|
||||
@ -281,6 +281,8 @@ The `optional` modifier takes one or two arguments.
|
||||
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.
|
||||
|
||||
An optional attribute with a non-`null` default value is guaranteed to never have the value `null` within the receiving module. Terraform will substitute the default value both when a caller omits the attribute altogether and when a caller explicitly sets it to `null`, thereby avoiding the need for additional checks to handle a possible null value.
|
||||
|
||||
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
|
||||
@ -383,3 +385,33 @@ tolist([
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Example: Conditionally setting an optional attribute
|
||||
|
||||
Sometimes the decision about whether or not to set a value for an optional argument needs to be made dynamically based on some other data. In that case, the calling `module` block can use a conditional expression with `null` as one of its result arms to represent dynamically leaving the argument unset.
|
||||
|
||||
With the `variable "buckets"` declaration shown in the previous section, the following example conditionally overrides the `index_document` and `error_document` settings in the `website` object based on a new variable `var.legacy_filenames`:
|
||||
|
||||
```hcl
|
||||
variable "legacy_filenames" {
|
||||
type = bool
|
||||
default = false
|
||||
nullable = false
|
||||
}
|
||||
|
||||
module "buckets" {
|
||||
source = "./modules/buckets"
|
||||
|
||||
buckets = [
|
||||
{
|
||||
name = "maybe_legacy"
|
||||
website = {
|
||||
error_document = var.legacy_filenames ? "ERROR.HTM" : null
|
||||
index_document = var.legacy_filenames ? "INDEX.HTM" : null
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When `var.legacy_filenames` is set to `true`, the call will override the document filenames. When it is `false`, the call will leave the two filenames unspecified, thereby allowing the module to use its specified default values.
|
||||
|
Loading…
Reference in New Issue
Block a user