1
16
17
18 package apimachinery
19
20 import (
21 "bytes"
22 "context"
23 "fmt"
24 "strings"
25 "time"
26
27 "github.com/onsi/ginkgo/v2"
28 "github.com/onsi/gomega"
29 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
31 apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
32 "k8s.io/apimachinery/pkg/api/meta"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
35 "k8s.io/apimachinery/pkg/runtime/schema"
36 "k8s.io/apimachinery/pkg/util/uuid"
37 "k8s.io/apimachinery/pkg/util/wait"
38 utilyaml "k8s.io/apimachinery/pkg/util/yaml"
39 "k8s.io/client-go/dynamic"
40 "k8s.io/kubernetes/test/e2e/framework"
41 "k8s.io/kubernetes/test/utils/crd"
42 )
43
44 var _ = SIGDescribe("CRDValidationRatcheting [Privileged:ClusterAdmin]", framework.WithFeatureGate(apiextensionsfeatures.CRDValidationRatcheting), func() {
45 f := framework.NewDefaultFramework("crd-validation-ratcheting")
46 var apiExtensionClient *clientset.Clientset
47 var dynamicClient dynamic.Interface
48 var restmapper meta.RESTMapper
49 var ctx context.Context
50 var testCRD *crd.TestCrd
51 var testCRDGVR schema.GroupVersionResource
52
53 ginkgo.BeforeEach(func() {
54 var err error
55 ctx = context.TODO()
56
57 apiExtensionClient, err = clientset.NewForConfig(f.ClientConfig())
58 framework.ExpectNoError(err, "initializing apiExtensionClient")
59
60 dynamicClient, err = dynamic.NewForConfig(f.ClientConfig())
61 framework.ExpectNoError(err, "initializing dynamicClient")
62
63 testCRD, err = crd.CreateTestCRD(f)
64 framework.ExpectNoError(err, "creating test CRD")
65
66 testCRDGVR = schema.GroupVersionResource{
67 Group: testCRD.Crd.Spec.Group,
68 Version: testCRD.Crd.Spec.Versions[0].Name,
69 Resource: testCRD.Crd.Spec.Names.Plural,
70 }
71
72
73
74 restmapper = &fakeRESTMapper{
75 m: map[schema.GroupVersionResource]schema.GroupVersionKind{
76 testCRDGVR: {
77 Group: testCRDGVR.Group,
78 Version: testCRDGVR.Version,
79 Kind: testCRD.Crd.Spec.Names.Kind,
80 },
81 },
82 }
83 })
84
85 ginkgo.AfterEach(func() {
86 framework.ExpectNoError(testCRD.CleanUp(ctx), "cleaning up test CRD")
87 })
88
89
90
91
92 applyPatch := func(gvr schema.GroupVersionResource, name string, patchObj map[string]interface{}) error {
93 gvk, err := restmapper.KindFor(gvr)
94 if err != nil {
95 return fmt.Errorf("no mapping for %s", gvr)
96 }
97 patch := &unstructured.Unstructured{
98 Object: patchObj,
99 }
100 patch = patch.DeepCopy()
101
102 patch.SetKind(gvk.Kind)
103 patch.SetAPIVersion(gvk.GroupVersion().Identifier())
104 patch.SetName(name)
105 patch.SetNamespace("default")
106
107 _, err = dynamicClient.
108 Resource(gvr).
109 Namespace(patch.GetNamespace()).
110 Apply(
111 context.TODO(),
112 patch.GetName(),
113 patch,
114 metav1.ApplyOptions{
115 FieldManager: "manager",
116 })
117
118 return err
119 }
120
121
122
123 updateCRDSchema := func(gvr schema.GroupVersionResource, props apiextensionsv1.JSONSchemaProps) error {
124 myCRD, err := apiExtensionClient.
125 ApiextensionsV1().
126 CustomResourceDefinitions().
127 Get(
128 context.TODO(),
129 gvr.Resource+"."+gvr.Group,
130 metav1.GetOptions{},
131 )
132 if err != nil {
133 return fmt.Errorf("getting CRD %s: %v", gvr, err)
134 }
135
136
137
138 uniqueErrorUUID := string(uuid.NewUUID())
139 sentinelName := "__update_schema_sentinel_field__"
140 props.Properties[sentinelName] = apiextensionsv1.JSONSchemaProps{
141 Type: "string",
142 Enum: []apiextensionsv1.JSON{
143 {Raw: []byte(`"` + uniqueErrorUUID + `"`)},
144 },
145 }
146
147 for i, v := range myCRD.Spec.Versions {
148 if v.Name == gvr.Version {
149 myCRD.Spec.Versions[i].Schema.OpenAPIV3Schema = &props
150 }
151 }
152
153 _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), myCRD, metav1.UpdateOptions{
154 FieldManager: "manager",
155 })
156 if err != nil {
157 return fmt.Errorf("updating CRD %s: %v", gvr, err)
158 }
159
160
161
162
163 counter := 0
164 err = wait.PollUntilContextCancel(context.TODO(), 100*time.Millisecond, true, func(_ context.Context) (done bool, err error) {
165 counter += 1
166 err = applyPatch(gvr, "sentinel-resource", map[string]interface{}{
167 "metadata": map[string]interface{}{
168 "finalizers": []interface{}{
169 "unqualified-finalizer",
170 },
171 "labels": map[string]interface{}{
172 "#inv/($%)/alid=": ">htt$://",
173 },
174 },
175
176 sentinelName: fmt.Sprintf("%v", counter),
177 })
178
179 if err == nil {
180 return false, fmt.Errorf("expected error when creating sentinel resource")
181 }
182
183
184
185 if strings.Contains(err.Error(), uniqueErrorUUID) {
186 return true, nil
187 }
188 return false, nil
189
190 })
191 if err == nil {
192 return nil
193 }
194 return fmt.Errorf("waiting for CRD %s to be updated: %v", gvr, err)
195 }
196
197 ginkgo.It("MUST NOT fail to update a resource due to JSONSchema errors on unchanged correlatable fields", func() {
198 sch, err := parseSchema(`
199 type: object
200 properties:
201 field: {type: string, enum: ["notfoo"]}
202 struct:
203 type: object
204 properties:
205 field: {type: string, enum: ["notfoo"]}
206 list:
207 type: array
208 x-kubernetes-list-type: map
209 x-kubernetes-list-map-keys: ["key"]
210 items:
211 type: object
212 properties:
213 key: {type: string}
214 field: {type: string, enum: ["notfoo"]}
215 required:
216 - key
217 map:
218 type: object
219 additionalProperties:
220 type: object
221 properties:
222 field: {type: string, enum: ["notfoo"]}
223 `)
224 framework.ExpectNoError(err, "parsing schema")
225
226 instance, err := parseUnstructured(`
227 field: "foo"
228 struct:
229 field: "foo"
230 list:
231 - key: "first"
232 field: "foo"
233 map:
234 foo:
235 field: "foo"
236 `)
237 framework.ExpectNoError(err, "parsing test resource")
238
239 ginkgo.By("creating test resource with correlatable fields")
240 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
241 ginkgo.By("updating CRD schema with constraints on correlatable fields to make instance invalid")
242 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
243
244
245
246 ginkgo.By("updating label on now-invalid test resource")
247 instance.SetLabels(map[string]string{
248 "foo": "bar",
249 })
250 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "update label on test resource")
251 })
252
253 ginkgo.It("MUST fail to update a resource due to JSONSchema errors on unchanged uncorrelatable fields", func() {
254 ginkgo.By("creating test resource with correlatable fields")
255 instance, err := parseUnstructured(`
256 setArray:
257 - "foo"
258 - "bar"
259 - "baz"
260 atomicArray:
261 - "foo"
262 - "bar"
263 - "baz"
264 `)
265 framework.ExpectNoError(err, "parsing test resource")
266 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
267
268 ginkgo.By("updating CRD schema with constraints on uncorrelatable fields to make instance invalid")
269 sch, err := parseSchema(`
270 type: object
271 properties:
272 atomicArray:
273 type: array
274 items:
275 type: string
276 enum: ["notfoo", "notbar", "notbaz"]
277 setArray:
278 type: array
279 x-kubernetes-list-type: set
280 items:
281 type: string
282 enum: ["notfoo", "notbar", "notbaz"]
283 `)
284 framework.ExpectNoError(err, "parsing schema")
285 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
286
287 ginkgo.By("updating label on now-invalid test resource")
288 instance, err = parseUnstructured(`
289 setArray:
290 - "foo"
291 - "bar"
292 - "baz"
293 - "notfoo"
294 atomicArray:
295 - "foo"
296 - "bar"
297 - "baz"
298 - "notfoo"
299 `)
300 framework.ExpectNoError(err, "parsing modified resource")
301 instance.SetLabels(map[string]string{
302 "foo": "bar",
303 })
304 err = applyPatch(testCRDGVR, "test-resource", instance.Object)
305 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("atomicArray")))
306 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("setArray")))
307 })
308
309 ginkgo.It("MUST fail to update a resource due to JSONSchema errors on changed fields", func() {
310 ginkgo.By("creating an initial object with many correlatable fields")
311 instance, err := parseUnstructured(`
312 field: "foo"
313 struct:
314 field: "foo"
315 list:
316 - key: "foo"
317 field: "foo"
318 - key: "bar"
319 field: "foo"
320 map:
321 foo:
322 field: "foo"
323 bar:
324 field: "foo"
325 `)
326 framework.ExpectNoError(err, "parsing test resource")
327 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
328
329 ginkgo.By("updating CRD schema with constraints on correlatable fields to make instance invalid")
330 sch, err := parseSchema(`
331 type: object
332 properties:
333 field: {type: string, enum: ["foo"]}
334 struct:
335 type: object
336 properties:
337 field: {type: string, enum: ["foo"]}
338 list:
339 type: array
340 x-kubernetes-list-type: map
341 x-kubernetes-list-map-keys: ["key"]
342 items:
343 type: object
344 properties:
345 key: {type: string}
346 field: {type: string, enum: ["foo"]}
347 required:
348 - key
349 map:
350 type: object
351 additionalProperties:
352 type: object
353 properties:
354 field: {type: string, enum: ["foo"]}
355 `)
356
357 framework.ExpectNoError(err, "parsing schema")
358 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
359
360 ginkgo.By("changing every field to invalid value")
361 modifiedInstance, err := parseUnstructured(`
362 field: "notfoo"
363 struct:
364 field: "notfoo"
365 list:
366 - key: "foo"
367 field: "notfoo"
368 - key: "bar"
369 field: "notfoo"
370 map:
371 foo:
372 field: "notfoo"
373 bar:
374 field: "notfoo"
375 `)
376 framework.ExpectNoError(err, "parsing modified resource")
377 err = applyPatch(testCRDGVR, "test-resource", modifiedInstance.Object)
378 for _, fieldPath := range []string{
379 "field",
380 "struct.field",
381 "list[0].field",
382 "list[1].field",
383 "map.foo.field",
384 "map.bar.field",
385 } {
386 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring(fieldPath)))
387 }
388 })
389
390 ginkgo.It("MUST NOT fail to update a resource due to CRD Validation Rule errors on unchanged correlatable fields", func() {
391 ginkgo.By("creating an initial object with many correlatable fields")
392 instance, err := parseUnstructured(`
393 field: "notfoo"
394 struct:
395 field: "notfoo"
396 list:
397 - key: "foo"
398 field: "notfoo"
399 - key: "bar"
400 field: "notfoo"
401 map:
402 foo:
403 field: "notfoo"
404 bar:
405 field: "notfoo"
406 `)
407 framework.ExpectNoError(err, "parsing test resource")
408 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
409
410 ginkgo.By("updating CRD schema with constraints on correlatable fields to make instance invalid")
411 sch, err := parseSchema(`
412 type: object
413 properties:
414 field:
415 type: string
416 x-kubernetes-validations:
417 - rule: self == "foo"
418 otherField:
419 type: string
420 struct:
421 type: object
422 properties:
423 field:
424 type: string
425 x-kubernetes-validations:
426 - rule: self == "foo"
427 otherField:
428 type: string
429 list:
430 type: array
431 x-kubernetes-list-type: map
432 x-kubernetes-list-map-keys: ["key"]
433 items:
434 type: object
435 properties:
436 key:
437 type: string
438 field:
439 type: string
440 x-kubernetes-validations:
441 - rule: self == "foo"
442 otherField:
443 type: string
444 required:
445 - key
446 map:
447 type: object
448 additionalProperties:
449 type: object
450 properties:
451 field:
452 type: string
453 x-kubernetes-validations:
454 - rule: self == "foo"
455 otherField:
456 type: string
457 `)
458
459 framework.ExpectNoError(err, "parsing schema")
460 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
461
462 ginkgo.By("introducing new values, but leaving invalid old correlatable values untouched")
463 modifiedInstance, err := parseUnstructured(`
464 field: "notfoo"
465 otherField: "doesntmatter"
466 struct:
467 field: "notfoo"
468 otherField: "doesntmatter"
469 list:
470 - key: "foo"
471 field: "notfoo"
472 otherField: "doesntmatter"
473 - key: "bar"
474 field: "notfoo"
475 otherField: "doesntmatter"
476 - key: "baz"
477 field: "foo"
478 otherField: "doesntmatter"
479 map:
480 foo:
481 field: "notfoo"
482 otherField: "doesntmatter"
483 bar:
484 field: "notfoo"
485 otherField: "doesntmatter"
486 `)
487 framework.ExpectNoError(err, "parsing test resource")
488 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", modifiedInstance.Object), "failed updating test resource")
489 })
490
491 ginkgo.It("MUST fail to update a resource due to CRD Validation Rule errors on unchanged uncorrelatable fields", func() {
492 ginkgo.By("creating test resource with correlatable fields")
493 instance, err := parseUnstructured(`
494 setArray:
495 - "foo"
496 - "bar"
497 - "baz"
498 atomicArray:
499 - "foo"
500 - "bar"
501 - "baz"
502 `)
503 framework.ExpectNoError(err, "parsing test resource")
504 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
505
506 ginkgo.By("updating CRD schema with constraints on uncorrelatable fields to make instance invalid")
507 sch, err := parseSchema(`
508 type: object
509 properties:
510 atomicArray:
511 type: array
512 items:
513 type: string
514 x-kubernetes-validations:
515 - rule: self != "foo"
516 setArray:
517 type: array
518 x-kubernetes-list-type: set
519 items:
520 type: string
521 x-kubernetes-validations:
522 - rule: self != "foo"
523 `)
524 framework.ExpectNoError(err, "parsing schema")
525 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
526
527 ginkgo.By("updating label and adding valid elements to invalid lists")
528 instance, err = parseUnstructured(`
529 setArray:
530 - "foo"
531 - "bar"
532 - "baz"
533 - "notfoo"
534 atomicArray:
535 - "foo"
536 - "bar"
537 - "baz"
538 - "notfoo"
539 `)
540 framework.ExpectNoError(err, "parsing modified resource")
541 instance.SetLabels(map[string]string{
542 "foo": "bar",
543 })
544 err = applyPatch(testCRDGVR, "test-resource", instance.Object)
545 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("atomicArray")))
546 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("setArray")))
547 })
548
549 ginkgo.It("MUST fail to update a resource due to CRD Validation Rule errors on changed fields", func() {
550 ginkgo.By("creating an initial object with many correlatable fields")
551 instance, err := parseUnstructured(`
552 field: "foo"
553 struct:
554 field: "foo"
555 list:
556 - key: "foo"
557 field: "foo"
558 - key: "bar"
559 field: "foo"
560 map:
561 foo:
562 field: "foo"
563 bar:
564 field: "foo"
565 `)
566 framework.ExpectNoError(err, "parsing test resource")
567 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
568
569 ginkgo.By("updating CRD schema with constraints on correlatable fields to make instance invalid")
570 sch, err := parseSchema(`
571 type: object
572 properties:
573 field:
574 type: string
575 x-kubernetes-validations:
576 - rule: self == "foo"
577 struct:
578 type: object
579 properties:
580 field:
581 type: string
582 x-kubernetes-validations:
583 - rule: self == "foo"
584 list:
585 type: array
586 x-kubernetes-list-type: map
587 x-kubernetes-list-map-keys:
588 - key
589 items:
590 type: object
591 properties:
592 key:
593 type: string
594 field:
595 type: string
596 x-kubernetes-validations:
597 - rule: self == "foo"
598 required:
599 - key
600 map:
601 type: object
602 additionalProperties:
603 type: object
604 properties:
605 field:
606 type: string
607 x-kubernetes-validations:
608 - rule: self == "foo"
609 `)
610
611 framework.ExpectNoError(err, "parsing schema")
612 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
613
614 ginkgo.By("changing every field to invalid value")
615 modifiedInstance, err := parseUnstructured(`
616 field: "notfoo"
617 struct:
618 field: "notfoo"
619 list:
620 - key: "foo"
621 field: "notfoo"
622 - key: "bar"
623 field: "notfoo"
624 map:
625 foo:
626 field: "notfoo"
627 bar:
628 field: "notfoo"
629 `)
630 framework.ExpectNoError(err, "parsing modified resource")
631 err = applyPatch(testCRDGVR, "test-resource", modifiedInstance.Object)
632 for _, fieldPath := range []string{
633 "field",
634 "struct.field",
635 "list[0].field",
636 "list[1].field",
637 "map[foo].field",
638 "map[bar].field",
639 } {
640 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring(fieldPath)))
641 }
642 })
643
644 ginkgo.It("MUST NOT ratchet errors raised by transition rules", func() {
645 ginkgo.By("creating an initial object with many correlatable fields")
646 instance, err := parseUnstructured(`
647 field: "foo"
648 struct:
649 field: "foo"
650 list:
651 - key: "foo"
652 field: "foo"
653 - key: "bar"
654 field: "foo"
655 map:
656 foo:
657 field: "foo"
658 bar:
659 field: "foo"
660 `)
661 framework.ExpectNoError(err, "parsing test resource")
662 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
663
664 ginkgo.By("updating CRD schema with constraints on correlatable fields to make instance invalid")
665 sch, err := parseSchema(`
666 type: object
667 properties:
668 field:
669 type: string
670 maxLength: 5
671 x-kubernetes-validations:
672 - rule: self != oldSelf
673 struct:
674 type: object
675 properties:
676 field:
677 type: string
678 maxLength: 5
679 x-kubernetes-validations:
680 - rule: self != oldSelf
681 list:
682 type: array
683 maxItems: 5
684 x-kubernetes-list-type: map
685 x-kubernetes-list-map-keys: [key]
686 items:
687 type: object
688 properties:
689 key: {type: string}
690 field:
691 type: string
692 maxLength: 5
693 x-kubernetes-validations:
694 - rule: self != oldSelf
695 required:
696 - key
697 map:
698 type: object
699 maxProperties: 5
700 additionalProperties:
701 type: object
702 properties:
703 field:
704 type: string
705 maxLength: 5
706 x-kubernetes-validations:
707 - rule: self != oldSelf
708 `)
709
710 framework.ExpectNoError(err, "parsing schema")
711 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
712
713 ginkgo.By("updating a label on the test resource")
714 instance.SetLabels(map[string]string{
715 "foo": "bar",
716 })
717 err = applyPatch(testCRDGVR, "test-resource", instance.Object)
718 for _, fieldPath := range []string{
719 "field",
720 "struct.field",
721 "list[0].field",
722 "list[1].field",
723 "map[foo].field",
724 "map[bar].field",
725 } {
726 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring(fieldPath)))
727 }
728 })
729
730 ginkgo.It("MUST evaluate a CRD Validation Rule with oldSelf = nil for new values when optionalOldSelf is true", func() {
731 ginkgo.By("updating CRD schema to use optionalOldSelf")
732 sch, err := parseSchema(`
733 type: object
734 properties:
735 field:
736 type: string
737 maxLength: 5
738 x-kubernetes-validations:
739 - rule: "!oldSelf.hasValue() || self != oldSelf.value()"
740 optionalOldSelf: true
741 struct:
742 type: object
743 properties:
744 field:
745 type: string
746 maxLength: 5
747 x-kubernetes-validations:
748 - rule: "!oldSelf.hasValue() || self != oldSelf.value()"
749 optionalOldSelf: true
750 list:
751 type: array
752 maxItems: 5
753 x-kubernetes-list-type: map
754 x-kubernetes-list-map-keys: [key]
755 items:
756 type: object
757 properties:
758 key: {type: string}
759 field:
760 type: string
761 maxLength: 5
762 x-kubernetes-validations:
763 - rule: "!oldSelf.hasValue() || self != oldSelf.value()"
764 optionalOldSelf: true
765 required:
766 - key
767 map:
768 type: object
769 maxProperties: 5
770 additionalProperties:
771 type: object
772 properties:
773 field:
774 type: string
775 maxLength: 5
776 x-kubernetes-validations:
777 - rule: "!oldSelf.hasValue() || self != oldSelf.value()"
778 optionalOldSelf: true
779 `)
780 framework.ExpectNoError(err, "parsing schema")
781 framework.ExpectNoError(updateCRDSchema(testCRDGVR, *sch), "failed to update schema")
782
783 ginkgo.By("creating an object")
784 instance, err := parseUnstructured(`
785 field: "foo"
786 struct:
787 field: "foo"
788 list:
789 - key: "foo"
790 field: "foo"
791 - key: "bar"
792 field: "foo"
793 map:
794 foo:
795 field: "foo"
796 bar:
797 field: "foo"
798 `)
799 framework.ExpectNoError(err, "parsing test resource")
800 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed creating test resource")
801
802 ginkgo.By("updating a label on the test resource")
803 instance.SetLabels(map[string]string{
804 "foo": "bar",
805 })
806 err = applyPatch(testCRDGVR, "test-resource", instance.Object)
807 for _, fieldPath := range []string{
808 "field",
809 "struct.field",
810 "list[0].field",
811 "list[1].field",
812 "map[foo].field",
813 "map[bar].field",
814 } {
815 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring(fieldPath)))
816 }
817
818 ginkgo.By("updating all fields of the object to show the condition is checked")
819 instance, err = parseUnstructured(`
820 field: "new"
821 struct:
822 field: "new"
823 list:
824 - key: "foo"
825 field: "new"
826 - key: "bar"
827 field: "new"
828 map:
829 foo:
830 field: "new"
831 bar:
832 field: "new"
833 `)
834 framework.ExpectNoError(err, "parsing test resource")
835 framework.ExpectNoError(applyPatch(testCRDGVR, "test-resource", instance.Object), "failed updating test resource")
836 })
837
838 })
839
840 func parseSchema(source string) (*apiextensionsv1.JSONSchemaProps, error) {
841 source, err := fixTabs(source)
842 if err != nil {
843 return nil, err
844 }
845
846 d := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(source), 4096)
847 props := &apiextensionsv1.JSONSchemaProps{}
848 return props, d.Decode(props)
849 }
850
851 func parseUnstructured(source string) (*unstructured.Unstructured, error) {
852 source, err := fixTabs(source)
853 if err != nil {
854 return nil, err
855 }
856
857 d := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(source), 4096)
858 obj := &unstructured.Unstructured{}
859 return obj, d.Decode(&obj.Object)
860 }
861
862
863
864
865
866
867
868 func fixTabs(in string) (string, error) {
869 lines := bytes.Split([]byte(in), []byte{'\n'})
870 if len(lines[0]) == 0 && len(lines) > 1 {
871 lines = lines[1:]
872 }
873
874 var prefix []byte
875 for _, c := range lines[0] {
876 if c != '\t' {
877 break
878 }
879 prefix = append(prefix, byte('\t'))
880 }
881
882 for i := range lines {
883 line := lines[i]
884
885 if i == len(lines)-1 && len(line) <= len(prefix) && bytes.TrimSpace(line) == nil {
886 lines[i] = []byte{}
887 break
888 }
889 if !bytes.HasPrefix(line, prefix) {
890 minRange := i - 5
891 maxRange := i + 5
892 if minRange < 0 {
893 minRange = 0
894 }
895 if maxRange > len(lines) {
896 maxRange = len(lines)
897 }
898 return "", fmt.Errorf("line %d doesn't start with expected number (%d) of tabs (%v-%v):\n%v", i, len(prefix), minRange, maxRange, string(bytes.Join(lines[minRange:maxRange], []byte{'\n'})))
899 }
900 lines[i] = line[len(prefix):]
901 }
902 joined := string(bytes.Join(lines, []byte{'\n'}))
903
904
905
906 return strings.ReplaceAll(joined, "\t", " "), nil
907 }
908
909 type fakeRESTMapper struct {
910 m map[schema.GroupVersionResource]schema.GroupVersionKind
911 }
912
913 func (f *fakeRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
914 gvk, ok := f.m[resource]
915 if !ok {
916 return schema.GroupVersionKind{}, fmt.Errorf("no mapping for %s", resource)
917 }
918 return gvk, nil
919 }
920
921 func (f *fakeRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
922 return nil, nil
923 }
924
925 func (f *fakeRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
926 return schema.GroupVersionResource{}, nil
927 }
928
929 func (f *fakeRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
930 return nil, nil
931 }
932
933 func (f *fakeRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
934 return nil, nil
935 }
936
937 func (f *fakeRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
938 return nil, nil
939 }
940
941 func (f *fakeRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
942 return "", nil
943 }
944
View as plain text