diff --git a/internal/plans/objchange/plan_valid.go b/internal/plans/objchange/plan_valid.go index 1977ea7dd933..305bc10b18f9 100644 --- a/internal/plans/objchange/plan_valid.go +++ b/internal/plans/objchange/plan_valid.go @@ -101,6 +101,14 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat continue } + if configV.IsNull() { + // Configuration cannot decode a block into a null value, but + // we could be dealing with a null returned by a legacy + // provider and inserted via ignore_changes. Fix the value in + // place so the length can still be compared. + configV = cty.ListValEmpty(configV.Type().ElementType()) + } + plannedL := plannedV.LengthInt() configL := configV.LengthInt() if plannedL != configL { diff --git a/internal/plans/objchange/plan_valid_test.go b/internal/plans/objchange/plan_valid_test.go index 8ba1927eeca7..00a1602ac2bc 100644 --- a/internal/plans/objchange/plan_valid_test.go +++ b/internal/plans/objchange/plan_valid_test.go @@ -389,6 +389,41 @@ func TestAssertPlanValid(t *testing.T) { }, }, + // but don't panic on a null list just in case + "nested list, null in config": { + &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, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "b": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "c": cty.String, + })), + }), + cty.ObjectVal(map[string]cty.Value{ + "b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ + "c": cty.String, + }))), + }), + cty.ObjectVal(map[string]cty.Value{ + "b": cty.ListValEmpty(cty.Object(map[string]cty.Type{ + "c": cty.String, + })), + }), + nil, + }, + // blocks can be unknown when using dynamic "nested list, unknown nested dynamic": { &configschema.Block{ @@ -1671,7 +1706,7 @@ func TestAssertPlanValid(t *testing.T) { t.Logf( "\nprior: %sconfig: %splanned: %s", - dump.Value(test.Planned), + dump.Value(test.Prior), dump.Value(test.Config), dump.Value(test.Planned), )