use json.Number for decoding state

During the state upgrade process we may have attributes which are no
longer part of the schema type. Because cty requires the data to
strictly match the schema we can't ignore these extra attributes and
must actively filter them. In order to do that we use encoding/json to
decode the state in a generic manner, but we need to account for large
cty.Number values which may exceed float64 precision.
pull/35682/head
James Bardin 2 years ago
parent b6ac98122b
commit 0b8f2d7321

@ -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.

@ -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)
}
})
}
}

Loading…
Cancel
Save