diff --git a/helper/plugin/grpc_provider.go b/helper/plugin/grpc_provider.go index b268795d8..9705e269c 100644 --- a/helper/plugin/grpc_provider.go +++ b/helper/plugin/grpc_provider.go @@ -1184,11 +1184,8 @@ func normalizeNullValues(dst, src cty.Value, preferDst bool) cty.Value { dstMap = map[string]cty.Value{} } - ei := src.ElementIterator() - for ei.Next() { - k, v := ei.Element() - key := k.AsString() - + srcMap := src.AsValueMap() + for key, v := range srcMap { dstVal := dstMap[key] if dstVal == cty.NilVal { if preferDst && ty.IsMapType() { @@ -1211,6 +1208,24 @@ func normalizeNullValues(dst, src cty.Value, preferDst bool) cty.Value { } if ty.IsMapType() { + // helper/schema will populate an optional+computed map with + // unknowns which we have to fixup here. + // It would be preferable to simply prevent any known value from + // becoming unknown, but concessions have to be made to retain the + // broken legacy behavior when possible. + for k, srcVal := range srcMap { + if !srcVal.IsNull() && srcVal.IsKnown() { + dstVal, ok := dstMap[k] + if !ok { + continue + } + + if !dstVal.IsNull() && !dstVal.IsKnown() { + dstMap[k] = srcVal + } + } + } + return cty.MapVal(dstMap) } diff --git a/helper/plugin/grpc_provider_test.go b/helper/plugin/grpc_provider_test.go index a31fe57c5..fb33e4b26 100644 --- a/helper/plugin/grpc_provider_test.go +++ b/helper/plugin/grpc_provider_test.go @@ -909,7 +909,7 @@ func TestNormalizeNullValues(t *testing.T) { }), }), }, - // the empty list should be transferred, but the new unknown show not be overridden + // the empty list should be transferred, but the new unknown should not be overridden { Src: cty.ObjectVal(map[string]cty.Value{ "network_interface": cty.ListVal([]cty.Value{ @@ -942,6 +942,28 @@ func TestNormalizeNullValues(t *testing.T) { }), Plan: true, }, + { + // fix unknowns added to a map + Src: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.StringVal(""), + }), + }), + Dst: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.UnknownVal(cty.String), + }), + }), + Expect: cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("a"), + "b": cty.StringVal(""), + }), + }), + Plan: true, + }, } { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := normalizeNullValues(tc.Dst, tc.Src, tc.Plan)