mirror of https://github.com/k3s-io/k3s
Merge pull request #44026 from nikinath/precision-json
Automatic merge from submit-queue (batch tested with PRs 44424, 44026, 43939, 44386, 42914) Preserve int data when unmarshalling for TPR **What this PR does / why we need it**: The Go json package converts all numbers to float64 while unmarshalling. 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) Now, the number is preserved as an integer till the highest int64 number - `9223372036854775807`. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #30213 **Special notes for your reviewer**: See also https://github.com/kubernetes/kubernetes/pull/16964 **Release note**: ``` NONE ```pull/6/head
commit
9ef911edec
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue