mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
apiextensions: move ObjectMeta coercion into own package
This commit is contained in:
parent
78220fe380
commit
b807def8d9
@ -28,10 +28,12 @@ import (
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/validate"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
|
||||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
|
||||
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
|
||||
structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
|
||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
||||
@ -1022,7 +1024,7 @@ func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta, foundObjectMeta, err := getObjectMeta(u, v.dropInvalidMetadata)
|
||||
objectMeta, foundObjectMeta, err := schemaobjectmeta.GetObjectMeta(u, v.dropInvalidMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1044,72 +1046,10 @@ func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) error {
|
||||
u.SetAPIVersion(apiVersion)
|
||||
}
|
||||
if foundObjectMeta {
|
||||
if err := setObjectMeta(u, objectMeta); err != nil {
|
||||
if err := schemaobjectmeta.SetObjectMeta(u, objectMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var encodingjson = json.CaseSensitiveJsonIterator()
|
||||
|
||||
func getObjectMeta(u *unstructured.Unstructured, dropMalformedFields bool) (*metav1.ObjectMeta, bool, error) {
|
||||
metadata, found := u.UnstructuredContent()["metadata"]
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// round-trip through JSON first, hoping that unmarshaling just works
|
||||
objectMeta := &metav1.ObjectMeta{}
|
||||
metadataBytes, err := encodingjson.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err = encodingjson.Unmarshal(metadataBytes, objectMeta); err == nil {
|
||||
// if successful, return
|
||||
return objectMeta, true, nil
|
||||
}
|
||||
if !dropMalformedFields {
|
||||
// if we're not trying to drop malformed fields, return the error
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
metadataMap, ok := metadata.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("invalid metadata: expected object, got %T", metadata)
|
||||
}
|
||||
|
||||
// Go field by field accumulating into the metadata object.
|
||||
// This takes advantage of the fact that you can repeatedly unmarshal individual fields into a single struct,
|
||||
// each iteration preserving the old key-values.
|
||||
accumulatedObjectMeta := &metav1.ObjectMeta{}
|
||||
testObjectMeta := &metav1.ObjectMeta{}
|
||||
for k, v := range metadataMap {
|
||||
// serialize a single field
|
||||
if singleFieldBytes, err := encodingjson.Marshal(map[string]interface{}{k: v}); err == nil {
|
||||
// do a test unmarshal
|
||||
if encodingjson.Unmarshal(singleFieldBytes, testObjectMeta) == nil {
|
||||
// if that succeeds, unmarshal for real
|
||||
encodingjson.Unmarshal(singleFieldBytes, accumulatedObjectMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accumulatedObjectMeta, true, nil
|
||||
}
|
||||
|
||||
func setObjectMeta(u *unstructured.Unstructured, objectMeta *metav1.ObjectMeta) error {
|
||||
if objectMeta == nil {
|
||||
unstructured.RemoveNestedField(u.UnstructuredContent(), "metadata")
|
||||
return nil
|
||||
}
|
||||
|
||||
metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.UnstructuredContent()["metadata"] = metadata
|
||||
return nil
|
||||
}
|
||||
|
@ -17,24 +17,11 @@ limitations under the License.
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
|
||||
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func TestConvertFieldLabel(t *testing.T) {
|
||||
@ -118,202 +105,3 @@ func TestConvertFieldLabel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtripObjectMeta(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
codec := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
|
||||
seed := rand.Int63()
|
||||
fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs)
|
||||
|
||||
N := 1000
|
||||
for i := 0; i < N; i++ {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
original := &metav1.ObjectMeta{}
|
||||
fuzzer.Fuzz(original)
|
||||
if err := setObjectMeta(u, original); err != nil {
|
||||
t.Fatalf("unexpected error setting ObjectMeta: %v", err)
|
||||
}
|
||||
o, _, err := getObjectMeta(u, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting the Objectmeta: %v", err)
|
||||
}
|
||||
|
||||
if !equality.Semantic.DeepEqual(original, o) {
|
||||
t.Errorf("diff: %v\nCodec: %#v", diff.ObjectReflectDiff(original, o), codec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMalformedObjectMetaFields sets a number of different random values and types for all
|
||||
// metadata fields. If json.Unmarshal accepts them, compare that getObjectMeta
|
||||
// gives the same result. Otherwise, drop malformed fields.
|
||||
func TestMalformedObjectMetaFields(t *testing.T) {
|
||||
fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(rand.Int63()), serializer.NewCodecFactory(runtime.NewScheme()))
|
||||
spuriousValues := func() []interface{} {
|
||||
return []interface{}{
|
||||
// primitives
|
||||
nil,
|
||||
int64(1),
|
||||
float64(1.5),
|
||||
true,
|
||||
"a",
|
||||
// well-formed complex values
|
||||
[]interface{}{"a", "b"},
|
||||
map[string]interface{}{"a": "1", "b": "2"},
|
||||
[]interface{}{int64(1), int64(2)},
|
||||
[]interface{}{float64(1.5), float64(2.5)},
|
||||
// known things json decoding tolerates
|
||||
map[string]interface{}{"a": "1", "b": nil},
|
||||
// malformed things
|
||||
map[string]interface{}{"a": "1", "b": []interface{}{"nested"}},
|
||||
[]interface{}{"a", int64(1), float64(1.5), true, []interface{}{"nested"}},
|
||||
}
|
||||
}
|
||||
N := 100
|
||||
for i := 0; i < N; i++ {
|
||||
fuzzedObjectMeta := &metav1.ObjectMeta{}
|
||||
fuzzer.Fuzz(fuzzedObjectMeta)
|
||||
goodMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, pth := range jsonPaths(nil, goodMetaMap) {
|
||||
for _, v := range spuriousValues() {
|
||||
// skip values of same type, because they can only cause decoding errors further insides
|
||||
orig, err := JsonPathValue(goodMetaMap, pth, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected to not find something at %v: %v", pth, err)
|
||||
}
|
||||
if reflect.TypeOf(v) == reflect.TypeOf(orig) {
|
||||
continue
|
||||
}
|
||||
|
||||
// make a spurious map
|
||||
spuriousMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := SetJsonPath(spuriousMetaMap, pth, 0, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// See if it can unmarshal to object meta
|
||||
spuriousJSON, err := encodingjson.Marshal(spuriousMetaMap)
|
||||
if err != nil {
|
||||
t.Fatalf("error on %v=%#v: %v", pth, v, err)
|
||||
}
|
||||
expectedObjectMeta := &metav1.ObjectMeta{}
|
||||
if err := encodingjson.Unmarshal(spuriousJSON, expectedObjectMeta); err != nil {
|
||||
// if standard json unmarshal would fail decoding this field, drop the field entirely
|
||||
truncatedMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// we expect this logic for the different fields:
|
||||
switch {
|
||||
default:
|
||||
// delete complete top-level field by default
|
||||
DeleteJsonPath(truncatedMetaMap, pth[:1], 0)
|
||||
}
|
||||
|
||||
truncatedJSON, err := encodingjson.Marshal(truncatedMetaMap)
|
||||
if err != nil {
|
||||
t.Fatalf("error on %v=%#v: %v", pth, v, err)
|
||||
}
|
||||
expectedObjectMeta = &metav1.ObjectMeta{}
|
||||
if err := encodingjson.Unmarshal(truncatedJSON, expectedObjectMeta); err != nil {
|
||||
t.Fatalf("error on %v=%#v: %v", pth, v, err)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure dropInvalidTypedFields+getObjectMeta matches what we expect
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{"metadata": spuriousMetaMap}}
|
||||
actualObjectMeta, _, err := getObjectMeta(u, true)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error after dropping invalid typed fields on %v=%#v: %v", pth, v, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !equality.Semantic.DeepEqual(expectedObjectMeta, actualObjectMeta) {
|
||||
t.Errorf("%v=%#v, diff: %v\n", pth, v, diff.ObjectReflectDiff(expectedObjectMeta, actualObjectMeta))
|
||||
t.Errorf("expectedObjectMeta %#v", expectedObjectMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjectMetaNils(t *testing.T) {
|
||||
u := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"kind": "Pod",
|
||||
"apiVersion": "v1",
|
||||
"metadata": map[string]interface{}{
|
||||
"generateName": nil,
|
||||
"labels": map[string]interface{}{
|
||||
"foo": nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
o, _, err := getObjectMeta(u, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o.GenerateName != "" {
|
||||
t.Errorf("expected null json value to be read as \"\" string, but got: %q", o.GenerateName)
|
||||
}
|
||||
if got, expected := o.Labels, map[string]string{"foo": ""}; !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("unexpected labels, expected=%#v, got=%#v", expected, got)
|
||||
}
|
||||
|
||||
// double check this what the kube JSON decode is doing
|
||||
bs, _ := encodingjson.Marshal(u.UnstructuredContent())
|
||||
kubeObj, _, err := clientgoscheme.Codecs.UniversalDecoder(corev1.SchemeGroupVersion).Decode(bs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pod, ok := kubeObj.(*corev1.Pod)
|
||||
if !ok {
|
||||
t.Fatalf("expected v1 Pod, got: %T", pod)
|
||||
}
|
||||
if got, expected := o.GenerateName, pod.ObjectMeta.GenerateName; got != expected {
|
||||
t.Errorf("expected generatedName to be %q, got %q", expected, got)
|
||||
}
|
||||
if got, expected := o.Labels, pod.ObjectMeta.Labels; !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("expected labels to be %v, got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjectMeta(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "good",
|
||||
"Name": "bad1",
|
||||
"nAme": "bad2",
|
||||
"naMe": "bad3",
|
||||
"namE": "bad4",
|
||||
|
||||
"namespace": "good",
|
||||
"Namespace": "bad1",
|
||||
"nAmespace": "bad2",
|
||||
"naMespace": "bad3",
|
||||
"namEspace": "bad4",
|
||||
|
||||
"creationTimestamp": "a",
|
||||
},
|
||||
}}
|
||||
|
||||
meta, _, err := getObjectMeta(u, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if meta.Name != "good" || meta.Namespace != "good" {
|
||||
t.Fatalf("got %#v", meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2019 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 objectmeta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
)
|
||||
|
||||
var encodingjson = json.CaseSensitiveJsonIterator()
|
||||
|
||||
func GetObjectMeta(u *unstructured.Unstructured, dropMalformedFields bool) (*metav1.ObjectMeta, bool, error) {
|
||||
metadata, found := u.UnstructuredContent()["metadata"]
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// round-trip through JSON first, hoping that unmarshaling just works
|
||||
objectMeta := &metav1.ObjectMeta{}
|
||||
metadataBytes, err := encodingjson.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err = encodingjson.Unmarshal(metadataBytes, objectMeta); err == nil {
|
||||
// if successful, return
|
||||
return objectMeta, true, nil
|
||||
}
|
||||
if !dropMalformedFields {
|
||||
// if we're not trying to drop malformed fields, return the error
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
metadataMap, ok := metadata.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("invalid metadata: expected object, got %T", metadata)
|
||||
}
|
||||
|
||||
// Go field by field accumulating into the metadata object.
|
||||
// This takes advantage of the fact that you can repeatedly unmarshal individual fields into a single struct,
|
||||
// each iteration preserving the old key-values.
|
||||
accumulatedObjectMeta := &metav1.ObjectMeta{}
|
||||
testObjectMeta := &metav1.ObjectMeta{}
|
||||
for k, v := range metadataMap {
|
||||
// serialize a single field
|
||||
if singleFieldBytes, err := encodingjson.Marshal(map[string]interface{}{k: v}); err == nil {
|
||||
// do a test unmarshal
|
||||
if encodingjson.Unmarshal(singleFieldBytes, testObjectMeta) == nil {
|
||||
// if that succeeds, unmarshal for real
|
||||
encodingjson.Unmarshal(singleFieldBytes, accumulatedObjectMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accumulatedObjectMeta, true, nil
|
||||
}
|
||||
|
||||
func SetObjectMeta(u *unstructured.Unstructured, objectMeta *metav1.ObjectMeta) error {
|
||||
if objectMeta == nil {
|
||||
unstructured.RemoveNestedField(u.UnstructuredContent(), "metadata")
|
||||
return nil
|
||||
}
|
||||
|
||||
metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.UnstructuredContent()["metadata"] = metadata
|
||||
return nil
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
/*
|
||||
Copyright 2019 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 objectmeta
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func TestRoundtripObjectMeta(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
codec := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
|
||||
seed := rand.Int63()
|
||||
fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs)
|
||||
|
||||
N := 1000
|
||||
for i := 0; i < N; i++ {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
original := &metav1.ObjectMeta{}
|
||||
fuzzer.Fuzz(original)
|
||||
if err := SetObjectMeta(u, original); err != nil {
|
||||
t.Fatalf("unexpected error setting ObjectMeta: %v", err)
|
||||
}
|
||||
o, _, err := GetObjectMeta(u, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting the Objectmeta: %v", err)
|
||||
}
|
||||
|
||||
if !equality.Semantic.DeepEqual(original, o) {
|
||||
t.Errorf("diff: %v\nCodec: %#v", diff.ObjectReflectDiff(original, o), codec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMalformedObjectMetaFields sets a number of different random values and types for all
|
||||
// metadata fields. If json.Unmarshal accepts them, compare that getObjectMeta
|
||||
// gives the same result. Otherwise, drop malformed fields.
|
||||
func TestMalformedObjectMetaFields(t *testing.T) {
|
||||
fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(rand.Int63()), serializer.NewCodecFactory(runtime.NewScheme()))
|
||||
spuriousValues := func() []interface{} {
|
||||
return []interface{}{
|
||||
// primitives
|
||||
nil,
|
||||
int64(1),
|
||||
float64(1.5),
|
||||
true,
|
||||
"a",
|
||||
// well-formed complex values
|
||||
[]interface{}{"a", "b"},
|
||||
map[string]interface{}{"a": "1", "b": "2"},
|
||||
[]interface{}{int64(1), int64(2)},
|
||||
[]interface{}{float64(1.5), float64(2.5)},
|
||||
// known things json decoding tolerates
|
||||
map[string]interface{}{"a": "1", "b": nil},
|
||||
// malformed things
|
||||
map[string]interface{}{"a": "1", "b": []interface{}{"nested"}},
|
||||
[]interface{}{"a", int64(1), float64(1.5), true, []interface{}{"nested"}},
|
||||
}
|
||||
}
|
||||
N := 100
|
||||
for i := 0; i < N; i++ {
|
||||
fuzzedObjectMeta := &metav1.ObjectMeta{}
|
||||
fuzzer.Fuzz(fuzzedObjectMeta)
|
||||
goodMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, pth := range jsonPaths(nil, goodMetaMap) {
|
||||
for _, v := range spuriousValues() {
|
||||
// skip values of same type, because they can only cause decoding errors further insides
|
||||
orig, err := JsonPathValue(goodMetaMap, pth, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected to not find something at %v: %v", pth, err)
|
||||
}
|
||||
if reflect.TypeOf(v) == reflect.TypeOf(orig) {
|
||||
continue
|
||||
}
|
||||
|
||||
// make a spurious map
|
||||
spuriousMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := SetJsonPath(spuriousMetaMap, pth, 0, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// See if it can unmarshal to object meta
|
||||
spuriousJSON, err := encodingjson.Marshal(spuriousMetaMap)
|
||||
if err != nil {
|
||||
t.Fatalf("error on %v=%#v: %v", pth, v, err)
|
||||
}
|
||||
expectedObjectMeta := &metav1.ObjectMeta{}
|
||||
if err := encodingjson.Unmarshal(spuriousJSON, expectedObjectMeta); err != nil {
|
||||
// if standard json unmarshal would fail decoding this field, drop the field entirely
|
||||
truncatedMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// we expect this logic for the different fields:
|
||||
switch {
|
||||
default:
|
||||
// delete complete top-level field by default
|
||||
DeleteJsonPath(truncatedMetaMap, pth[:1], 0)
|
||||
}
|
||||
|
||||
truncatedJSON, err := encodingjson.Marshal(truncatedMetaMap)
|
||||
if err != nil {
|
||||
t.Fatalf("error on %v=%#v: %v", pth, v, err)
|
||||
}
|
||||
expectedObjectMeta = &metav1.ObjectMeta{}
|
||||
if err := encodingjson.Unmarshal(truncatedJSON, expectedObjectMeta); err != nil {
|
||||
t.Fatalf("error on %v=%#v: %v", pth, v, err)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure dropInvalidTypedFields+getObjectMeta matches what we expect
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{"metadata": spuriousMetaMap}}
|
||||
actualObjectMeta, _, err := GetObjectMeta(u, true)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error after dropping invalid typed fields on %v=%#v: %v", pth, v, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !equality.Semantic.DeepEqual(expectedObjectMeta, actualObjectMeta) {
|
||||
t.Errorf("%v=%#v, diff: %v\n", pth, v, diff.ObjectReflectDiff(expectedObjectMeta, actualObjectMeta))
|
||||
t.Errorf("expectedObjectMeta %#v", expectedObjectMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjectMetaNils(t *testing.T) {
|
||||
u := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"kind": "Pod",
|
||||
"apiVersion": "v1",
|
||||
"metadata": map[string]interface{}{
|
||||
"generateName": nil,
|
||||
"labels": map[string]interface{}{
|
||||
"foo": nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
o, _, err := GetObjectMeta(u, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o.GenerateName != "" {
|
||||
t.Errorf("expected null json value to be read as \"\" string, but got: %q", o.GenerateName)
|
||||
}
|
||||
if got, expected := o.Labels, map[string]string{"foo": ""}; !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("unexpected labels, expected=%#v, got=%#v", expected, got)
|
||||
}
|
||||
|
||||
// double check this what the kube JSON decode is doing
|
||||
bs, _ := encodingjson.Marshal(u.UnstructuredContent())
|
||||
kubeObj, _, err := clientgoscheme.Codecs.UniversalDecoder(corev1.SchemeGroupVersion).Decode(bs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pod, ok := kubeObj.(*corev1.Pod)
|
||||
if !ok {
|
||||
t.Fatalf("expected v1 Pod, got: %T", pod)
|
||||
}
|
||||
if got, expected := o.GenerateName, pod.ObjectMeta.GenerateName; got != expected {
|
||||
t.Errorf("expected generatedName to be %q, got %q", expected, got)
|
||||
}
|
||||
if got, expected := o.Labels, pod.ObjectMeta.Labels; !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("expected labels to be %v, got %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjectMeta(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "good",
|
||||
"Name": "bad1",
|
||||
"nAme": "bad2",
|
||||
"naMe": "bad3",
|
||||
"namE": "bad4",
|
||||
|
||||
"namespace": "good",
|
||||
"Namespace": "bad1",
|
||||
"nAmespace": "bad2",
|
||||
"naMespace": "bad3",
|
||||
"namEspace": "bad4",
|
||||
|
||||
"creationTimestamp": "a",
|
||||
},
|
||||
}}
|
||||
|
||||
meta, _, err := GetObjectMeta(u, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if meta.Name != "good" || meta.Namespace != "good" {
|
||||
t.Fatalf("got %#v", meta)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiserver
|
||||
package objectmeta
|
||||
|
||||
import (
|
||||
"bytes"
|
Loading…
Reference in New Issue
Block a user