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
|
// Easy path: nothing has changed at all
|
||||||
continue
|
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() {
|
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"))
|
errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
|
||||||
continue
|
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))
|
errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for it := plannedV.ElementIterator(); it.Next(); {
|
for it := plannedV.ElementIterator(); it.Next(); {
|
||||||
idx, plannedEV := it.Element()
|
idx, plannedEV := it.Element()
|
||||||
path := append(path, cty.IndexStep{Key: idx})
|
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`,
|
`.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": {
|
"nested set, null in plan": {
|
||||||
&configschema.Block{
|
&configschema.Block{
|
||||||
BlockTypes: map[string]*configschema.NestedBlock{
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
|
|
Loading…
Reference in New Issue