mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
plans/objchange: Providers must honor their unknown value refinements
If the original value was unknown but its range was refined then the provider must return a value that is within the refined range, because otherwise downstream planning decisions could be invalidated. This relies on cty's definition of whether a value is in a refined range, which has pretty good coverage for the "false" case and so should give a pretty good signal, but it'll probably improve over time and so providers must not rely on any loopholes in the current implementation and must keep their promises even if Terraform can't currently check them.
This commit is contained in:
parent
81c15f987e
commit
dfe5e1ddc4
@ -34,7 +34,7 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu
|
||||
var errs []error
|
||||
var atRoot string
|
||||
if len(path) == 0 {
|
||||
atRoot = "Root resource "
|
||||
atRoot = "Root object "
|
||||
}
|
||||
|
||||
if planned.IsNull() && !actual.IsNull() {
|
||||
@ -216,7 +216,12 @@ func assertValueCompatible(planned, actual cty.Value, path cty.Path) []error {
|
||||
|
||||
if !planned.IsKnown() {
|
||||
// We didn't know what were going to end up with during plan, so
|
||||
// anything goes during apply.
|
||||
// the final value needs only to match the type and refinements of
|
||||
// the unknown value placeholder.
|
||||
plannedRng := planned.Range()
|
||||
if ok := plannedRng.Includes(actual); ok.IsKnown() && ok.False() {
|
||||
errs = append(errs, path.NewErrorf("final value %#v does not conform to planning placeholder %#v", actual, planned))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
|
@ -118,6 +118,65 @@ func TestAssertObjectCompatible(t *testing.T) {
|
||||
`.name: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
|
||||
},
|
||||
},
|
||||
{
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.Zero,
|
||||
}),
|
||||
[]string{
|
||||
`.name: wrong final value type: string required`,
|
||||
},
|
||||
},
|
||||
{
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.UnknownVal(cty.String).RefineNotNull(),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.NullVal(cty.String),
|
||||
}),
|
||||
[]string{
|
||||
`.name: final value cty.NullVal(cty.String) does not conform to planning placeholder cty.UnknownVal(cty.String).RefineNotNull()`,
|
||||
},
|
||||
},
|
||||
{
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.UnknownVal(cty.String).Refine().
|
||||
StringPrefix("boop:").
|
||||
NewValue(),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("thingy"),
|
||||
}),
|
||||
[]string{
|
||||
`.name: final value cty.StringVal("thingy") does not conform to planning placeholder cty.UnknownVal(cty.String).Refine().StringPrefixFull("boop:").NewValue()`,
|
||||
},
|
||||
},
|
||||
{
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
|
Loading…
Reference in New Issue
Block a user