From ea193d5ce699ec707f4b96b44ccccb907f8aff11 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 21 Dec 2022 14:53:14 -0500 Subject: [PATCH] don't panic with a null list block value in config Using ignore_changes with a list block, where the provider returned an invalid null value for that block, can result in a panic when validating the plan. Future releases may prevent providers from storing a null block in state, however we can avoid the panic for now. Only the NestingList case needs to be handled, because legacy providers only have list and set blocks, and the set case does not use the config value. --- internal/plans/objchange/plan_valid.go | 8 +++++ internal/plans/objchange/plan_valid_test.go | 37 ++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) 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), )