From eb88c4bce4fc5e86f62fc416d28eda5be700ed28 Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Tue, 4 Apr 2017 12:49:40 +0530 Subject: [PATCH] Preserve int data when unmarshalling for TPR The Go json package converts all numbers to float64. This exposes many of the int64 fields to corruption when marshalled back to json. The json package provided by kubernetes also provides a way to defer conversion of numbers (https://golang.org/pkg/encoding/json/#Decoder.UseNumber) and does the conversions to int or float. This is also implemented in the custom json package. See: (https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/json/json.go) Fixes #30213 Update bazel build and add one more test case Fix for gofmt error --- .../extensions/thirdpartyresourcedata/BUILD | 1 + .../thirdpartyresourcedata/codec.go | 37 +++++++----------- .../thirdpartyresourcedata/codec_test.go | 38 ++++++++++++++++++- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/pkg/registry/extensions/thirdpartyresourcedata/BUILD b/pkg/registry/extensions/thirdpartyresourcedata/BUILD index 020b4dd0b0f..5a5a67d096e 100644 --- a/pkg/registry/extensions/thirdpartyresourcedata/BUILD +++ b/pkg/registry/extensions/thirdpartyresourcedata/BUILD @@ -32,6 +32,7 @@ go_library( "//vendor:k8s.io/apimachinery/pkg/labels", "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime/schema", + "//vendor:k8s.io/apimachinery/pkg/util/json", "//vendor:k8s.io/apimachinery/pkg/util/validation/field", "//vendor:k8s.io/apimachinery/pkg/util/yaml", "//vendor:k8s.io/apimachinery/pkg/watch", diff --git a/pkg/registry/extensions/thirdpartyresourcedata/codec.go b/pkg/registry/extensions/thirdpartyresourcedata/codec.go index e78ddb58d56..1ac82fd0509 100644 --- a/pkg/registry/extensions/thirdpartyresourcedata/codec.go +++ b/pkg/registry/extensions/thirdpartyresourcedata/codec.go @@ -18,7 +18,7 @@ package thirdpartyresourcedata import ( "bytes" - "encoding/json" + gojson "encoding/json" "fmt" "io" "net/url" @@ -28,6 +28,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/kubernetes/pkg/api" apiutil "k8s.io/kubernetes/pkg/api/util" @@ -233,14 +234,11 @@ func NewDecoder(delegate runtime.Decoder, kind string) runtime.Decoder { var _ runtime.Decoder = &thirdPartyResourceDataDecoder{} func parseObject(data []byte) (map[string]interface{}, error) { - var obj interface{} - if err := json.Unmarshal(data, &obj); err != nil { + var mapObj map[string]interface{} + if err := json.Unmarshal(data, &mapObj); err != nil { return nil, err } - mapObj, ok := obj.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("unexpected object: %#v", obj) - } + return mapObj, nil } @@ -297,6 +295,7 @@ func (t *thirdPartyResourceDataDecoder) populateResource(objIn *extensions.Third if err := json.Unmarshal(metadataData, &objIn.ObjectMeta); err != nil { return err } + // Override API Version with the ThirdPartyResourceData value // TODO: fix this hard code objIn.APIVersion = v1beta1.SchemeGroupVersion.String() @@ -372,15 +371,11 @@ func (t *thirdPartyResourceDataDecoder) Decode(data []byte, gvk *schema.GroupVer } thirdParty := into.(*extensions.ThirdPartyResourceData) - var dataObj interface{} - if err := json.Unmarshal(data, &dataObj); err != nil { + var mapObj map[string]interface{} + if err := json.Unmarshal(data, &mapObj); err != nil { return nil, nil, err } - mapObj, ok := dataObj.(map[string]interface{}) - if !ok { - return nil, nil, fmt.Errorf("unexpected object: %#v", dataObj) - } /*if gvk.Kind != "ThirdPartyResourceData" { return nil, nil, fmt.Errorf("unexpected kind: %s", gvk.Kind) }*/ @@ -466,14 +461,10 @@ func NewEncoder(delegate runtime.Encoder, gvk schema.GroupVersionKind) runtime.E var _ runtime.Encoder = &thirdPartyResourceDataEncoder{} func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) error { - var objOut interface{} - if err := json.Unmarshal(obj.Data, &objOut); err != nil { + var objMap map[string]interface{} + if err := json.Unmarshal(obj.Data, &objMap); err != nil { return err } - objMap, ok := objOut.(map[string]interface{}) - if !ok { - return fmt.Errorf("unexpected type: %v", objOut) - } objMap["metadata"] = &obj.ObjectMeta encoder := json.NewEncoder(stream) @@ -486,7 +477,7 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri return encodeToJSON(obj, stream) case *extensions.ThirdPartyResourceDataList: // TODO: There are likely still better ways to do this... - listItems := make([]json.RawMessage, len(obj.Items)) + listItems := make([]gojson.RawMessage, len(obj.Items)) for ix := range obj.Items { buff := &bytes.Buffer{} @@ -494,7 +485,7 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri if err != nil { return err } - listItems[ix] = json.RawMessage(buff.Bytes()) + listItems[ix] = gojson.RawMessage(buff.Bytes()) } if t.gvk.Empty() { @@ -503,8 +494,8 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri encMap := struct { // +optional - Kind string `json:"kind,omitempty"` - Items []json.RawMessage `json:"items"` + Kind string `json:"kind,omitempty"` + Items []gojson.RawMessage `json:"items"` // +optional Metadata metav1.ListMeta `json:"metadata,omitempty"` // +optional diff --git a/pkg/registry/extensions/thirdpartyresourcedata/codec_test.go b/pkg/registry/extensions/thirdpartyresourcedata/codec_test.go index 742f8a0bcb1..e5b354cd31d 100644 --- a/pkg/registry/extensions/thirdpartyresourcedata/codec_test.go +++ b/pkg/registry/extensions/thirdpartyresourcedata/codec_test.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "reflect" + "strings" "testing" "time" @@ -289,5 +290,40 @@ func TestThirdPartyResourceDataListEncoding(t *testing.T) { if targetOutput.APIVersion != gv.String() { t.Errorf("apiversion mismatch %v != %v", targetOutput.APIVersion, gv.String()) } - +} + +func TestDecodeNumbers(t *testing.T) { + gv := schema.GroupVersion{Group: "stable.foo.faz", Version: "v1"} + gvk := gv.WithKind("Foo") + e := &thirdPartyResourceDataEncoder{delegate: testapi.Extensions.Codec(), gvk: gvk} + d := &thirdPartyResourceDataDecoder{kind: "Foo", delegate: testapi.Extensions.Codec()} + + // Use highest int64 number and 1000000. + subject := &extensions.ThirdPartyResourceDataList{ + Items: []extensions.ThirdPartyResourceData{ + { + Data: []byte(`{"num1": 9223372036854775807, "num2": 1000000}`), + }, + }, + } + + // Encode to get original JSON. + originalJSON := bytes.NewBuffer([]byte{}) + err := e.Encode(subject, originalJSON) + if err != nil { + t.Errorf("encoding unexpected error: %v", err) + } + + // Decode original JSON. + var into runtime.Object + into, _, err = d.Decode(originalJSON.Bytes(), &gvk, into) + if err != nil { + t.Errorf("decoding unexpected error: %v", err) + } + + // Check if int is preserved. + decodedJSON := into.(*extensions.ThirdPartyResourceDataList).Items[0].Data + if !strings.Contains(string(decodedJSON), `"num1":9223372036854775807,"num2":1000000`) { + t.Errorf("Expected %s, got %s", `"num1":9223372036854775807,"num2":1000000`, string(decodedJSON)) + } }