Skip to content

Commit

Permalink
check for non-computed within optional+computed
Browse files Browse the repository at this point in the history
We can check if an object in state must have at least partially come
from configuration, by seeing if the prior value has any non-null
attributes which are not computed in the schema.

This is used when the configuration contains a null optional+computed
value, and we want to know if we should plan to send the null value or
the prior state.
  • Loading branch information
jbardin committed Jan 20, 2023
1 parent e16b848 commit 8e917e5
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 10 deletions.
48 changes: 48 additions & 0 deletions internal/plans/objchange/objchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ func proposedNewAttributes(attrs map[string]*configschema.Attribute, prior, conf
// configV will always be null in this case, by definition.
// priorV may also be null, but that's okay.
newV = priorV

// the exception to the above is that if the config is optional and
// the _prior_ value contains non-computed values, we can infer
// that the config must have been non-null previously.
if optionalValueNotComputable(attr, priorV) {
newV = configV
}

case attr.NestedType != nil:
// For non-computed NestedType attributes, we need to descend
// into the individual nested attributes to build the final
Expand Down Expand Up @@ -518,3 +526,43 @@ func setElementComputedAsNull(schema attrPath, elem cty.Value) cty.Value {

return elem
}

// optionalValueNotComputable is used to check if an object in state must
// have at least partially come from configuration. If the prior value has any
// non-null attributes which are not computed in the schema, then we know there
// was previously a configuration value which set those.
//
// This is used when the configuration contains a null optional+computed value,
// and we want to know if we should plan to send the null value or the prior
// state.
func optionalValueNotComputable(schema *configschema.Attribute, val cty.Value) bool {
if !schema.Optional {
return false
}

// We must have a NestedType for complex nested attributes in order
// to find nested computed values in the first place.
if schema.NestedType == nil {
return false
}

foundNonComputedAttr := false
cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) {
if v.IsNull() {
return true, nil
}

attr := schema.NestedType.AttributeByPath(path)
if attr == nil {
return true, nil
}

if !attr.Computed {
foundNonComputedAttr = true
return false, nil
}
return true, nil
})

return foundNonComputedAttr
}
20 changes: 10 additions & 10 deletions internal/plans/objchange/objchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,10 @@ func TestProposedNew(t *testing.T) {
})),
}),
cty.ObjectVal(map[string]cty.Value{
"bloop": cty.ObjectVal(map[string]cty.Value{
"blop": cty.StringVal("glub"),
"bleep": cty.NullVal(cty.String),
}),
"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
"blop": cty.String,
"bleep": cty.String,
})),
}),
},

Expand Down Expand Up @@ -2036,14 +2036,14 @@ func TestProposedNew(t *testing.T) {
)),
}),
cty.ObjectVal(map[string]cty.Value{
"list_obj": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"obj": cty.ObjectVal(map[string]cty.Value{
"optional": cty.StringVal("prior"),
"computed": cty.StringVal("prior computed"),
"list_obj": cty.NullVal(cty.List(
cty.Object(map[string]cty.Type{
"obj": cty.Object(map[string]cty.Type{
"optional": cty.String,
"computed": cty.String,
}),
}),
}),
)),
}),
},

Expand Down

0 comments on commit 8e917e5

Please sign in to comment.