1
16
17 package objectmeta
18
19 import (
20 "math/rand"
21 "reflect"
22 "testing"
23
24 "github.com/google/go-cmp/cmp"
25 corev1 "k8s.io/api/core/v1"
26 "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
27 "k8s.io/apimachinery/pkg/api/equality"
28 metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/runtime/serializer"
33 "k8s.io/apimachinery/pkg/runtime/serializer/json"
34 utiljson "k8s.io/apimachinery/pkg/util/json"
35 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
36 )
37
38 func TestRoundtripObjectMeta(t *testing.T) {
39 scheme := runtime.NewScheme()
40 codecs := serializer.NewCodecFactory(scheme)
41 codec := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
42 seed := rand.Int63()
43 fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs)
44
45 N := 1000
46 for i := 0; i < N; i++ {
47 u := &unstructured.Unstructured{Object: map[string]interface{}{}}
48 original := &metav1.ObjectMeta{}
49 fuzzer.Fuzz(original)
50 if err := SetObjectMeta(u.Object, original); err != nil {
51 t.Fatalf("unexpected error setting ObjectMeta: %v", err)
52 }
53 o, _, err := GetObjectMeta(u.Object, false)
54 if err != nil {
55 t.Fatalf("unexpected error getting the Objectmeta: %v", err)
56 }
57
58 if !equality.Semantic.DeepEqual(original, o) {
59 t.Errorf("diff: %v\nCodec: %#v", cmp.Diff(original, o), codec)
60 }
61 }
62 }
63
64
65
66
67 func TestMalformedObjectMetaFields(t *testing.T) {
68 fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(rand.Int63()), serializer.NewCodecFactory(runtime.NewScheme()))
69 spuriousValues := func() []interface{} {
70 return []interface{}{
71
72 nil,
73 int64(1),
74 float64(1.5),
75 true,
76 "a",
77
78 []interface{}{"a", "b"},
79 map[string]interface{}{"a": "1", "b": "2"},
80 []interface{}{int64(1), int64(2)},
81 []interface{}{float64(1.5), float64(2.5)},
82
83 map[string]interface{}{"a": "1", "b": nil},
84
85 map[string]interface{}{"a": "1", "b": []interface{}{"nested"}},
86 []interface{}{"a", int64(1), float64(1.5), true, []interface{}{"nested"}},
87 }
88 }
89 N := 100
90 for i := 0; i < N; i++ {
91 fuzzedObjectMeta := &metav1.ObjectMeta{}
92 fuzzer.Fuzz(fuzzedObjectMeta)
93 goodMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
94 if err != nil {
95 t.Fatal(err)
96 }
97 for _, pth := range jsonPaths(nil, goodMetaMap) {
98 for _, v := range spuriousValues() {
99
100 orig, err := JSONPathValue(goodMetaMap, pth, 0)
101 if err != nil {
102 t.Fatalf("unexpected to not find something at %v: %v", pth, err)
103 }
104 if reflect.TypeOf(v) == reflect.TypeOf(orig) {
105 continue
106 }
107
108
109 spuriousMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
110 if err != nil {
111 t.Fatal(err)
112 }
113 if err := SetJSONPath(spuriousMetaMap, pth, 0, v); err != nil {
114 t.Fatal(err)
115 }
116
117
118 spuriousJSON, err := utiljson.Marshal(spuriousMetaMap)
119 if err != nil {
120 t.Fatalf("error on %v=%#v: %v", pth, v, err)
121 }
122 expectedObjectMeta := &metav1.ObjectMeta{}
123 if err := utiljson.Unmarshal(spuriousJSON, expectedObjectMeta); err != nil {
124
125 truncatedMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
126 if err != nil {
127 t.Fatal(err)
128 }
129
130
131 switch {
132 default:
133
134 DeleteJSONPath(truncatedMetaMap, pth[:1], 0)
135 }
136
137 truncatedJSON, err := utiljson.Marshal(truncatedMetaMap)
138 if err != nil {
139 t.Fatalf("error on %v=%#v: %v", pth, v, err)
140 }
141 expectedObjectMeta = &metav1.ObjectMeta{}
142 if err := utiljson.Unmarshal(truncatedJSON, expectedObjectMeta); err != nil {
143 t.Fatalf("error on %v=%#v: %v", pth, v, err)
144 }
145 }
146
147
148 u := &unstructured.Unstructured{Object: map[string]interface{}{"metadata": spuriousMetaMap}}
149 actualObjectMeta, _, err := GetObjectMeta(u.Object, true)
150 if err != nil {
151 t.Errorf("got unexpected error after dropping invalid typed fields on %v=%#v: %v", pth, v, err)
152 continue
153 }
154
155 if !equality.Semantic.DeepEqual(expectedObjectMeta, actualObjectMeta) {
156 t.Errorf("%v=%#v, diff: %v\n", pth, v, cmp.Diff(expectedObjectMeta, actualObjectMeta))
157 t.Errorf("expectedObjectMeta %#v", expectedObjectMeta)
158 }
159 }
160 }
161 }
162 }
163
164 func TestGetObjectMetaWithOptions(t *testing.T) {
165 unknownAndMalformed := map[string]interface{}{
166 "kind": "Pod",
167 "apiVersion": "v1",
168 "metadata": map[string]interface{}{
169 "name": "my-meta",
170 "unknownField": "foo",
171 "generateName": nil,
172 "generation": nil,
173 "labels": map[string]string{
174 "foo": "bar",
175 },
176 "annotations": 11,
177 },
178 }
179
180 unknownOnly := map[string]interface{}{
181 "kind": "Pod",
182 "apiVersion": "v1",
183 "metadata": map[string]interface{}{
184 "name": "my-meta",
185 "unknownField": "foo",
186 "generateName": nil,
187 "generation": nil,
188 "labels": map[string]string{
189 "foo": "bar",
190 },
191 },
192 }
193
194 malformedOnly := map[string]interface{}{
195 "kind": "Pod",
196 "apiVersion": "v1",
197 "metadata": map[string]interface{}{
198 "name": "my-meta",
199 "generateName": nil,
200 "generation": nil,
201 "labels": map[string]string{
202 "foo": "bar",
203 },
204 "annotations": 11,
205 },
206 }
207
208 var testcases = []struct {
209 obj map[string]interface{}
210 dropMalformedFields bool
211 returnUnknownFieldPaths bool
212 expectedObject *metav1.ObjectMeta
213 expectedUnknownPaths []string
214 expectedErr string
215 }{
216 {
217 obj: unknownAndMalformed,
218 dropMalformedFields: false,
219 returnUnknownFieldPaths: false,
220 expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
221 },
222 {
223 obj: unknownAndMalformed,
224 dropMalformedFields: true,
225 returnUnknownFieldPaths: false,
226 expectedObject: &metav1.ObjectMeta{
227 Name: "my-meta",
228 Labels: map[string]string{
229 "foo": "bar",
230 },
231 },
232 },
233 {
234 obj: unknownAndMalformed,
235 dropMalformedFields: false,
236 returnUnknownFieldPaths: true,
237 expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
238 },
239 {
240 obj: unknownAndMalformed,
241 dropMalformedFields: true,
242 returnUnknownFieldPaths: true,
243 expectedObject: &metav1.ObjectMeta{
244 Name: "my-meta",
245 Labels: map[string]string{
246 "foo": "bar",
247 },
248 },
249 expectedUnknownPaths: []string{"metadata.unknownField"},
250 },
251
252 {
253 obj: unknownOnly,
254 dropMalformedFields: false,
255 returnUnknownFieldPaths: false,
256 expectedObject: &metav1.ObjectMeta{
257 Name: "my-meta",
258 Labels: map[string]string{
259 "foo": "bar",
260 },
261 },
262 },
263 {
264 obj: unknownOnly,
265 dropMalformedFields: true,
266 returnUnknownFieldPaths: false,
267 expectedObject: &metav1.ObjectMeta{
268 Name: "my-meta",
269 Labels: map[string]string{
270 "foo": "bar",
271 },
272 },
273 },
274 {
275 obj: unknownOnly,
276 dropMalformedFields: false,
277 returnUnknownFieldPaths: true,
278 expectedObject: &metav1.ObjectMeta{
279 Name: "my-meta",
280 Labels: map[string]string{
281 "foo": "bar",
282 },
283 },
284 expectedUnknownPaths: []string{"metadata.unknownField"},
285 },
286 {
287 obj: unknownOnly,
288 dropMalformedFields: true,
289 returnUnknownFieldPaths: true,
290 expectedObject: &metav1.ObjectMeta{
291 Name: "my-meta",
292 Labels: map[string]string{
293 "foo": "bar",
294 },
295 },
296 expectedUnknownPaths: []string{"metadata.unknownField"},
297 },
298
299 {
300 obj: malformedOnly,
301 dropMalformedFields: false,
302 returnUnknownFieldPaths: false,
303 expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
304 },
305 {
306 obj: malformedOnly,
307 dropMalformedFields: true,
308 returnUnknownFieldPaths: false,
309 expectedObject: &metav1.ObjectMeta{
310 Name: "my-meta",
311 Labels: map[string]string{
312 "foo": "bar",
313 },
314 },
315 },
316 {
317 obj: malformedOnly,
318 dropMalformedFields: false,
319 returnUnknownFieldPaths: true,
320 expectedErr: "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
321 },
322 {
323 obj: malformedOnly,
324 dropMalformedFields: true,
325 returnUnknownFieldPaths: true,
326 expectedObject: &metav1.ObjectMeta{
327 Name: "my-meta",
328 Labels: map[string]string{
329 "foo": "bar",
330 },
331 },
332 },
333 }
334 for _, tc := range testcases {
335 opts := ObjectMetaOptions{
336 ReturnUnknownFieldPaths: tc.returnUnknownFieldPaths,
337 DropMalformedFields: tc.dropMalformedFields,
338 }
339 obj, _, unknownPaths, err := GetObjectMetaWithOptions(tc.obj, opts)
340 if !reflect.DeepEqual(tc.expectedObject, obj) {
341 t.Errorf("expected: %v, got: %v", tc.expectedObject, obj)
342 }
343 if (err == nil && tc.expectedErr != "") || err != nil && (err.Error() != tc.expectedErr) {
344 t.Errorf("expected: %v, got: %v", tc.expectedErr, err)
345 }
346 if !reflect.DeepEqual(tc.expectedUnknownPaths, unknownPaths) {
347 t.Errorf("expected: %v, got: %v", tc.expectedUnknownPaths, unknownPaths)
348 }
349 }
350 }
351
352 func TestGetObjectMetaNils(t *testing.T) {
353 u := &unstructured.Unstructured{
354 Object: map[string]interface{}{
355 "kind": "Pod",
356 "apiVersion": "v1",
357 "metadata": map[string]interface{}{
358 "generateName": nil,
359 "generation": nil,
360 "labels": map[string]interface{}{
361 "foo": nil,
362 },
363 },
364 },
365 }
366
367 o, _, err := GetObjectMeta(u.Object, true)
368 if err != nil {
369 t.Fatal(err)
370 }
371 if o.GenerateName != "" {
372 t.Errorf("expected null json generateName value to be read as \"\" string, but got: %q", o.GenerateName)
373 }
374 if o.Generation != 0 {
375 t.Errorf("expected null json generation value to be read as zero, but got: %q", o.Generation)
376 }
377 if got, expected := o.Labels, map[string]string{"foo": ""}; !reflect.DeepEqual(got, expected) {
378 t.Errorf("unexpected labels, expected=%#v, got=%#v", expected, got)
379 }
380
381
382 bs, _ := utiljson.Marshal(u.UnstructuredContent())
383 kubeObj, _, err := clientgoscheme.Codecs.UniversalDecoder(corev1.SchemeGroupVersion).Decode(bs, nil, nil)
384 if err != nil {
385 t.Fatal(err)
386 }
387 pod, ok := kubeObj.(*corev1.Pod)
388 if !ok {
389 t.Fatalf("expected v1 Pod, got: %T", pod)
390 }
391 if got, expected := o.GenerateName, pod.ObjectMeta.GenerateName; got != expected {
392 t.Errorf("expected generatedName to be %q, got %q", expected, got)
393 }
394 if got, expected := o.Generation, pod.ObjectMeta.Generation; got != expected {
395 t.Errorf("expected generation to be %q, got %q", expected, got)
396 }
397 if got, expected := o.Labels, pod.ObjectMeta.Labels; !reflect.DeepEqual(got, expected) {
398 t.Errorf("expected labels to be %v, got %v", expected, got)
399 }
400 }
401
402 func TestGetObjectMeta(t *testing.T) {
403 for i := 0; i < 100; i++ {
404 u := &unstructured.Unstructured{Object: map[string]interface{}{
405 "metadata": map[string]interface{}{
406 "name": "good",
407 "Name": "bad1",
408 "nAme": "bad2",
409 "naMe": "bad3",
410 "namE": "bad4",
411
412 "namespace": "good",
413 "Namespace": "bad1",
414 "nAmespace": "bad2",
415 "naMespace": "bad3",
416 "namEspace": "bad4",
417
418 "creationTimestamp": "a",
419 },
420 }}
421
422 meta, _, err := GetObjectMeta(u.Object, true)
423 if err != nil {
424 t.Fatal(err)
425 }
426 if meta.Name != "good" || meta.Namespace != "good" {
427 t.Fatalf("got %#v", meta)
428 }
429 }
430 }
431
View as plain text