Skip to content

Commit b7f8ef4

Browse files
committed
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.
1 parent 8617d0f commit b7f8ef4

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

internal/plans/objchange/plan_valid.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,20 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
6969
// Easy path: nothing has changed at all
7070
continue
7171
}
72+
73+
if !configV.IsKnown() {
74+
// An unknown config block represents a dynamic block where the
75+
// for_each value is unknown, and therefor cannot be altered by the
76+
// provider.
77+
errs = append(errs, path.NewErrorf("planned value %#v for unknown dynamic block", plannedV))
78+
continue
79+
}
80+
7281
if !plannedV.IsKnown() {
82+
// Only dynamic configuration can set blocks to unknown, so this is
83+
// not allowed from the provider. This means that either the config
84+
// and plan should match, or we have an error where the plan
85+
// changed the config value, both of which have been checked.
7386
errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
7487
continue
7588
}
@@ -94,6 +107,7 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
94107
errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
95108
continue
96109
}
110+
97111
for it := plannedV.ElementIterator(); it.Next(); {
98112
idx, plannedEV := it.Element()
99113
path := append(path, cty.IndexStep{Key: idx})

internal/plans/objchange/plan_valid_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,110 @@ func TestAssertPlanValid(t *testing.T) {
388388
`.b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null`,
389389
},
390390
},
391+
392+
// blocks can be unknown when using dynamic
393+
"nested list, unknown nested dynamic": {
394+
&configschema.Block{
395+
BlockTypes: map[string]*configschema.NestedBlock{
396+
"a": {
397+
Nesting: configschema.NestingList,
398+
Block: configschema.Block{
399+
BlockTypes: map[string]*configschema.NestedBlock{
400+
"b": {
401+
Nesting: configschema.NestingList,
402+
Block: configschema.Block{
403+
Attributes: map[string]*configschema.Attribute{
404+
"c": {
405+
Type: cty.String,
406+
Optional: true,
407+
},
408+
"computed": {
409+
Type: cty.String,
410+
Computed: true,
411+
},
412+
},
413+
},
414+
},
415+
},
416+
},
417+
},
418+
},
419+
},
420+
421+
cty.ObjectVal(map[string]cty.Value{
422+
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
423+
"computed": cty.NullVal(cty.String),
424+
"b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
425+
"c": cty.StringVal("x"),
426+
})}),
427+
})}),
428+
}),
429+
cty.ObjectVal(map[string]cty.Value{
430+
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
431+
"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
432+
"c": cty.String,
433+
"computed": cty.String,
434+
}))),
435+
})}),
436+
}),
437+
cty.ObjectVal(map[string]cty.Value{
438+
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
439+
"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
440+
"c": cty.String,
441+
"computed": cty.String,
442+
}))),
443+
})}),
444+
}),
445+
[]string{},
446+
},
447+
448+
"nested set, unknown dynamic cannot be planned": {
449+
&configschema.Block{
450+
Attributes: map[string]*configschema.Attribute{
451+
"computed": {
452+
Type: cty.String,
453+
Computed: true,
454+
},
455+
},
456+
BlockTypes: map[string]*configschema.NestedBlock{
457+
"b": {
458+
Nesting: configschema.NestingSet,
459+
Block: configschema.Block{
460+
Attributes: map[string]*configschema.Attribute{
461+
"c": {
462+
Type: cty.String,
463+
Optional: true,
464+
},
465+
},
466+
},
467+
},
468+
},
469+
},
470+
471+
cty.ObjectVal(map[string]cty.Value{
472+
"computed": cty.NullVal(cty.String),
473+
"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
474+
"c": cty.StringVal("x"),
475+
})}),
476+
}),
477+
cty.ObjectVal(map[string]cty.Value{
478+
"computed": cty.NullVal(cty.String),
479+
"b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
480+
"c": cty.String,
481+
}))),
482+
}),
483+
cty.ObjectVal(map[string]cty.Value{
484+
"computed": cty.StringVal("default"),
485+
"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
486+
"c": cty.StringVal("oops"),
487+
})}),
488+
}),
489+
490+
[]string{
491+
`.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`,
492+
},
493+
},
494+
391495
"nested set, null in plan": {
392496
&configschema.Block{
393497
BlockTypes: map[string]*configschema.NestedBlock{

0 commit comments

Comments
 (0)