mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 08:51:02 -06:00
handle unexpected changes to unknown block
An unknown block represents a dynamic configuration block with an unknown for_each value. We were not catching the case where a provider modified this value unexpectedly, which would crash with block of type NestingList blocks where the config value has no length for comparison.
This commit is contained in:
parent
8617d0fed0
commit
b7f8ef4dc6
@ -69,7 +69,20 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
|
||||
// Easy path: nothing has changed at all
|
||||
continue
|
||||
}
|
||||
|
||||
if !configV.IsKnown() {
|
||||
// An unknown config block represents a dynamic block where the
|
||||
// for_each value is unknown, and therefor cannot be altered by the
|
||||
// provider.
|
||||
errs = append(errs, path.NewErrorf("planned value %#v for unknown dynamic block", plannedV))
|
||||
continue
|
||||
}
|
||||
|
||||
if !plannedV.IsKnown() {
|
||||
// Only dynamic configuration can set blocks to unknown, so this is
|
||||
// not allowed from the provider. This means that either the config
|
||||
// and plan should match, or we have an error where the plan
|
||||
// changed the config value, both of which have been checked.
|
||||
errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
|
||||
continue
|
||||
}
|
||||
@ -94,6 +107,7 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
|
||||
errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
|
||||
continue
|
||||
}
|
||||
|
||||
for it := plannedV.ElementIterator(); it.Next(); {
|
||||
idx, plannedEV := it.Element()
|
||||
path := append(path, cty.IndexStep{Key: idx})
|
||||
|
@ -388,6 +388,110 @@ func TestAssertPlanValid(t *testing.T) {
|
||||
`.b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null`,
|
||||
},
|
||||
},
|
||||
|
||||
// blocks can be unknown when using dynamic
|
||||
"nested list, unknown nested dynamic": {
|
||||
&configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"a": {
|
||||
Nesting: configschema.NestingList,
|
||||
Block: configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"b": {
|
||||
Nesting: configschema.NestingList,
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"c": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
"computed": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
"computed": cty.NullVal(cty.String),
|
||||
"b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
"c": cty.StringVal("x"),
|
||||
})}),
|
||||
})}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
|
||||
"c": cty.String,
|
||||
"computed": cty.String,
|
||||
}))),
|
||||
})}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
|
||||
"c": cty.String,
|
||||
"computed": cty.String,
|
||||
}))),
|
||||
})}),
|
||||
}),
|
||||
[]string{},
|
||||
},
|
||||
|
||||
"nested set, unknown dynamic cannot be planned": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"computed": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"b": {
|
||||
Nesting: configschema.NestingSet,
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"c": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"computed": cty.NullVal(cty.String),
|
||||
"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
"c": cty.StringVal("x"),
|
||||
})}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"computed": cty.NullVal(cty.String),
|
||||
"b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
|
||||
"c": cty.String,
|
||||
}))),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"computed": cty.StringVal("default"),
|
||||
"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||
"c": cty.StringVal("oops"),
|
||||
})}),
|
||||
}),
|
||||
|
||||
[]string{
|
||||
`.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`,
|
||||
},
|
||||
},
|
||||
|
||||
"nested set, null in plan": {
|
||||
&configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
|
Loading…
Reference in New Issue
Block a user