mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-30 18:01:21 +00:00
486 lines
17 KiB
Go
486 lines
17 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package json_test
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/runtime"
|
|
"sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/runtime/schema"
|
|
"sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/runtime/serializer/json"
|
|
"sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/util/diff"
|
|
)
|
|
|
|
type testDecodable struct {
|
|
Other string
|
|
Value int `json:"value"`
|
|
Spec DecodableSpec `json:"spec"`
|
|
Interface interface{} `json:"interface"`
|
|
gvk schema.GroupVersionKind
|
|
}
|
|
|
|
// DecodableSpec has 15 fields. json-iterator treats struct with more than 10
|
|
// fields differently from struct that has less than 10 fields.
|
|
type DecodableSpec struct {
|
|
A int `json:"A"`
|
|
B int `json:"B"`
|
|
C int `json:"C"`
|
|
D int `json:"D"`
|
|
E int `json:"E"`
|
|
F int `json:"F"`
|
|
G int `json:"G"`
|
|
H int `json:"h"`
|
|
I int `json:"i"`
|
|
J int `json:"j"`
|
|
K int `json:"k"`
|
|
L int `json:"l"`
|
|
M int `json:"m"`
|
|
N int `json:"n"`
|
|
O int `json:"o"`
|
|
}
|
|
|
|
func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d }
|
|
func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
|
|
func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk }
|
|
func (in *testDecodable) DeepCopyObject() runtime.Object {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
out := new(testDecodable)
|
|
in.DeepCopyInto(out)
|
|
return out
|
|
}
|
|
func (in *testDecodable) DeepCopyInto(out *testDecodable) {
|
|
*out = *in
|
|
out.Other = in.Other
|
|
out.Value = in.Value
|
|
out.Spec = in.Spec
|
|
out.Interface = in.Interface
|
|
out.gvk = in.gvk
|
|
return
|
|
}
|
|
|
|
func TestDecode(t *testing.T) {
|
|
testCases := []struct {
|
|
creater runtime.ObjectCreater
|
|
typer runtime.ObjectTyper
|
|
yaml bool
|
|
pretty bool
|
|
strict bool
|
|
|
|
data []byte
|
|
defaultGVK *schema.GroupVersionKind
|
|
into runtime.Object
|
|
|
|
errFn func(error) bool
|
|
expectedObject runtime.Object
|
|
expectedGVK *schema.GroupVersionKind
|
|
}{
|
|
{
|
|
data: []byte("{}"),
|
|
|
|
expectedGVK: &schema.GroupVersionKind{},
|
|
errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") },
|
|
},
|
|
{
|
|
data: []byte("{}"),
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
creater: &mockCreater{err: fmt.Errorf("fake error")},
|
|
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool { return err.Error() == "fake error" },
|
|
},
|
|
{
|
|
data: []byte("{}"),
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedObject: &testDecodable{},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
},
|
|
|
|
// version without group is not defaulted
|
|
{
|
|
data: []byte(`{"apiVersion":"blah"}`),
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedObject: &testDecodable{},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "", Version: "blah"},
|
|
},
|
|
// group without version is defaulted
|
|
{
|
|
data: []byte(`{"apiVersion":"other/"}`),
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedObject: &testDecodable{},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
},
|
|
// group version, kind is defaulted
|
|
{
|
|
data: []byte(`{"apiVersion":"other1/blah1"}`),
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedObject: &testDecodable{},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other1", Version: "blah1"},
|
|
},
|
|
// gvk all provided then not defaulted at all
|
|
{
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah"}`),
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test1", Group: "other1", Version: "blah1"},
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedObject: &testDecodable{},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
},
|
|
//gvk defaulting if kind not provided in data and defaultGVK use into's kind
|
|
{
|
|
data: []byte(`{"apiVersion":"b1/c1"}`),
|
|
into: &testDecodable{gvk: schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}},
|
|
defaultGVK: nil,
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedObject: &testDecodable{gvk: schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"},
|
|
},
|
|
|
|
// accept runtime.Unknown as into and bypass creator
|
|
{
|
|
data: []byte(`{}`),
|
|
into: &runtime.Unknown{},
|
|
|
|
expectedGVK: &schema.GroupVersionKind{},
|
|
expectedObject: &runtime.Unknown{
|
|
Raw: []byte(`{}`),
|
|
ContentType: runtime.ContentTypeJSON,
|
|
},
|
|
},
|
|
{
|
|
data: []byte(`{"test":"object"}`),
|
|
into: &runtime.Unknown{},
|
|
|
|
expectedGVK: &schema.GroupVersionKind{},
|
|
expectedObject: &runtime.Unknown{
|
|
Raw: []byte(`{"test":"object"}`),
|
|
ContentType: runtime.ContentTypeJSON,
|
|
},
|
|
},
|
|
{
|
|
data: []byte(`{"test":"object"}`),
|
|
into: &runtime.Unknown{},
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &runtime.Unknown{
|
|
TypeMeta: runtime.TypeMeta{APIVersion: "other/blah", Kind: "Test"},
|
|
Raw: []byte(`{"test":"object"}`),
|
|
ContentType: runtime.ContentTypeJSON,
|
|
},
|
|
},
|
|
|
|
// unregistered objects can be decoded into directly
|
|
{
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Other: "test",
|
|
Value: 1,
|
|
},
|
|
},
|
|
// registered types get defaulted by the into object kind
|
|
{
|
|
data: []byte(`{"value":1,"Other":"test"}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Other: "test",
|
|
Value: 1,
|
|
},
|
|
},
|
|
// registered types get defaulted by the into object kind even without version, but return an error
|
|
{
|
|
data: []byte(`{"value":1,"Other":"test"}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: ""}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: ""},
|
|
errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") },
|
|
expectedObject: &testDecodable{
|
|
Other: "test",
|
|
Value: 1,
|
|
},
|
|
},
|
|
|
|
// runtime.VersionedObjects are decoded
|
|
{
|
|
data: []byte(`{"value":1,"Other":"test"}`),
|
|
into: &runtime.VersionedObjects{Objects: []runtime.Object{}},
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &runtime.VersionedObjects{
|
|
Objects: []runtime.Object{
|
|
&testDecodable{
|
|
Other: "test",
|
|
Value: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// runtime.VersionedObjects with an object are decoded into
|
|
{
|
|
data: []byte(`{"Other":"test"}`),
|
|
into: &runtime.VersionedObjects{Objects: []runtime.Object{&testDecodable{Value: 2}}},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &runtime.VersionedObjects{
|
|
Objects: []runtime.Object{
|
|
&testDecodable{
|
|
Other: "test",
|
|
Value: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Error on invalid number
|
|
{
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","interface":1e1000}`),
|
|
creater: &mockCreater{obj: &testDecodable{}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), `json_test.testDecodable.Interface: DecodeNumber: strconv.ParseFloat: parsing "1e1000": value out of range`)
|
|
},
|
|
},
|
|
// Unmarshalling is case-sensitive
|
|
{
|
|
// "VaLue" should have been "value"
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","VaLue":1,"Other":"test"}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Other: "test",
|
|
},
|
|
},
|
|
// Unmarshalling is case-sensitive for big struct.
|
|
{
|
|
// "b" should have been "B", "I" should have been "i"
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","spec": {"A": 1, "b": 2, "h": 3, "I": 4}}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Spec: DecodableSpec{A: 1, H: 3},
|
|
},
|
|
},
|
|
// Unknown fields should return an error from the strict JSON deserializer.
|
|
{
|
|
data: []byte(`{"unknown": 1}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), "found unknown field")
|
|
},
|
|
strict: true,
|
|
},
|
|
// Unknown fields should return an error from the strict YAML deserializer.
|
|
{
|
|
data: []byte("unknown: 1\n"),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), "found unknown field")
|
|
},
|
|
yaml: true,
|
|
strict: true,
|
|
},
|
|
// Duplicate fields should return an error from the strict JSON deserializer.
|
|
{
|
|
data: []byte(`{"value":1,"value":1}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), "already set in map")
|
|
},
|
|
strict: true,
|
|
},
|
|
// Duplicate fields should return an error from the strict YAML deserializer.
|
|
{
|
|
data: []byte("value: 1\n" +
|
|
"value: 1\n"),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), "already set in map")
|
|
},
|
|
yaml: true,
|
|
strict: true,
|
|
},
|
|
// Strict JSON decode should fail for untagged fields.
|
|
{
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), "found unknown field")
|
|
},
|
|
strict: true,
|
|
},
|
|
// Strict YAML decode should fail for untagged fields.
|
|
{
|
|
data: []byte("kind: Test\n" +
|
|
"apiVersion: other/blah\n" +
|
|
"value: 1\n" +
|
|
"Other: test\n"),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
errFn: func(err error) bool {
|
|
return strings.Contains(err.Error(), "found unknown field")
|
|
},
|
|
yaml: true,
|
|
strict: true,
|
|
},
|
|
// Strict JSON decode into unregistered objects directly.
|
|
{
|
|
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Other: "test",
|
|
Value: 1,
|
|
},
|
|
strict: true,
|
|
},
|
|
// Strict YAML decode into unregistered objects directly.
|
|
{
|
|
data: []byte("kind: Test\n" +
|
|
"apiVersion: other/blah\n" +
|
|
"value: 1\n" +
|
|
"Other: test\n"),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Other: "test",
|
|
Value: 1,
|
|
},
|
|
yaml: true,
|
|
strict: true,
|
|
},
|
|
// Valid strict JSON decode without GVK.
|
|
{
|
|
data: []byte(`{"value":1234}`),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Value: 1234,
|
|
},
|
|
strict: true,
|
|
},
|
|
// Valid strict YAML decode without GVK.
|
|
{
|
|
data: []byte("value: 1234\n"),
|
|
into: &testDecodable{},
|
|
typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}},
|
|
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
|
|
expectedObject: &testDecodable{
|
|
Value: 1234,
|
|
},
|
|
yaml: true,
|
|
strict: true,
|
|
},
|
|
}
|
|
|
|
for i, test := range testCases {
|
|
var s runtime.Serializer
|
|
if test.yaml {
|
|
s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, json.SerializerOptions{Yaml: test.yaml, Pretty: false, Strict: test.strict})
|
|
} else {
|
|
s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, json.SerializerOptions{Yaml: test.yaml, Pretty: test.pretty, Strict: test.strict})
|
|
}
|
|
obj, gvk, err := s.Decode([]byte(test.data), test.defaultGVK, test.into)
|
|
|
|
if !reflect.DeepEqual(test.expectedGVK, gvk) {
|
|
t.Errorf("%d: unexpected GVK: %v", i, gvk)
|
|
}
|
|
|
|
switch {
|
|
case err == nil && test.errFn != nil:
|
|
t.Errorf("%d: failed: %v", i, err)
|
|
continue
|
|
case err != nil && test.errFn == nil:
|
|
t.Errorf("%d: failed: %v", i, err)
|
|
continue
|
|
case err != nil:
|
|
if !test.errFn(err) {
|
|
t.Errorf("%d: failed: %v", i, err)
|
|
}
|
|
if obj != nil {
|
|
t.Errorf("%d: should have returned nil object", i)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if test.into != nil && test.into != obj {
|
|
t.Errorf("%d: expected into to be returned: %v", i, obj)
|
|
continue
|
|
}
|
|
|
|
if !reflect.DeepEqual(test.expectedObject, obj) {
|
|
t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj))
|
|
}
|
|
}
|
|
}
|
|
|
|
type mockCreater struct {
|
|
apiVersion string
|
|
kind string
|
|
err error
|
|
obj runtime.Object
|
|
}
|
|
|
|
func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) {
|
|
c.apiVersion, c.kind = kind.GroupVersion().String(), kind.Kind
|
|
return c.obj, c.err
|
|
}
|
|
|
|
type mockTyper struct {
|
|
gvk *schema.GroupVersionKind
|
|
err error
|
|
}
|
|
|
|
func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
|
|
if t.gvk == nil {
|
|
return nil, false, t.err
|
|
}
|
|
return []schema.GroupVersionKind{*t.gvk}, false, t.err
|
|
}
|
|
|
|
func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool {
|
|
return false
|
|
}
|