diff --git a/internal/terraform/upgrade_resource_state.go b/internal/terraform/upgrade_resource_state.go index 07d5ea3bb8..c133f483b2 100644 --- a/internal/terraform/upgrade_resource_state.go +++ b/internal/terraform/upgrade_resource_state.go @@ -4,6 +4,7 @@ package terraform import ( + "bytes" "encoding/json" "fmt" "log" @@ -136,8 +137,12 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int // stripRemovedStateAttributes deletes any attributes no longer present in the // schema, so that the json can be correctly decoded. func stripRemovedStateAttributes(state []byte, ty cty.Type) []byte { + // we must use json.Number to avoid changing the precision of cty.Number values + decoder := json.NewDecoder(bytes.NewReader(state)) + decoder.UseNumber() + jsonMap := map[string]interface{}{} - err := json.Unmarshal(state, &jsonMap) + err := decoder.Decode(&jsonMap) if err != nil { // we just log any errors here, and let the normal decode process catch // invalid JSON. diff --git a/internal/terraform/upgrade_resource_state_test.go b/internal/terraform/upgrade_resource_state_test.go index bde9cfe061..ce998cf65e 100644 --- a/internal/terraform/upgrade_resource_state_test.go +++ b/internal/terraform/upgrade_resource_state_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" ) func TestStripRemovedStateAttributes(t *testing.T) { @@ -46,6 +47,20 @@ func TestStripRemovedStateAttributes(t *testing.T) { }), true, }, + { + "has large number", + map[string]interface{}{ + "a": "ok", + "b": nil, + }, + map[string]interface{}{ + "a": "ok", + }, + cty.Object(map[string]cty.Type{ + "a": cty.String, + }), + true, + }, { "removed nested string", map[string]interface{}{ @@ -149,3 +164,47 @@ func TestStripRemovedStateAttributes(t *testing.T) { }) } } + +func TestStripRemovedStateAttributesDecoder(t *testing.T) { + cases := []struct { + name string + state string + expect cty.Value + }{ + { + "removed string", + `{"a": "ok","b": "gone"}`, + cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("ok"), + }), + }, + { + "removed null", + `{"a": "ok","b": "gone"}`, + cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("ok"), + }), + }, + { + "with large number", + `{"a": 123456789123456789.123456789,"b": "gone"}`, + cty.ObjectVal(map[string]cty.Value{ + "a": cty.MustParseNumberVal("123456789123456789.123456789"), + }), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + upgraded := stripRemovedStateAttributes([]byte(tc.state), tc.expect.Type()) + got, err := ctyjson.Unmarshal(upgraded, tc.expect.Type()) + if err != nil { + t.Fatal(err) + } + + if !tc.expect.RawEquals(got) { + t.Fatalf("expected: %#v\n got: %#v\n", tc.expect, got) + } + }) + } +}