1
16
17 package validation
18
19 import (
20 "context"
21 "math"
22 "math/rand"
23 "reflect"
24 "strings"
25 "testing"
26
27 "github.com/google/cel-go/cel"
28
29 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
30 apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
31 apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
32 structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
33 celschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
34 apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
35 "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/runtime"
38 "k8s.io/apimachinery/pkg/runtime/serializer"
39 "k8s.io/apimachinery/pkg/util/json"
40 "k8s.io/apimachinery/pkg/util/validation/field"
41 "k8s.io/apimachinery/pkg/util/version"
42 "k8s.io/apiserver/pkg/cel/environment"
43 "k8s.io/apiserver/pkg/cel/library"
44 utilfeature "k8s.io/apiserver/pkg/util/feature"
45 featuregatetesting "k8s.io/component-base/featuregate/testing"
46 "k8s.io/utils/pointer"
47 "k8s.io/utils/ptr"
48 )
49
50 type validationMatch struct {
51 path *field.Path
52 errorType field.ErrorType
53 containsString string
54 }
55
56 func required(path ...string) validationMatch {
57 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeRequired}
58 }
59 func invalid(path ...string) validationMatch {
60 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
61 }
62 func invalidtypecode(path ...string) validationMatch {
63 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeTypeInvalid}
64 }
65 func invalidIndex(index int, path ...string) validationMatch {
66 return validationMatch{path: field.NewPath(path[0], path[1:]...).Index(index), errorType: field.ErrorTypeInvalid}
67 }
68 func unsupported(path ...string) validationMatch {
69 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeNotSupported}
70 }
71 func immutable(path ...string) validationMatch {
72 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
73 }
74 func forbidden(path ...string) validationMatch {
75 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeForbidden}
76 }
77 func duplicate(path ...string) validationMatch {
78 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeDuplicate}
79 }
80 func tooMany(path ...string) validationMatch {
81 return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeTooMany}
82 }
83
84 func (v validationMatch) matches(err *field.Error) bool {
85 return err.Type == v.errorType && err.Field == v.path.String() && strings.Contains(err.Error(), v.containsString)
86 }
87
88 func (v validationMatch) contains(s string) validationMatch {
89 v.containsString = s
90 return v
91 }
92
93 func strPtr(s string) *string { return &s }
94
95 func TestValidateCustomResourceDefinition(t *testing.T) {
96 singleVersionList := []apiextensions.CustomResourceDefinitionVersion{
97 {
98 Name: "version",
99 Served: true,
100 Storage: true,
101 },
102 }
103 tests := []struct {
104 name string
105 resource *apiextensions.CustomResourceDefinition
106 errors []validationMatch
107 }{
108 {
109 name: "invalid types disallowed",
110 resource: &apiextensions.CustomResourceDefinition{
111 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
112 Spec: apiextensions.CustomResourceDefinitionSpec{
113 Group: "group.com",
114 Scope: apiextensions.ResourceScope("Cluster"),
115 Names: apiextensions.CustomResourceDefinitionNames{
116 Plural: "plural",
117 Singular: "singular",
118 Kind: "Plural",
119 ListKind: "PluralList",
120 },
121 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
122 Validation: &apiextensions.CustomResourceValidation{
123 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
124 Type: "object",
125 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus"}},
126 },
127 },
128 PreserveUnknownFields: pointer.BoolPtr(false),
129 },
130 Status: apiextensions.CustomResourceDefinitionStatus{
131 StoredVersions: []string{"version"},
132 },
133 },
134 errors: []validationMatch{
135 unsupported("spec.validation.openAPIV3Schema.properties[foo].type"),
136 },
137 },
138 {
139 name: "webhookconfig: invalid port 0",
140 resource: &apiextensions.CustomResourceDefinition{
141 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
142 Spec: apiextensions.CustomResourceDefinitionSpec{
143 Group: "group.com",
144 Scope: apiextensions.ResourceScope("Cluster"),
145 Names: apiextensions.CustomResourceDefinitionNames{
146 Plural: "plural",
147 Singular: "singular",
148 Kind: "Plural",
149 ListKind: "PluralList",
150 },
151 Versions: []apiextensions.CustomResourceDefinitionVersion{
152 {
153 Name: "version",
154 Served: true,
155 Storage: true,
156 },
157 {
158 Name: "version2",
159 Served: true,
160 Storage: false,
161 },
162 },
163 Conversion: &apiextensions.CustomResourceConversion{
164 Strategy: apiextensions.ConversionStrategyType("Webhook"),
165 WebhookClientConfig: &apiextensions.WebhookClientConfig{
166 Service: &apiextensions.ServiceReference{
167 Name: "n",
168 Namespace: "ns",
169 Port: 0,
170 },
171 },
172 },
173 Validation: &apiextensions.CustomResourceValidation{
174 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
175 Type: "object",
176 },
177 },
178 PreserveUnknownFields: pointer.BoolPtr(false),
179 },
180 Status: apiextensions.CustomResourceDefinitionStatus{
181 StoredVersions: []string{"version"},
182 },
183 },
184 errors: []validationMatch{
185 invalid("spec", "conversion", "webhookClientConfig", "service", "port"),
186 },
187 },
188 {
189 name: "webhookconfig: invalid port 65536",
190 resource: &apiextensions.CustomResourceDefinition{
191 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
192 Spec: apiextensions.CustomResourceDefinitionSpec{
193 Group: "group.com",
194 Scope: apiextensions.ResourceScope("Cluster"),
195 Names: apiextensions.CustomResourceDefinitionNames{
196 Plural: "plural",
197 Singular: "singular",
198 Kind: "Plural",
199 ListKind: "PluralList",
200 },
201 Versions: []apiextensions.CustomResourceDefinitionVersion{
202 {
203 Name: "version",
204 Served: true,
205 Storage: true,
206 },
207 {
208 Name: "version2",
209 Served: true,
210 Storage: false,
211 },
212 },
213 Conversion: &apiextensions.CustomResourceConversion{
214 Strategy: apiextensions.ConversionStrategyType("Webhook"),
215 WebhookClientConfig: &apiextensions.WebhookClientConfig{
216 Service: &apiextensions.ServiceReference{
217 Name: "n",
218 Namespace: "ns",
219 Port: 65536,
220 },
221 },
222 },
223 Validation: &apiextensions.CustomResourceValidation{
224 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
225 Type: "object",
226 },
227 },
228 PreserveUnknownFields: pointer.BoolPtr(false),
229 },
230 Status: apiextensions.CustomResourceDefinitionStatus{
231 StoredVersions: []string{"version"},
232 },
233 },
234 errors: []validationMatch{
235 invalid("spec", "conversion", "webhookClientConfig", "service", "port"),
236 },
237 },
238 {
239 name: "webhookconfig: both service and URL provided",
240 resource: &apiextensions.CustomResourceDefinition{
241 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
242 Spec: apiextensions.CustomResourceDefinitionSpec{
243 Group: "group.com",
244 Scope: apiextensions.ResourceScope("Cluster"),
245 Names: apiextensions.CustomResourceDefinitionNames{
246 Plural: "plural",
247 Singular: "singular",
248 Kind: "Plural",
249 ListKind: "PluralList",
250 },
251 Versions: []apiextensions.CustomResourceDefinitionVersion{
252 {
253 Name: "version",
254 Served: true,
255 Storage: true,
256 },
257 {
258 Name: "version2",
259 Served: true,
260 Storage: false,
261 },
262 },
263 Conversion: &apiextensions.CustomResourceConversion{
264 Strategy: apiextensions.ConversionStrategyType("Webhook"),
265 WebhookClientConfig: &apiextensions.WebhookClientConfig{
266 URL: strPtr("https://example.com/webhook"),
267 Service: &apiextensions.ServiceReference{
268 Name: "n",
269 Namespace: "ns",
270 },
271 },
272 },
273 Validation: &apiextensions.CustomResourceValidation{
274 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
275 Type: "object",
276 },
277 },
278 PreserveUnknownFields: pointer.BoolPtr(false),
279 },
280 Status: apiextensions.CustomResourceDefinitionStatus{
281 StoredVersions: []string{"version"},
282 },
283 },
284 errors: []validationMatch{
285 required("spec", "conversion", "webhookClientConfig"),
286 },
287 },
288 {
289 name: "webhookconfig: blank URL",
290 resource: &apiextensions.CustomResourceDefinition{
291 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
292 Spec: apiextensions.CustomResourceDefinitionSpec{
293 Group: "group.com",
294 Scope: apiextensions.ResourceScope("Cluster"),
295 Names: apiextensions.CustomResourceDefinitionNames{
296 Plural: "plural",
297 Singular: "singular",
298 Kind: "Plural",
299 ListKind: "PluralList",
300 },
301 Versions: []apiextensions.CustomResourceDefinitionVersion{
302 {
303 Name: "version",
304 Served: true,
305 Storage: true,
306 },
307 {
308 Name: "version2",
309 Served: true,
310 Storage: false,
311 },
312 },
313 Conversion: &apiextensions.CustomResourceConversion{
314 Strategy: apiextensions.ConversionStrategyType("Webhook"),
315 WebhookClientConfig: &apiextensions.WebhookClientConfig{
316 URL: strPtr(""),
317 },
318 },
319 Validation: &apiextensions.CustomResourceValidation{
320 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
321 Type: "object",
322 },
323 },
324 PreserveUnknownFields: pointer.BoolPtr(false),
325 },
326 Status: apiextensions.CustomResourceDefinitionStatus{
327 StoredVersions: []string{"version"},
328 },
329 },
330 errors: []validationMatch{
331 invalid("spec", "conversion", "webhookClientConfig", "url"),
332 invalid("spec", "conversion", "webhookClientConfig", "url"),
333 },
334 },
335 {
336 name: "webhookconfig_should_not_be_set",
337 resource: &apiextensions.CustomResourceDefinition{
338 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
339 Spec: apiextensions.CustomResourceDefinitionSpec{
340 Group: "group.com",
341 Scope: apiextensions.ResourceScope("Cluster"),
342 Names: apiextensions.CustomResourceDefinitionNames{
343 Plural: "plural",
344 Singular: "singular",
345 Kind: "Plural",
346 ListKind: "PluralList",
347 },
348 Versions: []apiextensions.CustomResourceDefinitionVersion{
349 {
350 Name: "version",
351 Served: true,
352 Storage: true,
353 },
354 {
355 Name: "version2",
356 Served: true,
357 Storage: false,
358 },
359 },
360 Conversion: &apiextensions.CustomResourceConversion{
361 Strategy: apiextensions.ConversionStrategyType("None"),
362 WebhookClientConfig: &apiextensions.WebhookClientConfig{
363 URL: strPtr("https://example.com/webhook"),
364 },
365 },
366 PreserveUnknownFields: pointer.BoolPtr(false),
367 Validation: &apiextensions.CustomResourceValidation{
368 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
369 Type: "object",
370 },
371 },
372 },
373 Status: apiextensions.CustomResourceDefinitionStatus{
374 StoredVersions: []string{"version"},
375 },
376 },
377 errors: []validationMatch{
378 forbidden("spec", "conversion", "webhookClientConfig"),
379 },
380 },
381 {
382 name: "ConversionReviewVersions_should_not_be_set",
383 resource: &apiextensions.CustomResourceDefinition{
384 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
385 Spec: apiextensions.CustomResourceDefinitionSpec{
386 Group: "group.com",
387 Scope: apiextensions.ResourceScope("Cluster"),
388 Names: apiextensions.CustomResourceDefinitionNames{
389 Plural: "plural",
390 Singular: "singular",
391 Kind: "Plural",
392 ListKind: "PluralList",
393 },
394 Versions: []apiextensions.CustomResourceDefinitionVersion{
395 {
396 Name: "version",
397 Served: true,
398 Storage: true,
399 },
400 {
401 Name: "version2",
402 Served: true,
403 Storage: false,
404 },
405 },
406 Conversion: &apiextensions.CustomResourceConversion{
407 Strategy: apiextensions.ConversionStrategyType("None"),
408 ConversionReviewVersions: []string{"v1beta1"},
409 },
410 PreserveUnknownFields: pointer.BoolPtr(false),
411 Validation: &apiextensions.CustomResourceValidation{
412 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
413 Type: "object",
414 },
415 },
416 },
417 Status: apiextensions.CustomResourceDefinitionStatus{
418 StoredVersions: []string{"version"},
419 },
420 },
421 errors: []validationMatch{
422 forbidden("spec", "conversion", "conversionReviewVersions"),
423 },
424 },
425 {
426 name: "webhookconfig: invalid ConversionReviewVersion",
427 resource: &apiextensions.CustomResourceDefinition{
428 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
429 Spec: apiextensions.CustomResourceDefinitionSpec{
430 Group: "group.com",
431 Scope: apiextensions.ResourceScope("Cluster"),
432 Names: apiextensions.CustomResourceDefinitionNames{
433 Plural: "plural",
434 Singular: "singular",
435 Kind: "Plural",
436 ListKind: "PluralList",
437 },
438 Versions: []apiextensions.CustomResourceDefinitionVersion{
439 {
440 Name: "version",
441 Served: true,
442 Storage: true,
443 },
444 {
445 Name: "version2",
446 Served: true,
447 Storage: false,
448 },
449 },
450 Conversion: &apiextensions.CustomResourceConversion{
451 Strategy: apiextensions.ConversionStrategyType("Webhook"),
452 WebhookClientConfig: &apiextensions.WebhookClientConfig{
453 URL: strPtr("https://example.com/webhook"),
454 },
455 ConversionReviewVersions: []string{"invalid-version"},
456 },
457 Validation: &apiextensions.CustomResourceValidation{
458 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
459 Type: "object",
460 },
461 },
462 PreserveUnknownFields: pointer.BoolPtr(false),
463 },
464 Status: apiextensions.CustomResourceDefinitionStatus{
465 StoredVersions: []string{"version"},
466 },
467 },
468 errors: []validationMatch{
469 invalid("spec", "conversion", "conversionReviewVersions"),
470 },
471 },
472 {
473 name: "webhookconfig: invalid ConversionReviewVersion version string",
474 resource: &apiextensions.CustomResourceDefinition{
475 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
476 Spec: apiextensions.CustomResourceDefinitionSpec{
477 Group: "group.com",
478 Scope: apiextensions.ResourceScope("Cluster"),
479 Names: apiextensions.CustomResourceDefinitionNames{
480 Plural: "plural",
481 Singular: "singular",
482 Kind: "Plural",
483 ListKind: "PluralList",
484 },
485 Versions: []apiextensions.CustomResourceDefinitionVersion{
486 {
487 Name: "version",
488 Served: true,
489 Storage: true,
490 },
491 {
492 Name: "version2",
493 Served: true,
494 Storage: false,
495 },
496 },
497 Conversion: &apiextensions.CustomResourceConversion{
498 Strategy: apiextensions.ConversionStrategyType("Webhook"),
499 WebhookClientConfig: &apiextensions.WebhookClientConfig{
500 URL: strPtr("https://example.com/webhook"),
501 },
502 ConversionReviewVersions: []string{"0v"},
503 },
504 Validation: &apiextensions.CustomResourceValidation{
505 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
506 Type: "object",
507 },
508 },
509 PreserveUnknownFields: pointer.BoolPtr(false),
510 },
511 Status: apiextensions.CustomResourceDefinitionStatus{
512 StoredVersions: []string{"version"},
513 },
514 },
515 errors: []validationMatch{
516 invalidIndex(0, "spec", "conversion", "conversionReviewVersions"),
517 invalid("spec", "conversion", "conversionReviewVersions"),
518 },
519 },
520 {
521 name: "webhookconfig: at least one valid ConversionReviewVersion",
522 resource: &apiextensions.CustomResourceDefinition{
523 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
524 Spec: apiextensions.CustomResourceDefinitionSpec{
525 Group: "group.com",
526 Scope: apiextensions.ResourceScope("Cluster"),
527 Names: apiextensions.CustomResourceDefinitionNames{
528 Plural: "plural",
529 Singular: "singular",
530 Kind: "Plural",
531 ListKind: "PluralList",
532 },
533 Versions: []apiextensions.CustomResourceDefinitionVersion{
534 {
535 Name: "version",
536 Served: true,
537 Storage: true,
538 },
539 {
540 Name: "version2",
541 Served: true,
542 Storage: false,
543 },
544 },
545 Conversion: &apiextensions.CustomResourceConversion{
546 Strategy: apiextensions.ConversionStrategyType("Webhook"),
547 WebhookClientConfig: &apiextensions.WebhookClientConfig{
548 URL: strPtr("https://example.com/webhook"),
549 },
550 ConversionReviewVersions: []string{"invalid-version", "v1beta1"},
551 },
552 Validation: &apiextensions.CustomResourceValidation{
553 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
554 Type: "object",
555 },
556 },
557 PreserveUnknownFields: pointer.BoolPtr(false),
558 },
559 Status: apiextensions.CustomResourceDefinitionStatus{
560 StoredVersions: []string{"version"},
561 },
562 },
563 errors: []validationMatch{},
564 },
565 {
566 name: "webhookconfig: duplicate ConversionReviewVersion",
567 resource: &apiextensions.CustomResourceDefinition{
568 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
569 Spec: apiextensions.CustomResourceDefinitionSpec{
570 Group: "group.com",
571 Scope: apiextensions.ResourceScope("Cluster"),
572 Names: apiextensions.CustomResourceDefinitionNames{
573 Plural: "plural",
574 Singular: "singular",
575 Kind: "Plural",
576 ListKind: "PluralList",
577 },
578 Versions: []apiextensions.CustomResourceDefinitionVersion{
579 {
580 Name: "version",
581 Served: true,
582 Storage: true,
583 },
584 {
585 Name: "version2",
586 Served: true,
587 Storage: false,
588 },
589 },
590 Conversion: &apiextensions.CustomResourceConversion{
591 Strategy: apiextensions.ConversionStrategyType("Webhook"),
592 WebhookClientConfig: &apiextensions.WebhookClientConfig{
593 URL: strPtr("https://example.com/webhook"),
594 },
595 ConversionReviewVersions: []string{"v1beta1", "v1beta1"},
596 },
597 Validation: &apiextensions.CustomResourceValidation{
598 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
599 Type: "object",
600 },
601 },
602 PreserveUnknownFields: pointer.BoolPtr(false),
603 },
604 Status: apiextensions.CustomResourceDefinitionStatus{
605 StoredVersions: []string{"version"},
606 },
607 },
608 errors: []validationMatch{
609 invalidIndex(1, "spec", "conversion", "conversionReviewVersions"),
610 },
611 },
612 {
613 name: "missing_webhookconfig",
614 resource: &apiextensions.CustomResourceDefinition{
615 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
616 Spec: apiextensions.CustomResourceDefinitionSpec{
617 Group: "group.com",
618 Scope: apiextensions.ResourceScope("Cluster"),
619 Names: apiextensions.CustomResourceDefinitionNames{
620 Plural: "plural",
621 Singular: "singular",
622 Kind: "Plural",
623 ListKind: "PluralList",
624 },
625 Versions: []apiextensions.CustomResourceDefinitionVersion{
626 {
627 Name: "version",
628 Served: true,
629 Storage: true,
630 },
631 {
632 Name: "version2",
633 Served: true,
634 Storage: false,
635 },
636 },
637 Conversion: &apiextensions.CustomResourceConversion{
638 Strategy: apiextensions.ConversionStrategyType("Webhook"),
639 },
640 Validation: &apiextensions.CustomResourceValidation{
641 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
642 Type: "object",
643 },
644 },
645 PreserveUnknownFields: pointer.BoolPtr(false),
646 },
647 Status: apiextensions.CustomResourceDefinitionStatus{
648 StoredVersions: []string{"version"},
649 },
650 },
651 errors: []validationMatch{
652 required("spec", "conversion", "webhookClientConfig"),
653 },
654 },
655 {
656 name: "invalid_conversion_strategy",
657 resource: &apiextensions.CustomResourceDefinition{
658 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
659 Spec: apiextensions.CustomResourceDefinitionSpec{
660 Group: "group.com",
661 Scope: apiextensions.ResourceScope("Cluster"),
662 Names: apiextensions.CustomResourceDefinitionNames{
663 Plural: "plural",
664 Singular: "singular",
665 Kind: "Plural",
666 ListKind: "PluralList",
667 },
668 Versions: []apiextensions.CustomResourceDefinitionVersion{
669 {
670 Name: "version",
671 Served: true,
672 Storage: true,
673 },
674 {
675 Name: "version2",
676 Served: true,
677 Storage: false,
678 },
679 },
680 Conversion: &apiextensions.CustomResourceConversion{
681 Strategy: apiextensions.ConversionStrategyType("non_existing_conversion"),
682 },
683 Validation: &apiextensions.CustomResourceValidation{
684 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
685 Type: "object",
686 },
687 },
688 PreserveUnknownFields: pointer.BoolPtr(false),
689 },
690 Status: apiextensions.CustomResourceDefinitionStatus{
691 StoredVersions: []string{"version"},
692 },
693 },
694 errors: []validationMatch{
695 unsupported("spec", "conversion", "strategy"),
696 },
697 },
698 {
699 name: "none conversion without preserveUnknownFields=false",
700 resource: &apiextensions.CustomResourceDefinition{
701 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
702 Spec: apiextensions.CustomResourceDefinitionSpec{
703 Group: "group.com",
704 Scope: apiextensions.ResourceScope("Cluster"),
705 Names: apiextensions.CustomResourceDefinitionNames{
706 Plural: "plural",
707 Singular: "singular",
708 Kind: "Plural",
709 ListKind: "PluralList",
710 },
711 Versions: []apiextensions.CustomResourceDefinitionVersion{
712 {
713 Name: "version1",
714 Served: true,
715 Storage: true,
716 },
717 {
718 Name: "version2",
719 Served: true,
720 Storage: false,
721 },
722 },
723 Conversion: &apiextensions.CustomResourceConversion{
724 Strategy: apiextensions.ConversionStrategyType("None"),
725 },
726 PreserveUnknownFields: nil,
727 Validation: &apiextensions.CustomResourceValidation{
728 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
729 Type: "object",
730 },
731 },
732 },
733 Status: apiextensions.CustomResourceDefinitionStatus{
734 StoredVersions: []string{"version1"},
735 },
736 },
737 errors: []validationMatch{},
738 },
739 {
740 name: "webhook conversion without preserveUnknownFields=false",
741 resource: &apiextensions.CustomResourceDefinition{
742 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
743 Spec: apiextensions.CustomResourceDefinitionSpec{
744 Group: "group.com",
745 Scope: apiextensions.ResourceScope("Cluster"),
746 Names: apiextensions.CustomResourceDefinitionNames{
747 Plural: "plural",
748 Singular: "singular",
749 Kind: "Plural",
750 ListKind: "PluralList",
751 },
752 Versions: []apiextensions.CustomResourceDefinitionVersion{
753 {
754 Name: "version1",
755 Served: true,
756 Storage: true,
757 },
758 {
759 Name: "version2",
760 Served: true,
761 Storage: false,
762 },
763 },
764 Conversion: &apiextensions.CustomResourceConversion{
765 Strategy: apiextensions.ConversionStrategyType("Webhook"),
766 WebhookClientConfig: &apiextensions.WebhookClientConfig{
767 URL: strPtr("https://example.com/webhook"),
768 },
769 ConversionReviewVersions: []string{"v1beta1"},
770 },
771 PreserveUnknownFields: nil,
772 Validation: &apiextensions.CustomResourceValidation{
773 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
774 Type: "object",
775 },
776 },
777 },
778 Status: apiextensions.CustomResourceDefinitionStatus{
779 StoredVersions: []string{"version1"},
780 },
781 },
782 errors: []validationMatch{
783 invalid("spec", "conversion", "strategy"),
784 },
785 },
786 {
787 name: "webhook conversion with preserveUnknownFields=false, conversionReviewVersions=[v1beta1]",
788 resource: &apiextensions.CustomResourceDefinition{
789 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
790 Spec: apiextensions.CustomResourceDefinitionSpec{
791 Group: "group.com",
792 Scope: apiextensions.ResourceScope("Cluster"),
793 Names: apiextensions.CustomResourceDefinitionNames{
794 Plural: "plural",
795 Singular: "singular",
796 Kind: "Plural",
797 ListKind: "PluralList",
798 },
799 Versions: []apiextensions.CustomResourceDefinitionVersion{
800 {
801 Name: "version1",
802 Served: true,
803 Storage: true,
804 },
805 {
806 Name: "version2",
807 Served: true,
808 Storage: false,
809 },
810 },
811 Conversion: &apiextensions.CustomResourceConversion{
812 Strategy: apiextensions.ConversionStrategyType("Webhook"),
813 WebhookClientConfig: &apiextensions.WebhookClientConfig{
814 URL: strPtr("https://example.com/webhook"),
815 },
816 ConversionReviewVersions: []string{"v1beta1"},
817 },
818 Validation: &apiextensions.CustomResourceValidation{
819 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
820 Type: "object",
821 },
822 },
823 PreserveUnknownFields: pointer.BoolPtr(false),
824 },
825 Status: apiextensions.CustomResourceDefinitionStatus{
826 StoredVersions: []string{"version1"},
827 },
828 },
829 errors: []validationMatch{},
830 },
831 {
832 name: "webhook conversion with preserveUnknownFields=false, conversionReviewVersions=[v1]",
833 resource: &apiextensions.CustomResourceDefinition{
834 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
835 Spec: apiextensions.CustomResourceDefinitionSpec{
836 Group: "group.com",
837 Scope: apiextensions.ResourceScope("Cluster"),
838 Names: apiextensions.CustomResourceDefinitionNames{
839 Plural: "plural",
840 Singular: "singular",
841 Kind: "Plural",
842 ListKind: "PluralList",
843 },
844 Versions: []apiextensions.CustomResourceDefinitionVersion{
845 {
846 Name: "version1",
847 Served: true,
848 Storage: true,
849 },
850 {
851 Name: "version2",
852 Served: true,
853 Storage: false,
854 },
855 },
856 Conversion: &apiextensions.CustomResourceConversion{
857 Strategy: apiextensions.ConversionStrategyType("Webhook"),
858 WebhookClientConfig: &apiextensions.WebhookClientConfig{
859 URL: strPtr("https://example.com/webhook"),
860 },
861 ConversionReviewVersions: []string{"v1"},
862 },
863 Validation: &apiextensions.CustomResourceValidation{
864 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
865 Type: "object",
866 },
867 },
868 PreserveUnknownFields: pointer.BoolPtr(false),
869 },
870 Status: apiextensions.CustomResourceDefinitionStatus{
871 StoredVersions: []string{"version1"},
872 },
873 },
874 errors: []validationMatch{},
875 },
876 {
877 name: "no_storage_version",
878 resource: &apiextensions.CustomResourceDefinition{
879 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
880 Spec: apiextensions.CustomResourceDefinitionSpec{
881 Group: "group.com",
882 Scope: apiextensions.ResourceScope("Cluster"),
883 Names: apiextensions.CustomResourceDefinitionNames{
884 Plural: "plural",
885 Singular: "singular",
886 Kind: "Plural",
887 ListKind: "PluralList",
888 },
889 Versions: []apiextensions.CustomResourceDefinitionVersion{
890 {
891 Name: "version",
892 Served: true,
893 Storage: false,
894 },
895 {
896 Name: "version2",
897 Served: true,
898 Storage: false,
899 },
900 },
901 Conversion: &apiextensions.CustomResourceConversion{
902 Strategy: apiextensions.ConversionStrategyType("None"),
903 },
904 PreserveUnknownFields: pointer.BoolPtr(false),
905 Validation: &apiextensions.CustomResourceValidation{
906 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
907 Type: "object",
908 },
909 },
910 },
911 Status: apiextensions.CustomResourceDefinitionStatus{
912 StoredVersions: []string{"version"},
913 },
914 },
915 errors: []validationMatch{
916 invalid("spec", "versions"),
917 },
918 },
919 {
920 name: "multiple_storage_version",
921 resource: &apiextensions.CustomResourceDefinition{
922 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
923 Spec: apiextensions.CustomResourceDefinitionSpec{
924 Group: "group.com",
925 Scope: apiextensions.ResourceScope("Cluster"),
926 Names: apiextensions.CustomResourceDefinitionNames{
927 Plural: "plural",
928 Singular: "singular",
929 Kind: "Plural",
930 ListKind: "PluralList",
931 },
932 Versions: []apiextensions.CustomResourceDefinitionVersion{
933 {
934 Name: "version",
935 Served: true,
936 Storage: true,
937 },
938 {
939 Name: "version2",
940 Served: true,
941 Storage: true,
942 },
943 },
944 Conversion: &apiextensions.CustomResourceConversion{
945 Strategy: apiextensions.ConversionStrategyType("None"),
946 },
947 PreserveUnknownFields: pointer.BoolPtr(false),
948 Validation: &apiextensions.CustomResourceValidation{
949 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
950 Type: "object",
951 },
952 },
953 },
954 Status: apiextensions.CustomResourceDefinitionStatus{
955 StoredVersions: []string{"version"},
956 },
957 },
958 errors: []validationMatch{
959 invalid("spec", "versions"),
960 invalid("status", "storedVersions"),
961 },
962 },
963 {
964 name: "missing_storage_version_in_stored_versions",
965 resource: &apiextensions.CustomResourceDefinition{
966 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
967 Spec: apiextensions.CustomResourceDefinitionSpec{
968 Group: "group.com",
969 Scope: apiextensions.ResourceScope("Cluster"),
970 Names: apiextensions.CustomResourceDefinitionNames{
971 Plural: "plural",
972 Singular: "singular",
973 Kind: "Plural",
974 ListKind: "PluralList",
975 },
976 Versions: []apiextensions.CustomResourceDefinitionVersion{
977 {
978 Name: "version",
979 Served: true,
980 Storage: false,
981 },
982 {
983 Name: "version2",
984 Served: true,
985 Storage: true,
986 },
987 },
988 Conversion: &apiextensions.CustomResourceConversion{
989 Strategy: apiextensions.ConversionStrategyType("None"),
990 },
991 PreserveUnknownFields: pointer.BoolPtr(false),
992 Validation: &apiextensions.CustomResourceValidation{
993 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
994 Type: "object",
995 },
996 },
997 },
998 Status: apiextensions.CustomResourceDefinitionStatus{
999 StoredVersions: []string{"version"},
1000 },
1001 },
1002 errors: []validationMatch{
1003 invalid("status", "storedVersions"),
1004 },
1005 },
1006 {
1007 name: "empty_stored_version",
1008 resource: &apiextensions.CustomResourceDefinition{
1009 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1010 Spec: apiextensions.CustomResourceDefinitionSpec{
1011 Group: "group.com",
1012 Scope: apiextensions.ResourceScope("Cluster"),
1013 Names: apiextensions.CustomResourceDefinitionNames{
1014 Plural: "plural",
1015 Singular: "singular",
1016 Kind: "Plural",
1017 ListKind: "PluralList",
1018 },
1019 Versions: []apiextensions.CustomResourceDefinitionVersion{
1020 {
1021 Name: "version",
1022 Served: true,
1023 Storage: true,
1024 },
1025 },
1026 Conversion: &apiextensions.CustomResourceConversion{
1027 Strategy: apiextensions.ConversionStrategyType("None"),
1028 },
1029 PreserveUnknownFields: pointer.BoolPtr(false),
1030 Validation: &apiextensions.CustomResourceValidation{
1031 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1032 Type: "object",
1033 },
1034 },
1035 },
1036 Status: apiextensions.CustomResourceDefinitionStatus{
1037 StoredVersions: []string{},
1038 },
1039 },
1040 errors: []validationMatch{
1041 invalid("status", "storedVersions"),
1042 },
1043 },
1044 {
1045 name: "mismatched name",
1046 resource: &apiextensions.CustomResourceDefinition{
1047 ObjectMeta: metav1.ObjectMeta{Name: "plural.not.group.com"},
1048 Spec: apiextensions.CustomResourceDefinitionSpec{
1049 Group: "group.com",
1050 Names: apiextensions.CustomResourceDefinitionNames{
1051 Plural: "plural",
1052 },
1053 PreserveUnknownFields: pointer.BoolPtr(false),
1054 Validation: &apiextensions.CustomResourceValidation{
1055 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1056 Type: "object",
1057 },
1058 },
1059 },
1060 },
1061 errors: []validationMatch{
1062 invalid("status", "storedVersions"),
1063 invalid("metadata", "name"),
1064 invalid("spec", "versions"),
1065 required("spec", "scope"),
1066 required("spec", "names", "singular"),
1067 required("spec", "names", "kind"),
1068 required("spec", "names", "listKind"),
1069 },
1070 },
1071 {
1072 name: "missing values",
1073 resource: &apiextensions.CustomResourceDefinition{
1074 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1075 },
1076 errors: []validationMatch{
1077 invalid("status", "storedVersions"),
1078 invalid("metadata", "name"),
1079 invalid("spec", "versions"),
1080 required("spec", "group"),
1081 required("spec", "scope"),
1082 required("spec", "names", "plural"),
1083 required("spec", "names", "singular"),
1084 required("spec", "names", "kind"),
1085 required("spec", "names", "listKind"),
1086 },
1087 },
1088 {
1089 name: "bad names 01",
1090 resource: &apiextensions.CustomResourceDefinition{
1091 ObjectMeta: metav1.ObjectMeta{Name: "plural.group"},
1092 Spec: apiextensions.CustomResourceDefinitionSpec{
1093 Group: "group",
1094 Version: "ve()*rsion",
1095 Scope: apiextensions.ResourceScope("foo"),
1096 Names: apiextensions.CustomResourceDefinitionNames{
1097 Plural: "pl()*ural",
1098 Singular: "value()*a",
1099 Kind: "value()*a",
1100 ListKind: "value()*a",
1101 },
1102 PreserveUnknownFields: pointer.BoolPtr(false),
1103 Validation: &apiextensions.CustomResourceValidation{
1104 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1105 Type: "object",
1106 },
1107 },
1108 },
1109 Status: apiextensions.CustomResourceDefinitionStatus{
1110 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
1111 Plural: "pl()*ural",
1112 Singular: "value()*a",
1113 Kind: "value()*a",
1114 ListKind: "value()*a",
1115 },
1116 },
1117 },
1118 errors: []validationMatch{
1119 invalid("status", "storedVersions"),
1120 invalid("metadata", "name"),
1121 invalid("spec", "group"),
1122 unsupported("spec", "scope"),
1123 invalid("spec", "names", "plural"),
1124 invalid("spec", "names", "singular"),
1125 invalid("spec", "names", "kind"),
1126 invalid("spec", "names", "listKind"),
1127 invalid("spec", "names", "listKind"),
1128 invalid("status", "acceptedNames", "plural"),
1129 invalid("status", "acceptedNames", "singular"),
1130 invalid("status", "acceptedNames", "kind"),
1131 invalid("status", "acceptedNames", "listKind"),
1132 invalid("status", "acceptedNames", "listKind"),
1133 invalid("spec", "versions"),
1134 invalid("spec", "version"),
1135 },
1136 },
1137 {
1138 name: "bad names 02",
1139 resource: &apiextensions.CustomResourceDefinition{
1140 ObjectMeta: metav1.ObjectMeta{Name: "plural.group"},
1141 Spec: apiextensions.CustomResourceDefinitionSpec{
1142 Group: "group.c(*&om",
1143 Version: "version",
1144 Versions: singleVersionList,
1145 Conversion: &apiextensions.CustomResourceConversion{
1146 Strategy: apiextensions.ConversionStrategyType("None"),
1147 },
1148 Names: apiextensions.CustomResourceDefinitionNames{
1149 Plural: "plural",
1150 Singular: "singular",
1151 Kind: "matching",
1152 ListKind: "matching",
1153 },
1154 PreserveUnknownFields: pointer.BoolPtr(false),
1155 Validation: &apiextensions.CustomResourceValidation{
1156 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1157 Type: "object",
1158 },
1159 },
1160 },
1161 Status: apiextensions.CustomResourceDefinitionStatus{
1162 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
1163 Plural: "plural",
1164 Singular: "singular",
1165 Kind: "matching",
1166 ListKind: "matching",
1167 },
1168 StoredVersions: []string{"version"},
1169 },
1170 },
1171 errors: []validationMatch{
1172 invalid("metadata", "name"),
1173 invalid("spec", "group"),
1174 required("spec", "scope"),
1175 invalid("spec", "names", "listKind"),
1176 invalid("status", "acceptedNames", "listKind"),
1177 },
1178 },
1179 {
1180 name: "additionalProperties and properties forbidden",
1181 resource: &apiextensions.CustomResourceDefinition{
1182 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1183 Spec: apiextensions.CustomResourceDefinitionSpec{
1184 Group: "group.com",
1185 Version: "version",
1186 Versions: singleVersionList,
1187 Conversion: &apiextensions.CustomResourceConversion{
1188 Strategy: apiextensions.ConversionStrategyType("None"),
1189 },
1190 Scope: apiextensions.NamespaceScoped,
1191 Names: apiextensions.CustomResourceDefinitionNames{
1192 Plural: "plural",
1193 Singular: "singular",
1194 Kind: "Plural",
1195 ListKind: "PluralList",
1196 },
1197 Validation: &apiextensions.CustomResourceValidation{
1198 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1199 Type: "object",
1200 Properties: map[string]apiextensions.JSONSchemaProps{
1201 "foo": {
1202 Type: "object",
1203 Properties: map[string]apiextensions.JSONSchemaProps{
1204 "bar": {Type: "object"},
1205 },
1206 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{Allows: false},
1207 },
1208 },
1209 },
1210 },
1211 PreserveUnknownFields: pointer.BoolPtr(false),
1212 },
1213 Status: apiextensions.CustomResourceDefinitionStatus{
1214 StoredVersions: []string{"version"},
1215 },
1216 },
1217 errors: []validationMatch{
1218 forbidden("spec", "validation", "openAPIV3Schema", "properties[foo]", "additionalProperties"),
1219 },
1220 },
1221 {
1222 name: "additionalProperties without properties allowed (map[string]string)",
1223 resource: &apiextensions.CustomResourceDefinition{
1224 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1225 Spec: apiextensions.CustomResourceDefinitionSpec{
1226 Group: "group.com",
1227 Version: "version",
1228 Versions: singleVersionList,
1229 Conversion: &apiextensions.CustomResourceConversion{
1230 Strategy: apiextensions.ConversionStrategyType("None"),
1231 },
1232 Scope: apiextensions.NamespaceScoped,
1233 Names: apiextensions.CustomResourceDefinitionNames{
1234 Plural: "plural",
1235 Singular: "singular",
1236 Kind: "Plural",
1237 ListKind: "PluralList",
1238 },
1239 Validation: &apiextensions.CustomResourceValidation{
1240 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1241 Type: "object",
1242 Properties: map[string]apiextensions.JSONSchemaProps{
1243 "foo": {
1244 Type: "object",
1245 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
1246 Allows: true,
1247 Schema: &apiextensions.JSONSchemaProps{
1248 Type: "string",
1249 },
1250 },
1251 },
1252 },
1253 },
1254 },
1255 PreserveUnknownFields: pointer.BoolPtr(false),
1256 },
1257 Status: apiextensions.CustomResourceDefinitionStatus{
1258 StoredVersions: []string{"version"},
1259 },
1260 },
1261 errors: []validationMatch{},
1262 },
1263 {
1264 name: "per-version fields may not all be set to identical values (top-level field should be used instead)",
1265 resource: &apiextensions.CustomResourceDefinition{
1266 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1267 Spec: apiextensions.CustomResourceDefinitionSpec{
1268 Group: "group.com",
1269 Version: "version",
1270 Versions: []apiextensions.CustomResourceDefinitionVersion{
1271 {
1272 Name: "version",
1273 Served: true,
1274 Storage: true,
1275 Schema: &apiextensions.CustomResourceValidation{
1276 OpenAPIV3Schema: validValidationSchema,
1277 },
1278 Subresources: &apiextensions.CustomResourceSubresources{},
1279 AdditionalPrinterColumns: []apiextensions.CustomResourceColumnDefinition{{Name: "Alpha", Type: "string", JSONPath: ".spec.alpha"}},
1280 },
1281 {
1282 Name: "version2",
1283 Served: true,
1284 Storage: false,
1285 Schema: &apiextensions.CustomResourceValidation{
1286 OpenAPIV3Schema: validValidationSchema,
1287 },
1288 Subresources: &apiextensions.CustomResourceSubresources{},
1289 AdditionalPrinterColumns: []apiextensions.CustomResourceColumnDefinition{{Name: "Alpha", Type: "string", JSONPath: ".spec.alpha"}},
1290 },
1291 },
1292 Scope: apiextensions.NamespaceScoped,
1293 Names: apiextensions.CustomResourceDefinitionNames{
1294 Plural: "plural",
1295 Singular: "singular",
1296 Kind: "Plural",
1297 ListKind: "PluralList",
1298 },
1299 PreserveUnknownFields: pointer.BoolPtr(false),
1300 },
1301 Status: apiextensions.CustomResourceDefinitionStatus{
1302 StoredVersions: []string{"version"},
1303 },
1304 },
1305 errors: []validationMatch{
1306
1307
1308 invalid("spec", "versions"),
1309 invalid("spec", "versions"),
1310 invalid("spec", "versions"),
1311 },
1312 },
1313 {
1314 name: "x-kubernetes-preserve-unknown-field: false",
1315 resource: &apiextensions.CustomResourceDefinition{
1316 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1317 Spec: apiextensions.CustomResourceDefinitionSpec{
1318 Group: "group.com",
1319 Version: "version",
1320 Validation: &apiextensions.CustomResourceValidation{
1321 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1322 XPreserveUnknownFields: pointer.BoolPtr(false),
1323 },
1324 },
1325 Versions: []apiextensions.CustomResourceDefinitionVersion{
1326 {
1327 Name: "version",
1328 Served: true,
1329 Storage: true,
1330 },
1331 },
1332 Scope: apiextensions.NamespaceScoped,
1333 Names: apiextensions.CustomResourceDefinitionNames{
1334 Plural: "plural",
1335 Singular: "singular",
1336 Kind: "Plural",
1337 ListKind: "PluralList",
1338 },
1339 PreserveUnknownFields: pointer.BoolPtr(false),
1340 },
1341 Status: apiextensions.CustomResourceDefinitionStatus{
1342 StoredVersions: []string{"version"},
1343 },
1344 },
1345 errors: []validationMatch{
1346 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-preserve-unknown-fields"),
1347 },
1348 },
1349 {
1350 name: "preserveUnknownFields with unstructural global schema",
1351 resource: &apiextensions.CustomResourceDefinition{
1352 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1353 Spec: apiextensions.CustomResourceDefinitionSpec{
1354 Group: "group.com",
1355 Version: "version",
1356 Validation: &apiextensions.CustomResourceValidation{
1357 OpenAPIV3Schema: validUnstructuralValidationSchema,
1358 },
1359 Versions: []apiextensions.CustomResourceDefinitionVersion{
1360 {
1361 Name: "version",
1362 Served: true,
1363 Storage: true,
1364 },
1365 {
1366 Name: "version2",
1367 Served: true,
1368 Storage: false,
1369 },
1370 },
1371 Scope: apiextensions.NamespaceScoped,
1372 Names: apiextensions.CustomResourceDefinitionNames{
1373 Plural: "plural",
1374 Singular: "singular",
1375 Kind: "Plural",
1376 ListKind: "PluralList",
1377 },
1378 PreserveUnknownFields: pointer.BoolPtr(false),
1379 },
1380 Status: apiextensions.CustomResourceDefinitionStatus{
1381 StoredVersions: []string{"version"},
1382 },
1383 },
1384 errors: []validationMatch{
1385 required("spec", "validation", "openAPIV3Schema", "properties[spec]", "type"),
1386 required("spec", "validation", "openAPIV3Schema", "properties[status]", "type"),
1387 required("spec", "validation", "openAPIV3Schema", "items", "type"),
1388 },
1389 },
1390 {
1391 name: "preserveUnknownFields with unstructural schema in one version",
1392 resource: &apiextensions.CustomResourceDefinition{
1393 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1394 Spec: apiextensions.CustomResourceDefinitionSpec{
1395 Group: "group.com",
1396 Version: "version",
1397 Versions: []apiextensions.CustomResourceDefinitionVersion{
1398 {
1399 Name: "version",
1400 Served: true,
1401 Storage: true,
1402 Schema: &apiextensions.CustomResourceValidation{
1403 OpenAPIV3Schema: validValidationSchema,
1404 },
1405 },
1406 {
1407 Name: "version2",
1408 Served: true,
1409 Storage: false,
1410 Schema: &apiextensions.CustomResourceValidation{
1411 OpenAPIV3Schema: validUnstructuralValidationSchema,
1412 },
1413 },
1414 },
1415 Scope: apiextensions.NamespaceScoped,
1416 Names: apiextensions.CustomResourceDefinitionNames{
1417 Plural: "plural",
1418 Singular: "singular",
1419 Kind: "Plural",
1420 ListKind: "PluralList",
1421 },
1422 PreserveUnknownFields: pointer.BoolPtr(false),
1423 },
1424 Status: apiextensions.CustomResourceDefinitionStatus{
1425 StoredVersions: []string{"version"},
1426 },
1427 },
1428 errors: []validationMatch{
1429 required("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[spec]", "type"),
1430 required("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[status]", "type"),
1431 required("spec", "versions[1]", "schema", "openAPIV3Schema", "items", "type"),
1432 },
1433 },
1434 {
1435 name: "preserveUnknownFields with no schema in one version",
1436 resource: &apiextensions.CustomResourceDefinition{
1437 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1438 Spec: apiextensions.CustomResourceDefinitionSpec{
1439 Group: "group.com",
1440 Version: "version",
1441 Versions: []apiextensions.CustomResourceDefinitionVersion{
1442 {
1443 Name: "version",
1444 Served: true,
1445 Storage: true,
1446 Schema: &apiextensions.CustomResourceValidation{
1447 OpenAPIV3Schema: validValidationSchema,
1448 },
1449 },
1450 {
1451 Name: "version2",
1452 Served: true,
1453 Storage: false,
1454 Schema: &apiextensions.CustomResourceValidation{
1455 OpenAPIV3Schema: nil,
1456 },
1457 },
1458 },
1459 Scope: apiextensions.NamespaceScoped,
1460 Names: apiextensions.CustomResourceDefinitionNames{
1461 Plural: "plural",
1462 Singular: "singular",
1463 Kind: "Plural",
1464 ListKind: "PluralList",
1465 },
1466 PreserveUnknownFields: pointer.BoolPtr(false),
1467 },
1468 Status: apiextensions.CustomResourceDefinitionStatus{
1469 StoredVersions: []string{"version"},
1470 },
1471 },
1472 errors: []validationMatch{
1473 required("spec", "versions[1]", "schema", "openAPIV3Schema"),
1474 },
1475 },
1476 {
1477 name: "preserveUnknownFields with no schema at all",
1478 resource: &apiextensions.CustomResourceDefinition{
1479 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1480 Spec: apiextensions.CustomResourceDefinitionSpec{
1481 Group: "group.com",
1482 Version: "version",
1483 Versions: []apiextensions.CustomResourceDefinitionVersion{
1484 {
1485 Name: "version",
1486 Served: true,
1487 Storage: true,
1488 Schema: nil,
1489 },
1490 {
1491 Name: "version2",
1492 Served: true,
1493 Storage: false,
1494 Schema: &apiextensions.CustomResourceValidation{
1495 OpenAPIV3Schema: nil,
1496 },
1497 },
1498 },
1499 Scope: apiextensions.NamespaceScoped,
1500 Names: apiextensions.CustomResourceDefinitionNames{
1501 Plural: "plural",
1502 Singular: "singular",
1503 Kind: "Plural",
1504 ListKind: "PluralList",
1505 },
1506 PreserveUnknownFields: pointer.BoolPtr(false),
1507 },
1508 Status: apiextensions.CustomResourceDefinitionStatus{
1509 StoredVersions: []string{"version"},
1510 },
1511 },
1512 errors: []validationMatch{
1513 required("spec", "versions[0]", "schema", "openAPIV3Schema"),
1514 required("spec", "versions[1]", "schema", "openAPIV3Schema"),
1515 },
1516 },
1517 {
1518 name: "schema is required",
1519 resource: &apiextensions.CustomResourceDefinition{
1520 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1521 Spec: apiextensions.CustomResourceDefinitionSpec{
1522 Group: "group.com",
1523 Version: "version",
1524 Versions: []apiextensions.CustomResourceDefinitionVersion{
1525 {
1526 Name: "version",
1527 Served: true,
1528 Storage: true,
1529 Schema: nil,
1530 },
1531 {
1532 Name: "version2",
1533 Served: true,
1534 Storage: false,
1535 Schema: &apiextensions.CustomResourceValidation{
1536 OpenAPIV3Schema: nil,
1537 },
1538 },
1539 },
1540 Scope: apiextensions.NamespaceScoped,
1541 Names: apiextensions.CustomResourceDefinitionNames{
1542 Plural: "plural",
1543 Singular: "singular",
1544 Kind: "Plural",
1545 ListKind: "PluralList",
1546 },
1547 PreserveUnknownFields: pointer.BoolPtr(false),
1548 },
1549 Status: apiextensions.CustomResourceDefinitionStatus{
1550 StoredVersions: []string{"version"},
1551 },
1552 },
1553 errors: []validationMatch{
1554 required("spec", "versions[0]", "schema", "openAPIV3Schema"),
1555 required("spec", "versions[1]", "schema", "openAPIV3Schema"),
1556 },
1557 },
1558 {
1559 name: "preserveUnknownFields: true",
1560 resource: &apiextensions.CustomResourceDefinition{
1561 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1562 Spec: apiextensions.CustomResourceDefinitionSpec{
1563 Group: "group.com",
1564 Version: "version",
1565 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
1566 Validation: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
1567 Scope: apiextensions.NamespaceScoped,
1568 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
1569 PreserveUnknownFields: pointer.BoolPtr(true),
1570 },
1571 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
1572 },
1573 errors: []validationMatch{invalid("spec.preserveUnknownFields")},
1574 },
1575 {
1576 name: "labelSelectorPath outside of .spec and .status",
1577 resource: &apiextensions.CustomResourceDefinition{
1578 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1579 Spec: apiextensions.CustomResourceDefinitionSpec{
1580 Group: "group.com",
1581 Version: "version0",
1582 Versions: []apiextensions.CustomResourceDefinitionVersion{
1583 {
1584
1585 Name: "version0",
1586 Served: true,
1587 Storage: true,
1588 Subresources: &apiextensions.CustomResourceSubresources{
1589 Scale: &apiextensions.CustomResourceSubresourceScale{
1590 SpecReplicasPath: ".spec.replicas",
1591 StatusReplicasPath: ".status.replicas",
1592 },
1593 },
1594 },
1595 {
1596
1597 Name: "version1",
1598 Served: true,
1599 Storage: false,
1600 Subresources: &apiextensions.CustomResourceSubresources{
1601 Scale: &apiextensions.CustomResourceSubresourceScale{
1602 SpecReplicasPath: ".spec.replicas",
1603 StatusReplicasPath: ".status.replicas",
1604 LabelSelectorPath: strPtr(".status.labelSelector"),
1605 },
1606 },
1607 },
1608 {
1609
1610 Name: "version2",
1611 Served: true,
1612 Storage: false,
1613 Subresources: &apiextensions.CustomResourceSubresources{
1614 Scale: &apiextensions.CustomResourceSubresourceScale{
1615 SpecReplicasPath: ".spec.replicas",
1616 StatusReplicasPath: ".status.replicas",
1617 LabelSelectorPath: strPtr(".spec.labelSelector"),
1618 },
1619 },
1620 },
1621 {
1622
1623 Name: "version3",
1624 Served: true,
1625 Storage: false,
1626 Subresources: &apiextensions.CustomResourceSubresources{
1627 Scale: &apiextensions.CustomResourceSubresourceScale{
1628 SpecReplicasPath: ".spec.replicas",
1629 StatusReplicasPath: ".status.replicas",
1630 LabelSelectorPath: strPtr(".labelSelector"),
1631 },
1632 },
1633 },
1634 },
1635 Scope: apiextensions.NamespaceScoped,
1636 Names: apiextensions.CustomResourceDefinitionNames{
1637 Plural: "plural",
1638 Singular: "singular",
1639 Kind: "Plural",
1640 ListKind: "PluralList",
1641 },
1642 PreserveUnknownFields: pointer.BoolPtr(false),
1643 Validation: &apiextensions.CustomResourceValidation{
1644 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1645 Type: "object",
1646 },
1647 },
1648 },
1649 Status: apiextensions.CustomResourceDefinitionStatus{
1650 StoredVersions: []string{"version0"},
1651 },
1652 },
1653 errors: []validationMatch{
1654 invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"),
1655 },
1656 },
1657 {
1658 name: "defaults with enabled feature gate",
1659 resource: &apiextensions.CustomResourceDefinition{
1660 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1661 Spec: apiextensions.CustomResourceDefinitionSpec{
1662 Group: "group.com",
1663 Version: "version",
1664 Versions: singleVersionList,
1665 Scope: apiextensions.NamespaceScoped,
1666 Names: apiextensions.CustomResourceDefinitionNames{
1667 Plural: "plural",
1668 Singular: "singular",
1669 Kind: "Plural",
1670 ListKind: "PluralList",
1671 },
1672 Validation: &apiextensions.CustomResourceValidation{
1673 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1674 Type: "object",
1675 Properties: map[string]apiextensions.JSONSchemaProps{
1676 "a": {
1677 Type: "number",
1678 Default: jsonPtr(42.0),
1679 },
1680 },
1681 },
1682 },
1683 PreserveUnknownFields: pointer.BoolPtr(false),
1684 },
1685 Status: apiextensions.CustomResourceDefinitionStatus{
1686 StoredVersions: []string{"version"},
1687 },
1688 },
1689 },
1690 {
1691 name: "x-kubernetes-embedded-resource with pruning and empty properties",
1692 resource: &apiextensions.CustomResourceDefinition{
1693 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1694 Spec: apiextensions.CustomResourceDefinitionSpec{
1695 Group: "group.com",
1696 Version: "version",
1697 Versions: singleVersionList,
1698 Scope: apiextensions.NamespaceScoped,
1699 Names: apiextensions.CustomResourceDefinitionNames{
1700 Plural: "plural",
1701 Singular: "singular",
1702 Kind: "Plural",
1703 ListKind: "PluralList",
1704 },
1705 Validation: &apiextensions.CustomResourceValidation{
1706 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1707 Type: "object",
1708 XPreserveUnknownFields: pointer.BoolPtr(true),
1709 Properties: map[string]apiextensions.JSONSchemaProps{
1710 "nil": {
1711 Type: "object",
1712 XEmbeddedResource: true,
1713 Properties: nil,
1714 },
1715 "empty": {
1716 Type: "object",
1717 XEmbeddedResource: true,
1718 Properties: map[string]apiextensions.JSONSchemaProps{},
1719 },
1720 },
1721 },
1722 },
1723 },
1724 Status: apiextensions.CustomResourceDefinitionStatus{
1725 StoredVersions: []string{"version"},
1726 },
1727 },
1728 errors: []validationMatch{
1729 required("spec", "validation", "openAPIV3Schema", "properties[nil]", "properties"),
1730 required("spec", "validation", "openAPIV3Schema", "properties[empty]", "properties"),
1731 },
1732 },
1733 {
1734 name: "x-kubernetes-embedded-resource inside resource meta",
1735 resource: &apiextensions.CustomResourceDefinition{
1736 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1737 Spec: apiextensions.CustomResourceDefinitionSpec{
1738 Group: "group.com",
1739 Version: "version",
1740 Versions: singleVersionList,
1741 Scope: apiextensions.NamespaceScoped,
1742 Names: apiextensions.CustomResourceDefinitionNames{
1743 Plural: "plural",
1744 Singular: "singular",
1745 Kind: "Plural",
1746 ListKind: "PluralList",
1747 },
1748 Validation: &apiextensions.CustomResourceValidation{
1749 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1750 Type: "object",
1751 Properties: map[string]apiextensions.JSONSchemaProps{
1752 "embedded": {
1753 Type: "object",
1754 XEmbeddedResource: true,
1755 Properties: map[string]apiextensions.JSONSchemaProps{
1756 "metadata": {
1757 Type: "object",
1758 XEmbeddedResource: true,
1759 XPreserveUnknownFields: pointer.BoolPtr(true),
1760 },
1761 "apiVersion": {
1762 Type: "string",
1763 Properties: map[string]apiextensions.JSONSchemaProps{
1764 "foo": {
1765 Type: "object",
1766 XEmbeddedResource: true,
1767 XPreserveUnknownFields: pointer.BoolPtr(true),
1768 },
1769 },
1770 },
1771 "kind": {
1772 Type: "string",
1773 Properties: map[string]apiextensions.JSONSchemaProps{
1774 "foo": {
1775 Type: "object",
1776 XEmbeddedResource: true,
1777 XPreserveUnknownFields: pointer.BoolPtr(true),
1778 },
1779 },
1780 },
1781 },
1782 },
1783 },
1784 },
1785 },
1786 },
1787 Status: apiextensions.CustomResourceDefinitionStatus{
1788 StoredVersions: []string{"version"},
1789 },
1790 },
1791 errors: []validationMatch{
1792 forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "x-kubernetes-embedded-resource"),
1793 forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[apiVersion]", "properties[foo]", "x-kubernetes-embedded-resource"),
1794 forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[kind]", "properties[foo]", "x-kubernetes-embedded-resource"),
1795 },
1796 },
1797 {
1798 name: "x-kubernetes-validations access metadata name",
1799 resource: &apiextensions.CustomResourceDefinition{
1800 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1801 Spec: apiextensions.CustomResourceDefinitionSpec{
1802 Group: "group.com",
1803 Versions: singleVersionList,
1804 Subresources: &apiextensions.CustomResourceSubresources{
1805 Status: &apiextensions.CustomResourceSubresourceStatus{},
1806 },
1807 Scope: apiextensions.NamespaceScoped,
1808 Names: apiextensions.CustomResourceDefinitionNames{
1809 Plural: "plural",
1810 Singular: "singular",
1811 Kind: "Plural",
1812 ListKind: "PluralList",
1813 },
1814 PreserveUnknownFields: pointer.BoolPtr(false),
1815 Validation: &apiextensions.CustomResourceValidation{
1816 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1817 Type: "object",
1818 XPreserveUnknownFields: pointer.BoolPtr(true),
1819 XValidations: apiextensions.ValidationRules{
1820 {
1821 Rule: "size(self.metadata.name) > 3",
1822 },
1823 },
1824 Properties: map[string]apiextensions.JSONSchemaProps{
1825 "metadata": {
1826 Type: "object",
1827 Properties: map[string]apiextensions.JSONSchemaProps{
1828 "name": {
1829 Type: "string",
1830 },
1831 },
1832 },
1833 },
1834 },
1835 },
1836 },
1837 Status: apiextensions.CustomResourceDefinitionStatus{
1838 StoredVersions: []string{"version"},
1839 },
1840 },
1841 },
1842 {
1843 name: "defaults with enabled feature gate, unstructural schema",
1844 resource: &apiextensions.CustomResourceDefinition{
1845 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1846 Spec: apiextensions.CustomResourceDefinitionSpec{
1847 Group: "group.com",
1848 Version: "version",
1849 Versions: singleVersionList,
1850 Scope: apiextensions.NamespaceScoped,
1851 Names: apiextensions.CustomResourceDefinitionNames{
1852 Plural: "plural",
1853 Singular: "singular",
1854 Kind: "Plural",
1855 ListKind: "PluralList",
1856 },
1857 Validation: &apiextensions.CustomResourceValidation{
1858 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1859 Properties: map[string]apiextensions.JSONSchemaProps{
1860 "a": {Default: jsonPtr(42.0)},
1861 },
1862 },
1863 },
1864 PreserveUnknownFields: pointer.BoolPtr(false),
1865 },
1866 Status: apiextensions.CustomResourceDefinitionStatus{
1867 StoredVersions: []string{"version"},
1868 },
1869 },
1870 errors: []validationMatch{
1871 required("spec", "validation", "openAPIV3Schema", "properties[a]", "type"),
1872 required("spec", "validation", "openAPIV3Schema", "type"),
1873 },
1874 },
1875 {
1876 name: "defaults with enabled feature gate, structural schema",
1877 resource: &apiextensions.CustomResourceDefinition{
1878 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1879 Spec: apiextensions.CustomResourceDefinitionSpec{
1880 Group: "group.com",
1881 Version: "version",
1882 Versions: singleVersionList,
1883 Scope: apiextensions.NamespaceScoped,
1884 Names: apiextensions.CustomResourceDefinitionNames{
1885 Plural: "plural",
1886 Singular: "singular",
1887 Kind: "Plural",
1888 ListKind: "PluralList",
1889 },
1890 Validation: &apiextensions.CustomResourceValidation{
1891 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1892 Type: "object",
1893 Properties: map[string]apiextensions.JSONSchemaProps{
1894 "a": {
1895 Type: "number",
1896 Default: jsonPtr(42.0),
1897 },
1898 },
1899 },
1900 },
1901 PreserveUnknownFields: pointer.BoolPtr(false),
1902 },
1903 Status: apiextensions.CustomResourceDefinitionStatus{
1904 StoredVersions: []string{"version"},
1905 },
1906 },
1907 errors: []validationMatch{},
1908 },
1909 {
1910 name: "defaults in value validation with enabled feature gate, structural schema",
1911 resource: &apiextensions.CustomResourceDefinition{
1912 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1913 Spec: apiextensions.CustomResourceDefinitionSpec{
1914 Group: "group.com",
1915 Version: "version",
1916 Versions: singleVersionList,
1917 Scope: apiextensions.NamespaceScoped,
1918 Names: apiextensions.CustomResourceDefinitionNames{
1919 Plural: "plural",
1920 Singular: "singular",
1921 Kind: "Plural",
1922 ListKind: "PluralList",
1923 },
1924 Validation: &apiextensions.CustomResourceValidation{
1925 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1926 Type: "object",
1927 Properties: map[string]apiextensions.JSONSchemaProps{
1928 "a": {
1929 Type: "number",
1930 Not: &apiextensions.JSONSchemaProps{
1931 Default: jsonPtr(42.0),
1932 },
1933 AnyOf: []apiextensions.JSONSchemaProps{
1934 {
1935 Default: jsonPtr(42.0),
1936 },
1937 },
1938 AllOf: []apiextensions.JSONSchemaProps{
1939 {
1940 Default: jsonPtr(42.0),
1941 },
1942 },
1943 OneOf: []apiextensions.JSONSchemaProps{
1944 {
1945 Default: jsonPtr(42.0),
1946 },
1947 },
1948 },
1949 },
1950 },
1951 },
1952 PreserveUnknownFields: pointer.BoolPtr(false),
1953 },
1954 Status: apiextensions.CustomResourceDefinitionStatus{
1955 StoredVersions: []string{"version"},
1956 },
1957 },
1958 errors: []validationMatch{
1959 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "not", "default"),
1960 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "allOf[0]", "default"),
1961 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "anyOf[0]", "default"),
1962 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "oneOf[0]", "default"),
1963 },
1964 },
1965 {
1966 name: "invalid defaults with enabled feature gate, structural schema",
1967 resource: &apiextensions.CustomResourceDefinition{
1968 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
1969 Spec: apiextensions.CustomResourceDefinitionSpec{
1970 Group: "group.com",
1971 Version: "version",
1972 Versions: singleVersionList,
1973 Scope: apiextensions.NamespaceScoped,
1974 Names: apiextensions.CustomResourceDefinitionNames{
1975 Plural: "plural",
1976 Singular: "singular",
1977 Kind: "Plural",
1978 ListKind: "PluralList",
1979 },
1980 Validation: &apiextensions.CustomResourceValidation{
1981 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
1982 Type: "object",
1983 Properties: map[string]apiextensions.JSONSchemaProps{
1984 "a": {
1985 Type: "object",
1986 Properties: map[string]apiextensions.JSONSchemaProps{
1987 "foo": {
1988 Type: "string",
1989 },
1990 },
1991 Default: jsonPtr(map[string]interface{}{
1992 "foo": "abc",
1993 "bar": int64(42.0),
1994 }),
1995 },
1996 "b": {
1997 Type: "object",
1998 Properties: map[string]apiextensions.JSONSchemaProps{
1999 "foo": {
2000 Type: "string",
2001 },
2002 },
2003 Default: jsonPtr(map[string]interface{}{
2004 "foo": "abc",
2005 }),
2006 },
2007 "c": {
2008 Type: "object",
2009 Properties: map[string]apiextensions.JSONSchemaProps{
2010 "foo": {
2011 Type: "string",
2012 },
2013 },
2014 Default: jsonPtr(map[string]interface{}{
2015 "foo": int64(42),
2016 }),
2017 },
2018 "d": {
2019 Type: "object",
2020 Properties: map[string]apiextensions.JSONSchemaProps{
2021 "good": {
2022 Type: "string",
2023 Pattern: "a",
2024 },
2025 "bad": {
2026 Type: "string",
2027 Pattern: "b",
2028 },
2029 },
2030 Default: jsonPtr(map[string]interface{}{
2031 "good": "a",
2032 "bad": "a",
2033 }),
2034 },
2035 "e": {
2036 Type: "object",
2037 Properties: map[string]apiextensions.JSONSchemaProps{
2038 "preserveUnknownFields": {
2039 Type: "object",
2040 Default: jsonPtr(map[string]interface{}{
2041 "foo": "abc",
2042
2043 "bar": int64(42.0),
2044 }),
2045 },
2046 "nestedProperties": {
2047 Type: "object",
2048 Properties: map[string]apiextensions.JSONSchemaProps{
2049 "foo": {
2050 Type: "string",
2051 },
2052 },
2053 Default: jsonPtr(map[string]interface{}{
2054 "foo": "abc",
2055 "bar": int64(42.0),
2056 }),
2057 },
2058 },
2059 XPreserveUnknownFields: pointer.BoolPtr(true),
2060 },
2061
2062 "embedded-fine": {
2063 Type: "object",
2064 XEmbeddedResource: true,
2065 Properties: map[string]apiextensions.JSONSchemaProps{
2066 "foo": {
2067 Type: "string",
2068 },
2069 },
2070 Default: jsonPtr(map[string]interface{}{
2071 "foo": "abc",
2072 "apiVersion": "foo/v1",
2073 "kind": "v1",
2074 "metadata": map[string]interface{}{
2075 "name": "foo",
2076 },
2077 }),
2078 },
2079 "embedded-preserve": {
2080 Type: "object",
2081 XEmbeddedResource: true,
2082 XPreserveUnknownFields: pointer.BoolPtr(true),
2083 Properties: map[string]apiextensions.JSONSchemaProps{
2084 "foo": {
2085 Type: "string",
2086 },
2087 },
2088 Default: jsonPtr(map[string]interface{}{
2089 "foo": "abc",
2090 "apiVersion": "foo/v1",
2091 "kind": "v1",
2092 "metadata": map[string]interface{}{
2093 "name": "foo",
2094 },
2095 "bar": int64(42),
2096 }),
2097 },
2098 "embedded-preserve-unpruned-objectmeta": {
2099 Type: "object",
2100 XEmbeddedResource: true,
2101 XPreserveUnknownFields: pointer.BoolPtr(true),
2102 Properties: map[string]apiextensions.JSONSchemaProps{
2103 "foo": {
2104 Type: "string",
2105 },
2106 },
2107 Default: jsonPtr(map[string]interface{}{
2108 "foo": "abc",
2109 "apiVersion": "foo/v1",
2110 "kind": "v1",
2111 "metadata": map[string]interface{}{
2112 "name": "foo",
2113
2114 "unspecified": "bar",
2115 },
2116 "bar": int64(42),
2117 }),
2118 },
2119 },
2120 },
2121 },
2122 PreserveUnknownFields: pointer.BoolPtr(false),
2123 },
2124 Status: apiextensions.CustomResourceDefinitionStatus{
2125 StoredVersions: []string{"version"},
2126 },
2127 },
2128 errors: []validationMatch{
2129 invalid("spec", "validation", "openAPIV3Schema", "properties[a]", "default"),
2130 invalidtypecode("spec", "validation", "openAPIV3Schema", "properties[c]", "default", "foo"),
2131 invalid("spec", "validation", "openAPIV3Schema", "properties[d]", "default", "bad"),
2132
2133
2134 invalid("spec", "validation", "openAPIV3Schema", "properties[e]", "properties[preserveUnknownFields]", "default"),
2135 invalid("spec", "validation", "openAPIV3Schema", "properties[e]", "properties[nestedProperties]", "default"),
2136 },
2137 },
2138 {
2139 name: "additionalProperties at resource root",
2140 resource: &apiextensions.CustomResourceDefinition{
2141 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
2142 Spec: apiextensions.CustomResourceDefinitionSpec{
2143 Group: "group.com",
2144 Version: "version",
2145 Versions: singleVersionList,
2146 Scope: apiextensions.NamespaceScoped,
2147 Names: apiextensions.CustomResourceDefinitionNames{
2148 Plural: "plural",
2149 Singular: "singular",
2150 Kind: "Plural",
2151 ListKind: "PluralList",
2152 },
2153 Validation: &apiextensions.CustomResourceValidation{
2154 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2155 Type: "object",
2156 Properties: map[string]apiextensions.JSONSchemaProps{
2157 "embedded1": {
2158 Type: "object",
2159 XEmbeddedResource: true,
2160 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
2161 Schema: &apiextensions.JSONSchemaProps{Type: "string"},
2162 },
2163 },
2164 "embedded2": {
2165 Type: "object",
2166 XEmbeddedResource: true,
2167 XPreserveUnknownFields: pointer.BoolPtr(true),
2168 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
2169 Schema: &apiextensions.JSONSchemaProps{Type: "string"},
2170 },
2171 },
2172 },
2173 },
2174 },
2175 PreserveUnknownFields: pointer.BoolPtr(false),
2176 },
2177 Status: apiextensions.CustomResourceDefinitionStatus{
2178 StoredVersions: []string{"version"},
2179 },
2180 },
2181 errors: []validationMatch{
2182 forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded1]", "additionalProperties"),
2183 required("spec", "validation", "openAPIV3Schema", "properties[embedded1]", "properties"),
2184 forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded2]", "additionalProperties"),
2185 },
2186 },
2187 {
2188
2189 name: "v1.15 era tests for metadata defaults",
2190 resource: &apiextensions.CustomResourceDefinition{
2191 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
2192 Spec: apiextensions.CustomResourceDefinitionSpec{
2193 Group: "group.com",
2194 Version: "v1",
2195 Versions: []apiextensions.CustomResourceDefinitionVersion{
2196 {
2197 Name: "v1",
2198 Served: true,
2199 Storage: true,
2200 Schema: &apiextensions.CustomResourceValidation{
2201 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2202 Type: "object",
2203 Properties: map[string]apiextensions.JSONSchemaProps{
2204 "metadata": {
2205 Type: "object",
2206
2207 Default: jsonPtr(map[string]interface{}{
2208 "name": "foo",
2209 }),
2210 },
2211 "embedded": {
2212 Type: "object",
2213 XEmbeddedResource: true,
2214 Properties: map[string]apiextensions.JSONSchemaProps{
2215 "metadata": {
2216 Type: "object",
2217 Default: jsonPtr(map[string]interface{}{
2218 "name": "foo",
2219
2220 "unknown": int64(42),
2221 }),
2222 },
2223 },
2224 },
2225 },
2226 },
2227 },
2228 },
2229 {
2230 Name: "v2",
2231 Served: true,
2232 Storage: false,
2233 Schema: &apiextensions.CustomResourceValidation{
2234 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2235 Type: "object",
2236 Properties: map[string]apiextensions.JSONSchemaProps{
2237 "metadata": {
2238 Type: "object",
2239 Properties: map[string]apiextensions.JSONSchemaProps{
2240 "name": {
2241 Type: "string",
2242
2243 Default: jsonPtr("foo"),
2244 },
2245 },
2246 },
2247 "embedded": {
2248 Type: "object",
2249 XEmbeddedResource: true,
2250 Properties: map[string]apiextensions.JSONSchemaProps{
2251 "apiVersion": {
2252 Type: "string",
2253 Default: jsonPtr("v1"),
2254 },
2255 "kind": {
2256 Type: "string",
2257 Default: jsonPtr("Pod"),
2258 },
2259 "metadata": {
2260 Type: "object",
2261 Properties: map[string]apiextensions.JSONSchemaProps{
2262 "name": {
2263 Type: "string",
2264 Default: jsonPtr("foo"),
2265 },
2266 },
2267 },
2268 },
2269 },
2270 },
2271 },
2272 },
2273 },
2274 {
2275 Name: "v3",
2276 Served: true,
2277 Storage: false,
2278 Schema: &apiextensions.CustomResourceValidation{
2279 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2280 Type: "object",
2281 Properties: map[string]apiextensions.JSONSchemaProps{
2282 "embedded": {
2283 Type: "object",
2284 XEmbeddedResource: true,
2285 Properties: map[string]apiextensions.JSONSchemaProps{
2286 "apiVersion": {
2287 Type: "string",
2288 Default: jsonPtr("v1"),
2289 },
2290 "kind": {
2291 Type: "string",
2292
2293 Default: jsonPtr("%"),
2294 },
2295 "metadata": {
2296 Type: "object",
2297 Default: jsonPtr(map[string]interface{}{
2298 "labels": map[string]interface{}{
2299
2300 "bar": "x y",
2301 },
2302 }),
2303 },
2304 },
2305 },
2306 },
2307 },
2308 },
2309 },
2310 {
2311 Name: "v4",
2312 Served: true,
2313 Storage: false,
2314 Schema: &apiextensions.CustomResourceValidation{
2315 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2316 Type: "object",
2317 Properties: map[string]apiextensions.JSONSchemaProps{
2318 "embedded": {
2319 Type: "object",
2320 XEmbeddedResource: true,
2321 Properties: map[string]apiextensions.JSONSchemaProps{
2322 "metadata": {
2323 Type: "object",
2324 Properties: map[string]apiextensions.JSONSchemaProps{
2325 "name": {
2326 Type: "string",
2327
2328 Default: jsonPtr(int64(42)),
2329 },
2330 "labels": {
2331 Type: "object",
2332 Properties: map[string]apiextensions.JSONSchemaProps{
2333 "bar": {
2334 Type: "string",
2335
2336 Default: jsonPtr(int64(42)),
2337 },
2338 },
2339 },
2340 },
2341 },
2342 },
2343 },
2344 },
2345 },
2346 },
2347 },
2348 },
2349 Scope: apiextensions.NamespaceScoped,
2350 Names: apiextensions.CustomResourceDefinitionNames{
2351 Plural: "plural",
2352 Singular: "singular",
2353 Kind: "Plural",
2354 ListKind: "PluralList",
2355 },
2356 PreserveUnknownFields: pointer.BoolPtr(false),
2357 },
2358 Status: apiextensions.CustomResourceDefinitionStatus{
2359 StoredVersions: []string{"v1"},
2360 },
2361 },
2362 errors: []validationMatch{
2363
2364 forbidden("spec", "versions[0]", "schema", "openAPIV3Schema", "properties[metadata]", "default"),
2365
2366
2367 forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[metadata]", "properties[name]", "default"),
2368
2369
2370 invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "default"),
2371
2372 invalid("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[kind]", "default"),
2373
2374
2375 invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[labels]", "properties[bar]", "default"),
2376
2377 invalid("spec", "versions[3]", "schema", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[name]", "default"),
2378 },
2379 },
2380 {
2381 name: "default inside additionalSchema",
2382 resource: &apiextensions.CustomResourceDefinition{
2383 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
2384 Spec: apiextensions.CustomResourceDefinitionSpec{
2385 Group: "group.com",
2386 Version: "v1",
2387 Versions: []apiextensions.CustomResourceDefinitionVersion{
2388 {
2389 Name: "v1",
2390 Served: true,
2391 Storage: true,
2392 },
2393 },
2394 Validation: &apiextensions.CustomResourceValidation{
2395 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2396 Type: "object",
2397 Properties: map[string]apiextensions.JSONSchemaProps{
2398 "embedded": {
2399 Type: "object",
2400 XEmbeddedResource: true,
2401 Properties: map[string]apiextensions.JSONSchemaProps{
2402 "metadata": {
2403 Type: "object",
2404 Properties: map[string]apiextensions.JSONSchemaProps{
2405 "annotations": {
2406 Type: "object",
2407 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
2408 Schema: &apiextensions.JSONSchemaProps{
2409 Type: "string",
2410
2411 Default: jsonPtr("abc"),
2412 },
2413 },
2414 },
2415 },
2416 },
2417 },
2418 },
2419 },
2420 },
2421 },
2422 Scope: apiextensions.NamespaceScoped,
2423 Names: apiextensions.CustomResourceDefinitionNames{
2424 Plural: "plural",
2425 Singular: "singular",
2426 Kind: "Plural",
2427 ListKind: "PluralList",
2428 },
2429 PreserveUnknownFields: pointer.BoolPtr(false),
2430 },
2431 Status: apiextensions.CustomResourceDefinitionStatus{
2432 StoredVersions: []string{"v1"},
2433 },
2434 },
2435 errors: []validationMatch{
2436
2437 forbidden("spec", "validation", "openAPIV3Schema", "properties[embedded]", "properties[metadata]", "properties[annotations]", "additionalProperties", "default"),
2438 },
2439 },
2440 {
2441 name: "top-level metadata default",
2442 resource: &apiextensions.CustomResourceDefinition{
2443 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
2444 Spec: apiextensions.CustomResourceDefinitionSpec{
2445 Group: "group.com",
2446 Version: "v1",
2447 Versions: []apiextensions.CustomResourceDefinitionVersion{
2448 {
2449 Name: "v1",
2450 Served: true,
2451 Storage: true,
2452 },
2453 },
2454 Validation: &apiextensions.CustomResourceValidation{
2455 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2456 Type: "object",
2457 Properties: map[string]apiextensions.JSONSchemaProps{
2458 "metadata": {
2459 Type: "object",
2460
2461 Default: jsonPtr(map[string]interface{}{
2462 "name": "foo",
2463 }),
2464 },
2465 },
2466 },
2467 },
2468 Scope: apiextensions.NamespaceScoped,
2469 Names: apiextensions.CustomResourceDefinitionNames{
2470 Plural: "plural",
2471 Singular: "singular",
2472 Kind: "Plural",
2473 ListKind: "PluralList",
2474 },
2475 PreserveUnknownFields: pointer.BoolPtr(false),
2476 },
2477 Status: apiextensions.CustomResourceDefinitionStatus{
2478 StoredVersions: []string{"v1"},
2479 },
2480 },
2481 errors: []validationMatch{
2482 forbidden("spec", "validation", "openAPIV3Schema", "properties[metadata]", "default"),
2483 },
2484 },
2485 {
2486 name: "embedded metadata defaults",
2487 resource: &apiextensions.CustomResourceDefinition{
2488 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
2489 Spec: apiextensions.CustomResourceDefinitionSpec{
2490 Group: "group.com",
2491 Version: "v1",
2492 Versions: []apiextensions.CustomResourceDefinitionVersion{
2493 {
2494 Name: "v1",
2495 Served: true,
2496 Storage: true,
2497 },
2498 },
2499 Validation: &apiextensions.CustomResourceValidation{
2500 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
2501 Type: "object",
2502 Properties: map[string]apiextensions.JSONSchemaProps{
2503 "embedded": {
2504 Type: "object",
2505 XEmbeddedResource: true,
2506 Properties: map[string]apiextensions.JSONSchemaProps{
2507 "metadata": {
2508 Type: "object",
2509 Default: jsonPtr(map[string]interface{}{
2510 "name": "foo",
2511 }),
2512 },
2513 },
2514 },
2515
2516 "allowed-in-object-defaults": {
2517 Type: "object",
2518 XEmbeddedResource: true,
2519 Properties: map[string]apiextensions.JSONSchemaProps{
2520 "apiVersion": {
2521 Type: "string",
2522 Default: jsonPtr("v1"),
2523 },
2524 "kind": {
2525 Type: "string",
2526 Default: jsonPtr("Pod"),
2527 },
2528 "metadata": {
2529 Type: "object",
2530 Properties: map[string]apiextensions.JSONSchemaProps{
2531 "name": {
2532 Type: "string",
2533 Default: jsonPtr("foo"),
2534 },
2535 },
2536
2537 Default: jsonPtr(map[string]interface{}{
2538 "unknown": int64(42),
2539 }),
2540 },
2541 },
2542 },
2543 "allowed-object-defaults": {
2544 Type: "object",
2545 Properties: map[string]apiextensions.JSONSchemaProps{
2546 "something": {
2547 Type: "string",
2548 },
2549 },
2550 XEmbeddedResource: true,
2551 Default: jsonPtr(map[string]interface{}{
2552 "apiVersion": "v1",
2553 "kind": "Pod",
2554 "metadata": map[string]interface{}{
2555 "name": "foo",
2556 "unknown": int64(42),
2557 },
2558 }),
2559 },
2560 "allowed-spanning-object-defaults": {
2561 Type: "object",
2562 Properties: map[string]apiextensions.JSONSchemaProps{
2563 "embedded": {
2564 Type: "object",
2565 XEmbeddedResource: true,
2566 Properties: map[string]apiextensions.JSONSchemaProps{
2567 "something": {
2568 Type: "string",
2569 },
2570 },
2571 },
2572 },
2573 Default: jsonPtr(map[string]interface{}{
2574 "embedded": map[string]interface{}{
2575 "apiVersion": "v1",
2576 "kind": "Pod",
2577 "metadata": map[string]interface{}{
2578 "name": "foo",
2579 "unknown": int64(42),
2580 },
2581 },
2582 }),
2583 },
2584
2585 "unknown-field-object-defaults": {
2586 Type: "object",
2587 XEmbeddedResource: true,
2588 Properties: map[string]apiextensions.JSONSchemaProps{
2589 "something": {
2590 Type: "string",
2591 },
2592 },
2593 Default: jsonPtr(map[string]interface{}{
2594 "apiVersion": "v1",
2595 "kind": "Pod",
2596 "metadata": map[string]interface{}{
2597 "name": "foo",
2598
2599 "unknown": int64(42),
2600 },
2601
2602 "unknown": int64(42),
2603 }),
2604 },
2605 "unknown-field-spanning-object-defaults": {
2606 Type: "object",
2607 Properties: map[string]apiextensions.JSONSchemaProps{
2608 "embedded": {
2609 Type: "object",
2610 XEmbeddedResource: true,
2611 Properties: map[string]apiextensions.JSONSchemaProps{
2612 "something": {
2613 Type: "string",
2614 },
2615 },
2616 },
2617 },
2618 Default: jsonPtr(map[string]interface{}{
2619 "embedded": map[string]interface{}{
2620 "apiVersion": "v1",
2621 "kind": "Pod",
2622 "metadata": map[string]interface{}{
2623 "name": "foo",
2624
2625 "unknown": int64(42),
2626 },
2627
2628 "unknown": int64(42),
2629 },
2630
2631 "unknown": int64(42),
2632 }),
2633 },
2634
2635 "x-preserve-unknown-fields-unknown-field-object-defaults": {
2636 Type: "object",
2637 XEmbeddedResource: true,
2638 XPreserveUnknownFields: pointer.BoolPtr(true),
2639 Properties: map[string]apiextensions.JSONSchemaProps{},
2640 Default: jsonPtr(map[string]interface{}{
2641 "apiVersion": "v1",
2642 "kind": "Pod",
2643 "metadata": map[string]interface{}{
2644 "name": "foo",
2645
2646 "unknown": int64(42),
2647 },
2648
2649 "unknown": int64(42),
2650 }),
2651 },
2652 "x-preserve-unknown-fields-unknown-field-spanning-object-defaults": {
2653 Type: "object",
2654 Properties: map[string]apiextensions.JSONSchemaProps{
2655 "embedded": {
2656 Type: "object",
2657 XEmbeddedResource: true,
2658 XPreserveUnknownFields: pointer.BoolPtr(true),
2659 Properties: map[string]apiextensions.JSONSchemaProps{},
2660 },
2661 },
2662 Default: jsonPtr(map[string]interface{}{
2663 "embedded": map[string]interface{}{
2664 "apiVersion": "v1",
2665 "kind": "Pod",
2666 "metadata": map[string]interface{}{
2667 "name": "foo",
2668
2669 "unknown": int64(42),
2670 },
2671
2672 "unknown": int64(42),
2673 },
2674 }),
2675 },
2676 "x-preserve-unknown-fields-unknown-field-outside": {
2677 Type: "object",
2678 Properties: map[string]apiextensions.JSONSchemaProps{
2679 "embedded": {
2680 Type: "object",
2681 XEmbeddedResource: true,
2682 XPreserveUnknownFields: pointer.BoolPtr(true),
2683 Properties: map[string]apiextensions.JSONSchemaProps{},
2684 },
2685 },
2686 Default: jsonPtr(map[string]interface{}{
2687 "embedded": map[string]interface{}{
2688 "apiVersion": "v1",
2689 "kind": "Pod",
2690 "metadata": map[string]interface{}{
2691 "name": "foo",
2692
2693 "unknown": int64(42),
2694 },
2695
2696 "unknown": int64(42),
2697 },
2698
2699 "unknown": int64(42),
2700 }),
2701 },
2702
2703 "wrongly-typed-in-object-defaults": {
2704 Type: "object",
2705 XEmbeddedResource: true,
2706 Properties: map[string]apiextensions.JSONSchemaProps{
2707 "apiVersion": {
2708 Type: "string",
2709
2710 Default: jsonPtr(int64(42)),
2711 },
2712 "kind": {
2713 Type: "string",
2714
2715 Default: jsonPtr(int64(42)),
2716 },
2717 "metadata": {
2718 Type: "object",
2719 Properties: map[string]apiextensions.JSONSchemaProps{
2720 "name": {
2721 Type: "string",
2722
2723 Default: jsonPtr(int64(42)),
2724 },
2725 "annotations": {
2726 Type: "object",
2727 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
2728 Schema: &apiextensions.JSONSchemaProps{
2729 Type: "string",
2730 },
2731 },
2732
2733 Default: jsonPtr(int64(42)),
2734 },
2735 },
2736 },
2737 },
2738 },
2739 "wrongly-typed-object-defaults-apiVersion": {
2740 Type: "object",
2741 XEmbeddedResource: true,
2742 Properties: map[string]apiextensions.JSONSchemaProps{
2743 "something": {
2744 Type: "string",
2745 },
2746 },
2747 Default: jsonPtr(map[string]interface{}{
2748
2749 "apiVersion": int64(42),
2750 }),
2751 },
2752 "wrongly-typed-object-defaults-kind": {
2753 Type: "object",
2754 XEmbeddedResource: true,
2755 Properties: map[string]apiextensions.JSONSchemaProps{
2756 "something": {
2757 Type: "string",
2758 },
2759 },
2760 Default: jsonPtr(map[string]interface{}{
2761
2762 "kind": int64(42),
2763 }),
2764 },
2765 "wrongly-typed-object-defaults-name": {
2766 Type: "object",
2767 XEmbeddedResource: true,
2768 Properties: map[string]apiextensions.JSONSchemaProps{
2769 "something": {
2770 Type: "string",
2771 },
2772 },
2773 Default: jsonPtr(map[string]interface{}{
2774 "metadata": map[string]interface{}{
2775
2776 "name": int64(42),
2777 },
2778 }),
2779 },
2780 "wrongly-typed-object-defaults-labels": {
2781 Type: "object",
2782 XEmbeddedResource: true,
2783 Properties: map[string]apiextensions.JSONSchemaProps{
2784 "something": {
2785 Type: "string",
2786 },
2787 },
2788 Default: jsonPtr(map[string]interface{}{
2789 "metadata": map[string]interface{}{
2790 "labels": map[string]interface{}{
2791
2792 "foo": int64(42),
2793 },
2794 },
2795 }),
2796 },
2797 "wrongly-typed-object-defaults-annotations": {
2798 Type: "object",
2799 XEmbeddedResource: true,
2800 Properties: map[string]apiextensions.JSONSchemaProps{
2801 "something": {
2802 Type: "string",
2803 },
2804 },
2805 Default: jsonPtr(map[string]interface{}{
2806 "metadata": map[string]interface{}{
2807
2808 "annotations": int64(42),
2809 },
2810 }),
2811 },
2812 "wrongly-typed-object-defaults-metadata": {
2813 Type: "object",
2814 XEmbeddedResource: true,
2815 Properties: map[string]apiextensions.JSONSchemaProps{
2816 "something": {
2817 Type: "string",
2818 },
2819 },
2820 Default: jsonPtr(map[string]interface{}{
2821
2822 "metadata": int64(42),
2823 }),
2824 },
2825
2826 "wrongly-typed-spanning-object-defaults-apiVersion": {
2827 Type: "object",
2828 Properties: map[string]apiextensions.JSONSchemaProps{
2829 "embedded": {
2830 Type: "object",
2831 XEmbeddedResource: true,
2832 Properties: map[string]apiextensions.JSONSchemaProps{
2833 "something": {
2834 Type: "string",
2835 },
2836 },
2837 },
2838 },
2839 Default: jsonPtr(map[string]interface{}{
2840 "embedded": map[string]interface{}{
2841
2842 "apiVersion": int64(42),
2843 },
2844 }),
2845 },
2846 "wrongly-typed-spanning-object-defaults-kind": {
2847 Type: "object",
2848 Properties: map[string]apiextensions.JSONSchemaProps{
2849 "embedded": {
2850 Type: "object",
2851 XEmbeddedResource: true,
2852 Properties: map[string]apiextensions.JSONSchemaProps{
2853 "something": {
2854 Type: "string",
2855 },
2856 },
2857 },
2858 },
2859 Default: jsonPtr(map[string]interface{}{
2860 "embedded": map[string]interface{}{
2861
2862 "kind": int64(42),
2863 },
2864 }),
2865 },
2866 "wrongly-typed-spanning-object-defaults-name": {
2867 Type: "object",
2868 Properties: map[string]apiextensions.JSONSchemaProps{
2869 "embedded": {
2870 Type: "object",
2871 XEmbeddedResource: true,
2872 Properties: map[string]apiextensions.JSONSchemaProps{
2873 "something": {
2874 Type: "string",
2875 },
2876 },
2877 },
2878 },
2879 Default: jsonPtr(map[string]interface{}{
2880 "embedded": map[string]interface{}{
2881 "metadata": map[string]interface{}{
2882 "name": int64(42),
2883 },
2884 },
2885 }),
2886 },
2887 "wrongly-typed-spanning-object-defaults-labels": {
2888 Type: "object",
2889 Properties: map[string]apiextensions.JSONSchemaProps{
2890 "embedded": {
2891 Type: "object",
2892 XEmbeddedResource: true,
2893 Properties: map[string]apiextensions.JSONSchemaProps{
2894 "something": {
2895 Type: "string",
2896 },
2897 },
2898 },
2899 },
2900 Default: jsonPtr(map[string]interface{}{
2901 "embedded": map[string]interface{}{
2902 "metadata": map[string]interface{}{
2903 "labels": map[string]interface{}{
2904
2905 "foo": int64(42),
2906 },
2907 },
2908 },
2909 }),
2910 },
2911 "wrongly-typed-spanning-object-defaults-annotations": {
2912 Type: "object",
2913 Properties: map[string]apiextensions.JSONSchemaProps{
2914 "embedded": {
2915 Type: "object",
2916 XEmbeddedResource: true,
2917 Properties: map[string]apiextensions.JSONSchemaProps{
2918 "something": {
2919 Type: "string",
2920 },
2921 },
2922 },
2923 },
2924 Default: jsonPtr(map[string]interface{}{
2925 "embedded": map[string]interface{}{
2926 "metadata": map[string]interface{}{
2927
2928 "annotations": int64(42),
2929 },
2930 },
2931 }),
2932 },
2933 "wrongly-typed-spanning-object-defaults-metadata": {
2934 Type: "object",
2935 Properties: map[string]apiextensions.JSONSchemaProps{
2936 "embedded": {
2937 Type: "object",
2938 XEmbeddedResource: true,
2939 Properties: map[string]apiextensions.JSONSchemaProps{
2940 "something": {
2941 Type: "string",
2942 },
2943 },
2944 },
2945 },
2946 Default: jsonPtr(map[string]interface{}{
2947 "embedded": map[string]interface{}{
2948 "metadata": int64(42),
2949 },
2950 }),
2951 },
2952
2953 "invalid-in-object-defaults": {
2954 Type: "object",
2955 XEmbeddedResource: true,
2956 Properties: map[string]apiextensions.JSONSchemaProps{
2957 "kind": {
2958 Type: "string",
2959
2960 Default: jsonPtr("%"),
2961 },
2962 "metadata": {
2963 Type: "object",
2964 Properties: map[string]apiextensions.JSONSchemaProps{
2965 "name": {
2966 Type: "string",
2967
2968 Default: jsonPtr("%"),
2969 },
2970 "labels": {
2971 Type: "object",
2972 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
2973 Schema: &apiextensions.JSONSchemaProps{
2974 Type: "string",
2975 },
2976 },
2977
2978 Default: jsonPtr(map[string]interface{}{
2979 "foo": "x y",
2980 }),
2981 },
2982 },
2983 },
2984 },
2985 },
2986 "invalid-object-defaults-kind": {
2987 Type: "object",
2988 XEmbeddedResource: true,
2989 Properties: map[string]apiextensions.JSONSchemaProps{
2990 "something": {
2991 Type: "string",
2992 },
2993 },
2994 Default: jsonPtr(map[string]interface{}{
2995 "apiVersion": "foo/v1",
2996
2997 "kind": "%",
2998 }),
2999 },
3000 "invalid-object-defaults-name": {
3001 Type: "object",
3002 XEmbeddedResource: true,
3003 Properties: map[string]apiextensions.JSONSchemaProps{
3004 "something": {
3005 Type: "string",
3006 },
3007 },
3008 Default: jsonPtr(map[string]interface{}{
3009 "apiVersion": "foo/v1",
3010 "kind": "Foo",
3011 "metadata": map[string]interface{}{
3012
3013 "name": "%",
3014 },
3015 }),
3016 },
3017 "invalid-object-defaults-labels": {
3018 Type: "object",
3019 XEmbeddedResource: true,
3020 Properties: map[string]apiextensions.JSONSchemaProps{
3021 "something": {
3022 Type: "string",
3023 },
3024 },
3025 Default: jsonPtr(map[string]interface{}{
3026 "apiVersion": "foo/v1",
3027 "kind": "Foo",
3028 "metadata": map[string]interface{}{
3029 "labels": map[string]interface{}{
3030
3031 "foo": "x y",
3032 },
3033 },
3034 }),
3035 },
3036 "invalid-spanning-object-defaults-kind": {
3037 Type: "object",
3038 Properties: map[string]apiextensions.JSONSchemaProps{
3039 "embedded": {
3040 Type: "object",
3041 XEmbeddedResource: true,
3042 Properties: map[string]apiextensions.JSONSchemaProps{
3043 "something": {
3044 Type: "string",
3045 },
3046 },
3047 },
3048 },
3049 Default: jsonPtr(map[string]interface{}{
3050 "embedded": map[string]interface{}{
3051 "apiVersion": "foo/v1",
3052
3053 "kind": "%",
3054 },
3055 }),
3056 },
3057 "invalid-spanning-object-defaults-name": {
3058 Type: "object",
3059 Properties: map[string]apiextensions.JSONSchemaProps{
3060 "embedded": {
3061 Type: "object",
3062 XEmbeddedResource: true,
3063 Properties: map[string]apiextensions.JSONSchemaProps{
3064 "something": {
3065 Type: "string",
3066 },
3067 },
3068 },
3069 },
3070 Default: jsonPtr(map[string]interface{}{
3071 "embedded": map[string]interface{}{
3072 "apiVersion": "foo/v1",
3073 "kind": "Foo",
3074 "metadata": map[string]interface{}{
3075
3076 "name": "%",
3077 },
3078 },
3079 }),
3080 },
3081 "invalid-spanning-object-defaults-labels": {
3082 Type: "object",
3083 Properties: map[string]apiextensions.JSONSchemaProps{
3084 "embedded": {
3085 Type: "object",
3086 XEmbeddedResource: true,
3087 Properties: map[string]apiextensions.JSONSchemaProps{
3088 "something": {
3089 Type: "string",
3090 },
3091 },
3092 },
3093 },
3094 Default: jsonPtr(map[string]interface{}{
3095 "embedded": map[string]interface{}{
3096 "apiVersion": "foo/v1",
3097 "kind": "Foo",
3098 "metadata": map[string]interface{}{
3099 "labels": map[string]interface{}{
3100
3101 "foo": "x y",
3102 },
3103 },
3104 },
3105 }),
3106 },
3107
3108 "in-object-defaults-with-valid-constraints": {
3109 Type: "object",
3110 XEmbeddedResource: true,
3111 Properties: map[string]apiextensions.JSONSchemaProps{
3112 "apiVersion": {
3113 Type: "string",
3114
3115 Default: jsonPtr("foo/v1"),
3116 Enum: jsonSlice("foo/v1"),
3117 },
3118 "kind": {
3119 Type: "string",
3120
3121 Default: jsonPtr("Foo"),
3122 Enum: jsonSlice("Foo"),
3123 },
3124 "metadata": {
3125 Type: "object",
3126 Properties: map[string]apiextensions.JSONSchemaProps{
3127 "name": {
3128 Type: "string",
3129
3130 Default: jsonPtr("foo"),
3131 Enum: jsonSlice("foo"),
3132 },
3133 "labels": {
3134 Type: "object",
3135 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3136 Schema: &apiextensions.JSONSchemaProps{
3137 Type: "string",
3138 Enum: jsonSlice("foo"),
3139 },
3140 },
3141
3142 Default: jsonPtr(map[string]interface{}{
3143 "foo": "foo",
3144 }),
3145 },
3146 },
3147 },
3148 },
3149 },
3150 "metadata-defaults-with-valid-constraints": {
3151 Type: "object",
3152 XEmbeddedResource: true,
3153 Properties: map[string]apiextensions.JSONSchemaProps{
3154 "metadata": {
3155 Type: "object",
3156 Properties: map[string]apiextensions.JSONSchemaProps{
3157 "name": {
3158 Type: "string",
3159 Enum: jsonSlice("foo"),
3160 },
3161 "labels": {
3162 Type: "object",
3163 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3164 Schema: &apiextensions.JSONSchemaProps{
3165 Type: "string",
3166 Enum: jsonSlice("foo"),
3167 },
3168 },
3169 },
3170 },
3171
3172 Default: jsonPtr(map[string]interface{}{
3173 "name": "foo",
3174 "labels": map[string]interface{}{
3175 "foo": "foo",
3176 },
3177 }),
3178 },
3179 },
3180 },
3181 "object-defaults-with-valid-constraints": {
3182 Type: "object",
3183 XEmbeddedResource: true,
3184 Properties: map[string]apiextensions.JSONSchemaProps{
3185 "apiVersion": {
3186 Type: "string",
3187 Enum: jsonSlice("foo/v1"),
3188 },
3189 "kind": {
3190 Type: "string",
3191 Enum: jsonSlice("Foo"),
3192 },
3193 "metadata": {
3194 Type: "object",
3195 Properties: map[string]apiextensions.JSONSchemaProps{
3196 "name": {
3197 Type: "string",
3198 Enum: jsonSlice("foo"),
3199 },
3200 "labels": {
3201 Type: "object",
3202 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3203 Schema: &apiextensions.JSONSchemaProps{
3204 Type: "string",
3205 Enum: jsonSlice("foo"),
3206 },
3207 },
3208 },
3209 },
3210 },
3211 },
3212
3213 Default: jsonPtr(map[string]interface{}{
3214 "apiVersion": "foo/v1",
3215 "kind": "Foo",
3216 "metadata": map[string]interface{}{
3217 "name": "foo",
3218 "labels": map[string]interface{}{
3219 "foo": "foo",
3220 },
3221 },
3222 }),
3223 },
3224 "spanning-defaults-with-valid-constraints": {
3225 Type: "object",
3226 Properties: map[string]apiextensions.JSONSchemaProps{
3227 "embedded": {
3228 Type: "object",
3229 XEmbeddedResource: true,
3230 Properties: map[string]apiextensions.JSONSchemaProps{
3231 "apiVersion": {
3232 Type: "string",
3233 Enum: jsonSlice("foo/v1"),
3234 },
3235 "kind": {
3236 Type: "string",
3237 Enum: jsonSlice("Foo"),
3238 },
3239 "metadata": {
3240 Type: "object",
3241 Properties: map[string]apiextensions.JSONSchemaProps{
3242 "name": {
3243 Type: "string",
3244 Enum: jsonSlice("foo"),
3245 },
3246 "labels": {
3247 Type: "object",
3248 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3249 Schema: &apiextensions.JSONSchemaProps{
3250 Type: "string",
3251 Enum: jsonSlice("foo"),
3252 },
3253 },
3254 },
3255 },
3256 },
3257 },
3258 },
3259 },
3260
3261 Default: jsonPtr(map[string]interface{}{
3262 "embedded": map[string]interface{}{
3263 "apiVersion": "foo/v1",
3264 "kind": "Foo",
3265 "metadata": map[string]interface{}{
3266 "name": "foo",
3267 "labels": map[string]interface{}{
3268 "foo": "foo",
3269 },
3270 },
3271 },
3272 }),
3273 },
3274
3275 "in-object-defaults-with-invalid-constraints": {
3276 Type: "object",
3277 XEmbeddedResource: true,
3278 Properties: map[string]apiextensions.JSONSchemaProps{
3279 "apiVersion": {
3280 Type: "string",
3281 Description: "BREAK",
3282
3283 Default: jsonPtr("bar/v1"),
3284 Enum: jsonSlice("foo/v1"),
3285 },
3286 "kind": {
3287 Type: "string",
3288
3289 Default: jsonPtr("Bar"),
3290 Enum: jsonSlice("Foo"),
3291 },
3292 "metadata": {
3293 Type: "object",
3294 Properties: map[string]apiextensions.JSONSchemaProps{
3295 "name": {
3296 Type: "string",
3297
3298 Default: jsonPtr("bar"),
3299 Enum: jsonSlice("foo"),
3300 },
3301 "labels": {
3302 Type: "object",
3303 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3304 Schema: &apiextensions.JSONSchemaProps{
3305 Type: "string",
3306 Enum: jsonSlice("foo"),
3307 },
3308 },
3309
3310 Default: jsonPtr(map[string]interface{}{
3311 "foo": "bar",
3312 }),
3313 },
3314 },
3315 },
3316 },
3317 },
3318 "metadata-defaults-with-invalid-constraints-name": {
3319 Type: "object",
3320 XEmbeddedResource: true,
3321 Properties: map[string]apiextensions.JSONSchemaProps{
3322 "metadata": {
3323 Type: "object",
3324 Properties: map[string]apiextensions.JSONSchemaProps{
3325 "name": {
3326 Type: "string",
3327 Enum: jsonSlice("foo"),
3328 },
3329 "labels": {
3330 Type: "object",
3331 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3332 Schema: &apiextensions.JSONSchemaProps{
3333 Type: "string",
3334 Enum: jsonSlice("foo"),
3335 },
3336 },
3337 },
3338 },
3339
3340 Default: jsonPtr(map[string]interface{}{
3341 "name": "bar",
3342 }),
3343 },
3344 },
3345 },
3346 "metadata-defaults-with-invalid-constraints-labels": {
3347 Type: "object",
3348 XEmbeddedResource: true,
3349 Properties: map[string]apiextensions.JSONSchemaProps{
3350 "metadata": {
3351 Type: "object",
3352 Properties: map[string]apiextensions.JSONSchemaProps{
3353 "name": {
3354 Type: "string",
3355 Enum: jsonSlice("foo"),
3356 },
3357 "labels": {
3358 Type: "object",
3359 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3360 Schema: &apiextensions.JSONSchemaProps{
3361 Type: "string",
3362 Enum: jsonSlice("foo"),
3363 },
3364 },
3365 },
3366 },
3367
3368 Default: jsonPtr(map[string]interface{}{
3369 "name": "foo",
3370 "labels": map[string]interface{}{
3371 "foo": "bar",
3372 },
3373 }),
3374 },
3375 },
3376 },
3377 "object-defaults-with-invalid-constraints-name": {
3378 Type: "object",
3379 XEmbeddedResource: true,
3380 Properties: map[string]apiextensions.JSONSchemaProps{
3381 "apiVersion": {
3382 Type: "string",
3383 Enum: jsonSlice("foo/v1"),
3384 },
3385 "kind": {
3386 Type: "string",
3387 Enum: jsonSlice("Foo"),
3388 },
3389 "metadata": {
3390 Type: "object",
3391 Properties: map[string]apiextensions.JSONSchemaProps{
3392 "name": {
3393 Type: "string",
3394 Enum: jsonSlice("foo"),
3395 },
3396 "labels": {
3397 Type: "object",
3398 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3399 Schema: &apiextensions.JSONSchemaProps{
3400 Type: "string",
3401 Enum: jsonSlice("foo"),
3402 },
3403 },
3404 },
3405 },
3406 },
3407 },
3408
3409 Default: jsonPtr(map[string]interface{}{
3410 "apiVersion": "foo/v1",
3411 "kind": "Foo",
3412 "metadata": map[string]interface{}{
3413 "name": "bar",
3414 },
3415 }),
3416 },
3417 "object-defaults-with-invalid-constraints-labels": {
3418 Type: "object",
3419 XEmbeddedResource: true,
3420 Properties: map[string]apiextensions.JSONSchemaProps{
3421 "apiVersion": {
3422 Type: "string",
3423 Enum: jsonSlice("foo/v1"),
3424 },
3425 "kind": {
3426 Type: "string",
3427 Enum: jsonSlice("Foo"),
3428 },
3429 "metadata": {
3430 Type: "object",
3431 Properties: map[string]apiextensions.JSONSchemaProps{
3432 "name": {
3433 Type: "string",
3434 Enum: jsonSlice("foo"),
3435 },
3436 "labels": {
3437 Type: "object",
3438 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3439 Schema: &apiextensions.JSONSchemaProps{
3440 Type: "string",
3441 Enum: jsonSlice("foo"),
3442 },
3443 },
3444 },
3445 },
3446 },
3447 },
3448
3449 Default: jsonPtr(map[string]interface{}{
3450 "apiVersion": "foo/v1",
3451 "kind": "Foo",
3452 "metadata": map[string]interface{}{
3453 "name": "foo",
3454 "labels": map[string]interface{}{
3455 "foo": "bar",
3456 },
3457 },
3458 }),
3459 },
3460 "object-defaults-with-invalid-constraints-apiVersion": {
3461 Type: "object",
3462 XEmbeddedResource: true,
3463 Properties: map[string]apiextensions.JSONSchemaProps{
3464 "apiVersion": {
3465 Type: "string",
3466 Enum: jsonSlice("foo/v1"),
3467 },
3468 "kind": {
3469 Type: "string",
3470 Enum: jsonSlice("Foo"),
3471 },
3472 "metadata": {
3473 Type: "object",
3474 Properties: map[string]apiextensions.JSONSchemaProps{
3475 "name": {
3476 Type: "string",
3477 Enum: jsonSlice("foo"),
3478 },
3479 "labels": {
3480 Type: "object",
3481 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3482 Schema: &apiextensions.JSONSchemaProps{
3483 Type: "string",
3484 Enum: jsonSlice("foo"),
3485 },
3486 },
3487 },
3488 },
3489 },
3490 },
3491
3492 Default: jsonPtr(map[string]interface{}{
3493 "apiVersion": "bar/v1",
3494 "kind": "Foo",
3495 "metadata": map[string]interface{}{
3496 "name": "foo",
3497 },
3498 }),
3499 },
3500 "object-defaults-with-invalid-constraints-kind": {
3501 Type: "object",
3502 XEmbeddedResource: true,
3503 Properties: map[string]apiextensions.JSONSchemaProps{
3504 "apiVersion": {
3505 Type: "string",
3506 Enum: jsonSlice("foo/v1"),
3507 },
3508 "kind": {
3509 Type: "string",
3510 Enum: jsonSlice("Foo"),
3511 },
3512 "metadata": {
3513 Type: "object",
3514 Properties: map[string]apiextensions.JSONSchemaProps{
3515 "name": {
3516 Type: "string",
3517 Enum: jsonSlice("foo"),
3518 },
3519 "labels": {
3520 Type: "object",
3521 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3522 Schema: &apiextensions.JSONSchemaProps{
3523 Type: "string",
3524 Enum: jsonSlice("foo"),
3525 },
3526 },
3527 },
3528 },
3529 },
3530 },
3531
3532 Default: jsonPtr(map[string]interface{}{
3533 "apiVersion": "foo/v1",
3534 "kind": "Bar",
3535 "metadata": map[string]interface{}{
3536 "name": "foo",
3537 },
3538 }),
3539 },
3540 "spanning-defaults-with-invalid-constraints-name": {
3541 Type: "object",
3542 Properties: map[string]apiextensions.JSONSchemaProps{
3543 "embedded": {
3544 Type: "object",
3545 XEmbeddedResource: true,
3546 Properties: map[string]apiextensions.JSONSchemaProps{
3547 "apiVersion": {
3548 Type: "string",
3549 Enum: jsonSlice("foo/v1"),
3550 },
3551 "kind": {
3552 Type: "string",
3553 Enum: jsonSlice("Foo"),
3554 },
3555 "metadata": {
3556 Type: "object",
3557 Properties: map[string]apiextensions.JSONSchemaProps{
3558 "name": {
3559 Type: "string",
3560 Enum: jsonSlice("foo"),
3561 },
3562 "labels": {
3563 Type: "object",
3564 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3565 Schema: &apiextensions.JSONSchemaProps{
3566 Type: "string",
3567 Enum: jsonSlice("foo"),
3568 },
3569 },
3570 },
3571 },
3572 },
3573 },
3574 },
3575 },
3576
3577 Default: jsonPtr(map[string]interface{}{
3578 "embedded": map[string]interface{}{
3579 "apiVersion": "foo/v1",
3580 "kind": "Foo",
3581 "metadata": map[string]interface{}{
3582 "name": "bar",
3583 "labels": map[string]interface{}{
3584 "foo": "foo",
3585 },
3586 },
3587 },
3588 }),
3589 },
3590 "spanning-defaults-with-invalid-constraints-labels": {
3591 Type: "object",
3592 Properties: map[string]apiextensions.JSONSchemaProps{
3593 "embedded": {
3594 Type: "object",
3595 XEmbeddedResource: true,
3596 Properties: map[string]apiextensions.JSONSchemaProps{
3597 "apiVersion": {
3598 Type: "string",
3599 Enum: jsonSlice("foo/v1"),
3600 },
3601 "kind": {
3602 Type: "string",
3603 Enum: jsonSlice("Foo"),
3604 },
3605 "metadata": {
3606 Type: "object",
3607 Properties: map[string]apiextensions.JSONSchemaProps{
3608 "name": {
3609 Type: "string",
3610 Enum: jsonSlice("foo"),
3611 },
3612 "labels": {
3613 Type: "object",
3614 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3615 Schema: &apiextensions.JSONSchemaProps{
3616 Type: "string",
3617 Enum: jsonSlice("foo"),
3618 },
3619 },
3620 },
3621 },
3622 },
3623 },
3624 },
3625 },
3626
3627 Default: jsonPtr(map[string]interface{}{
3628 "embedded": map[string]interface{}{
3629 "apiVersion": "foo/v1",
3630 "kind": "Foo",
3631 "metadata": map[string]interface{}{
3632 "name": "foo",
3633 "labels": map[string]interface{}{
3634 "foo": "bar",
3635 },
3636 },
3637 },
3638 }),
3639 },
3640 "spanning-defaults-with-invalid-constraints-apiVersion": {
3641 Type: "object",
3642 Properties: map[string]apiextensions.JSONSchemaProps{
3643 "embedded": {
3644 Type: "object",
3645 XEmbeddedResource: true,
3646 Properties: map[string]apiextensions.JSONSchemaProps{
3647 "apiVersion": {
3648 Type: "string",
3649 Enum: jsonSlice("foo/v1"),
3650 },
3651 "kind": {
3652 Type: "string",
3653 Enum: jsonSlice("Foo"),
3654 },
3655 "metadata": {
3656 Type: "object",
3657 Properties: map[string]apiextensions.JSONSchemaProps{
3658 "name": {
3659 Type: "string",
3660 Enum: jsonSlice("foo"),
3661 },
3662 "labels": {
3663 Type: "object",
3664 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3665 Schema: &apiextensions.JSONSchemaProps{
3666 Type: "string",
3667 Enum: jsonSlice("foo"),
3668 },
3669 },
3670 },
3671 },
3672 },
3673 },
3674 },
3675 },
3676
3677 Default: jsonPtr(map[string]interface{}{
3678 "embedded": map[string]interface{}{
3679 "apiVersion": "bar/v1",
3680 "kind": "Foo",
3681 "metadata": map[string]interface{}{
3682 "name": "foo",
3683 "labels": map[string]interface{}{
3684 "foo": "foo",
3685 },
3686 },
3687 },
3688 }),
3689 },
3690 "spanning-defaults-with-invalid-constraints-kind": {
3691 Type: "object",
3692 Properties: map[string]apiextensions.JSONSchemaProps{
3693 "embedded": {
3694 Type: "object",
3695 XEmbeddedResource: true,
3696 Properties: map[string]apiextensions.JSONSchemaProps{
3697 "apiVersion": {
3698 Type: "string",
3699 Enum: jsonSlice("foo/v1"),
3700 },
3701 "kind": {
3702 Type: "string",
3703 Enum: jsonSlice("Foo"),
3704 },
3705 "metadata": {
3706 Type: "object",
3707 Properties: map[string]apiextensions.JSONSchemaProps{
3708 "name": {
3709 Type: "string",
3710 Enum: jsonSlice("foo"),
3711 },
3712 "labels": {
3713 Type: "object",
3714 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
3715 Schema: &apiextensions.JSONSchemaProps{
3716 Type: "string",
3717 Enum: jsonSlice("foo"),
3718 },
3719 },
3720 },
3721 },
3722 },
3723 },
3724 },
3725 },
3726
3727 Default: jsonPtr(map[string]interface{}{
3728 "embedded": map[string]interface{}{
3729 "apiVersion": "foo/v1",
3730 "kind": "Bar",
3731 "metadata": map[string]interface{}{
3732 "name": "foo",
3733 "labels": map[string]interface{}{
3734 "foo": "foo",
3735 },
3736 },
3737 },
3738 }),
3739 },
3740
3741 "object-defaults-with-missing-typemeta": {
3742 Type: "object",
3743 XEmbeddedResource: true,
3744 Properties: map[string]apiextensions.JSONSchemaProps{
3745 "apiVersion": {
3746 Type: "string",
3747 Enum: jsonSlice("foo/v1"),
3748 },
3749 "kind": {
3750 Type: "string",
3751 Enum: jsonSlice("Foo"),
3752 },
3753 },
3754
3755 Default: jsonPtr(map[string]interface{}{
3756 "metadata": map[string]interface{}{
3757 "name": "bar",
3758 },
3759 }),
3760 },
3761 "spanning-defaults-with-missing-typemeta": {
3762 Type: "object",
3763 Properties: map[string]apiextensions.JSONSchemaProps{
3764 "embedded": {
3765 Type: "object",
3766 XEmbeddedResource: true,
3767 Properties: map[string]apiextensions.JSONSchemaProps{
3768 "apiVersion": {
3769 Type: "string",
3770 Enum: jsonSlice("foo/v1"),
3771 },
3772 "kind": {
3773 Type: "string",
3774 Enum: jsonSlice("Foo"),
3775 },
3776 },
3777 },
3778 },
3779
3780 Default: jsonPtr(map[string]interface{}{
3781 "embedded": map[string]interface{}{
3782 "metadata": map[string]interface{}{
3783 "name": "bar",
3784 },
3785 },
3786 }),
3787 },
3788 },
3789 },
3790 },
3791 Scope: apiextensions.NamespaceScoped,
3792 Names: apiextensions.CustomResourceDefinitionNames{
3793 Plural: "plural",
3794 Singular: "singular",
3795 Kind: "Plural",
3796 ListKind: "PluralList",
3797 },
3798 PreserveUnknownFields: pointer.BoolPtr(false),
3799 },
3800 Status: apiextensions.CustomResourceDefinitionStatus{
3801 StoredVersions: []string{"v1"},
3802 },
3803 },
3804 errors: []validationMatch{
3805 invalid("spec", "validation", "openAPIV3Schema", "properties[unknown-field-object-defaults]", "default"),
3806 invalid("spec", "validation", "openAPIV3Schema", "properties[unknown-field-spanning-object-defaults]", "default"),
3807
3808 invalid("spec", "validation", "openAPIV3Schema", "properties[x-preserve-unknown-fields-unknown-field-outside]", "default"),
3809
3810 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[kind]", "default"),
3811 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[apiVersion]", "default"),
3812 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[metadata]", "properties[name]", "default"),
3813 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-in-object-defaults]", "properties[metadata]", "properties[annotations]", "default"),
3814
3815 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-metadata]", "default", "metadata"),
3816 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-apiVersion]", "default", "apiVersion"),
3817 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-kind]", "default", "kind"),
3818 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-name]", "default", "metadata"),
3819 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-labels]", "default", "metadata"),
3820 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-object-defaults-annotations]", "default", "metadata"),
3821
3822 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-metadata]", "default", "embedded", "metadata"),
3823 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-apiVersion]", "default", "embedded", "apiVersion"),
3824 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-kind]", "default", "embedded", "kind"),
3825 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-name]", "default", "embedded", "metadata"),
3826 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-labels]", "default", "embedded", "metadata"),
3827 invalid("spec", "validation", "openAPIV3Schema", "properties[wrongly-typed-spanning-object-defaults-annotations]", "default", "embedded", "metadata"),
3828
3829 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[metadata]", "properties[name]", "default"),
3830 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[metadata]", "properties[labels]", "default"),
3831 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-in-object-defaults]", "properties[kind]", "default"),
3832
3833 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-kind]", "default", "kind"),
3834 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-name]", "default", "metadata", "name"),
3835 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-object-defaults-labels]", "default", "metadata", "labels"),
3836
3837 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-kind]", "default", "embedded", "kind"),
3838 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-name]", "default", "embedded", "metadata", "name"),
3839 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid-spanning-object-defaults-labels]", "default", "embedded", "metadata", "labels"),
3840
3841 unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[apiVersion]", "default"),
3842 unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[kind]", "default"),
3843 unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[metadata]", "properties[name]", "default"),
3844 unsupported("spec", "validation", "openAPIV3Schema", "properties[in-object-defaults-with-invalid-constraints]", "properties[metadata]", "properties[labels]", "default", "foo"),
3845
3846 unsupported("spec", "validation", "openAPIV3Schema", "properties[metadata-defaults-with-invalid-constraints-name]", "properties[metadata]", "default", "name"),
3847 unsupported("spec", "validation", "openAPIV3Schema", "properties[metadata-defaults-with-invalid-constraints-labels]", "properties[metadata]", "default", "labels", "foo"),
3848
3849 unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-name]", "default", "metadata", "name"),
3850 unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-labels]", "default", "metadata", "labels", "foo"),
3851 unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-apiVersion]", "default", "apiVersion"),
3852 unsupported("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-invalid-constraints-kind]", "default", "kind"),
3853
3854 unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-kind]", "default", "embedded", "kind"),
3855 unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-labels]", "default", "embedded", "metadata", "labels", "foo"),
3856 unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-apiVersion]", "default", "embedded", "apiVersion"),
3857 unsupported("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-invalid-constraints-name]", "default", "embedded", "metadata", "name"),
3858
3859 required("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-missing-typemeta]", "default", "apiVersion"),
3860 required("spec", "validation", "openAPIV3Schema", "properties[object-defaults-with-missing-typemeta]", "default", "kind"),
3861
3862 required("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-missing-typemeta]", "default", "embedded", "apiVersion"),
3863 required("spec", "validation", "openAPIV3Schema", "properties[spanning-defaults-with-missing-typemeta]", "default", "embedded", "kind"),
3864 },
3865 },
3866 {
3867 name: "contradicting meta field types",
3868 resource: &apiextensions.CustomResourceDefinition{
3869 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
3870 Spec: apiextensions.CustomResourceDefinitionSpec{
3871 Group: "group.com",
3872 Version: "version",
3873 Versions: singleVersionList,
3874 Scope: apiextensions.NamespaceScoped,
3875 Names: apiextensions.CustomResourceDefinitionNames{
3876 Plural: "plural",
3877 Singular: "singular",
3878 Kind: "Plural",
3879 ListKind: "PluralList",
3880 },
3881 Validation: &apiextensions.CustomResourceValidation{
3882 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
3883 Type: "object",
3884 Properties: map[string]apiextensions.JSONSchemaProps{
3885 "apiVersion": {Type: "number"},
3886 "kind": {Type: "number"},
3887 "metadata": {
3888 Type: "number",
3889 Properties: map[string]apiextensions.JSONSchemaProps{
3890 "name": {
3891 Type: "string",
3892 Pattern: "abc",
3893 },
3894 "generateName": {
3895 Type: "string",
3896 Pattern: "abc",
3897 },
3898 "generation": {
3899 Type: "integer",
3900 },
3901 },
3902 },
3903 "valid": {
3904 Type: "object",
3905 XEmbeddedResource: true,
3906 Properties: map[string]apiextensions.JSONSchemaProps{
3907 "apiVersion": {Type: "string"},
3908 "kind": {Type: "string"},
3909 "metadata": {
3910 Type: "object",
3911 Properties: map[string]apiextensions.JSONSchemaProps{
3912 "name": {
3913 Type: "string",
3914 Pattern: "abc",
3915 },
3916 "generateName": {
3917 Type: "string",
3918 Pattern: "abc",
3919 },
3920 "generation": {
3921 Type: "integer",
3922 Minimum: float64Ptr(42.0),
3923 },
3924 },
3925 },
3926 },
3927 },
3928 "invalid": {
3929 Type: "object",
3930 XEmbeddedResource: true,
3931 Properties: map[string]apiextensions.JSONSchemaProps{
3932 "apiVersion": {Type: "number"},
3933 "kind": {Type: "number"},
3934 "metadata": {
3935 Type: "number",
3936 Properties: map[string]apiextensions.JSONSchemaProps{
3937 "name": {
3938 Type: "string",
3939 Pattern: "abc",
3940 },
3941 "generateName": {
3942 Type: "string",
3943 Pattern: "abc",
3944 },
3945 "generation": {
3946 Type: "integer",
3947 Minimum: float64Ptr(42.0),
3948 },
3949 },
3950 },
3951 },
3952 },
3953 "nested": {
3954 Type: "object",
3955 XEmbeddedResource: true,
3956 Properties: map[string]apiextensions.JSONSchemaProps{
3957 "invalid": {
3958 Type: "object",
3959 XEmbeddedResource: true,
3960 Properties: map[string]apiextensions.JSONSchemaProps{
3961 "apiVersion": {Type: "number"},
3962 "kind": {Type: "number"},
3963 "metadata": {
3964 Type: "number",
3965 Properties: map[string]apiextensions.JSONSchemaProps{
3966 "name": {
3967 Type: "string",
3968 Pattern: "abc",
3969 },
3970 "generateName": {
3971 Type: "string",
3972 Pattern: "abc",
3973 },
3974 "generation": {
3975 Type: "integer",
3976 Minimum: float64Ptr(42.0),
3977 },
3978 },
3979 },
3980 },
3981 },
3982 },
3983 },
3984 "noEmbeddedObject": {
3985 Type: "object",
3986 Properties: map[string]apiextensions.JSONSchemaProps{
3987 "apiVersion": {Type: "number"},
3988 "kind": {Type: "number"},
3989 "metadata": {Type: "number"},
3990 },
3991 },
3992 },
3993 },
3994 },
3995 PreserveUnknownFields: pointer.BoolPtr(false),
3996 },
3997 Status: apiextensions.CustomResourceDefinitionStatus{
3998 StoredVersions: []string{"version"},
3999 },
4000 },
4001 errors: []validationMatch{
4002 forbidden("spec", "validation", "openAPIV3Schema", "properties[metadata]"),
4003 invalid("spec", "validation", "openAPIV3Schema", "properties[apiVersion]", "type"),
4004 invalid("spec", "validation", "openAPIV3Schema", "properties[kind]", "type"),
4005 invalid("spec", "validation", "openAPIV3Schema", "properties[metadata]", "type"),
4006 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid]", "properties[apiVersion]", "type"),
4007 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid]", "properties[kind]", "type"),
4008 invalid("spec", "validation", "openAPIV3Schema", "properties[invalid]", "properties[metadata]", "type"),
4009 invalid("spec", "validation", "openAPIV3Schema", "properties[nested]", "properties[invalid]", "properties[apiVersion]", "type"),
4010 invalid("spec", "validation", "openAPIV3Schema", "properties[nested]", "properties[invalid]", "properties[kind]", "type"),
4011 invalid("spec", "validation", "openAPIV3Schema", "properties[nested]", "properties[invalid]", "properties[metadata]", "type"),
4012 },
4013 },
4014 {
4015 name: "x-kubernetes-validations should be forbidden under oneOf/anyOf/allOf/not, structural schema",
4016 resource: &apiextensions.CustomResourceDefinition{
4017 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4018 Spec: apiextensions.CustomResourceDefinitionSpec{
4019 Group: "group.com",
4020 Version: "version",
4021 Versions: singleVersionList,
4022 Scope: apiextensions.NamespaceScoped,
4023 Names: apiextensions.CustomResourceDefinitionNames{
4024 Plural: "plural",
4025 Singular: "singular",
4026 Kind: "Plural",
4027 ListKind: "PluralList",
4028 },
4029 Validation: &apiextensions.CustomResourceValidation{
4030 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4031 Type: "object",
4032 Properties: map[string]apiextensions.JSONSchemaProps{
4033 "a": {
4034 Type: "number",
4035 Not: &apiextensions.JSONSchemaProps{
4036 XValidations: apiextensions.ValidationRules{
4037 {
4038 Rule: "should be forbidden",
4039 },
4040 },
4041 },
4042 AnyOf: []apiextensions.JSONSchemaProps{
4043 {
4044 XValidations: apiextensions.ValidationRules{
4045 {
4046 Rule: "should be forbidden",
4047 },
4048 },
4049 },
4050 },
4051 AllOf: []apiextensions.JSONSchemaProps{
4052 {
4053 XValidations: apiextensions.ValidationRules{
4054 {
4055 Rule: "should be forbidden",
4056 },
4057 },
4058 },
4059 },
4060 OneOf: []apiextensions.JSONSchemaProps{
4061 {
4062 XValidations: apiextensions.ValidationRules{
4063 {
4064 Rule: "should be forbidden",
4065 },
4066 },
4067 },
4068 },
4069 },
4070 },
4071 },
4072 },
4073 PreserveUnknownFields: pointer.BoolPtr(false),
4074 },
4075 Status: apiextensions.CustomResourceDefinitionStatus{
4076 StoredVersions: []string{"version"},
4077 },
4078 },
4079 errors: []validationMatch{
4080 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "not", "x-kubernetes-validations"),
4081 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "allOf[0]", "x-kubernetes-validations"),
4082 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "anyOf[0]", "x-kubernetes-validations"),
4083 forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "oneOf[0]", "x-kubernetes-validations"),
4084 },
4085 },
4086 {
4087 name: "x-kubernetes-validations should have valid reason and fieldPath",
4088 resource: &apiextensions.CustomResourceDefinition{
4089 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4090 Spec: apiextensions.CustomResourceDefinitionSpec{
4091 Group: "group.com",
4092 Version: "version",
4093 Versions: singleVersionList,
4094 Scope: apiextensions.NamespaceScoped,
4095 Names: apiextensions.CustomResourceDefinitionNames{
4096 Plural: "plural",
4097 Singular: "singular",
4098 Kind: "Plural",
4099 ListKind: "PluralList",
4100 },
4101 Validation: &apiextensions.CustomResourceValidation{
4102 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4103 Type: "object",
4104 XValidations: apiextensions.ValidationRules{
4105 {
4106 Rule: "self.a > 0",
4107 Reason: func() *apiextensions.FieldValueErrorReason {
4108 r := apiextensions.FieldValueErrorReason("InternalError")
4109 return &r
4110 }(),
4111 FieldPath: ".a",
4112 },
4113 },
4114 Properties: map[string]apiextensions.JSONSchemaProps{
4115 "a": {
4116 Type: "number",
4117 XValidations: apiextensions.ValidationRules{
4118 {
4119 Rule: "true",
4120 Reason: func() *apiextensions.FieldValueErrorReason {
4121 r := apiextensions.FieldValueRequired
4122 return &r
4123 }(),
4124 },
4125 {
4126 Rule: "true",
4127 Reason: func() *apiextensions.FieldValueErrorReason {
4128 r := apiextensions.FieldValueInvalid
4129 return &r
4130 }(),
4131 },
4132 {
4133 Rule: "true",
4134 Reason: func() *apiextensions.FieldValueErrorReason {
4135 r := apiextensions.FieldValueDuplicate
4136 return &r
4137 }(),
4138 },
4139 {
4140 Rule: "true",
4141 Reason: func() *apiextensions.FieldValueErrorReason {
4142 r := apiextensions.FieldValueForbidden
4143 return &r
4144 }(),
4145 },
4146 },
4147 },
4148 },
4149 },
4150 },
4151 },
4152 Status: apiextensions.CustomResourceDefinitionStatus{
4153 StoredVersions: []string{"version"},
4154 },
4155 },
4156 errors: []validationMatch{
4157 unsupported("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[0]", "reason"),
4158 },
4159 },
4160 {
4161 name: "x-kubernetes-validations should have valid fieldPath for array",
4162 resource: &apiextensions.CustomResourceDefinition{
4163 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4164 Spec: apiextensions.CustomResourceDefinitionSpec{
4165 Group: "group.com",
4166 Version: "version",
4167 Versions: singleVersionList,
4168 Scope: apiextensions.NamespaceScoped,
4169 Names: apiextensions.CustomResourceDefinitionNames{
4170 Plural: "plural",
4171 Singular: "singular",
4172 Kind: "Plural",
4173 ListKind: "PluralList",
4174 },
4175 Validation: &apiextensions.CustomResourceValidation{
4176 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4177 Type: "object",
4178 XValidations: apiextensions.ValidationRules{
4179 {
4180 Rule: "true",
4181 FieldPath: ".foo['b.c']['c\\a']",
4182 },
4183 {
4184 Rule: "true",
4185 FieldPath: "['a.c']",
4186 },
4187 {
4188 Rule: "true",
4189 FieldPath: ".a.c",
4190 },
4191 {
4192 Rule: "true",
4193 FieldPath: ".list[0]",
4194 },
4195 {
4196 Rule: "true",
4197 FieldPath: " ",
4198 },
4199 {
4200 Rule: "true",
4201 FieldPath: ".",
4202 },
4203 {
4204 Rule: "true",
4205 FieldPath: "..",
4206 },
4207 },
4208 Properties: map[string]apiextensions.JSONSchemaProps{
4209 "a.c": {
4210 Type: "number",
4211 },
4212 "foo": {
4213 Type: "object",
4214 Properties: map[string]apiextensions.JSONSchemaProps{
4215 "b.c": {
4216 Type: "object",
4217 Properties: map[string]apiextensions.JSONSchemaProps{
4218 "c\a": {
4219 Type: "number",
4220 },
4221 },
4222 },
4223 },
4224 },
4225 "list": {
4226 Type: "array",
4227 Items: &apiextensions.JSONSchemaPropsOrArray{
4228 Schema: &apiextensions.JSONSchemaProps{
4229 Type: "object",
4230 Properties: map[string]apiextensions.JSONSchemaProps{
4231 "a": {
4232 Type: "number",
4233 },
4234 },
4235 },
4236 },
4237 },
4238 },
4239 },
4240 },
4241 },
4242 Status: apiextensions.CustomResourceDefinitionStatus{
4243 StoredVersions: []string{"version"},
4244 },
4245 },
4246 errors: []validationMatch{
4247 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[2]", "fieldPath"),
4248 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[3]", "fieldPath"),
4249 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
4250 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
4251 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[5]", "fieldPath"),
4252 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[6]", "fieldPath"),
4253 },
4254 },
4255 {
4256 name: "x-kubernetes-validations have invalid fieldPath",
4257 resource: &apiextensions.CustomResourceDefinition{
4258 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4259 Spec: apiextensions.CustomResourceDefinitionSpec{
4260 Group: "group.com",
4261 Version: "version",
4262 Versions: singleVersionList,
4263 Scope: apiextensions.NamespaceScoped,
4264 Names: apiextensions.CustomResourceDefinitionNames{
4265 Plural: "plural",
4266 Singular: "singular",
4267 Kind: "Plural",
4268 ListKind: "PluralList",
4269 },
4270 Validation: &apiextensions.CustomResourceValidation{
4271 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4272 Type: "object",
4273 XValidations: apiextensions.ValidationRules{
4274 {
4275 Rule: "self.a.b.c > 0.0",
4276 FieldPath: ".list[0].b",
4277 },
4278 {
4279 Rule: "self.a.b.c > 0.0",
4280 FieldPath: ".list[0.b",
4281 },
4282 {
4283 Rule: "self.a.b.c > 0.0",
4284 FieldPath: ".list0].b",
4285 },
4286 {
4287 Rule: "self.a.b.c > 0.0",
4288 FieldPath: ".a.c",
4289 },
4290 {
4291 Rule: "self.a.b.c > 0.0",
4292 FieldPath: ".a.b.d",
4293 },
4294 },
4295 Properties: map[string]apiextensions.JSONSchemaProps{
4296 "a": {
4297 Type: "object",
4298 Properties: map[string]apiextensions.JSONSchemaProps{
4299 "b": {
4300 Type: "object",
4301 Properties: map[string]apiextensions.JSONSchemaProps{
4302 "c": {
4303 Type: "number",
4304 },
4305 },
4306 },
4307 },
4308 },
4309 "list": {
4310 Type: "array",
4311 Items: &apiextensions.JSONSchemaPropsOrArray{
4312 Schema: &apiextensions.JSONSchemaProps{
4313 Type: "object",
4314 Properties: map[string]apiextensions.JSONSchemaProps{
4315 "a": {
4316 Type: "number",
4317 },
4318 },
4319 },
4320 },
4321 },
4322 },
4323 },
4324 },
4325 },
4326 Status: apiextensions.CustomResourceDefinitionStatus{
4327 StoredVersions: []string{"version"},
4328 },
4329 },
4330 errors: []validationMatch{
4331 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[0]", "fieldPath"),
4332 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[1]", "fieldPath"),
4333 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[2]", "fieldPath"),
4334 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[3]", "fieldPath"),
4335 invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
4336 },
4337 },
4338 }
4339
4340 for _, tc := range tests {
4341 t.Run(tc.name, func(t *testing.T) {
4342
4343
4344 if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
4345 tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
4346 }
4347 ctx := context.TODO()
4348 errs := ValidateCustomResourceDefinition(ctx, tc.resource)
4349 seenErrs := make([]bool, len(errs))
4350
4351 for _, expectedError := range tc.errors {
4352 found := false
4353 for i, err := range errs {
4354 if expectedError.matches(err) && !seenErrs[i] {
4355 found = true
4356 seenErrs[i] = true
4357 break
4358 }
4359 }
4360
4361 if !found {
4362 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
4363 }
4364 }
4365
4366 for i, seen := range seenErrs {
4367 if !seen {
4368 t.Errorf("unexpected error: %v", errs[i])
4369 }
4370 }
4371 })
4372 }
4373 }
4374
4375 func TestSelectableFields(t *testing.T) {
4376 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceFieldSelectors, true)()
4377 singleVersionList := []apiextensions.CustomResourceDefinitionVersion{
4378 {
4379 Name: "version",
4380 Served: true,
4381 Storage: true,
4382 },
4383 }
4384 tests := []struct {
4385 name string
4386 resource *apiextensions.CustomResourceDefinition
4387 errors []validationMatch
4388 }{
4389 {
4390 name: "selectableFields with jsonPaths that do not refer to a field in the schema are invalid",
4391 resource: &apiextensions.CustomResourceDefinition{
4392 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4393 Spec: apiextensions.CustomResourceDefinitionSpec{
4394 Group: "group.com",
4395 Version: "version",
4396 Versions: []apiextensions.CustomResourceDefinitionVersion{
4397 {Name: "version", Served: true, Storage: true,
4398 Schema: &apiextensions.CustomResourceValidation{
4399 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4400 Type: "object",
4401 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}},
4402 Required: []string{"foo"},
4403 },
4404 },
4405 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".xyz"}},
4406 },
4407 {Name: "version2", Served: true, Storage: false,
4408 Schema: &apiextensions.CustomResourceValidation{
4409 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4410 Type: "object",
4411 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
4412 Required: []string{"foo"},
4413 },
4414 },
4415 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".xyz"}, {JSONPath: ".foo"}, {JSONPath: ".abc"}},
4416 },
4417 },
4418 Scope: apiextensions.NamespaceScoped,
4419 Names: apiextensions.CustomResourceDefinitionNames{
4420 Plural: "plural",
4421 Singular: "singular",
4422 Kind: "Plural",
4423 ListKind: "PluralList",
4424 },
4425 PreserveUnknownFields: ptr.To(false),
4426 },
4427 Status: apiextensions.CustomResourceDefinitionStatus{
4428 StoredVersions: []string{"version"},
4429 },
4430 },
4431 errors: []validationMatch{
4432 invalid("spec", "versions[0]", "selectableFields[1].jsonPath"),
4433 invalid("spec", "versions[1]", "selectableFields[0].jsonPath"),
4434 invalid("spec", "versions[1]", "selectableFields[2].jsonPath"),
4435 },
4436 },
4437 {
4438 name: "in top level schema, selectableFields with jsonPaths that do not refer to a field in the schema are invalid",
4439 resource: &apiextensions.CustomResourceDefinition{
4440 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4441 Spec: apiextensions.CustomResourceDefinitionSpec{
4442 Group: "group.com",
4443 Version: "version",
4444 Versions: singleVersionList,
4445 Validation: &apiextensions.CustomResourceValidation{
4446 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4447 Type: "object",
4448 Properties: map[string]apiextensions.JSONSchemaProps{
4449 "spec": {
4450 Type: "object",
4451 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}},
4452 Required: []string{"foo"},
4453 },
4454 "status": {
4455 Type: "object",
4456 Properties: map[string]apiextensions.JSONSchemaProps{"phase": {Type: "string"}},
4457 Required: []string{"phase"},
4458 },
4459 },
4460 },
4461 },
4462 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".spec.foo"}, {JSONPath: ".spec.xyz"}, {JSONPath: ".status.phase"}},
4463 Scope: apiextensions.NamespaceScoped,
4464 Names: apiextensions.CustomResourceDefinitionNames{
4465 Plural: "plural",
4466 Singular: "singular",
4467 Kind: "Plural",
4468 ListKind: "PluralList",
4469 },
4470 PreserveUnknownFields: ptr.To(false),
4471 },
4472 Status: apiextensions.CustomResourceDefinitionStatus{
4473 StoredVersions: []string{"version"},
4474 },
4475 },
4476 errors: []validationMatch{
4477 invalid("spec", "selectableFields[1].jsonPath"),
4478 },
4479 },
4480 {
4481 name: "selectableFields with jsonPaths that do not refer to fields that are not strings, booleans or integers are invalid",
4482 resource: &apiextensions.CustomResourceDefinition{
4483 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4484 Spec: apiextensions.CustomResourceDefinitionSpec{
4485 Group: "group.com",
4486 Version: "version",
4487 Versions: []apiextensions.CustomResourceDefinitionVersion{
4488 {Name: "version", Served: true, Storage: true,
4489 Schema: &apiextensions.CustomResourceValidation{
4490 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4491 Type: "object",
4492 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}, "obj": {Type: "object"}},
4493 Required: []string{"foo", "obj"},
4494 },
4495 },
4496 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".obj"}},
4497 },
4498 {Name: "version2", Served: true, Storage: false,
4499 Schema: &apiextensions.CustomResourceValidation{
4500 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4501 Type: "object",
4502 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}, "obj": {Type: "object"}, "bool": {Type: "boolean"}},
4503 Required: []string{"foo", "obj", "bool"},
4504 },
4505 },
4506 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".obj"}, {JSONPath: ".foo"}, {JSONPath: ".bool"}},
4507 },
4508 },
4509 Scope: apiextensions.NamespaceScoped,
4510 Names: apiextensions.CustomResourceDefinitionNames{
4511 Plural: "plural",
4512 Singular: "singular",
4513 Kind: "Plural",
4514 ListKind: "PluralList",
4515 },
4516 PreserveUnknownFields: ptr.To(false),
4517 },
4518 Status: apiextensions.CustomResourceDefinitionStatus{
4519 StoredVersions: []string{"version"},
4520 },
4521 },
4522 errors: []validationMatch{
4523 invalid("spec", "versions[0]", "selectableFields[1].jsonPath"),
4524 invalid("spec", "versions[1]", "selectableFields[0].jsonPath"),
4525 },
4526 },
4527 {
4528 name: "selectableFields with duplicate jsonPaths are invalid",
4529 resource: &apiextensions.CustomResourceDefinition{
4530 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4531 Spec: apiextensions.CustomResourceDefinitionSpec{
4532 Group: "group.com",
4533 Version: "version",
4534 Versions: []apiextensions.CustomResourceDefinitionVersion{
4535 {Name: "version", Served: true, Storage: true,
4536 Schema: &apiextensions.CustomResourceValidation{
4537 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4538 Type: "object",
4539 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "string"}},
4540 Required: []string{"foo"},
4541 },
4542 },
4543 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".foo"}},
4544 },
4545 {Name: "version2", Served: true, Storage: false,
4546 Schema: &apiextensions.CustomResourceValidation{
4547 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4548 Type: "object",
4549 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
4550 Required: []string{"foo"},
4551 },
4552 },
4553 SelectableFields: []apiextensions.SelectableField{{JSONPath: ".foo"}, {JSONPath: ".foo"}},
4554 },
4555 },
4556 Scope: apiextensions.NamespaceScoped,
4557 Names: apiextensions.CustomResourceDefinitionNames{
4558 Plural: "plural",
4559 Singular: "singular",
4560 Kind: "Plural",
4561 ListKind: "PluralList",
4562 },
4563 PreserveUnknownFields: ptr.To(false),
4564 },
4565 Status: apiextensions.CustomResourceDefinitionStatus{
4566 StoredVersions: []string{"version"},
4567 },
4568 },
4569 errors: []validationMatch{
4570 duplicate("spec", "versions[0]", "selectableFields[1].jsonPath"),
4571 duplicate("spec", "versions[1]", "selectableFields[1].jsonPath"),
4572 },
4573 },
4574 {
4575 name: "too many selectableFields are not allowed",
4576 resource: &apiextensions.CustomResourceDefinition{
4577 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
4578 Spec: apiextensions.CustomResourceDefinitionSpec{
4579 Group: "group.com",
4580 Version: "version",
4581 Versions: []apiextensions.CustomResourceDefinitionVersion{
4582 {Name: "version", Served: true, Storage: true,
4583 Schema: &apiextensions.CustomResourceValidation{
4584 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4585 Type: "object",
4586 Properties: map[string]apiextensions.JSONSchemaProps{
4587 "a1": {Type: "string"}, "a2": {Type: "string"}, "a3": {Type: "string"},
4588 "a4": {Type: "string"}, "a5": {Type: "string"}, "a6": {Type: "string"},
4589 "a7": {Type: "string"}, "a8": {Type: "string"}, "a9": {Type: "string"},
4590 },
4591 Required: []string{"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"},
4592 },
4593 },
4594 SelectableFields: []apiextensions.SelectableField{
4595 {JSONPath: ".a1"}, {JSONPath: ".a2"}, {JSONPath: ".a3"},
4596 {JSONPath: ".a4"}, {JSONPath: ".a5"}, {JSONPath: ".a6"},
4597 {JSONPath: ".a7"}, {JSONPath: ".a8"}, {JSONPath: ".a9"},
4598 },
4599 },
4600 {Name: "version2", Served: true, Storage: false,
4601 Schema: &apiextensions.CustomResourceValidation{
4602 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4603 Type: "object",
4604 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
4605 },
4606 },
4607 },
4608 },
4609 Scope: apiextensions.NamespaceScoped,
4610 Names: apiextensions.CustomResourceDefinitionNames{
4611 Plural: "plural",
4612 Singular: "singular",
4613 Kind: "Plural",
4614 ListKind: "PluralList",
4615 },
4616 PreserveUnknownFields: ptr.To(false),
4617 },
4618 Status: apiextensions.CustomResourceDefinitionStatus{
4619 StoredVersions: []string{"version"},
4620 },
4621 },
4622 errors: []validationMatch{
4623 tooMany("spec", "versions[0]", "selectableFields"),
4624 },
4625 },
4626 }
4627
4628 for _, tc := range tests {
4629 t.Run(tc.name, func(t *testing.T) {
4630
4631
4632 if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
4633 tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
4634 }
4635 ctx := context.TODO()
4636 errs := ValidateCustomResourceDefinition(ctx, tc.resource)
4637 seenErrs := make([]bool, len(errs))
4638
4639 for _, expectedError := range tc.errors {
4640 found := false
4641 for i, err := range errs {
4642 if expectedError.matches(err) && !seenErrs[i] {
4643 found = true
4644 seenErrs[i] = true
4645 break
4646 }
4647 }
4648
4649 if !found {
4650 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
4651 }
4652 }
4653
4654 for i, seen := range seenErrs {
4655 if !seen {
4656 t.Errorf("unexpected error: %v", errs[i])
4657 }
4658 }
4659 })
4660 }
4661 }
4662
4663 func TestValidateFieldPath(t *testing.T) {
4664 schema := apiextensions.JSONSchemaProps{
4665 Type: "object",
4666 Properties: map[string]apiextensions.JSONSchemaProps{
4667 "foo": {
4668 Type: "object",
4669 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
4670 Schema: &apiextensions.JSONSchemaProps{
4671 Type: "object",
4672 Properties: map[string]apiextensions.JSONSchemaProps{
4673 "f1": {
4674 Type: "number",
4675 },
4676 },
4677 },
4678 },
4679 },
4680 "a": {
4681 Type: "object",
4682 Properties: map[string]apiextensions.JSONSchemaProps{
4683 "bbb": {
4684 Type: "object",
4685 Properties: map[string]apiextensions.JSONSchemaProps{
4686 "c": {
4687 Type: "number",
4688 },
4689 "34": {
4690 Type: "number",
4691 },
4692 },
4693 },
4694 "bbb.c": {
4695 Type: "object",
4696 Properties: map[string]apiextensions.JSONSchemaProps{
4697 "a-b34": {
4698 Type: "number",
4699 },
4700 },
4701 },
4702 },
4703 },
4704 "list": {
4705 Type: "array",
4706 Items: &apiextensions.JSONSchemaPropsOrArray{
4707 Schema: &apiextensions.JSONSchemaProps{
4708 Type: "object",
4709 Properties: map[string]apiextensions.JSONSchemaProps{
4710 "a": {
4711 Type: "number",
4712 },
4713 "a-b.34": {
4714 Type: "number",
4715 },
4716 },
4717 },
4718 },
4719 },
4720 },
4721 }
4722
4723 path := field.NewPath("")
4724
4725 tests := []struct {
4726 name string
4727 fieldPath string
4728 pathOfFieldPath *field.Path
4729 schema *apiextensions.JSONSchemaProps
4730 errMsg string
4731 }{
4732 {
4733 name: "Valid .a",
4734 fieldPath: ".a",
4735 pathOfFieldPath: path,
4736 schema: &schema,
4737 },
4738 {
4739 name: "Valid .a.b",
4740 fieldPath: ".a.bbb",
4741 pathOfFieldPath: path,
4742 schema: &schema,
4743 },
4744 {
4745 name: "Valid .foo.f1",
4746 fieldPath: ".foo.f1",
4747 pathOfFieldPath: path,
4748 schema: &schema,
4749 },
4750 {
4751 name: "Invalid map syntax .a.b",
4752 fieldPath: ".a['bbb']",
4753 pathOfFieldPath: path,
4754 schema: &schema,
4755 },
4756 {
4757 name: "Valid .a['bbb.c']",
4758 fieldPath: ".a['bbb.c']",
4759 pathOfFieldPath: path,
4760 schema: &schema,
4761 },
4762 {
4763 name: "Valid .a['bbb.c'].a-b34",
4764 fieldPath: ".a['bbb.c'].a-b34",
4765 pathOfFieldPath: path,
4766 schema: &schema,
4767 },
4768 {
4769 name: "Valid .a['bbb.c']['a-b34']",
4770 fieldPath: ".a['bbb.c']['a-b34']",
4771 pathOfFieldPath: path,
4772 schema: &schema,
4773 },
4774 {
4775 name: "Valid .a.bbb.c",
4776 fieldPath: ".a.bbb.c",
4777 pathOfFieldPath: path,
4778 schema: &schema,
4779 },
4780 {
4781 name: "Valid .a.bbb.34",
4782 fieldPath: ".a.bbb['34']",
4783 pathOfFieldPath: path,
4784 schema: &schema,
4785 },
4786 {
4787 name: "Invalid map key",
4788 fieldPath: ".a.foo",
4789 pathOfFieldPath: path,
4790 schema: &schema,
4791 errMsg: "does not refer to a valid field",
4792 },
4793 {
4794 name: "Malformed map key",
4795 fieldPath: ".a.bbb[0]",
4796 pathOfFieldPath: path,
4797 schema: &schema,
4798 errMsg: "expected single quoted string but got 0",
4799 },
4800 {
4801 name: "number in field names",
4802 fieldPath: ".a.bbb.34",
4803 pathOfFieldPath: path,
4804 schema: &schema,
4805 },
4806 {
4807 name: "Special field names",
4808 fieldPath: ".a.bbb['34']",
4809 pathOfFieldPath: path,
4810 schema: &schema,
4811 },
4812 {
4813 name: "Valid .list",
4814 fieldPath: ".list",
4815 pathOfFieldPath: path,
4816 schema: &schema,
4817 },
4818 {
4819 name: "Invalid .list[1]",
4820 fieldPath: ".list[1]",
4821 pathOfFieldPath: path,
4822 schema: &schema,
4823 errMsg: "expected single quoted string but got 1",
4824 },
4825 {
4826 name: "Unsopported .list.a",
4827 fieldPath: ".list.a",
4828 pathOfFieldPath: path,
4829 schema: &schema,
4830 errMsg: "does not refer to a valid field",
4831 },
4832 {
4833 name: "Unsupported .list['a-b.34']",
4834 fieldPath: ".list['a-b.34']",
4835 pathOfFieldPath: path,
4836 schema: &schema,
4837 errMsg: "does not refer to a valid field",
4838 },
4839 {
4840 name: "Invalid .list.a-b.34",
4841 fieldPath: ".list.a-b.34",
4842 pathOfFieldPath: path,
4843 schema: &schema,
4844 errMsg: "does not refer to a valid field",
4845 },
4846 {
4847 name: "Missing leading dot",
4848 fieldPath: "a",
4849 pathOfFieldPath: path,
4850 schema: &schema,
4851 errMsg: "expected [ or . but got: a",
4852 },
4853 {
4854 name: "Nonexistent field",
4855 fieldPath: ".c",
4856 pathOfFieldPath: path,
4857 schema: &schema,
4858 errMsg: "does not refer to a valid field",
4859 },
4860 {
4861 name: "Duplicate dots",
4862 fieldPath: ".a..b",
4863 pathOfFieldPath: path,
4864 schema: &schema,
4865 errMsg: "does not refer to a valid field",
4866 },
4867 {
4868 name: "Negative array index",
4869 fieldPath: ".list[-1]",
4870 pathOfFieldPath: path,
4871 schema: &schema,
4872 errMsg: "expected single quoted string but got -1",
4873 },
4874 {
4875 name: "Floating-point array index",
4876 fieldPath: ".list[1.0]",
4877 pathOfFieldPath: path,
4878 schema: &schema,
4879 errMsg: "expected single quoted string but got 1",
4880 },
4881 }
4882
4883 for _, tc := range tests {
4884 t.Run(tc.name, func(t *testing.T) {
4885 ss, err := structuralschema.NewStructural(tc.schema)
4886 if err != nil {
4887 t.Fatalf("error when converting schema to structural schema: %v", err)
4888 }
4889 _, _, err = celschema.ValidFieldPath(tc.fieldPath, ss)
4890 if err == nil && tc.errMsg != "" {
4891 t.Errorf("expected err contains: %v but get nil", tc.errMsg)
4892 }
4893 if err != nil && tc.errMsg == "" {
4894 t.Errorf("unexpected error: %v", err)
4895 }
4896 if err != nil && !strings.Contains(err.Error(), tc.errMsg) {
4897 t.Errorf("expected error to contain: %v, but get: %v", tc.errMsg, err)
4898 }
4899 })
4900 }
4901 }
4902
4903 func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
4904 tests := []struct {
4905 name string
4906 old *apiextensions.CustomResourceDefinition
4907 resource *apiextensions.CustomResourceDefinition
4908 errors []validationMatch
4909 }{
4910 {
4911 name: "invalid types updates disallowed",
4912 old: &apiextensions.CustomResourceDefinition{
4913 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
4914 Spec: apiextensions.CustomResourceDefinitionSpec{
4915 Group: "group.com",
4916 Scope: apiextensions.ResourceScope("Cluster"),
4917 Names: apiextensions.CustomResourceDefinitionNames{
4918 Plural: "plural",
4919 Singular: "singular",
4920 Kind: "Plural",
4921 ListKind: "PluralList",
4922 },
4923 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
4924 Validation: &apiextensions.CustomResourceValidation{
4925 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4926 Type: "object",
4927 },
4928 },
4929 PreserveUnknownFields: pointer.BoolPtr(false),
4930 },
4931 Status: apiextensions.CustomResourceDefinitionStatus{
4932 StoredVersions: []string{"version"},
4933 },
4934 },
4935 resource: &apiextensions.CustomResourceDefinition{
4936 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
4937 Spec: apiextensions.CustomResourceDefinitionSpec{
4938 Group: "group.com",
4939 Scope: apiextensions.ResourceScope("Cluster"),
4940 Names: apiextensions.CustomResourceDefinitionNames{
4941 Plural: "plural",
4942 Singular: "singular",
4943 Kind: "Plural",
4944 ListKind: "PluralList",
4945 },
4946 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
4947 Validation: &apiextensions.CustomResourceValidation{
4948 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4949 Type: "object",
4950 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus"}},
4951 },
4952 },
4953 PreserveUnknownFields: pointer.BoolPtr(false),
4954 },
4955 Status: apiextensions.CustomResourceDefinitionStatus{
4956 StoredVersions: []string{"version"},
4957 },
4958 },
4959 errors: []validationMatch{
4960 unsupported("spec.validation.openAPIV3Schema.properties[foo].type"),
4961 },
4962 },
4963 {
4964 name: "invalid types updates allowed if old object has invalid types",
4965 old: &apiextensions.CustomResourceDefinition{
4966 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
4967 Spec: apiextensions.CustomResourceDefinitionSpec{
4968 Group: "group.com",
4969 Scope: apiextensions.ResourceScope("Cluster"),
4970 Names: apiextensions.CustomResourceDefinitionNames{
4971 Plural: "plural",
4972 Singular: "singular",
4973 Kind: "Plural",
4974 ListKind: "PluralList",
4975 },
4976 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
4977 Validation: &apiextensions.CustomResourceValidation{
4978 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
4979 Type: "object",
4980 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus"}},
4981 },
4982 },
4983 PreserveUnknownFields: pointer.BoolPtr(false),
4984 },
4985 Status: apiextensions.CustomResourceDefinitionStatus{
4986 StoredVersions: []string{"version"},
4987 },
4988 },
4989 resource: &apiextensions.CustomResourceDefinition{
4990 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
4991 Spec: apiextensions.CustomResourceDefinitionSpec{
4992 Group: "group.com",
4993 Scope: apiextensions.ResourceScope("Cluster"),
4994 Names: apiextensions.CustomResourceDefinitionNames{
4995 Plural: "plural",
4996 Singular: "singular",
4997 Kind: "Plural",
4998 ListKind: "PluralList",
4999 },
5000 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5001 Validation: &apiextensions.CustomResourceValidation{
5002 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5003 Type: "object",
5004 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "bogus2"}},
5005 },
5006 },
5007 PreserveUnknownFields: pointer.BoolPtr(false),
5008 },
5009 Status: apiextensions.CustomResourceDefinitionStatus{
5010 StoredVersions: []string{"version"},
5011 },
5012 },
5013 },
5014 {
5015 name: "non-atomic items in lists of type set allowed if pre-existing",
5016 old: &apiextensions.CustomResourceDefinition{
5017 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5018 Spec: apiextensions.CustomResourceDefinitionSpec{
5019 Group: "group.com",
5020 Scope: apiextensions.ResourceScope("Cluster"),
5021 Names: apiextensions.CustomResourceDefinitionNames{
5022 Plural: "plural",
5023 Singular: "singular",
5024 Kind: "Plural",
5025 ListKind: "PluralList",
5026 },
5027 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5028 Validation: &apiextensions.CustomResourceValidation{
5029 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5030 Type: "object",
5031 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
5032 Type: "array",
5033 XListType: strPtr("set"),
5034 Items: &apiextensions.JSONSchemaPropsOrArray{
5035 Schema: &apiextensions.JSONSchemaProps{
5036 Type: "object",
5037 Properties: map[string]apiextensions.JSONSchemaProps{},
5038 },
5039 },
5040 }},
5041 },
5042 },
5043 PreserveUnknownFields: pointer.BoolPtr(false),
5044 },
5045 Status: apiextensions.CustomResourceDefinitionStatus{
5046 StoredVersions: []string{"version"},
5047 },
5048 },
5049 resource: &apiextensions.CustomResourceDefinition{
5050 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5051 Spec: apiextensions.CustomResourceDefinitionSpec{
5052 Group: "group.com",
5053 Scope: apiextensions.ResourceScope("Cluster"),
5054 Names: apiextensions.CustomResourceDefinitionNames{
5055 Plural: "plural",
5056 Singular: "singular",
5057 Kind: "Plural",
5058 ListKind: "PluralList",
5059 },
5060 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5061 Validation: &apiextensions.CustomResourceValidation{
5062 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5063 Type: "object",
5064 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
5065 Type: "array",
5066 XListType: strPtr("set"),
5067 Items: &apiextensions.JSONSchemaPropsOrArray{
5068 Schema: &apiextensions.JSONSchemaProps{
5069 Type: "object",
5070 Properties: map[string]apiextensions.JSONSchemaProps{},
5071 },
5072 },
5073 }},
5074 },
5075 },
5076 PreserveUnknownFields: pointer.BoolPtr(false),
5077 },
5078 Status: apiextensions.CustomResourceDefinitionStatus{
5079 StoredVersions: []string{"version"},
5080 },
5081 },
5082 },
5083 {
5084 name: "reject non-atomic items in lists of type set if not pre-existing",
5085 old: &apiextensions.CustomResourceDefinition{
5086 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5087 Spec: apiextensions.CustomResourceDefinitionSpec{
5088 Group: "group.com",
5089 Scope: apiextensions.ResourceScope("Cluster"),
5090 Names: apiextensions.CustomResourceDefinitionNames{
5091 Plural: "plural",
5092 Singular: "singular",
5093 Kind: "Plural",
5094 ListKind: "PluralList",
5095 },
5096 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5097 Validation: &apiextensions.CustomResourceValidation{
5098 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5099 Type: "object",
5100 Properties: map[string]apiextensions.JSONSchemaProps{},
5101 },
5102 },
5103 PreserveUnknownFields: pointer.BoolPtr(false),
5104 },
5105 Status: apiextensions.CustomResourceDefinitionStatus{
5106 StoredVersions: []string{"version"},
5107 },
5108 },
5109 resource: &apiextensions.CustomResourceDefinition{
5110 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5111 Spec: apiextensions.CustomResourceDefinitionSpec{
5112 Group: "group.com",
5113 Scope: apiextensions.ResourceScope("Cluster"),
5114 Names: apiextensions.CustomResourceDefinitionNames{
5115 Plural: "plural",
5116 Singular: "singular",
5117 Kind: "Plural",
5118 ListKind: "PluralList",
5119 },
5120 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5121 Validation: &apiextensions.CustomResourceValidation{
5122 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5123 Type: "object",
5124 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
5125 Type: "array",
5126 XListType: strPtr("set"),
5127 Items: &apiextensions.JSONSchemaPropsOrArray{
5128 Schema: &apiextensions.JSONSchemaProps{
5129 Type: "object",
5130 Properties: map[string]apiextensions.JSONSchemaProps{},
5131 },
5132 },
5133 }},
5134 },
5135 },
5136 PreserveUnknownFields: pointer.BoolPtr(false),
5137 },
5138 Status: apiextensions.CustomResourceDefinitionStatus{
5139 StoredVersions: []string{"version"},
5140 },
5141 },
5142 errors: []validationMatch{
5143 invalid("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "x-kubernetes-map-type"),
5144 },
5145 },
5146 {
5147 name: "structural to non-structural updates not allowed",
5148 old: &apiextensions.CustomResourceDefinition{
5149 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5150 Spec: apiextensions.CustomResourceDefinitionSpec{
5151 Group: "group.com",
5152 Scope: apiextensions.ResourceScope("Cluster"),
5153 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
5154 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5155 Validation: &apiextensions.CustomResourceValidation{
5156 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5157 Type: "object",
5158 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "integer"}},
5159 },
5160 },
5161 PreserveUnknownFields: pointer.BoolPtr(true),
5162 },
5163 Status: apiextensions.CustomResourceDefinitionStatus{
5164 StoredVersions: []string{"version"},
5165 },
5166 },
5167 resource: &apiextensions.CustomResourceDefinition{
5168 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5169 Spec: apiextensions.CustomResourceDefinitionSpec{
5170 Group: "group.com",
5171 Scope: apiextensions.ResourceScope("Cluster"),
5172 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
5173 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5174 Validation: &apiextensions.CustomResourceValidation{
5175 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5176 Type: "object",
5177 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {}},
5178 },
5179 },
5180 PreserveUnknownFields: pointer.BoolPtr(true),
5181 },
5182 Status: apiextensions.CustomResourceDefinitionStatus{
5183 StoredVersions: []string{"version"},
5184 },
5185 },
5186 errors: []validationMatch{
5187 required("spec.validation.openAPIV3Schema.properties[foo].type"),
5188 },
5189 },
5190 {
5191 name: "absent schema to non-structural updates not allowed",
5192 old: &apiextensions.CustomResourceDefinition{
5193 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5194 Spec: apiextensions.CustomResourceDefinitionSpec{
5195 Group: "group.com",
5196 Scope: apiextensions.ResourceScope("Cluster"),
5197 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
5198 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5199 Validation: &apiextensions.CustomResourceValidation{},
5200 PreserveUnknownFields: pointer.BoolPtr(true),
5201 },
5202 Status: apiextensions.CustomResourceDefinitionStatus{
5203 StoredVersions: []string{"version"},
5204 },
5205 },
5206 resource: &apiextensions.CustomResourceDefinition{
5207 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5208 Spec: apiextensions.CustomResourceDefinitionSpec{
5209 Group: "group.com",
5210 Scope: apiextensions.ResourceScope("Cluster"),
5211 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
5212 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
5213 Validation: &apiextensions.CustomResourceValidation{
5214 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5215 Type: "object",
5216 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {}},
5217 },
5218 },
5219 PreserveUnknownFields: pointer.BoolPtr(true),
5220 },
5221 Status: apiextensions.CustomResourceDefinitionStatus{
5222 StoredVersions: []string{"version"},
5223 },
5224 },
5225 errors: []validationMatch{
5226 required("spec.validation.openAPIV3Schema.properties[foo].type"),
5227 },
5228 },
5229 {
5230 name: "non-structural updates allowed if old object has non-structural schema",
5231 old: &apiextensions.CustomResourceDefinition{
5232 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5233 Spec: apiextensions.CustomResourceDefinitionSpec{
5234 Group: "group.com",
5235 Scope: apiextensions.ResourceScope("Cluster"),
5236 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
5237 Versions: []apiextensions.CustomResourceDefinitionVersion{
5238 {Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{
5239 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5240 Type: "object",
5241 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {}},
5242 },
5243 }},
5244 {Name: "version2", Served: true, Storage: false, Schema: &apiextensions.CustomResourceValidation{
5245 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5246 Type: "object",
5247 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Type: "number"}},
5248 },
5249 }},
5250 },
5251 PreserveUnknownFields: pointer.BoolPtr(true),
5252 },
5253 Status: apiextensions.CustomResourceDefinitionStatus{
5254 StoredVersions: []string{"version"},
5255 },
5256 },
5257 resource: &apiextensions.CustomResourceDefinition{
5258 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
5259 Spec: apiextensions.CustomResourceDefinitionSpec{
5260 Group: "group.com",
5261 Scope: apiextensions.ResourceScope("Cluster"),
5262 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
5263 Versions: []apiextensions.CustomResourceDefinitionVersion{
5264 {Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{
5265 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5266 Type: "object",
5267 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Description: "b"}},
5268 },
5269 }},
5270 {Name: "version2", Served: true, Storage: false, Schema: &apiextensions.CustomResourceValidation{
5271 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5272 Type: "object",
5273 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {Description: "a"}},
5274 },
5275 }},
5276 },
5277 PreserveUnknownFields: pointer.BoolPtr(true),
5278 },
5279 Status: apiextensions.CustomResourceDefinitionStatus{
5280 StoredVersions: []string{"version"},
5281 },
5282 },
5283 errors: []validationMatch{},
5284 },
5285 {
5286 name: "webhookconfig: should pass on invalid ConversionReviewVersion with old invalid versions",
5287 resource: &apiextensions.CustomResourceDefinition{
5288 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
5289 Spec: apiextensions.CustomResourceDefinitionSpec{
5290 Group: "group.com",
5291 Scope: apiextensions.ResourceScope("Cluster"),
5292 Names: apiextensions.CustomResourceDefinitionNames{
5293 Plural: "plural",
5294 Singular: "singular",
5295 Kind: "Plural",
5296 ListKind: "PluralList",
5297 },
5298 Versions: []apiextensions.CustomResourceDefinitionVersion{
5299 {
5300 Name: "version",
5301 Served: true,
5302 Storage: true,
5303 },
5304 {
5305 Name: "version2",
5306 Served: true,
5307 Storage: false,
5308 },
5309 },
5310 Conversion: &apiextensions.CustomResourceConversion{
5311 Strategy: apiextensions.ConversionStrategyType("Webhook"),
5312 WebhookClientConfig: &apiextensions.WebhookClientConfig{
5313 URL: strPtr("https://example.com/webhook"),
5314 },
5315 ConversionReviewVersions: []string{"invalid-version"},
5316 },
5317 Validation: &apiextensions.CustomResourceValidation{
5318 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5319 Type: "object",
5320 },
5321 },
5322 PreserveUnknownFields: pointer.BoolPtr(false),
5323 },
5324 Status: apiextensions.CustomResourceDefinitionStatus{
5325 StoredVersions: []string{"version"},
5326 },
5327 },
5328 old: &apiextensions.CustomResourceDefinition{
5329 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
5330 Spec: apiextensions.CustomResourceDefinitionSpec{
5331 Group: "group.com",
5332 Scope: apiextensions.ResourceScope("Cluster"),
5333 Names: apiextensions.CustomResourceDefinitionNames{
5334 Plural: "plural",
5335 Singular: "singular",
5336 Kind: "Plural",
5337 ListKind: "PluralList",
5338 },
5339 Versions: []apiextensions.CustomResourceDefinitionVersion{
5340 {
5341 Name: "version",
5342 Served: true,
5343 Storage: true,
5344 },
5345 {
5346 Name: "version2",
5347 Served: true,
5348 Storage: false,
5349 },
5350 },
5351 Conversion: &apiextensions.CustomResourceConversion{
5352 Strategy: apiextensions.ConversionStrategyType("Webhook"),
5353 WebhookClientConfig: &apiextensions.WebhookClientConfig{
5354 URL: strPtr("https://example.com/webhook"),
5355 },
5356 ConversionReviewVersions: []string{"invalid-version_0, invalid-version"},
5357 },
5358 Validation: &apiextensions.CustomResourceValidation{
5359 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5360 Type: "object",
5361 },
5362 },
5363 PreserveUnknownFields: pointer.BoolPtr(false),
5364 },
5365 Status: apiextensions.CustomResourceDefinitionStatus{
5366 StoredVersions: []string{"version"},
5367 },
5368 },
5369 errors: []validationMatch{},
5370 },
5371 {
5372 name: "webhookconfig: should fail on invalid ConversionReviewVersion with old valid versions",
5373 resource: &apiextensions.CustomResourceDefinition{
5374 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
5375 Spec: apiextensions.CustomResourceDefinitionSpec{
5376 Group: "group.com",
5377 Scope: apiextensions.ResourceScope("Cluster"),
5378 Names: apiextensions.CustomResourceDefinitionNames{
5379 Plural: "plural",
5380 Singular: "singular",
5381 Kind: "Plural",
5382 ListKind: "PluralList",
5383 },
5384 Versions: []apiextensions.CustomResourceDefinitionVersion{
5385 {
5386 Name: "version",
5387 Served: true,
5388 Storage: true,
5389 },
5390 {
5391 Name: "version2",
5392 Served: true,
5393 Storage: false,
5394 },
5395 },
5396 Conversion: &apiextensions.CustomResourceConversion{
5397 Strategy: apiextensions.ConversionStrategyType("Webhook"),
5398 WebhookClientConfig: &apiextensions.WebhookClientConfig{
5399 URL: strPtr("https://example.com/webhook"),
5400 },
5401 ConversionReviewVersions: []string{"invalid-version"},
5402 },
5403 Validation: &apiextensions.CustomResourceValidation{
5404 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5405 Type: "object",
5406 },
5407 },
5408 PreserveUnknownFields: pointer.BoolPtr(false),
5409 },
5410 Status: apiextensions.CustomResourceDefinitionStatus{
5411 StoredVersions: []string{"version"},
5412 },
5413 },
5414 old: &apiextensions.CustomResourceDefinition{
5415 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
5416 Spec: apiextensions.CustomResourceDefinitionSpec{
5417 Group: "group.com",
5418 Scope: apiextensions.ResourceScope("Cluster"),
5419 Names: apiextensions.CustomResourceDefinitionNames{
5420 Plural: "plural",
5421 Singular: "singular",
5422 Kind: "Plural",
5423 ListKind: "PluralList",
5424 },
5425 Versions: []apiextensions.CustomResourceDefinitionVersion{
5426 {
5427 Name: "version",
5428 Served: true,
5429 Storage: true,
5430 },
5431 {
5432 Name: "version2",
5433 Served: true,
5434 Storage: false,
5435 },
5436 },
5437 Conversion: &apiextensions.CustomResourceConversion{
5438 Strategy: apiextensions.ConversionStrategyType("Webhook"),
5439 WebhookClientConfig: &apiextensions.WebhookClientConfig{
5440 URL: strPtr("https://example.com/webhook"),
5441 },
5442 ConversionReviewVersions: []string{"v1beta1", "invalid-version"},
5443 },
5444 Validation: &apiextensions.CustomResourceValidation{
5445 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5446 Type: "object",
5447 },
5448 },
5449 PreserveUnknownFields: pointer.BoolPtr(false),
5450 },
5451 Status: apiextensions.CustomResourceDefinitionStatus{
5452 StoredVersions: []string{"version"},
5453 },
5454 },
5455 errors: []validationMatch{
5456 invalid("spec", "conversion", "conversionReviewVersions"),
5457 },
5458 },
5459 {
5460 name: "webhookconfig: should fail on invalid ConversionReviewVersion with missing old versions",
5461 resource: &apiextensions.CustomResourceDefinition{
5462 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
5463 Spec: apiextensions.CustomResourceDefinitionSpec{
5464 Group: "group.com",
5465 Scope: apiextensions.ResourceScope("Cluster"),
5466 Names: apiextensions.CustomResourceDefinitionNames{
5467 Plural: "plural",
5468 Singular: "singular",
5469 Kind: "Plural",
5470 ListKind: "PluralList",
5471 },
5472 Versions: []apiextensions.CustomResourceDefinitionVersion{
5473 {
5474 Name: "version",
5475 Served: true,
5476 Storage: true,
5477 },
5478 {
5479 Name: "version2",
5480 Served: true,
5481 Storage: false,
5482 },
5483 },
5484 Conversion: &apiextensions.CustomResourceConversion{
5485 Strategy: apiextensions.ConversionStrategyType("Webhook"),
5486 WebhookClientConfig: &apiextensions.WebhookClientConfig{
5487 URL: strPtr("https://example.com/webhook"),
5488 },
5489 ConversionReviewVersions: []string{"invalid-version"},
5490 },
5491 Validation: &apiextensions.CustomResourceValidation{
5492 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5493 Type: "object",
5494 },
5495 },
5496 PreserveUnknownFields: pointer.BoolPtr(false),
5497 },
5498 Status: apiextensions.CustomResourceDefinitionStatus{
5499 StoredVersions: []string{"version"},
5500 },
5501 },
5502 old: &apiextensions.CustomResourceDefinition{
5503 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
5504 Spec: apiextensions.CustomResourceDefinitionSpec{
5505 Group: "group.com",
5506 Scope: apiextensions.ResourceScope("Cluster"),
5507 Names: apiextensions.CustomResourceDefinitionNames{
5508 Plural: "plural",
5509 Singular: "singular",
5510 Kind: "Plural",
5511 ListKind: "PluralList",
5512 },
5513 Versions: []apiextensions.CustomResourceDefinitionVersion{
5514 {
5515 Name: "version",
5516 Served: true,
5517 Storage: true,
5518 },
5519 {
5520 Name: "version2",
5521 Served: true,
5522 Storage: false,
5523 },
5524 },
5525 Conversion: &apiextensions.CustomResourceConversion{
5526 Strategy: apiextensions.ConversionStrategyType("Webhook"),
5527 WebhookClientConfig: &apiextensions.WebhookClientConfig{
5528 URL: strPtr("https://example.com/webhook"),
5529 },
5530 },
5531 Validation: &apiextensions.CustomResourceValidation{
5532 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
5533 Type: "object",
5534 },
5535 },
5536 PreserveUnknownFields: pointer.BoolPtr(false),
5537 },
5538 Status: apiextensions.CustomResourceDefinitionStatus{
5539 StoredVersions: []string{"version"},
5540 },
5541 },
5542 errors: []validationMatch{
5543 invalid("spec", "conversion", "conversionReviewVersions"),
5544 },
5545 },
5546 {
5547 name: "unchanged",
5548 old: &apiextensions.CustomResourceDefinition{
5549 ObjectMeta: metav1.ObjectMeta{
5550 Name: "plural.group.com",
5551 ResourceVersion: "42",
5552 },
5553 Spec: apiextensions.CustomResourceDefinitionSpec{
5554 Group: "group.com",
5555 Version: "version",
5556 Versions: []apiextensions.CustomResourceDefinitionVersion{
5557 {
5558 Name: "version",
5559 Served: true,
5560 Storage: true,
5561 },
5562 },
5563 Scope: apiextensions.ResourceScope("Cluster"),
5564 Names: apiextensions.CustomResourceDefinitionNames{
5565 Plural: "plural",
5566 Singular: "singular",
5567 Kind: "kind",
5568 ListKind: "listkind",
5569 },
5570 PreserveUnknownFields: pointer.BoolPtr(true),
5571 },
5572 Status: apiextensions.CustomResourceDefinitionStatus{
5573 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5574 Plural: "plural",
5575 Singular: "singular",
5576 Kind: "kind",
5577 ListKind: "listkind",
5578 },
5579 },
5580 },
5581 resource: &apiextensions.CustomResourceDefinition{
5582 ObjectMeta: metav1.ObjectMeta{
5583 Name: "plural.group.com",
5584 ResourceVersion: "42",
5585 },
5586 Spec: apiextensions.CustomResourceDefinitionSpec{
5587 Group: "group.com",
5588 Version: "version",
5589 Versions: []apiextensions.CustomResourceDefinitionVersion{
5590 {
5591 Name: "version",
5592 Served: true,
5593 Storage: true,
5594 },
5595 },
5596 Scope: apiextensions.ResourceScope("Cluster"),
5597 Names: apiextensions.CustomResourceDefinitionNames{
5598 Plural: "plural",
5599 Singular: "singular",
5600 Kind: "kind",
5601 ListKind: "listkind",
5602 },
5603 PreserveUnknownFields: pointer.BoolPtr(true),
5604 },
5605 Status: apiextensions.CustomResourceDefinitionStatus{
5606 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5607 Plural: "plural",
5608 Singular: "singular",
5609 Kind: "kind",
5610 ListKind: "listkind",
5611 },
5612 StoredVersions: []string{"version"},
5613 },
5614 },
5615 errors: []validationMatch{},
5616 },
5617 {
5618 name: "unchanged-established",
5619 old: &apiextensions.CustomResourceDefinition{
5620 ObjectMeta: metav1.ObjectMeta{
5621 Name: "plural.group.com",
5622 ResourceVersion: "42",
5623 },
5624 Spec: apiextensions.CustomResourceDefinitionSpec{
5625 Group: "group.com",
5626 Version: "version",
5627 Versions: []apiextensions.CustomResourceDefinitionVersion{
5628 {
5629 Name: "version",
5630 Served: true,
5631 Storage: true,
5632 },
5633 },
5634 Scope: apiextensions.ResourceScope("Cluster"),
5635 Names: apiextensions.CustomResourceDefinitionNames{
5636 Plural: "plural",
5637 Singular: "singular",
5638 Kind: "kind",
5639 ListKind: "listkind",
5640 },
5641 PreserveUnknownFields: pointer.BoolPtr(true),
5642 },
5643 Status: apiextensions.CustomResourceDefinitionStatus{
5644 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5645 Plural: "plural",
5646 Singular: "singular",
5647 Kind: "kind",
5648 ListKind: "listkind",
5649 },
5650 Conditions: []apiextensions.CustomResourceDefinitionCondition{
5651 {Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
5652 },
5653 },
5654 },
5655 resource: &apiextensions.CustomResourceDefinition{
5656 ObjectMeta: metav1.ObjectMeta{
5657 Name: "plural.group.com",
5658 ResourceVersion: "42",
5659 },
5660 Spec: apiextensions.CustomResourceDefinitionSpec{
5661 Group: "group.com",
5662 Version: "version",
5663 Versions: []apiextensions.CustomResourceDefinitionVersion{
5664 {
5665 Name: "version",
5666 Served: true,
5667 Storage: true,
5668 },
5669 },
5670 Scope: apiextensions.ResourceScope("Cluster"),
5671 Names: apiextensions.CustomResourceDefinitionNames{
5672 Plural: "plural",
5673 Singular: "singular",
5674 Kind: "kind",
5675 ListKind: "listkind",
5676 },
5677 PreserveUnknownFields: pointer.BoolPtr(true),
5678 },
5679 Status: apiextensions.CustomResourceDefinitionStatus{
5680 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5681 Plural: "plural",
5682 Singular: "singular",
5683 Kind: "kind",
5684 ListKind: "listkind",
5685 },
5686 StoredVersions: []string{"version"},
5687 },
5688 },
5689 errors: []validationMatch{},
5690 },
5691 {
5692 name: "version-deleted",
5693 old: &apiextensions.CustomResourceDefinition{
5694 ObjectMeta: metav1.ObjectMeta{
5695 Name: "plural.group.com",
5696 ResourceVersion: "42",
5697 },
5698 Spec: apiextensions.CustomResourceDefinitionSpec{
5699 Group: "group.com",
5700 Version: "version",
5701 Versions: []apiextensions.CustomResourceDefinitionVersion{
5702 {
5703 Name: "version",
5704 Served: true,
5705 Storage: true,
5706 },
5707 {
5708 Name: "version2",
5709 Served: true,
5710 Storage: false,
5711 },
5712 },
5713 Scope: apiextensions.ResourceScope("Cluster"),
5714 Names: apiextensions.CustomResourceDefinitionNames{
5715 Plural: "plural",
5716 Singular: "singular",
5717 Kind: "kind",
5718 ListKind: "listkind",
5719 },
5720 PreserveUnknownFields: pointer.BoolPtr(true),
5721 },
5722 Status: apiextensions.CustomResourceDefinitionStatus{
5723 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5724 Plural: "plural",
5725 Singular: "singular",
5726 Kind: "kind",
5727 ListKind: "listkind",
5728 },
5729 StoredVersions: []string{"version", "version2"},
5730 Conditions: []apiextensions.CustomResourceDefinitionCondition{
5731 {Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
5732 },
5733 },
5734 },
5735 resource: &apiextensions.CustomResourceDefinition{
5736 ObjectMeta: metav1.ObjectMeta{
5737 Name: "plural.group.com",
5738 ResourceVersion: "42",
5739 },
5740 Spec: apiextensions.CustomResourceDefinitionSpec{
5741 Group: "group.com",
5742 Version: "version",
5743 Versions: []apiextensions.CustomResourceDefinitionVersion{
5744 {
5745 Name: "version",
5746 Served: true,
5747 Storage: true,
5748 },
5749 },
5750 Scope: apiextensions.ResourceScope("Cluster"),
5751 Names: apiextensions.CustomResourceDefinitionNames{
5752 Plural: "plural",
5753 Singular: "singular",
5754 Kind: "kind",
5755 ListKind: "listkind",
5756 },
5757 PreserveUnknownFields: pointer.BoolPtr(true),
5758 },
5759 Status: apiextensions.CustomResourceDefinitionStatus{
5760 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5761 Plural: "plural",
5762 Singular: "singular",
5763 Kind: "kind",
5764 ListKind: "listkind",
5765 },
5766 StoredVersions: []string{"version", "version2"},
5767 },
5768 },
5769 errors: []validationMatch{
5770 invalid("status", "storedVersions[1]"),
5771 },
5772 },
5773 {
5774 name: "changes",
5775 old: &apiextensions.CustomResourceDefinition{
5776 ObjectMeta: metav1.ObjectMeta{
5777 Name: "plural.group.com",
5778 ResourceVersion: "42",
5779 },
5780 Spec: apiextensions.CustomResourceDefinitionSpec{
5781 Group: "group.com",
5782 Version: "version",
5783 Versions: []apiextensions.CustomResourceDefinitionVersion{
5784 {
5785 Name: "version",
5786 Served: true,
5787 Storage: true,
5788 },
5789 },
5790 Scope: apiextensions.ResourceScope("Cluster"),
5791 Names: apiextensions.CustomResourceDefinitionNames{
5792 Plural: "plural",
5793 Singular: "singular",
5794 Kind: "kind",
5795 ListKind: "listkind",
5796 },
5797 PreserveUnknownFields: pointer.BoolPtr(true),
5798 },
5799 Status: apiextensions.CustomResourceDefinitionStatus{
5800 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5801 Plural: "plural",
5802 Singular: "singular",
5803 Kind: "kind",
5804 ListKind: "listkind",
5805 },
5806 Conditions: []apiextensions.CustomResourceDefinitionCondition{
5807 {Type: apiextensions.Established, Status: apiextensions.ConditionFalse},
5808 },
5809 },
5810 },
5811 resource: &apiextensions.CustomResourceDefinition{
5812 ObjectMeta: metav1.ObjectMeta{
5813 Name: "plural.group.com",
5814 ResourceVersion: "42",
5815 },
5816 Spec: apiextensions.CustomResourceDefinitionSpec{
5817 Group: "abc.com",
5818 Version: "version2",
5819 Versions: []apiextensions.CustomResourceDefinitionVersion{
5820 {
5821 Name: "version2",
5822 Served: true,
5823 Storage: true,
5824 },
5825 },
5826 Scope: apiextensions.ResourceScope("Namespaced"),
5827 Names: apiextensions.CustomResourceDefinitionNames{
5828 Plural: "plural2",
5829 Singular: "singular2",
5830 Kind: "kind2",
5831 ListKind: "listkind2",
5832 },
5833 PreserveUnknownFields: pointer.BoolPtr(true),
5834 },
5835 Status: apiextensions.CustomResourceDefinitionStatus{
5836 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5837 Plural: "plural2",
5838 Singular: "singular2",
5839 Kind: "kind2",
5840 ListKind: "listkind2",
5841 },
5842 StoredVersions: []string{"version2"},
5843 },
5844 },
5845 errors: []validationMatch{
5846 immutable("spec", "group"),
5847 immutable("spec", "names", "plural"),
5848 },
5849 },
5850 {
5851 name: "changes-established",
5852 old: &apiextensions.CustomResourceDefinition{
5853 ObjectMeta: metav1.ObjectMeta{
5854 Name: "plural.group.com",
5855 ResourceVersion: "42",
5856 },
5857 Spec: apiextensions.CustomResourceDefinitionSpec{
5858 Group: "group.com",
5859 Version: "version",
5860 Versions: []apiextensions.CustomResourceDefinitionVersion{
5861 {
5862 Name: "version",
5863 Served: true,
5864 Storage: true,
5865 },
5866 },
5867 Scope: apiextensions.ResourceScope("Cluster"),
5868 Names: apiextensions.CustomResourceDefinitionNames{
5869 Plural: "plural",
5870 Singular: "singular",
5871 Kind: "kind",
5872 ListKind: "listkind",
5873 },
5874 PreserveUnknownFields: pointer.BoolPtr(true),
5875 },
5876 Status: apiextensions.CustomResourceDefinitionStatus{
5877 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5878 Plural: "plural",
5879 Singular: "singular",
5880 Kind: "kind",
5881 ListKind: "listkind",
5882 },
5883 Conditions: []apiextensions.CustomResourceDefinitionCondition{
5884 {Type: apiextensions.Established, Status: apiextensions.ConditionTrue},
5885 },
5886 },
5887 },
5888 resource: &apiextensions.CustomResourceDefinition{
5889 ObjectMeta: metav1.ObjectMeta{
5890 Name: "plural.group.com",
5891 ResourceVersion: "42",
5892 },
5893 Spec: apiextensions.CustomResourceDefinitionSpec{
5894 Group: "abc.com",
5895 Version: "version2",
5896 Versions: []apiextensions.CustomResourceDefinitionVersion{
5897 {
5898 Name: "version2",
5899 Served: true,
5900 Storage: true,
5901 },
5902 },
5903 Scope: apiextensions.ResourceScope("Namespaced"),
5904 Names: apiextensions.CustomResourceDefinitionNames{
5905 Plural: "plural2",
5906 Singular: "singular2",
5907 Kind: "kind2",
5908 ListKind: "listkind2",
5909 },
5910 PreserveUnknownFields: pointer.BoolPtr(true),
5911 },
5912 Status: apiextensions.CustomResourceDefinitionStatus{
5913 AcceptedNames: apiextensions.CustomResourceDefinitionNames{
5914 Plural: "plural2",
5915 Singular: "singular2",
5916 Kind: "kind2",
5917 ListKind: "listkind2",
5918 },
5919 StoredVersions: []string{"version2"},
5920 },
5921 },
5922 errors: []validationMatch{
5923 immutable("spec", "group"),
5924 immutable("spec", "scope"),
5925 immutable("spec", "names", "kind"),
5926 immutable("spec", "names", "plural"),
5927 },
5928 },
5929 {
5930 name: "top-level and per-version fields are mutually exclusive",
5931 old: &apiextensions.CustomResourceDefinition{
5932 ObjectMeta: metav1.ObjectMeta{
5933 Name: "plural.group.com",
5934 ResourceVersion: "42",
5935 },
5936 Spec: apiextensions.CustomResourceDefinitionSpec{
5937 Group: "group.com",
5938 Version: "version",
5939 Versions: []apiextensions.CustomResourceDefinitionVersion{
5940 {
5941 Name: "version",
5942 Served: true,
5943 Storage: true,
5944 Subresources: &apiextensions.CustomResourceSubresources{},
5945 },
5946 {
5947 Name: "version2",
5948 Served: true,
5949 Storage: false,
5950 },
5951 },
5952 Scope: apiextensions.NamespaceScoped,
5953 Names: apiextensions.CustomResourceDefinitionNames{
5954 Plural: "plural",
5955 Singular: "singular",
5956 Kind: "Plural",
5957 ListKind: "PluralList",
5958 },
5959 PreserveUnknownFields: pointer.BoolPtr(true),
5960 },
5961 Status: apiextensions.CustomResourceDefinitionStatus{
5962 StoredVersions: []string{"version"},
5963 },
5964 },
5965 resource: &apiextensions.CustomResourceDefinition{
5966 ObjectMeta: metav1.ObjectMeta{
5967 Name: "plural.group.com",
5968 ResourceVersion: "42",
5969 },
5970 Spec: apiextensions.CustomResourceDefinitionSpec{
5971 Group: "group.com",
5972 Version: "version",
5973 Versions: []apiextensions.CustomResourceDefinitionVersion{
5974 {
5975 Name: "version",
5976 Served: true,
5977 Storage: true,
5978 },
5979 {
5980 Name: "version2",
5981 Served: true,
5982 Storage: false,
5983 Schema: &apiextensions.CustomResourceValidation{
5984 OpenAPIV3Schema: validValidationSchema,
5985 },
5986 Subresources: &apiextensions.CustomResourceSubresources{},
5987 AdditionalPrinterColumns: []apiextensions.CustomResourceColumnDefinition{{Name: "Alpha", Type: "string", JSONPath: ".spec.alpha"}},
5988 },
5989 },
5990 Validation: &apiextensions.CustomResourceValidation{
5991 OpenAPIV3Schema: validValidationSchema,
5992 },
5993 Subresources: &apiextensions.CustomResourceSubresources{},
5994 Scope: apiextensions.NamespaceScoped,
5995 Names: apiextensions.CustomResourceDefinitionNames{
5996 Plural: "plural",
5997 Singular: "singular",
5998 Kind: "Plural",
5999 ListKind: "PluralList",
6000 },
6001 PreserveUnknownFields: pointer.BoolPtr(true),
6002 },
6003 Status: apiextensions.CustomResourceDefinitionStatus{
6004 StoredVersions: []string{"version"},
6005 },
6006 },
6007 errors: []validationMatch{
6008 forbidden("spec", "validation"),
6009 forbidden("spec", "subresources"),
6010 },
6011 },
6012 {
6013 name: "switch off preserveUnknownFields with structural schema before and after",
6014 old: &apiextensions.CustomResourceDefinition{
6015 ObjectMeta: metav1.ObjectMeta{
6016 Name: "plural.group.com",
6017 ResourceVersion: "42",
6018 },
6019 Spec: apiextensions.CustomResourceDefinitionSpec{
6020 Group: "group.com",
6021 Version: "version",
6022 Validation: &apiextensions.CustomResourceValidation{
6023 OpenAPIV3Schema: validValidationSchema,
6024 },
6025 Versions: []apiextensions.CustomResourceDefinitionVersion{
6026 {
6027 Name: "version",
6028 Served: true,
6029 Storage: true,
6030 },
6031 },
6032 Scope: apiextensions.NamespaceScoped,
6033 Names: apiextensions.CustomResourceDefinitionNames{
6034 Plural: "plural",
6035 Singular: "singular",
6036 Kind: "Plural",
6037 ListKind: "PluralList",
6038 },
6039 PreserveUnknownFields: pointer.BoolPtr(true),
6040 },
6041 Status: apiextensions.CustomResourceDefinitionStatus{
6042 StoredVersions: []string{"version"},
6043 },
6044 },
6045 resource: &apiextensions.CustomResourceDefinition{
6046 ObjectMeta: metav1.ObjectMeta{
6047 Name: "plural.group.com",
6048 ResourceVersion: "42",
6049 },
6050 Spec: apiextensions.CustomResourceDefinitionSpec{
6051 Group: "group.com",
6052 Version: "version",
6053 Validation: &apiextensions.CustomResourceValidation{
6054 OpenAPIV3Schema: validUnstructuralValidationSchema,
6055 },
6056 Versions: []apiextensions.CustomResourceDefinitionVersion{
6057 {
6058 Name: "version",
6059 Served: true,
6060 Storage: true,
6061 },
6062 },
6063 Scope: apiextensions.NamespaceScoped,
6064 Names: apiextensions.CustomResourceDefinitionNames{
6065 Plural: "plural",
6066 Singular: "singular",
6067 Kind: "Plural",
6068 ListKind: "PluralList",
6069 },
6070 PreserveUnknownFields: pointer.BoolPtr(false),
6071 },
6072 Status: apiextensions.CustomResourceDefinitionStatus{
6073 StoredVersions: []string{"version"},
6074 },
6075 },
6076 errors: []validationMatch{
6077 required("spec", "validation", "openAPIV3Schema", "properties[spec]", "type"),
6078 required("spec", "validation", "openAPIV3Schema", "properties[status]", "type"),
6079 required("spec", "validation", "openAPIV3Schema", "items", "type"),
6080 },
6081 },
6082 {
6083 name: "switch off preserveUnknownFields without structural schema before, but with after",
6084 old: &apiextensions.CustomResourceDefinition{
6085 ObjectMeta: metav1.ObjectMeta{
6086 Name: "plural.group.com",
6087 ResourceVersion: "42",
6088 },
6089 Spec: apiextensions.CustomResourceDefinitionSpec{
6090 Group: "group.com",
6091 Version: "version",
6092 Validation: &apiextensions.CustomResourceValidation{
6093 OpenAPIV3Schema: validUnstructuralValidationSchema,
6094 },
6095 Versions: []apiextensions.CustomResourceDefinitionVersion{
6096 {
6097 Name: "version",
6098 Served: true,
6099 Storage: true,
6100 },
6101 },
6102 Scope: apiextensions.NamespaceScoped,
6103 Names: apiextensions.CustomResourceDefinitionNames{
6104 Plural: "plural",
6105 Singular: "singular",
6106 Kind: "Plural",
6107 ListKind: "PluralList",
6108 },
6109 PreserveUnknownFields: pointer.BoolPtr(true),
6110 },
6111 Status: apiextensions.CustomResourceDefinitionStatus{
6112 StoredVersions: []string{"version"},
6113 },
6114 },
6115 resource: &apiextensions.CustomResourceDefinition{
6116 ObjectMeta: metav1.ObjectMeta{
6117 Name: "plural.group.com",
6118 ResourceVersion: "42",
6119 },
6120 Spec: apiextensions.CustomResourceDefinitionSpec{
6121 Group: "group.com",
6122 Version: "version",
6123 Validation: &apiextensions.CustomResourceValidation{
6124 OpenAPIV3Schema: validValidationSchema,
6125 },
6126 Versions: []apiextensions.CustomResourceDefinitionVersion{
6127 {
6128 Name: "version",
6129 Served: true,
6130 Storage: true,
6131 },
6132 },
6133 Scope: apiextensions.NamespaceScoped,
6134 Names: apiextensions.CustomResourceDefinitionNames{
6135 Plural: "plural",
6136 Singular: "singular",
6137 Kind: "Plural",
6138 ListKind: "PluralList",
6139 },
6140 PreserveUnknownFields: pointer.BoolPtr(false),
6141 },
6142 Status: apiextensions.CustomResourceDefinitionStatus{
6143 StoredVersions: []string{"version"},
6144 },
6145 },
6146 errors: []validationMatch{},
6147 },
6148 {
6149 name: "switch to preserveUnknownFields: true is forbidden",
6150 old: &apiextensions.CustomResourceDefinition{
6151 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6152 Spec: apiextensions.CustomResourceDefinitionSpec{
6153 Group: "group.com",
6154 Version: "version",
6155 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6156 Validation: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
6157 Scope: apiextensions.NamespaceScoped,
6158 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6159 PreserveUnknownFields: pointer.BoolPtr(false),
6160 },
6161 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6162 },
6163 resource: &apiextensions.CustomResourceDefinition{
6164 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6165 Spec: apiextensions.CustomResourceDefinitionSpec{
6166 Group: "group.com",
6167 Version: "version",
6168 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6169 Validation: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
6170 Scope: apiextensions.NamespaceScoped,
6171 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6172 PreserveUnknownFields: pointer.BoolPtr(true),
6173 },
6174 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6175 },
6176 errors: []validationMatch{invalid("spec.preserveUnknownFields")},
6177 },
6178 {
6179 name: "keep preserveUnknownFields: true is allowed",
6180 old: &apiextensions.CustomResourceDefinition{
6181 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6182 Spec: apiextensions.CustomResourceDefinitionSpec{
6183 Group: "group.com",
6184 Version: "version",
6185 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6186 Validation: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
6187 Scope: apiextensions.NamespaceScoped,
6188 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6189 PreserveUnknownFields: pointer.BoolPtr(true),
6190 },
6191 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6192 },
6193 resource: &apiextensions.CustomResourceDefinition{
6194 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6195 Spec: apiextensions.CustomResourceDefinitionSpec{
6196 Group: "group.com",
6197 Version: "version",
6198 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6199 Validation: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
6200 Scope: apiextensions.NamespaceScoped,
6201 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6202 PreserveUnknownFields: pointer.BoolPtr(true),
6203 },
6204 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6205 },
6206 errors: []validationMatch{},
6207 },
6208 {
6209 name: "schema not required if old object is missing schema",
6210 old: &apiextensions.CustomResourceDefinition{
6211 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6212 Spec: apiextensions.CustomResourceDefinitionSpec{
6213 Group: "group.com",
6214 Version: "version",
6215 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6216 Scope: apiextensions.NamespaceScoped,
6217 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6218 PreserveUnknownFields: pointer.BoolPtr(true),
6219 },
6220 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6221 },
6222 resource: &apiextensions.CustomResourceDefinition{
6223 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6224 Spec: apiextensions.CustomResourceDefinitionSpec{
6225 Group: "group.com",
6226 Version: "version",
6227 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6228 Scope: apiextensions.NamespaceScoped,
6229 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6230 PreserveUnknownFields: pointer.BoolPtr(true),
6231 },
6232 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6233 },
6234 errors: []validationMatch{},
6235 },
6236 {
6237 name: "schema not required if old object is missing schema for some versions",
6238 old: &apiextensions.CustomResourceDefinition{
6239 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6240 Spec: apiextensions.CustomResourceDefinitionSpec{
6241 Group: "group.com",
6242 Version: "version",
6243 Versions: []apiextensions.CustomResourceDefinitionVersion{
6244 {Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}}},
6245 {Name: "version2", Served: true, Storage: false},
6246 },
6247 Scope: apiextensions.NamespaceScoped,
6248 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6249 PreserveUnknownFields: pointer.BoolPtr(true),
6250 },
6251 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6252 },
6253 resource: &apiextensions.CustomResourceDefinition{
6254 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6255 Spec: apiextensions.CustomResourceDefinitionSpec{
6256 Group: "group.com",
6257 Version: "version",
6258 Versions: []apiextensions.CustomResourceDefinitionVersion{
6259 {Name: "version", Served: true, Storage: true},
6260 {Name: "version2", Served: true, Storage: false},
6261 },
6262 Scope: apiextensions.NamespaceScoped,
6263 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6264 PreserveUnknownFields: pointer.BoolPtr(true),
6265 },
6266 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6267 },
6268 errors: []validationMatch{},
6269 },
6270 {
6271 name: "schema required if old object has top-level schema",
6272 old: &apiextensions.CustomResourceDefinition{
6273 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6274 Spec: apiextensions.CustomResourceDefinitionSpec{
6275 Group: "group.com",
6276 Version: "version",
6277 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6278 Validation: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object"}},
6279 Scope: apiextensions.NamespaceScoped,
6280 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6281 PreserveUnknownFields: pointer.BoolPtr(true),
6282 },
6283 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6284 },
6285 resource: &apiextensions.CustomResourceDefinition{
6286 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6287 Spec: apiextensions.CustomResourceDefinitionSpec{
6288 Group: "group.com",
6289 Version: "version",
6290 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6291 Scope: apiextensions.NamespaceScoped,
6292 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6293 PreserveUnknownFields: pointer.BoolPtr(true),
6294 },
6295 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6296 },
6297 errors: []validationMatch{
6298 required("spec.versions[0].schema.openAPIV3Schema"),
6299 },
6300 },
6301 {
6302 name: "schema required if all versions of old object have schema",
6303 old: &apiextensions.CustomResourceDefinition{
6304 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6305 Spec: apiextensions.CustomResourceDefinitionSpec{
6306 Group: "group.com",
6307 Version: "version",
6308 Versions: []apiextensions.CustomResourceDefinitionVersion{
6309 {Name: "version", Served: true, Storage: true, Schema: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object", Description: "1"}}},
6310 {Name: "version2", Served: true, Storage: false, Schema: &apiextensions.CustomResourceValidation{OpenAPIV3Schema: &apiextensions.JSONSchemaProps{Type: "object", Description: "2"}}},
6311 },
6312 Scope: apiextensions.NamespaceScoped,
6313 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6314 PreserveUnknownFields: pointer.BoolPtr(true),
6315 },
6316 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6317 },
6318 resource: &apiextensions.CustomResourceDefinition{
6319 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6320 Spec: apiextensions.CustomResourceDefinitionSpec{
6321 Group: "group.com",
6322 Version: "version",
6323 Versions: []apiextensions.CustomResourceDefinitionVersion{
6324 {Name: "version", Served: true, Storage: true},
6325 {Name: "version2", Served: true, Storage: false},
6326 },
6327 Scope: apiextensions.NamespaceScoped,
6328 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6329 PreserveUnknownFields: pointer.BoolPtr(true),
6330 },
6331 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6332 },
6333 errors: []validationMatch{
6334 required("spec.versions[0].schema.openAPIV3Schema"),
6335 required("spec.versions[1].schema.openAPIV3Schema"),
6336 },
6337 },
6338 {
6339 name: "setting defaults with enabled feature gate",
6340 old: &apiextensions.CustomResourceDefinition{
6341 ObjectMeta: metav1.ObjectMeta{
6342 Name: "plural.group.com",
6343 ResourceVersion: "42",
6344 },
6345 Spec: apiextensions.CustomResourceDefinitionSpec{
6346 Group: "group.com",
6347 Version: "version",
6348 Versions: []apiextensions.CustomResourceDefinitionVersion{
6349 {
6350 Name: "version",
6351 Served: true,
6352 Storage: true,
6353 },
6354 },
6355 Scope: apiextensions.NamespaceScoped,
6356 Names: apiextensions.CustomResourceDefinitionNames{
6357 Plural: "plural",
6358 Singular: "singular",
6359 Kind: "Plural",
6360 ListKind: "PluralList",
6361 },
6362 Validation: &apiextensions.CustomResourceValidation{
6363 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6364 Type: "object",
6365 Properties: map[string]apiextensions.JSONSchemaProps{
6366 "a": {
6367 Type: "number",
6368 },
6369 },
6370 },
6371 },
6372 PreserveUnknownFields: pointer.BoolPtr(false),
6373 },
6374 Status: apiextensions.CustomResourceDefinitionStatus{
6375 StoredVersions: []string{"version"},
6376 },
6377 },
6378 resource: &apiextensions.CustomResourceDefinition{
6379 ObjectMeta: metav1.ObjectMeta{
6380 Name: "plural.group.com",
6381 ResourceVersion: "42",
6382 },
6383 Spec: apiextensions.CustomResourceDefinitionSpec{
6384 Group: "group.com",
6385 Version: "version",
6386 Versions: []apiextensions.CustomResourceDefinitionVersion{
6387 {
6388 Name: "version",
6389 Served: true,
6390 Storage: true,
6391 },
6392 },
6393 Scope: apiextensions.NamespaceScoped,
6394 Names: apiextensions.CustomResourceDefinitionNames{
6395 Plural: "plural",
6396 Singular: "singular",
6397 Kind: "Plural",
6398 ListKind: "PluralList",
6399 },
6400 Validation: &apiextensions.CustomResourceValidation{
6401 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6402 Type: "object",
6403 Properties: map[string]apiextensions.JSONSchemaProps{
6404 "a": {
6405 Type: "number",
6406 Default: jsonPtr(42.0),
6407 },
6408 },
6409 },
6410 },
6411 PreserveUnknownFields: pointer.BoolPtr(false),
6412 },
6413 Status: apiextensions.CustomResourceDefinitionStatus{
6414 StoredVersions: []string{"version"},
6415 },
6416 },
6417 errors: []validationMatch{},
6418 },
6419 {
6420 name: "add default with enabled feature gate, structural schema, without pruning",
6421 old: &apiextensions.CustomResourceDefinition{
6422 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
6423 Spec: apiextensions.CustomResourceDefinitionSpec{
6424 Group: "group.com",
6425 Version: "version",
6426 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6427 Scope: apiextensions.NamespaceScoped,
6428 Names: apiextensions.CustomResourceDefinitionNames{
6429 Plural: "plural",
6430 Singular: "singular",
6431 Kind: "Plural",
6432 ListKind: "PluralList",
6433 },
6434 Validation: &apiextensions.CustomResourceValidation{
6435 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6436 Type: "object",
6437 Properties: map[string]apiextensions.JSONSchemaProps{
6438 "a": {
6439 Type: "number",
6440
6441 },
6442 },
6443 },
6444 },
6445 PreserveUnknownFields: pointer.BoolPtr(true),
6446 },
6447 Status: apiextensions.CustomResourceDefinitionStatus{
6448 StoredVersions: []string{"version"},
6449 },
6450 },
6451 resource: &apiextensions.CustomResourceDefinition{
6452 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "42"},
6453 Spec: apiextensions.CustomResourceDefinitionSpec{
6454 Group: "group.com",
6455 Version: "version",
6456 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6457 Scope: apiextensions.NamespaceScoped,
6458 Names: apiextensions.CustomResourceDefinitionNames{
6459 Plural: "plural",
6460 Singular: "singular",
6461 Kind: "Plural",
6462 ListKind: "PluralList",
6463 },
6464 Validation: &apiextensions.CustomResourceValidation{
6465 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6466 Type: "object",
6467 Properties: map[string]apiextensions.JSONSchemaProps{
6468 "a": {
6469 Type: "number",
6470 Default: jsonPtr(42.0),
6471 },
6472 },
6473 },
6474 },
6475 PreserveUnknownFields: pointer.BoolPtr(true),
6476 },
6477 Status: apiextensions.CustomResourceDefinitionStatus{
6478 StoredVersions: []string{"version"},
6479 },
6480 },
6481 errors: []validationMatch{
6482 invalid("spec", "preserveUnknownFields"),
6483 },
6484 },
6485 {
6486 name: "allow non-required key with no default in list of type map if pre-existing",
6487 old: &apiextensions.CustomResourceDefinition{
6488 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6489 Spec: apiextensions.CustomResourceDefinitionSpec{
6490 Group: "group.com",
6491 Scope: apiextensions.ResourceScope("Cluster"),
6492 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6493 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6494 Validation: &apiextensions.CustomResourceValidation{
6495 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6496 Type: "object",
6497 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6498 Type: "array",
6499 XListType: strPtr("map"),
6500 XListMapKeys: []string{"key"},
6501 Items: &apiextensions.JSONSchemaPropsOrArray{
6502 Schema: &apiextensions.JSONSchemaProps{
6503 Type: "object",
6504 Properties: map[string]apiextensions.JSONSchemaProps{
6505 "key": {
6506 Type: "string",
6507 },
6508 },
6509 },
6510 },
6511 }},
6512 },
6513 },
6514 },
6515 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6516 },
6517 resource: &apiextensions.CustomResourceDefinition{
6518 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6519 Spec: apiextensions.CustomResourceDefinitionSpec{
6520 Group: "group.com",
6521 Scope: apiextensions.ResourceScope("Cluster"),
6522 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6523 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6524 Validation: &apiextensions.CustomResourceValidation{
6525 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6526 Type: "object",
6527 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6528 Type: "array",
6529 XListType: strPtr("map"),
6530 XListMapKeys: []string{"key"},
6531 Items: &apiextensions.JSONSchemaPropsOrArray{
6532 Schema: &apiextensions.JSONSchemaProps{
6533 Type: "object",
6534 Properties: map[string]apiextensions.JSONSchemaProps{
6535 "key": {
6536 Type: "string",
6537 },
6538 },
6539 },
6540 },
6541 }},
6542 },
6543 },
6544 },
6545 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6546 },
6547 errors: nil,
6548 },
6549 {
6550 name: "reject non-required key with no default in list of type map if not pre-existing",
6551 old: &apiextensions.CustomResourceDefinition{
6552 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6553 Spec: apiextensions.CustomResourceDefinitionSpec{
6554 Group: "group.com",
6555 Scope: apiextensions.ResourceScope("Cluster"),
6556 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6557 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6558 Validation: &apiextensions.CustomResourceValidation{
6559 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6560 Type: "object",
6561 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6562 Type: "array",
6563 XListType: strPtr("map"),
6564 XListMapKeys: []string{"key"},
6565 Items: &apiextensions.JSONSchemaPropsOrArray{
6566 Schema: &apiextensions.JSONSchemaProps{
6567 Type: "object",
6568 Properties: map[string]apiextensions.JSONSchemaProps{
6569 "key": {
6570 Type: "string",
6571 Default: jsonPtr("stuff"),
6572 },
6573 },
6574 },
6575 },
6576 }},
6577 },
6578 },
6579 },
6580 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6581 },
6582 resource: &apiextensions.CustomResourceDefinition{
6583 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6584 Spec: apiextensions.CustomResourceDefinitionSpec{
6585 Group: "group.com",
6586 Scope: apiextensions.ResourceScope("Cluster"),
6587 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6588 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6589 Validation: &apiextensions.CustomResourceValidation{
6590 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6591 Type: "object",
6592 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6593 Type: "array",
6594 XListType: strPtr("map"),
6595 XListMapKeys: []string{"key"},
6596 Items: &apiextensions.JSONSchemaPropsOrArray{
6597 Schema: &apiextensions.JSONSchemaProps{
6598 Type: "object",
6599 Properties: map[string]apiextensions.JSONSchemaProps{
6600 "key": {
6601 Type: "string",
6602 },
6603 },
6604 },
6605 },
6606 }},
6607 },
6608 },
6609 },
6610 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6611 },
6612 errors: []validationMatch{
6613 required("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "properties[key]", "default"),
6614 },
6615 },
6616 {
6617 name: "allow nullable key in list of type map if pre-existing",
6618 old: &apiextensions.CustomResourceDefinition{
6619 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6620 Spec: apiextensions.CustomResourceDefinitionSpec{
6621 Group: "group.com",
6622 Scope: apiextensions.ResourceScope("Cluster"),
6623 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6624 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6625 Validation: &apiextensions.CustomResourceValidation{
6626 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6627 Type: "object",
6628 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6629 Type: "array",
6630 XListType: strPtr("map"),
6631 XListMapKeys: []string{"key"},
6632 Items: &apiextensions.JSONSchemaPropsOrArray{
6633 Schema: &apiextensions.JSONSchemaProps{
6634 Type: "object",
6635 Required: []string{"key"},
6636 Properties: map[string]apiextensions.JSONSchemaProps{
6637 "key": {
6638 Type: "string",
6639 Nullable: true,
6640 },
6641 },
6642 },
6643 },
6644 }},
6645 },
6646 },
6647 },
6648 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6649 },
6650 resource: &apiextensions.CustomResourceDefinition{
6651 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6652 Spec: apiextensions.CustomResourceDefinitionSpec{
6653 Group: "group.com",
6654 Scope: apiextensions.ResourceScope("Cluster"),
6655 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6656 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6657 Validation: &apiextensions.CustomResourceValidation{
6658 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6659 Type: "object",
6660 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6661 Type: "array",
6662 XListType: strPtr("map"),
6663 XListMapKeys: []string{"key"},
6664 Items: &apiextensions.JSONSchemaPropsOrArray{
6665 Schema: &apiextensions.JSONSchemaProps{
6666 Type: "object",
6667 Required: []string{"key"},
6668 Properties: map[string]apiextensions.JSONSchemaProps{
6669 "key": {
6670 Type: "string",
6671 Nullable: true,
6672 },
6673 },
6674 },
6675 },
6676 }},
6677 },
6678 },
6679 },
6680 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6681 },
6682 errors: nil,
6683 },
6684 {
6685 name: "reject nullable key in list of type map if not pre-existing",
6686 old: &apiextensions.CustomResourceDefinition{
6687 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6688 Spec: apiextensions.CustomResourceDefinitionSpec{
6689 Group: "group.com",
6690 Scope: apiextensions.ResourceScope("Cluster"),
6691 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6692 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6693 Validation: &apiextensions.CustomResourceValidation{
6694 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6695 Type: "object",
6696 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6697 Type: "array",
6698 XListType: strPtr("map"),
6699 XListMapKeys: []string{"key"},
6700 Items: &apiextensions.JSONSchemaPropsOrArray{
6701 Schema: &apiextensions.JSONSchemaProps{
6702 Type: "object",
6703 Required: []string{"key"},
6704 Properties: map[string]apiextensions.JSONSchemaProps{
6705 "key": {
6706 Type: "string",
6707 },
6708 },
6709 },
6710 },
6711 }},
6712 },
6713 },
6714 },
6715 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6716 },
6717 resource: &apiextensions.CustomResourceDefinition{
6718 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6719 Spec: apiextensions.CustomResourceDefinitionSpec{
6720 Group: "group.com",
6721 Scope: apiextensions.ResourceScope("Cluster"),
6722 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6723 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6724 Validation: &apiextensions.CustomResourceValidation{
6725 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6726 Type: "object",
6727 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6728 Type: "array",
6729 XListType: strPtr("map"),
6730 XListMapKeys: []string{"key"},
6731 Items: &apiextensions.JSONSchemaPropsOrArray{
6732 Schema: &apiextensions.JSONSchemaProps{
6733 Type: "object",
6734 Required: []string{"key"},
6735 Properties: map[string]apiextensions.JSONSchemaProps{
6736 "key": {
6737 Type: "string",
6738 Nullable: true,
6739 },
6740 },
6741 },
6742 },
6743 }},
6744 },
6745 },
6746 },
6747 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6748 },
6749 errors: []validationMatch{
6750 forbidden("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "properties[key]", "nullable"),
6751 },
6752 },
6753 {
6754 name: "allow nullable item in list of type map if pre-existing",
6755 old: &apiextensions.CustomResourceDefinition{
6756 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6757 Spec: apiextensions.CustomResourceDefinitionSpec{
6758 Group: "group.com",
6759 Scope: apiextensions.ResourceScope("Cluster"),
6760 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6761 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6762 Validation: &apiextensions.CustomResourceValidation{
6763 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6764 Type: "object",
6765 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6766 Type: "array",
6767 XListType: strPtr("map"),
6768 XListMapKeys: []string{"key"},
6769 Items: &apiextensions.JSONSchemaPropsOrArray{
6770 Schema: &apiextensions.JSONSchemaProps{
6771 Type: "object",
6772 Nullable: true,
6773 Required: []string{"key"},
6774 Properties: map[string]apiextensions.JSONSchemaProps{
6775 "key": {
6776 Type: "string",
6777 },
6778 },
6779 },
6780 },
6781 }},
6782 },
6783 },
6784 },
6785 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6786 },
6787 resource: &apiextensions.CustomResourceDefinition{
6788 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6789 Spec: apiextensions.CustomResourceDefinitionSpec{
6790 Group: "group.com",
6791 Scope: apiextensions.ResourceScope("Cluster"),
6792 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6793 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6794 Validation: &apiextensions.CustomResourceValidation{
6795 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6796 Type: "object",
6797 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6798 Type: "array",
6799 XListType: strPtr("map"),
6800 XListMapKeys: []string{"key"},
6801 Items: &apiextensions.JSONSchemaPropsOrArray{
6802 Schema: &apiextensions.JSONSchemaProps{
6803 Type: "object",
6804 Nullable: true,
6805 Required: []string{"key"},
6806 Properties: map[string]apiextensions.JSONSchemaProps{
6807 "key": {
6808 Type: "string",
6809 },
6810 },
6811 },
6812 },
6813 }},
6814 },
6815 },
6816 },
6817 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6818 },
6819 errors: nil,
6820 },
6821 {
6822 name: "reject nullable item in list of type map if not pre-existing",
6823 old: &apiextensions.CustomResourceDefinition{
6824 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6825 Spec: apiextensions.CustomResourceDefinitionSpec{
6826 Group: "group.com",
6827 Scope: apiextensions.ResourceScope("Cluster"),
6828 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6829 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6830 Validation: &apiextensions.CustomResourceValidation{
6831 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6832 Type: "object",
6833 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6834 Type: "array",
6835 XListType: strPtr("map"),
6836 XListMapKeys: []string{"key"},
6837 Items: &apiextensions.JSONSchemaPropsOrArray{
6838 Schema: &apiextensions.JSONSchemaProps{
6839 Type: "object",
6840 Required: []string{"key"},
6841 Properties: map[string]apiextensions.JSONSchemaProps{
6842 "key": {
6843 Type: "string",
6844 },
6845 },
6846 },
6847 },
6848 }},
6849 },
6850 },
6851 },
6852 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6853 },
6854 resource: &apiextensions.CustomResourceDefinition{
6855 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6856 Spec: apiextensions.CustomResourceDefinitionSpec{
6857 Group: "group.com",
6858 Scope: apiextensions.ResourceScope("Cluster"),
6859 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6860 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6861 Validation: &apiextensions.CustomResourceValidation{
6862 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6863 Type: "object",
6864 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6865 Type: "array",
6866 XListType: strPtr("map"),
6867 XListMapKeys: []string{"key"},
6868 Items: &apiextensions.JSONSchemaPropsOrArray{
6869 Schema: &apiextensions.JSONSchemaProps{
6870 Type: "object",
6871 Nullable: true,
6872 Required: []string{"key"},
6873 Properties: map[string]apiextensions.JSONSchemaProps{
6874 "key": {
6875 Type: "string",
6876 },
6877 },
6878 },
6879 },
6880 }},
6881 },
6882 },
6883 },
6884 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6885 },
6886 errors: []validationMatch{
6887 forbidden("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "nullable"),
6888 },
6889 },
6890 {
6891 name: "allow nullable items in list of type set if pre-existing",
6892 old: &apiextensions.CustomResourceDefinition{
6893 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6894 Spec: apiextensions.CustomResourceDefinitionSpec{
6895 Group: "group.com",
6896 Scope: apiextensions.ResourceScope("Cluster"),
6897 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6898 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6899 Validation: &apiextensions.CustomResourceValidation{
6900 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6901 Type: "object",
6902 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6903 Type: "array",
6904 XListType: strPtr("set"),
6905 Items: &apiextensions.JSONSchemaPropsOrArray{
6906 Schema: &apiextensions.JSONSchemaProps{
6907 Type: "string",
6908 Nullable: true,
6909 },
6910 },
6911 }},
6912 },
6913 },
6914 },
6915 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6916 },
6917 resource: &apiextensions.CustomResourceDefinition{
6918 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6919 Spec: apiextensions.CustomResourceDefinitionSpec{
6920 Group: "group.com",
6921 Scope: apiextensions.ResourceScope("Cluster"),
6922 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6923 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6924 Validation: &apiextensions.CustomResourceValidation{
6925 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6926 Type: "object",
6927 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6928 Type: "array",
6929 XListType: strPtr("set"),
6930 Items: &apiextensions.JSONSchemaPropsOrArray{
6931 Schema: &apiextensions.JSONSchemaProps{
6932 Type: "string",
6933 Nullable: true,
6934 },
6935 },
6936 }},
6937 },
6938 },
6939 },
6940 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6941 },
6942 errors: nil,
6943 },
6944 {
6945 name: "reject nullable items in list of type set if not pre-exisiting",
6946 old: &apiextensions.CustomResourceDefinition{
6947 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6948 Spec: apiextensions.CustomResourceDefinitionSpec{
6949 Group: "group.com",
6950 Scope: apiextensions.ResourceScope("Cluster"),
6951 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6952 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6953 Validation: &apiextensions.CustomResourceValidation{
6954 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6955 Type: "object",
6956 Properties: map[string]apiextensions.JSONSchemaProps{"foo": {
6957 Type: "array",
6958 XListType: strPtr("set"),
6959 Items: &apiextensions.JSONSchemaPropsOrArray{
6960 Schema: &apiextensions.JSONSchemaProps{
6961 Type: "string",
6962 },
6963 },
6964 }},
6965 },
6966 },
6967 },
6968 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6969 },
6970 resource: &apiextensions.CustomResourceDefinition{
6971 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
6972 Spec: apiextensions.CustomResourceDefinitionSpec{
6973 Group: "group.com",
6974 Scope: apiextensions.ResourceScope("Cluster"),
6975 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
6976 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
6977 Validation: &apiextensions.CustomResourceValidation{
6978 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
6979 Type: "object",
6980 Properties: map[string]apiextensions.JSONSchemaProps{"bar": {
6981 Type: "array",
6982 XListType: strPtr("set"),
6983 Items: &apiextensions.JSONSchemaPropsOrArray{
6984 Schema: &apiextensions.JSONSchemaProps{
6985 Type: "string",
6986 Nullable: true,
6987 },
6988 },
6989 }},
6990 },
6991 },
6992 },
6993 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
6994 },
6995 errors: []validationMatch{
6996 forbidden("spec", "validation", "openAPIV3Schema", "properties[bar]", "items", "nullable"),
6997 },
6998 },
6999 {
7000 name: "suppress per-expression cost limit in pre-existing versions",
7001 old: &apiextensions.CustomResourceDefinition{
7002 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
7003 Spec: apiextensions.CustomResourceDefinitionSpec{
7004 Group: "group.com",
7005 Scope: apiextensions.ResourceScope("Cluster"),
7006 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
7007 Versions: []apiextensions.CustomResourceDefinitionVersion{
7008 {
7009 Name: "v1",
7010 Served: true,
7011 Storage: true,
7012 Schema: &apiextensions.CustomResourceValidation{
7013 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7014 Type: "object",
7015 Properties: map[string]apiextensions.JSONSchemaProps{
7016 "f": {
7017 Type: "array",
7018 Items: &apiextensions.JSONSchemaPropsOrArray{
7019 Schema: &apiextensions.JSONSchemaProps{
7020 Type: "string",
7021 },
7022 },
7023 XValidations: apiextensions.ValidationRules{
7024 {
7025 Rule: "true",
7026 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
7027 },
7028 },
7029 },
7030 },
7031 },
7032 },
7033 },
7034 {
7035 Name: "v2",
7036 Schema: &apiextensions.CustomResourceValidation{
7037 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7038 Type: "object",
7039 Properties: map[string]apiextensions.JSONSchemaProps{
7040 "f": {
7041 Type: "array",
7042 Items: &apiextensions.JSONSchemaPropsOrArray{
7043 Schema: &apiextensions.JSONSchemaProps{
7044 Type: "string",
7045 },
7046 },
7047 XValidations: apiextensions.ValidationRules{
7048 {
7049 Rule: "true",
7050 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
7051 },
7052 },
7053 },
7054 },
7055 },
7056 },
7057 },
7058 },
7059 },
7060 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
7061 },
7062 resource: &apiextensions.CustomResourceDefinition{
7063 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
7064 Spec: apiextensions.CustomResourceDefinitionSpec{
7065 Group: "group.com",
7066 Scope: apiextensions.ResourceScope("Cluster"),
7067 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
7068 Versions: []apiextensions.CustomResourceDefinitionVersion{
7069 {
7070 Name: "v1",
7071 Served: true,
7072 Storage: true,
7073 Schema: &apiextensions.CustomResourceValidation{
7074 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7075 Type: "object",
7076 Properties: map[string]apiextensions.JSONSchemaProps{
7077 "f": {
7078 Type: "array",
7079 Items: &apiextensions.JSONSchemaPropsOrArray{
7080 Schema: &apiextensions.JSONSchemaProps{
7081 Type: "string",
7082 },
7083 },
7084 XValidations: apiextensions.ValidationRules{
7085 {
7086 Rule: "true",
7087 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
7088 },
7089 },
7090 },
7091 },
7092 },
7093 },
7094 },
7095 {
7096 Name: "v2",
7097 Schema: &apiextensions.CustomResourceValidation{
7098 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7099 Type: "object",
7100 Properties: map[string]apiextensions.JSONSchemaProps{
7101 "f": {
7102 Type: "array",
7103 Items: &apiextensions.JSONSchemaPropsOrArray{
7104 Schema: &apiextensions.JSONSchemaProps{
7105 Type: "string",
7106 },
7107 },
7108 XValidations: apiextensions.ValidationRules{
7109 {
7110 Rule: "true",
7111 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7] + self[8]`,
7112 },
7113 },
7114 },
7115 },
7116 },
7117 },
7118 },
7119 {
7120 Name: "v3",
7121 Schema: &apiextensions.CustomResourceValidation{
7122 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7123 Type: "object",
7124 Properties: map[string]apiextensions.JSONSchemaProps{
7125 "f": {
7126 Type: "array",
7127 Items: &apiextensions.JSONSchemaPropsOrArray{
7128 Schema: &apiextensions.JSONSchemaProps{
7129 Type: "string",
7130 },
7131 },
7132 XValidations: apiextensions.ValidationRules{
7133 {
7134 Rule: "true",
7135 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
7136 },
7137 },
7138 },
7139 },
7140 },
7141 },
7142 },
7143 },
7144 },
7145 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
7146 },
7147 errors: []validationMatch{
7148
7149 forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[f]", "x-kubernetes-validations[0]", "messageExpression"),
7150 forbidden("spec", "versions[2]", "schema", "openAPIV3Schema", "properties[f]", "x-kubernetes-validations[0]", "messageExpression"),
7151 },
7152 },
7153 {
7154 name: "suppress per-expression cost limit in new object during top-level schema to Versions extraction",
7155 old: &apiextensions.CustomResourceDefinition{
7156 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
7157 Spec: apiextensions.CustomResourceDefinitionSpec{
7158 Group: "group.com",
7159 Scope: apiextensions.ResourceScope("Cluster"),
7160 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
7161 Version: "v1",
7162 Validation: &apiextensions.CustomResourceValidation{
7163 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7164 Type: "object",
7165 Properties: map[string]apiextensions.JSONSchemaProps{
7166 "f": {
7167 Type: "array",
7168 Items: &apiextensions.JSONSchemaPropsOrArray{
7169 Schema: &apiextensions.JSONSchemaProps{
7170 Type: "string",
7171 },
7172 },
7173 XValidations: apiextensions.ValidationRules{
7174 {
7175 Rule: "true",
7176 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
7177 },
7178 },
7179 },
7180 },
7181 },
7182 },
7183 },
7184 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
7185 },
7186 resource: &apiextensions.CustomResourceDefinition{
7187 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
7188 Spec: apiextensions.CustomResourceDefinitionSpec{
7189 Group: "group.com",
7190 Scope: apiextensions.ResourceScope("Cluster"),
7191 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
7192 Versions: []apiextensions.CustomResourceDefinitionVersion{
7193 {
7194 Name: "v1",
7195 Served: true,
7196 Storage: true,
7197 Schema: &apiextensions.CustomResourceValidation{
7198 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7199 Type: "object",
7200 Properties: map[string]apiextensions.JSONSchemaProps{
7201 "f": {
7202 Type: "array",
7203 Items: &apiextensions.JSONSchemaPropsOrArray{
7204 Schema: &apiextensions.JSONSchemaProps{
7205 Type: "string",
7206 },
7207 },
7208 XValidations: apiextensions.ValidationRules{
7209 {
7210 Rule: "true",
7211 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
7212 },
7213 },
7214 },
7215 },
7216 },
7217 },
7218 },
7219 {
7220 Name: "v2",
7221 Schema: &apiextensions.CustomResourceValidation{
7222 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7223 Type: "object",
7224 Properties: map[string]apiextensions.JSONSchemaProps{
7225 "f": {
7226 Type: "array",
7227 Items: &apiextensions.JSONSchemaPropsOrArray{
7228 Schema: &apiextensions.JSONSchemaProps{
7229 Type: "string",
7230 },
7231 },
7232 XValidations: apiextensions.ValidationRules{
7233 {
7234 Rule: "true",
7235 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7] + self[8]`,
7236 },
7237 },
7238 },
7239 },
7240 },
7241 },
7242 },
7243 },
7244 },
7245 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"v1"}},
7246 },
7247 errors: []validationMatch{
7248
7249 forbidden("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[f]", "x-kubernetes-validations[0]", "messageExpression"),
7250 },
7251 },
7252 }
7253
7254 for _, tc := range tests {
7255 t.Run(tc.name, func(t *testing.T) {
7256 ctx := context.TODO()
7257 errs := ValidateCustomResourceDefinitionUpdate(ctx, tc.resource, tc.old)
7258 seenErrs := make([]bool, len(errs))
7259
7260 for _, expectedError := range tc.errors {
7261 found := false
7262 for i, err := range errs {
7263 if expectedError.matches(err) && !seenErrs[i] {
7264 found = true
7265 seenErrs[i] = true
7266 break
7267 }
7268 }
7269
7270 if !found {
7271 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
7272 }
7273 }
7274
7275 for i, seen := range seenErrs {
7276 if !seen {
7277 t.Errorf("unexpected error: %v", errs[i])
7278 }
7279 }
7280 })
7281 }
7282 }
7283
7284 func TestValidateCustomResourceDefinitionValidationRuleCompatibility(t *testing.T) {
7285 allValidationsErrors := []validationMatch{
7286 invalid("spec", "validation", "openAPIV3Schema", "properties[x]", "x-kubernetes-validations[0]", "rule"),
7287 invalid("spec", "validation", "openAPIV3Schema", "properties[obj]", "x-kubernetes-validations[0]", "rule"),
7288 invalid("spec", "validation", "openAPIV3Schema", "properties[obj]", "properties[a]", "x-kubernetes-validations[0]", "rule"),
7289 invalid("spec", "validation", "openAPIV3Schema", "properties[array]", "x-kubernetes-validations[0]", "rule"),
7290 invalid("spec", "validation", "openAPIV3Schema", "properties[array]", "items", "x-kubernetes-validations[0]", "rule"),
7291 invalid("spec", "validation", "openAPIV3Schema", "properties[map]", "x-kubernetes-validations[0]", "rule"),
7292 invalid("spec", "validation", "openAPIV3Schema", "properties[map]", "additionalProperties", "x-kubernetes-validations[0]", "rule"),
7293 }
7294
7295 tests := []struct {
7296 name string
7297 storedRule string
7298 updatedRule string
7299 errors []validationMatch
7300 }{
7301 {
7302 name: "functions declared for storage mode allowed if expression is unchanged from what is stored",
7303 storedRule: "test() == true",
7304 updatedRule: "test() == true",
7305 },
7306 {
7307 name: "functions declared for storage mode not allowed if expression is changed",
7308 storedRule: "test() == false",
7309 updatedRule: "test() == true",
7310 errors: allValidationsErrors,
7311 },
7312 }
7313
7314
7315 base := environment.MustBaseEnvSet(version.MajorMinor(1, 998))
7316 envSet, err := base.Extend(environment.VersionedOptions{
7317 IntroducedVersion: version.MajorMinor(1, 999),
7318 EnvOptions: []cel.EnvOption{library.Test()},
7319 })
7320 if err != nil {
7321 t.Fatal(err)
7322 }
7323
7324 for _, tc := range tests {
7325 fn := func(rule string) *apiextensions.CustomResourceDefinition {
7326 validationRules := []apiextensions.ValidationRule{
7327 {
7328 Rule: rule,
7329 },
7330 }
7331 return &apiextensions.CustomResourceDefinition{
7332 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
7333 Spec: apiextensions.CustomResourceDefinitionSpec{
7334 Group: "group.com",
7335 Scope: apiextensions.ResourceScope("Cluster"),
7336 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
7337 Versions: []apiextensions.CustomResourceDefinitionVersion{{Name: "version", Served: true, Storage: true}},
7338 Validation: &apiextensions.CustomResourceValidation{
7339 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7340 Type: "object",
7341 Properties: map[string]apiextensions.JSONSchemaProps{
7342 "x": {
7343 Type: "string",
7344 XValidations: validationRules,
7345 },
7346 "obj": {
7347 Type: "object",
7348 Properties: map[string]apiextensions.JSONSchemaProps{
7349 "a": {
7350 Type: "string",
7351 XValidations: validationRules,
7352 },
7353 },
7354 XValidations: validationRules,
7355 },
7356 "array": {
7357 Type: "array",
7358 MaxItems: pointer.Int64(1),
7359 Items: &apiextensions.JSONSchemaPropsOrArray{
7360 Schema: &apiextensions.JSONSchemaProps{
7361 Type: "string",
7362 XValidations: validationRules,
7363 },
7364 },
7365 XValidations: validationRules,
7366 },
7367 "map": {
7368 Type: "object",
7369 MaxProperties: pointer.Int64(1),
7370 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
7371 Schema: &apiextensions.JSONSchemaProps{
7372 Type: "string",
7373 XValidations: validationRules,
7374 },
7375 },
7376 XValidations: validationRules,
7377 },
7378 },
7379 },
7380 },
7381 },
7382 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: []string{"version"}},
7383 }
7384 }
7385 old := fn(tc.storedRule)
7386 resource := fn(tc.updatedRule)
7387
7388 t.Run(tc.name, func(t *testing.T) {
7389 ctx := context.TODO()
7390 errs := validateCustomResourceDefinitionUpdate(ctx, resource, old, validationOptions{
7391 preexistingExpressions: findPreexistingExpressions(&old.Spec),
7392 celEnvironmentSet: envSet,
7393 })
7394 seenErrs := make([]bool, len(errs))
7395
7396 for _, expectedError := range tc.errors {
7397 found := false
7398 for i, err := range errs {
7399 if expectedError.matches(err) && !seenErrs[i] {
7400 found = true
7401 seenErrs[i] = true
7402 break
7403 }
7404 }
7405
7406 if !found {
7407 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
7408 }
7409 }
7410
7411 for i, seen := range seenErrs {
7412 if !seen {
7413 t.Errorf("unexpected error: %v", errs[i])
7414 }
7415 }
7416 })
7417 }
7418 }
7419
7420 func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
7421 tests := []struct {
7422 name string
7423 input apiextensions.CustomResourceValidation
7424 statusEnabled bool
7425 opts validationOptions
7426 expectedErrors []validationMatch
7427 }{
7428 {
7429 name: "empty",
7430 input: apiextensions.CustomResourceValidation{},
7431 },
7432 {
7433 name: "empty with status",
7434 input: apiextensions.CustomResourceValidation{},
7435 statusEnabled: true,
7436 },
7437 {
7438 name: "root type without status",
7439 input: apiextensions.CustomResourceValidation{
7440 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7441 Type: "string",
7442 },
7443 },
7444 statusEnabled: false,
7445 },
7446 {
7447 name: "root type having invalid value, with status",
7448 input: apiextensions.CustomResourceValidation{
7449 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7450 Type: "string",
7451 },
7452 },
7453 statusEnabled: true,
7454 expectedErrors: []validationMatch{
7455 invalid("spec.validation.openAPIV3Schema.type"),
7456 },
7457 },
7458 {
7459 name: "non-allowed root field with status",
7460 input: apiextensions.CustomResourceValidation{
7461 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7462 AnyOf: []apiextensions.JSONSchemaProps{
7463 {
7464 Description: "First schema",
7465 },
7466 {
7467 Description: "Second schema",
7468 },
7469 },
7470 },
7471 },
7472 statusEnabled: true,
7473 expectedErrors: []validationMatch{
7474 invalid("spec.validation.openAPIV3Schema"),
7475 },
7476 },
7477 {
7478 name: "all allowed fields at the root of the schema with status",
7479 input: apiextensions.CustomResourceValidation{
7480 OpenAPIV3Schema: validValidationSchema,
7481 },
7482 statusEnabled: true,
7483 },
7484 {
7485 name: "null type",
7486 input: apiextensions.CustomResourceValidation{
7487 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7488 Properties: map[string]apiextensions.JSONSchemaProps{
7489 "null": {
7490 Type: "null",
7491 },
7492 },
7493 },
7494 },
7495 expectedErrors: []validationMatch{
7496 forbidden("spec.validation.openAPIV3Schema.properties[null].type"),
7497 },
7498 },
7499 {
7500 name: "nullable at the root",
7501 input: apiextensions.CustomResourceValidation{
7502 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7503 Type: "object",
7504 Nullable: true,
7505 },
7506 },
7507 expectedErrors: []validationMatch{
7508 forbidden("spec.validation.openAPIV3Schema.nullable"),
7509 },
7510 },
7511 {
7512 name: "nullable without type",
7513 input: apiextensions.CustomResourceValidation{
7514 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7515 Properties: map[string]apiextensions.JSONSchemaProps{
7516 "nullable": {
7517 Nullable: true,
7518 },
7519 },
7520 },
7521 },
7522 },
7523 {
7524 name: "nullable with types",
7525 input: apiextensions.CustomResourceValidation{
7526 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7527 Properties: map[string]apiextensions.JSONSchemaProps{
7528 "object": {
7529 Type: "object",
7530 Nullable: true,
7531 },
7532 "array": {
7533 Type: "array",
7534 Nullable: true,
7535 },
7536 "number": {
7537 Type: "number",
7538 Nullable: true,
7539 },
7540 "string": {
7541 Type: "string",
7542 Nullable: true,
7543 },
7544 },
7545 },
7546 },
7547 },
7548 {
7549 name: "must be structural, but isn't",
7550 input: apiextensions.CustomResourceValidation{
7551 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
7552 },
7553 opts: validationOptions{requireStructuralSchema: true},
7554 expectedErrors: []validationMatch{
7555 required("spec.validation.openAPIV3Schema.type"),
7556 },
7557 },
7558 {
7559 name: "must be structural",
7560 input: apiextensions.CustomResourceValidation{
7561 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7562 Type: "object",
7563 },
7564 },
7565 opts: validationOptions{requireStructuralSchema: true},
7566 },
7567 {
7568 name: "require valid types, valid",
7569 input: apiextensions.CustomResourceValidation{
7570 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7571 Type: "object",
7572 },
7573 },
7574 opts: validationOptions{requireValidPropertyType: true, requireStructuralSchema: true},
7575 },
7576 {
7577 name: "require valid types, invalid",
7578 input: apiextensions.CustomResourceValidation{
7579 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7580 Type: "null",
7581 },
7582 },
7583 opts: validationOptions{requireValidPropertyType: true, requireStructuralSchema: true},
7584 expectedErrors: []validationMatch{
7585
7586 unsupported("spec.validation.openAPIV3Schema.type"),
7587
7588 forbidden("spec.validation.openAPIV3Schema.type"),
7589
7590 invalid("spec.validation.openAPIV3Schema.type"),
7591 },
7592 },
7593 {
7594 name: "require valid types, invalid",
7595 input: apiextensions.CustomResourceValidation{
7596 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7597 Type: "bogus",
7598 },
7599 },
7600 opts: validationOptions{requireValidPropertyType: true, requireStructuralSchema: true},
7601 expectedErrors: []validationMatch{
7602 unsupported("spec.validation.openAPIV3Schema.type"),
7603 invalid("spec.validation.openAPIV3Schema.type"),
7604 },
7605 },
7606 {
7607 name: "invalid type with list type extension set",
7608 input: apiextensions.CustomResourceValidation{
7609 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7610 Type: "object",
7611 XListType: strPtr("set"),
7612 },
7613 },
7614 expectedErrors: []validationMatch{
7615 invalid("spec.validation.openAPIV3Schema.type"),
7616 },
7617 },
7618 {
7619 name: "unset type with list type extension set",
7620 input: apiextensions.CustomResourceValidation{
7621 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7622 XListType: strPtr("set"),
7623 },
7624 },
7625 expectedErrors: []validationMatch{
7626 required("spec.validation.openAPIV3Schema.type"),
7627 },
7628 },
7629 {
7630 name: "invalid list type extension",
7631 input: apiextensions.CustomResourceValidation{
7632 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7633 Type: "array",
7634 XListType: strPtr("invalid"),
7635 },
7636 },
7637 expectedErrors: []validationMatch{
7638 unsupported("spec.validation.openAPIV3Schema.x-kubernetes-list-type"),
7639 },
7640 },
7641 {
7642 name: "invalid list type extension with list map keys extension non-empty",
7643 input: apiextensions.CustomResourceValidation{
7644 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7645 Type: "array",
7646 XListType: strPtr("set"),
7647 XListMapKeys: []string{"key"},
7648 },
7649 },
7650 expectedErrors: []validationMatch{
7651 invalid("spec.validation.openAPIV3Schema.x-kubernetes-list-type"),
7652 },
7653 },
7654 {
7655 name: "unset list type extension with list map keys extension non-empty",
7656 input: apiextensions.CustomResourceValidation{
7657 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7658 XListMapKeys: []string{"key"},
7659 },
7660 },
7661 expectedErrors: []validationMatch{
7662 required("spec.validation.openAPIV3Schema.x-kubernetes-list-type"),
7663 },
7664 },
7665 {
7666 name: "empty list map keys extension with list type extension map",
7667 input: apiextensions.CustomResourceValidation{
7668 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7669 Type: "array",
7670 XListType: strPtr("map"),
7671 },
7672 },
7673 expectedErrors: []validationMatch{
7674 required("spec.validation.openAPIV3Schema.x-kubernetes-list-map-keys"),
7675 required("spec.validation.openAPIV3Schema.items"),
7676 },
7677 },
7678 {
7679 name: "no items schema with list type extension map",
7680 input: apiextensions.CustomResourceValidation{
7681 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7682 Type: "array",
7683 XListType: strPtr("map"),
7684 XListMapKeys: []string{"key"},
7685 },
7686 },
7687 expectedErrors: []validationMatch{
7688 required("spec.validation.openAPIV3Schema.items"),
7689 },
7690 },
7691 {
7692 name: "multiple schema items with list type extension map",
7693 input: apiextensions.CustomResourceValidation{
7694 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7695 Type: "array",
7696 XListType: strPtr("map"),
7697 XListMapKeys: []string{"key"},
7698 Items: &apiextensions.JSONSchemaPropsOrArray{
7699 JSONSchemas: []apiextensions.JSONSchemaProps{
7700 {
7701 Type: "string",
7702 }, {
7703 Type: "integer",
7704 },
7705 },
7706 },
7707 },
7708 },
7709 expectedErrors: []validationMatch{
7710 forbidden("spec.validation.openAPIV3Schema.items"),
7711 invalid("spec.validation.openAPIV3Schema.items"),
7712 },
7713 },
7714 {
7715 name: "non object item with list type extension map",
7716 input: apiextensions.CustomResourceValidation{
7717 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7718 Type: "array",
7719 XListType: strPtr("map"),
7720 XListMapKeys: []string{"key"},
7721 Items: &apiextensions.JSONSchemaPropsOrArray{
7722 Schema: &apiextensions.JSONSchemaProps{
7723 Type: "string",
7724 },
7725 },
7726 },
7727 },
7728 expectedErrors: []validationMatch{
7729 invalid("spec.validation.openAPIV3Schema.items.type"),
7730 },
7731 },
7732 {
7733 name: "items with key missing from properties with list type extension map",
7734 input: apiextensions.CustomResourceValidation{
7735 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7736 Type: "array",
7737 XListType: strPtr("map"),
7738 XListMapKeys: []string{"key"},
7739 Items: &apiextensions.JSONSchemaPropsOrArray{
7740 Schema: &apiextensions.JSONSchemaProps{
7741 Type: "object",
7742 },
7743 },
7744 },
7745 },
7746 expectedErrors: []validationMatch{
7747 invalid("spec.validation.openAPIV3Schema.x-kubernetes-list-map-keys"),
7748 },
7749 },
7750 {
7751 name: "items with non scalar key property type with list type extension map",
7752 input: apiextensions.CustomResourceValidation{
7753 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7754 Type: "array",
7755 XListType: strPtr("map"),
7756 XListMapKeys: []string{"key"},
7757 Items: &apiextensions.JSONSchemaPropsOrArray{
7758 Schema: &apiextensions.JSONSchemaProps{
7759 Type: "object",
7760 Properties: map[string]apiextensions.JSONSchemaProps{
7761 "key": {
7762 Type: "object",
7763 },
7764 },
7765 },
7766 },
7767 },
7768 },
7769 expectedErrors: []validationMatch{
7770 invalid("spec.validation.openAPIV3Schema.items.properties[key].type"),
7771 },
7772 },
7773 {
7774 name: "duplicate map keys with list type extension map",
7775 input: apiextensions.CustomResourceValidation{
7776 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7777 Type: "array",
7778 XListType: strPtr("map"),
7779 XListMapKeys: []string{"key", "key"},
7780 Items: &apiextensions.JSONSchemaPropsOrArray{
7781 Schema: &apiextensions.JSONSchemaProps{
7782 Type: "object",
7783 Properties: map[string]apiextensions.JSONSchemaProps{
7784 "key": {
7785 Type: "string",
7786 },
7787 },
7788 },
7789 },
7790 },
7791 },
7792 expectedErrors: []validationMatch{
7793 invalid("spec.validation.openAPIV3Schema.x-kubernetes-list-map-keys"),
7794 },
7795 },
7796 {
7797 name: "allowed schema with list type extension map",
7798 input: apiextensions.CustomResourceValidation{
7799 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7800 Type: "array",
7801 XListType: strPtr("map"),
7802 XListMapKeys: []string{"keyA", "keyB"},
7803 Items: &apiextensions.JSONSchemaPropsOrArray{
7804 Schema: &apiextensions.JSONSchemaProps{
7805 Type: "object",
7806 Properties: map[string]apiextensions.JSONSchemaProps{
7807 "keyA": {
7808 Type: "string",
7809 },
7810 "keyB": {
7811 Type: "integer",
7812 },
7813 },
7814 },
7815 },
7816 },
7817 },
7818 },
7819 {
7820 name: "allowed list-type atomic",
7821 input: apiextensions.CustomResourceValidation{
7822 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7823 Type: "array",
7824 XListType: strPtr("atomic"),
7825 Items: &apiextensions.JSONSchemaPropsOrArray{
7826 Schema: &apiextensions.JSONSchemaProps{
7827 Type: "string",
7828 },
7829 },
7830 },
7831 },
7832 },
7833 {
7834 name: "allowed list-type atomic with non-atomic items",
7835 input: apiextensions.CustomResourceValidation{
7836 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7837 Type: "array",
7838 XListType: strPtr("atomic"),
7839 Items: &apiextensions.JSONSchemaPropsOrArray{
7840 Schema: &apiextensions.JSONSchemaProps{
7841 Type: "object",
7842 Properties: map[string]apiextensions.JSONSchemaProps{},
7843 },
7844 },
7845 },
7846 },
7847 },
7848 {
7849 name: "allowed list-type set with scalar items",
7850 input: apiextensions.CustomResourceValidation{
7851 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7852 Type: "array",
7853 XListType: strPtr("set"),
7854 Items: &apiextensions.JSONSchemaPropsOrArray{
7855 Schema: &apiextensions.JSONSchemaProps{
7856 Type: "string",
7857 },
7858 },
7859 },
7860 },
7861 },
7862 {
7863 name: "allowed list-type set with atomic map items",
7864 input: apiextensions.CustomResourceValidation{
7865 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7866 Type: "array",
7867 XListType: strPtr("set"),
7868 Items: &apiextensions.JSONSchemaPropsOrArray{
7869 Schema: &apiextensions.JSONSchemaProps{
7870 Type: "object",
7871 XMapType: strPtr("atomic"),
7872 Properties: map[string]apiextensions.JSONSchemaProps{
7873 "foo": {Type: "string"},
7874 },
7875 },
7876 },
7877 },
7878 },
7879 },
7880 {
7881 name: "invalid list-type set with non-atomic map items",
7882 input: apiextensions.CustomResourceValidation{
7883 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7884 Type: "array",
7885 XListType: strPtr("set"),
7886 Items: &apiextensions.JSONSchemaPropsOrArray{
7887 Schema: &apiextensions.JSONSchemaProps{
7888 Type: "object",
7889 XMapType: strPtr("granular"),
7890 Properties: map[string]apiextensions.JSONSchemaProps{
7891 "foo": {Type: "string"},
7892 },
7893 },
7894 },
7895 },
7896 },
7897 opts: validationOptions{requireAtomicSetType: true},
7898 expectedErrors: []validationMatch{
7899 invalid("spec.validation.openAPIV3Schema.items.x-kubernetes-map-type"),
7900 },
7901 },
7902 {
7903 name: "invalid list-type set with unspecified map-type for map items",
7904 input: apiextensions.CustomResourceValidation{
7905 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7906 Type: "array",
7907 XListType: strPtr("set"),
7908 Items: &apiextensions.JSONSchemaPropsOrArray{
7909 Schema: &apiextensions.JSONSchemaProps{
7910 Type: "object",
7911 Properties: map[string]apiextensions.JSONSchemaProps{
7912 "foo": {Type: "string"},
7913 },
7914 },
7915 },
7916 },
7917 },
7918 opts: validationOptions{requireAtomicSetType: true},
7919 expectedErrors: []validationMatch{
7920 invalid("spec.validation.openAPIV3Schema.items.x-kubernetes-map-type"),
7921 },
7922 },
7923 {
7924 name: "allowed list-type set with atomic list items",
7925 input: apiextensions.CustomResourceValidation{
7926 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7927 Type: "array",
7928 XListType: strPtr("set"),
7929 Items: &apiextensions.JSONSchemaPropsOrArray{
7930 Schema: &apiextensions.JSONSchemaProps{
7931 Type: "array",
7932 XListType: strPtr("atomic"),
7933 Items: &apiextensions.JSONSchemaPropsOrArray{
7934 Schema: &apiextensions.JSONSchemaProps{
7935 Type: "string",
7936 },
7937 },
7938 },
7939 },
7940 },
7941 },
7942 },
7943 {
7944 name: "allowed list-type set with unspecified list-type in list items",
7945 input: apiextensions.CustomResourceValidation{
7946 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7947 Type: "array",
7948 XListType: strPtr("set"),
7949 Items: &apiextensions.JSONSchemaPropsOrArray{
7950 Schema: &apiextensions.JSONSchemaProps{
7951 Type: "array",
7952 Items: &apiextensions.JSONSchemaPropsOrArray{
7953 Schema: &apiextensions.JSONSchemaProps{
7954 Type: "string",
7955 },
7956 },
7957 },
7958 },
7959 },
7960 },
7961 },
7962 {
7963 name: "invalid list-type set with with non-atomic list items",
7964 input: apiextensions.CustomResourceValidation{
7965 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7966 Type: "array",
7967 XListType: strPtr("set"),
7968 Items: &apiextensions.JSONSchemaPropsOrArray{
7969 Schema: &apiextensions.JSONSchemaProps{
7970 Type: "array",
7971 XListType: strPtr("set"),
7972 Items: &apiextensions.JSONSchemaPropsOrArray{
7973 Schema: &apiextensions.JSONSchemaProps{
7974 Type: "string",
7975 },
7976 },
7977 },
7978 },
7979 },
7980 },
7981 opts: validationOptions{requireAtomicSetType: true},
7982 expectedErrors: []validationMatch{
7983 invalid("spec.validation.openAPIV3Schema.items.x-kubernetes-list-type"),
7984 },
7985 },
7986 {
7987 name: "invalid type with map type extension (granular)",
7988 input: apiextensions.CustomResourceValidation{
7989 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
7990 Type: "array",
7991 XMapType: strPtr("granular"),
7992 },
7993 },
7994 expectedErrors: []validationMatch{
7995 invalid("spec.validation.openAPIV3Schema.type"),
7996 },
7997 },
7998 {
7999 name: "unset type with map type extension (granular)",
8000 input: apiextensions.CustomResourceValidation{
8001 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8002 XMapType: strPtr("granular"),
8003 },
8004 },
8005 expectedErrors: []validationMatch{
8006 required("spec.validation.openAPIV3Schema.type"),
8007 },
8008 },
8009 {
8010 name: "invalid type with map type extension (atomic)",
8011 input: apiextensions.CustomResourceValidation{
8012 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8013 Type: "array",
8014 XMapType: strPtr("atomic"),
8015 },
8016 },
8017 expectedErrors: []validationMatch{
8018 invalid("spec.validation.openAPIV3Schema.type"),
8019 },
8020 },
8021 {
8022 name: "unset type with map type extension (atomic)",
8023 input: apiextensions.CustomResourceValidation{
8024 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8025 XMapType: strPtr("atomic"),
8026 },
8027 },
8028 expectedErrors: []validationMatch{
8029 required("spec.validation.openAPIV3Schema.type"),
8030 },
8031 },
8032 {
8033 name: "invalid map type",
8034 input: apiextensions.CustomResourceValidation{
8035 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8036 Type: "object",
8037 XMapType: strPtr("badMapType"),
8038 },
8039 },
8040 expectedErrors: []validationMatch{
8041 unsupported("spec.validation.openAPIV3Schema.x-kubernetes-map-type"),
8042 },
8043 },
8044 {
8045 name: "allowed type with map type extension (granular)",
8046 input: apiextensions.CustomResourceValidation{
8047 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8048 Type: "object",
8049 XMapType: strPtr("granular"),
8050 },
8051 },
8052 },
8053 {
8054 name: "allowed type with map type extension (atomic)",
8055 input: apiextensions.CustomResourceValidation{
8056 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8057 Type: "object",
8058 XMapType: strPtr("atomic"),
8059 },
8060 },
8061 },
8062 {
8063 name: "invalid map with non-required key and no default",
8064 input: apiextensions.CustomResourceValidation{
8065 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8066 Type: "array",
8067 XListType: strPtr("map"),
8068 XListMapKeys: []string{"key"},
8069 Items: &apiextensions.JSONSchemaPropsOrArray{
8070 Schema: &apiextensions.JSONSchemaProps{
8071 Type: "object",
8072 Properties: map[string]apiextensions.JSONSchemaProps{
8073 "key": {
8074 Type: "string",
8075 },
8076 },
8077 },
8078 },
8079 },
8080 },
8081 opts: validationOptions{
8082 requireMapListKeysMapSetValidation: true,
8083 },
8084 expectedErrors: []validationMatch{
8085 required("spec.validation.openAPIV3Schema.items.properties[key].default"),
8086 },
8087 },
8088 {
8089 name: "allowed map with required key and no default",
8090 input: apiextensions.CustomResourceValidation{
8091 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8092 Type: "array",
8093 XListType: strPtr("map"),
8094 XListMapKeys: []string{"key"},
8095 Items: &apiextensions.JSONSchemaPropsOrArray{
8096 Schema: &apiextensions.JSONSchemaProps{
8097 Type: "object",
8098 Required: []string{"key"},
8099 Properties: map[string]apiextensions.JSONSchemaProps{
8100 "key": {
8101 Type: "string",
8102 },
8103 },
8104 },
8105 },
8106 },
8107 },
8108 opts: validationOptions{
8109 requireMapListKeysMapSetValidation: true,
8110 },
8111 },
8112 {
8113 name: "allowed map with non-required key and default",
8114 input: apiextensions.CustomResourceValidation{
8115 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8116 Type: "array",
8117 XListType: strPtr("map"),
8118 XListMapKeys: []string{"key"},
8119 Items: &apiextensions.JSONSchemaPropsOrArray{
8120 Schema: &apiextensions.JSONSchemaProps{
8121 Type: "object",
8122 Properties: map[string]apiextensions.JSONSchemaProps{
8123 "key": {
8124 Type: "string",
8125 Default: jsonPtr("stuff"),
8126 },
8127 },
8128 },
8129 },
8130 },
8131 },
8132 opts: validationOptions{
8133 allowDefaults: true,
8134 requireMapListKeysMapSetValidation: true,
8135 },
8136 },
8137 {
8138 name: "invalid map with nullable key",
8139 input: apiextensions.CustomResourceValidation{
8140 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8141 Type: "array",
8142 XListType: strPtr("map"),
8143 XListMapKeys: []string{"key"},
8144 Items: &apiextensions.JSONSchemaPropsOrArray{
8145 Schema: &apiextensions.JSONSchemaProps{
8146 Type: "object",
8147 Properties: map[string]apiextensions.JSONSchemaProps{
8148 "key": {
8149 Type: "string",
8150 Nullable: true,
8151 },
8152 },
8153 },
8154 },
8155 },
8156 },
8157 opts: validationOptions{
8158 requireMapListKeysMapSetValidation: true,
8159 },
8160 expectedErrors: []validationMatch{
8161 required("spec.validation.openAPIV3Schema.items.properties[key].default"),
8162 forbidden("spec.validation.openAPIV3Schema.items.properties[key].nullable"),
8163 },
8164 },
8165 {
8166 name: "invalid map with nullable items",
8167 input: apiextensions.CustomResourceValidation{
8168 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8169 Type: "array",
8170 XListType: strPtr("map"),
8171 XListMapKeys: []string{"key"},
8172 Items: &apiextensions.JSONSchemaPropsOrArray{
8173 Schema: &apiextensions.JSONSchemaProps{
8174 Type: "object",
8175 Nullable: true,
8176 Properties: map[string]apiextensions.JSONSchemaProps{
8177 "key": {
8178 Type: "string",
8179 },
8180 },
8181 },
8182 },
8183 },
8184 },
8185 opts: validationOptions{
8186 requireMapListKeysMapSetValidation: true,
8187 },
8188 expectedErrors: []validationMatch{
8189 forbidden("spec.validation.openAPIV3Schema.items.nullable"),
8190 required("spec.validation.openAPIV3Schema.items.properties[key].default"),
8191 },
8192 },
8193 {
8194 name: "valid map with some required, some defaulted, and non-key fields",
8195 input: apiextensions.CustomResourceValidation{
8196 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8197 Type: "array",
8198 XListType: strPtr("map"),
8199 XListMapKeys: []string{"a"},
8200 Items: &apiextensions.JSONSchemaPropsOrArray{
8201 Schema: &apiextensions.JSONSchemaProps{
8202 Type: "object",
8203 Required: []string{"a", "c"},
8204 Properties: map[string]apiextensions.JSONSchemaProps{
8205 "key": {
8206 Type: "string",
8207 },
8208 "a": {
8209 Type: "string",
8210 },
8211 "b": {
8212 Type: "string",
8213 Default: jsonPtr("stuff"),
8214 },
8215 "c": {
8216 Type: "int",
8217 },
8218 },
8219 },
8220 },
8221 },
8222 },
8223 opts: validationOptions{
8224 requireMapListKeysMapSetValidation: true,
8225 },
8226 expectedErrors: []validationMatch{
8227 forbidden("spec.validation.openAPIV3Schema.items.properties[b].default"),
8228 },
8229 },
8230 {
8231 name: "invalid set with nullable items",
8232 input: apiextensions.CustomResourceValidation{
8233 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8234 Type: "array",
8235 XListType: strPtr("set"),
8236 Items: &apiextensions.JSONSchemaPropsOrArray{
8237 Schema: &apiextensions.JSONSchemaProps{
8238 Nullable: true,
8239 },
8240 },
8241 },
8242 },
8243 opts: validationOptions{
8244 requireMapListKeysMapSetValidation: true,
8245 },
8246 expectedErrors: []validationMatch{
8247 forbidden("spec.validation.openAPIV3Schema.items.nullable"),
8248 },
8249 },
8250 {
8251 name: "allowed set with non-nullable items",
8252 input: apiextensions.CustomResourceValidation{
8253 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8254 Type: "array",
8255 XListType: strPtr("set"),
8256 Items: &apiextensions.JSONSchemaPropsOrArray{
8257 Schema: &apiextensions.JSONSchemaProps{
8258 Nullable: false,
8259 },
8260 },
8261 },
8262 },
8263 opts: validationOptions{
8264 requireMapListKeysMapSetValidation: true,
8265 },
8266 },
8267 {
8268 name: "valid x-kubernetes-validations for scalar element",
8269 input: apiextensions.CustomResourceValidation{
8270 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8271 Type: "object",
8272 Properties: map[string]apiextensions.JSONSchemaProps{
8273 "subRoot": {
8274 Type: "string",
8275 XValidations: apiextensions.ValidationRules{
8276 {
8277 Rule: "self.startsWith('s')",
8278 Message: "subRoot should start with 's'.",
8279 },
8280 {
8281 Rule: "self.endsWith('s')",
8282 Message: "subRoot should end with 's'.",
8283 },
8284 },
8285 },
8286 },
8287 },
8288 },
8289 opts: validationOptions{
8290 requireStructuralSchema: true,
8291 },
8292 },
8293 {
8294 name: "valid x-kubernetes-validations for object",
8295 input: apiextensions.CustomResourceValidation{
8296 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8297 Type: "object",
8298 XValidations: apiextensions.ValidationRules{
8299 {
8300 Rule: "self.minReplicas <= self.maxReplicas",
8301 Message: "minReplicas should be no greater than maxReplicas",
8302 },
8303 },
8304 Properties: map[string]apiextensions.JSONSchemaProps{
8305 "minReplicas": {
8306 Type: "integer",
8307 },
8308 "maxReplicas": {
8309 Type: "integer",
8310 },
8311 },
8312 },
8313 },
8314 opts: validationOptions{
8315 requireStructuralSchema: true,
8316 },
8317 },
8318 {
8319 name: "invalid x-kubernetes-validations with empty rule",
8320 input: apiextensions.CustomResourceValidation{
8321 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8322 Type: "object",
8323 XValidations: apiextensions.ValidationRules{
8324 {Message: "empty rule"},
8325 },
8326 },
8327 },
8328 expectedErrors: []validationMatch{
8329 required("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
8330 },
8331 opts: validationOptions{
8332 requireStructuralSchema: true,
8333 },
8334 },
8335 {
8336 name: "valid x-kubernetes-validations with empty validators",
8337 input: apiextensions.CustomResourceValidation{
8338 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8339 Type: "object",
8340 XValidations: apiextensions.ValidationRules{},
8341 },
8342 },
8343 opts: validationOptions{
8344 requireStructuralSchema: true,
8345 },
8346 },
8347 {
8348 name: "invalid rule in x-kubernetes-validations",
8349 input: apiextensions.CustomResourceValidation{
8350 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8351 Type: "object",
8352 Properties: map[string]apiextensions.JSONSchemaProps{
8353 "subRoot": {
8354 Type: "string",
8355 XValidations: apiextensions.ValidationRules{
8356 {
8357 Rule: "self == true",
8358 Message: "subRoot should be true.",
8359 },
8360 {
8361 Rule: "self.endsWith('s')",
8362 Message: "subRoot should end with 's'.",
8363 },
8364 },
8365 },
8366 },
8367 },
8368 },
8369 expectedErrors: []validationMatch{
8370 invalid("spec.validation.openAPIV3Schema.properties[subRoot].x-kubernetes-validations[0].rule"),
8371 },
8372 opts: validationOptions{
8373 requireStructuralSchema: true,
8374 },
8375 },
8376 {
8377 name: "valid x-kubernetes-validations for nested object under multiple fields",
8378 input: apiextensions.CustomResourceValidation{
8379 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8380 Type: "object",
8381 XValidations: apiextensions.ValidationRules{
8382 {
8383 Rule: "self.minReplicas <= self.maxReplicas",
8384 Message: "minReplicas should be no greater than maxReplicas.",
8385 },
8386 },
8387 Properties: map[string]apiextensions.JSONSchemaProps{
8388 "minReplicas": {
8389 Type: "integer",
8390 },
8391 "maxReplicas": {
8392 Type: "integer",
8393 },
8394 "subRule": {
8395 Type: "object",
8396 XValidations: apiextensions.ValidationRules{
8397 {
8398 Rule: "self.isTest == true",
8399 Message: "isTest should be true.",
8400 },
8401 },
8402 Properties: map[string]apiextensions.JSONSchemaProps{
8403 "isTest": {
8404 Type: "boolean",
8405 },
8406 },
8407 },
8408 },
8409 },
8410 },
8411 opts: validationOptions{
8412 requireStructuralSchema: true,
8413 },
8414 },
8415 {
8416 name: "valid x-kubernetes-validations for object of array",
8417 input: apiextensions.CustomResourceValidation{
8418 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8419 Type: "object",
8420 XValidations: apiextensions.ValidationRules{
8421 {
8422 Rule: "size(self.nestedObj[0]) == 10",
8423 Message: "size of first element in nestedObj should be equal to 10",
8424 },
8425 },
8426 Properties: map[string]apiextensions.JSONSchemaProps{
8427 "nestedObj": {
8428 Type: "array",
8429 Items: &apiextensions.JSONSchemaPropsOrArray{
8430 Schema: &apiextensions.JSONSchemaProps{
8431 Type: "array",
8432 Items: &apiextensions.JSONSchemaPropsOrArray{
8433 Schema: &apiextensions.JSONSchemaProps{
8434 Type: "string",
8435 },
8436 },
8437 },
8438 },
8439 },
8440 },
8441 },
8442 },
8443 opts: validationOptions{
8444 requireStructuralSchema: true,
8445 },
8446 },
8447 {
8448 name: "valid x-kubernetes-validations for array",
8449 input: apiextensions.CustomResourceValidation{
8450 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8451 Type: "object",
8452 Items: &apiextensions.JSONSchemaPropsOrArray{
8453 Schema: &apiextensions.JSONSchemaProps{
8454 Type: "array",
8455 XValidations: apiextensions.ValidationRules{
8456 {
8457 Rule: "size(self) > 0",
8458 Message: "scoped field should contain more than 0 element.",
8459 },
8460 },
8461 Items: &apiextensions.JSONSchemaPropsOrArray{
8462 Schema: &apiextensions.JSONSchemaProps{
8463 Type: "string",
8464 },
8465 },
8466 },
8467 },
8468 },
8469 },
8470 opts: validationOptions{
8471 requireStructuralSchema: true,
8472 },
8473 },
8474 {
8475 name: "valid x-kubernetes-validations for array of object",
8476 input: apiextensions.CustomResourceValidation{
8477 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8478 Type: "object",
8479 Items: &apiextensions.JSONSchemaPropsOrArray{
8480 Schema: &apiextensions.JSONSchemaProps{
8481 Type: "array",
8482 XValidations: apiextensions.ValidationRules{
8483 {
8484 Rule: "self[0].nestedObj.val > 0",
8485 Message: "val should be greater than 0.",
8486 },
8487 },
8488 Items: &apiextensions.JSONSchemaPropsOrArray{
8489 Schema: &apiextensions.JSONSchemaProps{
8490 Type: "object",
8491 Properties: map[string]apiextensions.JSONSchemaProps{
8492 "nestedObj": {
8493 Type: "object",
8494 Properties: map[string]apiextensions.JSONSchemaProps{
8495 "val": {
8496 Type: "integer",
8497 },
8498 },
8499 },
8500 },
8501 },
8502 },
8503 },
8504 },
8505 },
8506 },
8507 opts: validationOptions{
8508 requireStructuralSchema: true,
8509 },
8510 },
8511 {
8512 name: "valid x-kubernetes-validations for escaping",
8513 input: apiextensions.CustomResourceValidation{
8514 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8515 Type: "object",
8516 XValidations: apiextensions.ValidationRules{
8517 {
8518 Rule: "self.__if__ > 0",
8519 },
8520 {
8521 Rule: "self.__namespace__ > 0",
8522 },
8523 {
8524 Rule: "self.self > 0",
8525 },
8526 {
8527 Rule: "self.int > 0",
8528 },
8529 },
8530 Properties: map[string]apiextensions.JSONSchemaProps{
8531 "if": {
8532 Type: "integer",
8533 },
8534 "namespace": {
8535 Type: "integer",
8536 },
8537 "self": {
8538 Type: "integer",
8539 },
8540 "int": {
8541 Type: "integer",
8542 },
8543 },
8544 },
8545 },
8546 opts: validationOptions{
8547 requireStructuralSchema: true,
8548 },
8549 },
8550 {
8551 name: "invalid x-kubernetes-validations for escaping",
8552 input: apiextensions.CustomResourceValidation{
8553 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8554 Type: "object",
8555 XValidations: apiextensions.ValidationRules{
8556 {
8557 Rule: "self.if > 0",
8558 },
8559 {
8560 Rule: "self.namespace > 0",
8561 },
8562 {
8563 Rule: "self.unknownProp > 0",
8564 },
8565 },
8566 Properties: map[string]apiextensions.JSONSchemaProps{
8567 "if": {
8568 Type: "integer",
8569 },
8570 "namespace": {
8571 Type: "integer",
8572 },
8573 },
8574 },
8575 },
8576 expectedErrors: []validationMatch{
8577 invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
8578 invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[1].rule"),
8579 invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[2].rule"),
8580 },
8581 opts: validationOptions{
8582 requireStructuralSchema: true,
8583 },
8584 },
8585 {
8586 name: "valid default with x-kubernetes-validations",
8587 input: apiextensions.CustomResourceValidation{
8588 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8589 Type: "object",
8590 Properties: map[string]apiextensions.JSONSchemaProps{
8591 "embedded": {
8592 Type: "object",
8593 Properties: map[string]apiextensions.JSONSchemaProps{
8594 "metadata": {
8595 Type: "object",
8596 XEmbeddedResource: true,
8597 Properties: map[string]apiextensions.JSONSchemaProps{
8598 "name": {
8599 Type: "string",
8600 XValidations: apiextensions.ValidationRules{
8601 {
8602 Rule: "self == 'singleton'",
8603 },
8604 },
8605 Default: jsonPtr("singleton"),
8606 },
8607 },
8608 },
8609 },
8610 },
8611 "value": {
8612 Type: "string",
8613 XValidations: apiextensions.ValidationRules{
8614 {
8615 Rule: "self.startsWith('kube')",
8616 },
8617 },
8618 Default: jsonPtr("kube-everything"),
8619 },
8620 "object": {
8621 Type: "object",
8622 Properties: map[string]apiextensions.JSONSchemaProps{
8623 "field1": {
8624 Type: "integer",
8625 },
8626 "field2": {
8627 Type: "integer",
8628 },
8629 },
8630 XValidations: apiextensions.ValidationRules{
8631 {
8632 Rule: "self.field1 < self.field2",
8633 },
8634 },
8635 Default: jsonPtr(map[string]interface{}{"field1": 1, "field2": 2}),
8636 },
8637 },
8638 },
8639 },
8640 opts: validationOptions{
8641 requireStructuralSchema: true,
8642 allowDefaults: true,
8643 },
8644 },
8645 {
8646 name: "invalid default with x-kubernetes-validations",
8647 input: apiextensions.CustomResourceValidation{
8648 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8649 Type: "object",
8650 Properties: map[string]apiextensions.JSONSchemaProps{
8651 "embedded": {
8652 Type: "object",
8653 Properties: map[string]apiextensions.JSONSchemaProps{
8654 "metadata": {
8655 Type: "object",
8656 XEmbeddedResource: true,
8657 Properties: map[string]apiextensions.JSONSchemaProps{
8658 "name": {
8659 Type: "string",
8660 XValidations: apiextensions.ValidationRules{
8661 {
8662 Rule: "self == 'singleton'",
8663 },
8664 },
8665 Default: jsonPtr("nope"),
8666 },
8667 },
8668 },
8669 },
8670 },
8671 "value": {
8672 Type: "string",
8673 XValidations: apiextensions.ValidationRules{
8674 {
8675 Rule: "self.startsWith('kube')",
8676 },
8677 },
8678 Default: jsonPtr("nope"),
8679 },
8680 "object": {
8681 Type: "object",
8682 Properties: map[string]apiextensions.JSONSchemaProps{
8683 "field1": {
8684 Type: "integer",
8685 },
8686 "field2": {
8687 Type: "integer",
8688 },
8689 },
8690 XValidations: apiextensions.ValidationRules{
8691 {
8692 Rule: "self.field1 < self.field2",
8693 },
8694 },
8695 Default: jsonPtr(map[string]interface{}{"field1": 2, "field2": 1}),
8696 },
8697 },
8698 },
8699 },
8700 expectedErrors: []validationMatch{
8701 invalid("spec.validation.openAPIV3Schema.properties[embedded].properties[metadata].properties[name].default"),
8702 invalid("spec.validation.openAPIV3Schema.properties[value].default"),
8703 invalid("spec.validation.openAPIV3Schema.properties[object].default"),
8704 },
8705 opts: validationOptions{
8706 requireStructuralSchema: true,
8707 allowDefaults: true,
8708 },
8709 },
8710 {
8711 name: "rule is empty or not specified",
8712 input: apiextensions.CustomResourceValidation{
8713 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8714 Type: "object",
8715 Properties: map[string]apiextensions.JSONSchemaProps{
8716 "value": {
8717 Type: "integer",
8718 XValidations: apiextensions.ValidationRules{
8719 {
8720 Message: "something",
8721 },
8722 {
8723 Rule: " ",
8724 Message: "something",
8725 },
8726 },
8727 },
8728 },
8729 },
8730 },
8731 expectedErrors: []validationMatch{
8732 required("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
8733 required("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[1].rule"),
8734 },
8735 },
8736 {
8737 name: "multiline rule with message",
8738 input: apiextensions.CustomResourceValidation{
8739 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8740 Type: "object",
8741 Properties: map[string]apiextensions.JSONSchemaProps{
8742 "value": {
8743 Type: "integer",
8744 XValidations: apiextensions.ValidationRules{
8745 {
8746 Rule: "self >= 0 &&\nself <= 100",
8747 Message: "value must be between 0 and 100 (inclusive)",
8748 },
8749 },
8750 },
8751 },
8752 },
8753 },
8754 },
8755 {
8756 name: "invalid and required messages",
8757 input: apiextensions.CustomResourceValidation{
8758 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8759 Type: "object",
8760 Properties: map[string]apiextensions.JSONSchemaProps{
8761 "value": {
8762 Type: "integer",
8763 XValidations: apiextensions.ValidationRules{
8764 {
8765 Rule: "self >= 0 &&\nself <= 100",
8766 },
8767 {
8768 Rule: "self == 50",
8769 Message: "value requirements:\nmust be >= 0\nmust be <= 100 ",
8770 },
8771 {
8772 Rule: "self == 50",
8773 Message: " ",
8774 },
8775 },
8776 },
8777 },
8778 },
8779 },
8780 expectedErrors: []validationMatch{
8781
8782 required("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].message"),
8783
8784 invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[1].message"),
8785
8786 invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[2].message"),
8787 },
8788 },
8789 {
8790 name: "forbid transition rule on element of list of type atomic",
8791 opts: validationOptions{requireStructuralSchema: true},
8792 input: apiextensions.CustomResourceValidation{
8793 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8794 Type: "object",
8795 Properties: map[string]apiextensions.JSONSchemaProps{
8796 "value": {
8797 Type: "array",
8798 XListType: strPtr("atomic"),
8799 Items: &apiextensions.JSONSchemaPropsOrArray{
8800 Schema: &apiextensions.JSONSchemaProps{
8801 Type: "string",
8802 MaxLength: int64ptr(10),
8803 XValidations: apiextensions.ValidationRules{
8804 {Rule: "self == oldSelf"},
8805 },
8806 },
8807 },
8808 },
8809 },
8810 },
8811 },
8812 expectedErrors: []validationMatch{
8813 invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
8814 },
8815 },
8816 {
8817 name: "forbid transition rule on element of list defaulting to type atomic",
8818 opts: validationOptions{requireStructuralSchema: true},
8819 input: apiextensions.CustomResourceValidation{
8820 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8821 Type: "object",
8822 Properties: map[string]apiextensions.JSONSchemaProps{
8823 "value": {
8824 Type: "array",
8825 Items: &apiextensions.JSONSchemaPropsOrArray{
8826 Schema: &apiextensions.JSONSchemaProps{
8827 Type: "string",
8828 MaxLength: int64ptr(10),
8829 XValidations: apiextensions.ValidationRules{
8830 {Rule: "self == oldSelf"},
8831 },
8832 },
8833 },
8834 },
8835 },
8836 },
8837 },
8838 expectedErrors: []validationMatch{
8839 invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
8840 },
8841 },
8842 {
8843 name: "allow transition rule on list of type atomic",
8844 opts: validationOptions{requireStructuralSchema: true},
8845 input: apiextensions.CustomResourceValidation{
8846 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8847 Type: "object",
8848 Properties: map[string]apiextensions.JSONSchemaProps{
8849 "value": {
8850 Type: "array",
8851 MaxItems: int64ptr(10),
8852 XListType: strPtr("atomic"),
8853 Items: &apiextensions.JSONSchemaPropsOrArray{
8854 Schema: &apiextensions.JSONSchemaProps{
8855 Type: "string",
8856 },
8857 },
8858 XValidations: apiextensions.ValidationRules{
8859 {Rule: "self == oldSelf"},
8860 },
8861 },
8862 },
8863 },
8864 },
8865 },
8866 {
8867 name: "allow transition rule on list defaulting to type atomic",
8868 input: apiextensions.CustomResourceValidation{
8869 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8870 Type: "object",
8871 Properties: map[string]apiextensions.JSONSchemaProps{
8872 "value": {
8873 Type: "array",
8874 MaxItems: int64ptr(10),
8875 Items: &apiextensions.JSONSchemaPropsOrArray{
8876 Schema: &apiextensions.JSONSchemaProps{
8877 Type: "string",
8878 MaxLength: int64ptr(10),
8879 },
8880 },
8881 XValidations: apiextensions.ValidationRules{
8882 {Rule: "self == oldSelf"},
8883 },
8884 },
8885 },
8886 },
8887 },
8888 },
8889 {
8890 name: "forbid transition rule on element of list of type set",
8891 opts: validationOptions{requireStructuralSchema: true},
8892 input: apiextensions.CustomResourceValidation{
8893 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8894 Type: "object",
8895 Properties: map[string]apiextensions.JSONSchemaProps{
8896 "value": {
8897 Type: "array",
8898 MaxItems: int64ptr(10),
8899 XListType: strPtr("set"),
8900 Items: &apiextensions.JSONSchemaPropsOrArray{
8901 Schema: &apiextensions.JSONSchemaProps{
8902 Type: "string",
8903 MaxLength: int64ptr(10),
8904 XValidations: apiextensions.ValidationRules{
8905 {Rule: "self == oldSelf"},
8906 },
8907 },
8908 },
8909 },
8910 },
8911 },
8912 },
8913 expectedErrors: []validationMatch{
8914 invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
8915 },
8916 },
8917 {
8918 name: "allow transition rule on list of type set",
8919 opts: validationOptions{requireStructuralSchema: true},
8920 input: apiextensions.CustomResourceValidation{
8921 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8922 Type: "object",
8923 Properties: map[string]apiextensions.JSONSchemaProps{
8924 "value": {
8925 Type: "array",
8926 MaxItems: int64ptr(10),
8927 XListType: strPtr("set"),
8928 Items: &apiextensions.JSONSchemaPropsOrArray{
8929 Schema: &apiextensions.JSONSchemaProps{
8930 Type: "string",
8931 MaxLength: int64ptr(10),
8932 },
8933 },
8934 XValidations: apiextensions.ValidationRules{
8935 {Rule: "self == oldSelf"},
8936 },
8937 },
8938 },
8939 },
8940 },
8941 },
8942 {
8943 name: "allow transition rule on element of list of type map",
8944 opts: validationOptions{requireStructuralSchema: true},
8945 input: apiextensions.CustomResourceValidation{
8946 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8947 Type: "object",
8948 Properties: map[string]apiextensions.JSONSchemaProps{
8949 "value": {
8950 Type: "array",
8951 XListType: strPtr("map"),
8952 XListMapKeys: []string{"name"},
8953 Items: &apiextensions.JSONSchemaPropsOrArray{
8954 Schema: &apiextensions.JSONSchemaProps{
8955 Type: "object",
8956 XValidations: apiextensions.ValidationRules{
8957 {Rule: "self == oldSelf"},
8958 },
8959 Required: []string{"name"},
8960 Properties: map[string]apiextensions.JSONSchemaProps{
8961 "name": {Type: "string", MaxLength: int64ptr(5)},
8962 },
8963 },
8964 },
8965 },
8966 },
8967 },
8968 },
8969 },
8970 {
8971 name: "allow transition rule on list of type map",
8972 opts: validationOptions{requireStructuralSchema: true},
8973 input: apiextensions.CustomResourceValidation{
8974 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
8975 Type: "object",
8976 Properties: map[string]apiextensions.JSONSchemaProps{
8977 "value": {
8978 Type: "array",
8979 MaxItems: int64ptr(10),
8980 XListType: strPtr("map"),
8981 XListMapKeys: []string{"name"},
8982 Items: &apiextensions.JSONSchemaPropsOrArray{
8983 Schema: &apiextensions.JSONSchemaProps{
8984 Type: "object",
8985 Required: []string{"name"},
8986 Properties: map[string]apiextensions.JSONSchemaProps{
8987 "name": {Type: "string", MaxLength: int64ptr(5)},
8988 },
8989 },
8990 },
8991 XValidations: apiextensions.ValidationRules{
8992 {Rule: "self == oldSelf"},
8993 },
8994 },
8995 },
8996 },
8997 },
8998 },
8999 {
9000 name: "allow transition rule on element of map of type granular",
9001 opts: validationOptions{requireStructuralSchema: true},
9002 input: apiextensions.CustomResourceValidation{
9003 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9004 Type: "object",
9005 Properties: map[string]apiextensions.JSONSchemaProps{
9006 "value": {
9007 Type: "object",
9008 XMapType: strPtr("granular"),
9009 Properties: map[string]apiextensions.JSONSchemaProps{
9010 "subfield": {
9011 Type: "string",
9012 MaxLength: int64ptr(10),
9013 XValidations: apiextensions.ValidationRules{
9014 {Rule: "self == oldSelf"},
9015 },
9016 },
9017 },
9018 },
9019 },
9020 },
9021 },
9022 },
9023 {
9024 name: "forbid transition rule on element of map of unrecognized type",
9025 opts: validationOptions{requireStructuralSchema: true},
9026 input: apiextensions.CustomResourceValidation{
9027 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9028 Type: "object",
9029 Properties: map[string]apiextensions.JSONSchemaProps{
9030 "value": {
9031 Type: "object",
9032 XMapType: strPtr("future"),
9033 Properties: map[string]apiextensions.JSONSchemaProps{
9034 "subfield": {
9035 Type: "string",
9036 MaxLength: int64ptr(10),
9037 XValidations: apiextensions.ValidationRules{
9038 {Rule: "self == oldSelf"},
9039 },
9040 },
9041 },
9042 },
9043 },
9044 },
9045 },
9046 expectedErrors: []validationMatch{
9047 invalid("spec.validation.openAPIV3Schema.properties[value].properties[subfield].x-kubernetes-validations[0].rule"),
9048 unsupported("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-map-type"),
9049 },
9050 },
9051 {
9052 name: "allow transition rule on element of map defaulting to type granular",
9053 opts: validationOptions{requireStructuralSchema: true},
9054 input: apiextensions.CustomResourceValidation{
9055 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9056 Type: "object",
9057 Properties: map[string]apiextensions.JSONSchemaProps{
9058 "value": {
9059 Type: "object",
9060 Properties: map[string]apiextensions.JSONSchemaProps{
9061 "subfield": {
9062 Type: "string",
9063 MaxLength: int64ptr(10),
9064 XValidations: apiextensions.ValidationRules{
9065 {Rule: "self == oldSelf"},
9066 },
9067 },
9068 },
9069 },
9070 },
9071 },
9072 },
9073 },
9074 {
9075 name: "allow transition rule on map of type granular",
9076 opts: validationOptions{requireStructuralSchema: true},
9077 input: apiextensions.CustomResourceValidation{
9078 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9079 Type: "object",
9080 Properties: map[string]apiextensions.JSONSchemaProps{
9081 "value": {
9082 Type: "object",
9083 XMapType: strPtr("granular"),
9084 XValidations: apiextensions.ValidationRules{
9085 {Rule: "self == oldSelf"},
9086 },
9087 },
9088 },
9089 },
9090 },
9091 },
9092 {
9093 name: "allow transition rule on map defaulting to type granular",
9094 opts: validationOptions{requireStructuralSchema: true},
9095 input: apiextensions.CustomResourceValidation{
9096 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9097 Type: "object",
9098 Properties: map[string]apiextensions.JSONSchemaProps{
9099 "value": {
9100 Type: "object",
9101 XValidations: apiextensions.ValidationRules{
9102 {Rule: "self == oldSelf"},
9103 },
9104 },
9105 },
9106 },
9107 },
9108 },
9109 {
9110 name: "allow transition rule on element of map of type atomic",
9111 opts: validationOptions{requireStructuralSchema: true},
9112 input: apiextensions.CustomResourceValidation{
9113 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9114 Type: "object",
9115 Properties: map[string]apiextensions.JSONSchemaProps{
9116 "value": {
9117 Type: "object",
9118 XMapType: strPtr("atomic"),
9119 Properties: map[string]apiextensions.JSONSchemaProps{
9120 "subfield": {
9121 Type: "object",
9122 XValidations: apiextensions.ValidationRules{
9123 {Rule: "self == oldSelf"},
9124 },
9125 },
9126 },
9127 },
9128 },
9129 },
9130 },
9131 },
9132 {
9133 name: "allow transition rule on map of type atomic",
9134 opts: validationOptions{requireStructuralSchema: true},
9135 input: apiextensions.CustomResourceValidation{
9136 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9137 Type: "object",
9138 Properties: map[string]apiextensions.JSONSchemaProps{
9139 "value": {
9140 Type: "object",
9141 XMapType: strPtr("atomic"),
9142 XValidations: apiextensions.ValidationRules{
9143 {Rule: "self == oldSelf"},
9144 },
9145 },
9146 },
9147 },
9148 },
9149 },
9150 {
9151 name: "forbid double-nested rule with no limit set",
9152 opts: validationOptions{requireStructuralSchema: true},
9153 input: apiextensions.CustomResourceValidation{
9154 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9155 Type: "object",
9156 Properties: map[string]apiextensions.JSONSchemaProps{
9157 "value": {
9158 Type: "array",
9159 Items: &apiextensions.JSONSchemaPropsOrArray{
9160 Schema: &apiextensions.JSONSchemaProps{
9161 Type: "object",
9162 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9163 Schema: &apiextensions.JSONSchemaProps{
9164 Type: "object",
9165 Required: []string{"key"},
9166 Properties: map[string]apiextensions.JSONSchemaProps{
9167 "key": {Type: "string"},
9168 },
9169 },
9170 },
9171 },
9172 },
9173 XValidations: apiextensions.ValidationRules{
9174 {Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
9175 },
9176 },
9177 },
9178 },
9179 },
9180 expectedErrors: []validationMatch{
9181
9182 forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
9183 forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
9184
9185 forbidden("spec.validation.openAPIV3Schema"),
9186 },
9187 },
9188 {
9189 name: "forbid double-nested rule with one limit set",
9190 opts: validationOptions{requireStructuralSchema: true},
9191 input: apiextensions.CustomResourceValidation{
9192 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9193 Type: "object",
9194 Properties: map[string]apiextensions.JSONSchemaProps{
9195 "value": {
9196 Type: "array",
9197 Items: &apiextensions.JSONSchemaPropsOrArray{
9198 Schema: &apiextensions.JSONSchemaProps{
9199 Type: "object",
9200 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9201 Schema: &apiextensions.JSONSchemaProps{
9202 Type: "object",
9203 Required: []string{"key"},
9204 Properties: map[string]apiextensions.JSONSchemaProps{
9205 "key": {Type: "string", MaxLength: int64ptr(10)},
9206 },
9207 },
9208 },
9209 },
9210 },
9211 XValidations: apiextensions.ValidationRules{
9212 {Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
9213 },
9214 },
9215 },
9216 },
9217 },
9218 expectedErrors: []validationMatch{
9219
9220 forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
9221 forbidden("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].rule"),
9222
9223 forbidden("spec.validation.openAPIV3Schema"),
9224 },
9225 },
9226 {
9227 name: "allow double-nested rule with three limits set",
9228 opts: validationOptions{requireStructuralSchema: true},
9229 input: apiextensions.CustomResourceValidation{
9230 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9231 Type: "object",
9232 Properties: map[string]apiextensions.JSONSchemaProps{
9233 "value": {
9234 Type: "array",
9235 MaxItems: int64ptr(10),
9236 Items: &apiextensions.JSONSchemaPropsOrArray{
9237 Schema: &apiextensions.JSONSchemaProps{
9238 Type: "object",
9239 MaxProperties: int64ptr(10),
9240 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9241 Schema: &apiextensions.JSONSchemaProps{
9242 Type: "object",
9243 Required: []string{"key"},
9244 Properties: map[string]apiextensions.JSONSchemaProps{
9245 "key": {Type: "string", MaxLength: int64ptr(10)},
9246 },
9247 },
9248 },
9249 },
9250 },
9251 XValidations: apiextensions.ValidationRules{
9252 {Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
9253 },
9254 },
9255 },
9256 },
9257 },
9258 expectedErrors: []validationMatch{},
9259 },
9260 {
9261 name: "allow double-nested rule with one limit set on outermost array",
9262 opts: validationOptions{requireStructuralSchema: true},
9263 input: apiextensions.CustomResourceValidation{
9264 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9265 Type: "object",
9266 Properties: map[string]apiextensions.JSONSchemaProps{
9267 "value": {
9268 Type: "array",
9269 MaxItems: int64ptr(4),
9270 Items: &apiextensions.JSONSchemaPropsOrArray{
9271 Schema: &apiextensions.JSONSchemaProps{
9272 Type: "object",
9273 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9274 Schema: &apiextensions.JSONSchemaProps{
9275 Type: "object",
9276 Required: []string{"key"},
9277 Properties: map[string]apiextensions.JSONSchemaProps{
9278 "key": {Type: "number"},
9279 },
9280 },
9281 },
9282 },
9283 },
9284 XValidations: apiextensions.ValidationRules{
9285 {Rule: "self.all(x, x.all(y, x[y].key == x[y].key))"},
9286 },
9287 },
9288 },
9289 },
9290 },
9291 expectedErrors: []validationMatch{},
9292 },
9293 {
9294 name: "check for cardinality of 1 under root object",
9295 opts: validationOptions{requireStructuralSchema: true},
9296 input: apiextensions.CustomResourceValidation{
9297 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9298 Type: "object",
9299 Properties: map[string]apiextensions.JSONSchemaProps{
9300 "value": {
9301 Type: "integer",
9302 XValidations: apiextensions.ValidationRules{
9303 {Rule: "self < 1024"},
9304 },
9305 },
9306 },
9307 },
9308 },
9309 expectedErrors: []validationMatch{},
9310 },
9311 {
9312 name: "forbid validation rules where cost total exceeds total limit",
9313 opts: validationOptions{requireStructuralSchema: true},
9314 input: apiextensions.CustomResourceValidation{
9315 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9316 Type: "object",
9317 Properties: map[string]apiextensions.JSONSchemaProps{
9318 "list": {
9319 Type: "array",
9320 MaxItems: int64Ptr(100000),
9321 Items: &apiextensions.JSONSchemaPropsOrArray{
9322 Schema: &apiextensions.JSONSchemaProps{
9323 Type: "string",
9324 MaxLength: int64Ptr(5000),
9325 XValidations: apiextensions.ValidationRules{
9326 {Rule: "self.contains('keyword')"},
9327 },
9328 },
9329 },
9330 },
9331 "map": {
9332 Type: "object",
9333 MaxProperties: int64Ptr(1000),
9334 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9335 Allows: true,
9336 Schema: &apiextensions.JSONSchemaProps{
9337 Type: "string",
9338 MaxLength: int64Ptr(5000),
9339 XValidations: apiextensions.ValidationRules{
9340 {Rule: "self.contains('keyword')"},
9341 },
9342 },
9343 },
9344 },
9345 "field": {
9346 Type: "integer",
9347 XValidations: apiextensions.ValidationRules{
9348 {Rule: "self > 50 && self < 100"},
9349 },
9350 },
9351 },
9352 },
9353 },
9354 expectedErrors: []validationMatch{
9355
9356 forbidden("spec.validation.openAPIV3Schema.properties[list].items.x-kubernetes-validations[0].rule"),
9357 forbidden("spec.validation.openAPIV3Schema.properties[list].items.x-kubernetes-validations[0].rule"),
9358
9359 forbidden("spec.validation.openAPIV3Schema.properties[map].additionalProperties.x-kubernetes-validations[0].rule"),
9360
9361 forbidden("spec.validation.openAPIV3Schema"),
9362 },
9363 },
9364 {
9365 name: "skip CEL expression validation when OpenAPIv3 schema is an invalid structural schema",
9366 opts: validationOptions{requireStructuralSchema: true},
9367 input: apiextensions.CustomResourceValidation{
9368 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9369 Type: "object",
9370
9371 Properties: map[string]apiextensions.JSONSchemaProps{
9372 "field": {
9373 Type: "integer",
9374 },
9375 },
9376 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9377 Schema: &apiextensions.JSONSchemaProps{
9378 Type: "string",
9379 },
9380 },
9381 XValidations: apiextensions.ValidationRules{
9382 {Rule: "self.invalidFieldName > 50"},
9383 },
9384 },
9385 },
9386 expectedErrors: []validationMatch{
9387 forbidden("spec.validation.openAPIV3Schema.additionalProperties"),
9388 forbidden("spec.validation.openAPIV3Schema.additionalProperties"),
9389
9390 },
9391 },
9392 {
9393 name: "skip CEL expression validation when OpenAPIv3 schema is an invalid structural schema at level below",
9394 opts: validationOptions{requireStructuralSchema: true},
9395 input: apiextensions.CustomResourceValidation{
9396 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9397 Type: "object",
9398 Properties: map[string]apiextensions.JSONSchemaProps{
9399 "field": {
9400 Type: "object",
9401
9402 Properties: map[string]apiextensions.JSONSchemaProps{
9403 "field": {
9404 Type: "integer",
9405 },
9406 },
9407 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9408 Schema: &apiextensions.JSONSchemaProps{
9409 Type: "string",
9410 },
9411 },
9412 },
9413 },
9414 XValidations: apiextensions.ValidationRules{
9415 {Rule: "self.invalidFieldName > 50"},
9416 },
9417 },
9418 },
9419 expectedErrors: []validationMatch{
9420 forbidden("spec.validation.openAPIV3Schema.properties[field].additionalProperties"),
9421 },
9422 },
9423 {
9424
9425 name: "do not skip when OpenAPIv3 schema is an invalid structural schema in a separate part of the schema tree",
9426 opts: validationOptions{requireStructuralSchema: true},
9427 input: apiextensions.CustomResourceValidation{
9428 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9429 Type: "object",
9430 Properties: map[string]apiextensions.JSONSchemaProps{
9431 "a": {
9432 Type: "object",
9433
9434 Properties: map[string]apiextensions.JSONSchemaProps{
9435 "field": {
9436 Type: "integer",
9437 },
9438 },
9439 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9440 Schema: &apiextensions.JSONSchemaProps{
9441 Type: "string",
9442 },
9443 },
9444 },
9445 "b": {
9446 Type: "object",
9447 Properties: map[string]apiextensions.JSONSchemaProps{
9448 "field": {
9449 Type: "integer",
9450 },
9451 },
9452 XValidations: apiextensions.ValidationRules{
9453 {Rule: "self.invalidFieldName > 50"},
9454 },
9455 },
9456 },
9457 },
9458 },
9459 expectedErrors: []validationMatch{
9460 forbidden("spec.validation.openAPIV3Schema.properties[a].additionalProperties"),
9461 invalid("spec.validation.openAPIV3Schema.properties[b].x-kubernetes-validations[0].rule"),
9462 },
9463 },
9464 {
9465
9466 name: "do not skip CEL expression validation when OpenAPIv3 schema is an invalid structural schema at level above",
9467 opts: validationOptions{requireStructuralSchema: true},
9468 input: apiextensions.CustomResourceValidation{
9469 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9470 Type: "object",
9471 Properties: map[string]apiextensions.JSONSchemaProps{
9472 "a": {
9473 Type: "object",
9474
9475 Properties: map[string]apiextensions.JSONSchemaProps{
9476 "b": {
9477 Type: "integer",
9478 XValidations: apiextensions.ValidationRules{
9479 {Rule: "self == 'abc'"},
9480 },
9481 },
9482 },
9483 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9484 Schema: &apiextensions.JSONSchemaProps{
9485 Type: "string",
9486 },
9487 },
9488 },
9489 },
9490 },
9491 },
9492 expectedErrors: []validationMatch{
9493 forbidden("spec.validation.openAPIV3Schema.properties[a].additionalProperties"),
9494 invalid("spec.validation.openAPIV3Schema.properties[a].properties[b].x-kubernetes-validations[0].rule"),
9495 },
9496 },
9497 {
9498 name: "x-kubernetes-validations rule validated for escaped property name",
9499 opts: validationOptions{requireStructuralSchema: true},
9500 input: apiextensions.CustomResourceValidation{
9501 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9502 Type: "object",
9503 Properties: map[string]apiextensions.JSONSchemaProps{
9504 "f/2": {
9505 Type: "string",
9506 },
9507 },
9508 XValidations: apiextensions.ValidationRules{
9509 {Rule: "self.f__slash__2 == 1"},
9510 },
9511 },
9512 },
9513 expectedErrors: []validationMatch{
9514 invalid("spec.validation.openAPIV3Schema.x-kubernetes-validations[0].rule"),
9515 },
9516 },
9517 {
9518 name: "x-kubernetes-validations rule validated under array items",
9519 opts: validationOptions{requireStructuralSchema: true},
9520 input: apiextensions.CustomResourceValidation{
9521 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9522 Type: "object",
9523 Properties: map[string]apiextensions.JSONSchemaProps{
9524 "a": {
9525 Type: "array",
9526 Items: &apiextensions.JSONSchemaPropsOrArray{
9527 Schema: &apiextensions.JSONSchemaProps{
9528 Type: "string",
9529 XValidations: apiextensions.ValidationRules{
9530 {Rule: "self == 1"},
9531 },
9532 },
9533 },
9534 },
9535 },
9536 },
9537 },
9538 expectedErrors: []validationMatch{
9539 invalid("spec.validation.openAPIV3Schema.properties[a].items.x-kubernetes-validations[0].rule"),
9540 },
9541 },
9542 {
9543 name: "x-kubernetes-validations rule validated under array items, parent has rule",
9544 opts: validationOptions{requireStructuralSchema: true},
9545 input: apiextensions.CustomResourceValidation{
9546 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9547 Type: "object",
9548 Properties: map[string]apiextensions.JSONSchemaProps{
9549 "a": {Type: "array",
9550 Items: &apiextensions.JSONSchemaPropsOrArray{
9551 Schema: &apiextensions.JSONSchemaProps{
9552 Type: "string",
9553 XValidations: apiextensions.ValidationRules{
9554 {Rule: "self == 1"},
9555 },
9556 },
9557 },
9558 XValidations: apiextensions.ValidationRules{
9559 {Rule: "1 == 1"},
9560 },
9561 },
9562 },
9563 },
9564 },
9565 expectedErrors: []validationMatch{
9566 invalid("spec.validation.openAPIV3Schema.properties[a].items.x-kubernetes-validations[0].rule"),
9567 },
9568 },
9569 {
9570 name: "x-kubernetes-validations rule validated under additionalProperties",
9571 opts: validationOptions{requireStructuralSchema: true},
9572 input: apiextensions.CustomResourceValidation{
9573 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9574 Type: "object",
9575 Properties: map[string]apiextensions.JSONSchemaProps{
9576 "a": {
9577 Type: "object",
9578 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9579 Schema: &apiextensions.JSONSchemaProps{
9580 Type: "string",
9581 XValidations: apiextensions.ValidationRules{
9582 {Rule: "self == 1"},
9583 },
9584 },
9585 },
9586 },
9587 },
9588 },
9589 },
9590 expectedErrors: []validationMatch{
9591 invalid("spec.validation.openAPIV3Schema.properties[a].additionalProperties.x-kubernetes-validations[0].rule"),
9592 },
9593 },
9594 {
9595 name: "x-kubernetes-validations rule validated under additionalProperties, parent has rule",
9596 opts: validationOptions{requireStructuralSchema: true},
9597 input: apiextensions.CustomResourceValidation{
9598 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9599 Type: "object",
9600 Properties: map[string]apiextensions.JSONSchemaProps{
9601 "a": {
9602 Type: "object",
9603 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
9604 Schema: &apiextensions.JSONSchemaProps{
9605 Type: "string",
9606 XValidations: apiextensions.ValidationRules{
9607 {Rule: "self == 1"},
9608 },
9609 },
9610 },
9611 XValidations: apiextensions.ValidationRules{
9612 {Rule: "1 == 1"},
9613 },
9614 },
9615 },
9616 },
9617 },
9618 expectedErrors: []validationMatch{
9619 invalid("spec.validation.openAPIV3Schema.properties[a].additionalProperties.x-kubernetes-validations[0].rule"),
9620 },
9621 },
9622 {
9623 name: "x-kubernetes-validations rule validated under unescaped property name",
9624 opts: validationOptions{requireStructuralSchema: true},
9625 input: apiextensions.CustomResourceValidation{
9626 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9627 Type: "object",
9628 Properties: map[string]apiextensions.JSONSchemaProps{
9629 "f": {
9630 Type: "string",
9631 XValidations: apiextensions.ValidationRules{
9632 {Rule: "self == 1"},
9633 },
9634 },
9635 },
9636 },
9637 },
9638 expectedErrors: []validationMatch{
9639 invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].rule"),
9640 },
9641 },
9642 {
9643 name: "x-kubernetes-validations rule validated under unescaped property name, parent has rule",
9644 opts: validationOptions{requireStructuralSchema: true},
9645 input: apiextensions.CustomResourceValidation{
9646 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9647 Type: "object",
9648 Properties: map[string]apiextensions.JSONSchemaProps{
9649 "f": {
9650 Type: "string",
9651 XValidations: apiextensions.ValidationRules{
9652 {Rule: "self == 1"},
9653 },
9654 },
9655 },
9656 XValidations: apiextensions.ValidationRules{
9657 {Rule: "1 == 1"},
9658 },
9659 },
9660 },
9661 expectedErrors: []validationMatch{
9662 invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].rule"),
9663 },
9664 },
9665 {
9666 name: "x-kubernetes-validations rule validated under escaped property name",
9667 opts: validationOptions{requireStructuralSchema: true},
9668 input: apiextensions.CustomResourceValidation{
9669 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9670 Type: "object",
9671 Properties: map[string]apiextensions.JSONSchemaProps{
9672 "f/2": {
9673 Type: "string",
9674 XValidations: apiextensions.ValidationRules{
9675 {Rule: "self == 1"},
9676 },
9677 },
9678 },
9679 },
9680 },
9681 expectedErrors: []validationMatch{
9682 invalid("spec.validation.openAPIV3Schema.properties[f/2].x-kubernetes-validations[0].rule"),
9683 },
9684 },
9685 {
9686 name: "x-kubernetes-validations rule validated under escaped property name, parent has rule",
9687 opts: validationOptions{requireStructuralSchema: true},
9688 input: apiextensions.CustomResourceValidation{
9689 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9690 Type: "object",
9691 Properties: map[string]apiextensions.JSONSchemaProps{
9692 "f/2": {
9693 Type: "string",
9694 XValidations: apiextensions.ValidationRules{
9695 {Rule: "self == 1"},
9696 },
9697 },
9698 },
9699 XValidations: apiextensions.ValidationRules{
9700 {Rule: "1 == 1"},
9701 },
9702 },
9703 },
9704 expectedErrors: []validationMatch{
9705 invalid("spec.validation.openAPIV3Schema.properties[f/2].x-kubernetes-validations[0].rule"),
9706 },
9707 },
9708 {
9709 name: "x-kubernetes-validations rule validated under unescapable property name",
9710 opts: validationOptions{requireStructuralSchema: true},
9711 input: apiextensions.CustomResourceValidation{
9712 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9713 Type: "object",
9714 Properties: map[string]apiextensions.JSONSchemaProps{
9715 "f@2": {
9716 Type: "string",
9717 XValidations: apiextensions.ValidationRules{
9718 {Rule: "self == 1"},
9719 },
9720 },
9721 },
9722 },
9723 },
9724 expectedErrors: []validationMatch{
9725 invalid("spec.validation.openAPIV3Schema.properties[f@2].x-kubernetes-validations[0].rule"),
9726 },
9727 },
9728 {
9729 name: "x-kubernetes-validations rule validated under unescapable property name, parent has rule",
9730 opts: validationOptions{requireStructuralSchema: true},
9731 input: apiextensions.CustomResourceValidation{
9732 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9733 Type: "object",
9734 Properties: map[string]apiextensions.JSONSchemaProps{
9735 "f@2": {
9736 Type: "string",
9737 XValidations: apiextensions.ValidationRules{
9738 {Rule: "self == 1"},
9739 },
9740 },
9741 },
9742 XValidations: apiextensions.ValidationRules{
9743 {Rule: "1 == 1"},
9744 },
9745 },
9746 },
9747 expectedErrors: []validationMatch{
9748 invalid("spec.validation.openAPIV3Schema.properties[f@2].x-kubernetes-validations[0].rule"),
9749 },
9750 },
9751 {
9752 name: "x-kubernetes-validations rule with messageExpression",
9753 opts: validationOptions{requireStructuralSchema: true},
9754 input: apiextensions.CustomResourceValidation{
9755 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9756 Type: "object",
9757 Properties: map[string]apiextensions.JSONSchemaProps{
9758 "f": {
9759 Type: "string",
9760 XValidations: apiextensions.ValidationRules{
9761 {
9762 Rule: "self == \"string value\"",
9763 MessageExpression: `self + " should be \"string value\""`,
9764 },
9765 },
9766 },
9767 },
9768 },
9769 },
9770 expectedErrors: []validationMatch{},
9771 },
9772 {
9773 name: "x-kubernetes-validations rule allows both message and messageExpression",
9774 opts: validationOptions{requireStructuralSchema: true},
9775 input: apiextensions.CustomResourceValidation{
9776 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9777 Type: "object",
9778 Properties: map[string]apiextensions.JSONSchemaProps{
9779 "f": {
9780 Type: "string",
9781 XValidations: apiextensions.ValidationRules{
9782 {
9783 Rule: "self == \"string value\"",
9784 Message: `string should be set to "string value"`,
9785 MessageExpression: `self + " should be \"string value\""`,
9786 },
9787 },
9788 },
9789 },
9790 },
9791 },
9792 expectedErrors: []validationMatch{},
9793 },
9794 {
9795 name: "x-kubernetes-validations rule invalidated by messageExpression syntax error",
9796 opts: validationOptions{requireStructuralSchema: true},
9797 input: apiextensions.CustomResourceValidation{
9798 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9799 Type: "object",
9800 Properties: map[string]apiextensions.JSONSchemaProps{
9801 "f": {
9802 Type: "string",
9803 XValidations: apiextensions.ValidationRules{
9804 {
9805 Rule: "self == \"string value\"",
9806 MessageExpression: `self + " `,
9807 },
9808 },
9809 },
9810 },
9811 },
9812 },
9813 expectedErrors: []validationMatch{
9814 invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
9815 },
9816 },
9817 {
9818 name: "x-kubernetes-validations rule invalidated by messageExpression not returning a string",
9819 opts: validationOptions{requireStructuralSchema: true},
9820 input: apiextensions.CustomResourceValidation{
9821 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9822 Type: "object",
9823 Properties: map[string]apiextensions.JSONSchemaProps{
9824 "f": {
9825 Type: "string",
9826 XValidations: apiextensions.ValidationRules{
9827 {
9828 Rule: "self == \"string value\"",
9829 MessageExpression: `256`,
9830 },
9831 },
9832 },
9833 },
9834 },
9835 },
9836 expectedErrors: []validationMatch{
9837 invalid("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
9838 },
9839 },
9840 {
9841 name: "x-kubernetes-validations rule invalidated by messageExpression exceeding per-expression estimated cost limit",
9842 opts: validationOptions{requireStructuralSchema: true},
9843 input: apiextensions.CustomResourceValidation{
9844 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9845 Type: "object",
9846 Properties: map[string]apiextensions.JSONSchemaProps{
9847 "f": {
9848 Type: "array",
9849 Items: &apiextensions.JSONSchemaPropsOrArray{
9850 Schema: &apiextensions.JSONSchemaProps{
9851 Type: "string",
9852 },
9853 },
9854 XValidations: apiextensions.ValidationRules{
9855 {
9856 Rule: "true",
9857 MessageExpression: `self[0] + self[1] + self[2] + self[3] + self[4] + self[5] + self[6] + self[7]`,
9858 },
9859 },
9860 },
9861 },
9862 },
9863 },
9864 expectedErrors: []validationMatch{
9865
9866 forbidden("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
9867 },
9868 },
9869 {
9870 name: "x-kubernetes-validations rule with lowerAscii check should be within estimated cost limit",
9871 opts: validationOptions{requireStructuralSchema: true},
9872 input: apiextensions.CustomResourceValidation{
9873 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9874 Type: "object",
9875 Properties: map[string]apiextensions.JSONSchemaProps{
9876 "f": {
9877 Type: "array",
9878 MaxItems: pointer.Int64(5),
9879 Items: &apiextensions.JSONSchemaPropsOrArray{
9880 Schema: &apiextensions.JSONSchemaProps{
9881 Type: "string",
9882 MaxLength: pointer.Int64(5),
9883 },
9884 },
9885 XValidations: apiextensions.ValidationRules{
9886 {
9887 Rule: "self.all(x, self.exists_one(y, x.lowerAscii() == y.lowerAscii()))",
9888 },
9889 },
9890 },
9891 },
9892 },
9893 },
9894 },
9895 {
9896 name: "x-kubernetes-validations rule invalidated by messageExpression exceeding per-CRD estimated cost limit",
9897 opts: validationOptions{requireStructuralSchema: true},
9898 input: apiextensions.CustomResourceValidation{
9899 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9900 Type: "object",
9901 Properties: map[string]apiextensions.JSONSchemaProps{
9902 "f": {
9903 Type: "array",
9904 Items: &apiextensions.JSONSchemaPropsOrArray{
9905 Schema: &apiextensions.JSONSchemaProps{
9906 Type: "string",
9907 },
9908 },
9909 XValidations: apiextensions.ValidationRules{
9910 {
9911 Rule: "true",
9912 MessageExpression: `string(self[0]) + string(self[1]) + string(self[2])`,
9913 },
9914 },
9915 },
9916 },
9917 },
9918 },
9919 expectedErrors: []validationMatch{
9920
9921 forbidden("spec.validation.openAPIV3Schema"),
9922
9923 forbidden("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
9924
9925 forbidden("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
9926 },
9927 },
9928 {
9929 name: "x-kubernetes-validations rule invalidated by messageExpression being only empty spaces",
9930 opts: validationOptions{requireStructuralSchema: true},
9931 input: apiextensions.CustomResourceValidation{
9932 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9933 Type: "object",
9934 Properties: map[string]apiextensions.JSONSchemaProps{
9935 "f": {
9936 Type: "string",
9937 XValidations: apiextensions.ValidationRules{
9938 {
9939 Rule: "self == \"string value\"",
9940 MessageExpression: ` `,
9941 },
9942 },
9943 },
9944 },
9945 },
9946 },
9947 expectedErrors: []validationMatch{
9948 required("spec.validation.openAPIV3Schema.properties[f].x-kubernetes-validations[0].messageExpression"),
9949 },
9950 },
9951 {
9952 name: "forbid transition rule on element of list of type atomic when optionalOldSelf is set",
9953 opts: validationOptions{requireStructuralSchema: true},
9954 input: apiextensions.CustomResourceValidation{
9955 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9956 Type: "object",
9957 Properties: map[string]apiextensions.JSONSchemaProps{
9958 "value": {
9959 Type: "array",
9960 XListType: strPtr("atomic"),
9961 Items: &apiextensions.JSONSchemaPropsOrArray{
9962 Schema: &apiextensions.JSONSchemaProps{
9963 Type: "string",
9964 MaxLength: int64ptr(10),
9965 XValidations: apiextensions.ValidationRules{
9966 {Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
9967 },
9968 },
9969 },
9970 },
9971 },
9972 },
9973 },
9974 expectedErrors: []validationMatch{
9975 invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
9976 },
9977 },
9978 {
9979 name: "forbid transition rule on element of list defaulting to type atomic when optionalOldSelf is set",
9980 opts: validationOptions{requireStructuralSchema: true},
9981 input: apiextensions.CustomResourceValidation{
9982 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
9983 Type: "object",
9984 Properties: map[string]apiextensions.JSONSchemaProps{
9985 "value": {
9986 Type: "array",
9987 Items: &apiextensions.JSONSchemaPropsOrArray{
9988 Schema: &apiextensions.JSONSchemaProps{
9989 Type: "string",
9990 MaxLength: int64ptr(10),
9991 XValidations: apiextensions.ValidationRules{
9992 {Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
9993 },
9994 },
9995 },
9996 },
9997 },
9998 },
9999 },
10000 expectedErrors: []validationMatch{
10001 invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
10002 },
10003 },
10004 {
10005 name: "forbid transition rule on element of list of type set when optionalOldSelf is set",
10006 opts: validationOptions{requireStructuralSchema: true},
10007 input: apiextensions.CustomResourceValidation{
10008 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
10009 Type: "object",
10010 Properties: map[string]apiextensions.JSONSchemaProps{
10011 "value": {
10012 Type: "array",
10013 MaxItems: int64ptr(10),
10014 XListType: strPtr("set"),
10015 Items: &apiextensions.JSONSchemaPropsOrArray{
10016 Schema: &apiextensions.JSONSchemaProps{
10017 Type: "string",
10018 MaxLength: int64ptr(10),
10019 XValidations: apiextensions.ValidationRules{
10020 {Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
10021 },
10022 },
10023 },
10024 },
10025 },
10026 },
10027 },
10028 expectedErrors: []validationMatch{
10029 invalid("spec.validation.openAPIV3Schema.properties[value].items.x-kubernetes-validations[0].rule"),
10030 },
10031 },
10032 {
10033 name: "forbid transition rule on element of map of unrecognized type when optionalOldSelf is set",
10034 opts: validationOptions{requireStructuralSchema: true},
10035 input: apiextensions.CustomResourceValidation{
10036 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
10037 Type: "object",
10038 Properties: map[string]apiextensions.JSONSchemaProps{
10039 "value": {
10040 Type: "object",
10041 XMapType: strPtr("future"),
10042 Properties: map[string]apiextensions.JSONSchemaProps{
10043 "subfield": {
10044 Type: "string",
10045 MaxLength: int64ptr(10),
10046 XValidations: apiextensions.ValidationRules{
10047 {Rule: `self == oldSelf.orValue("")`, OptionalOldSelf: ptr.To(true)},
10048 },
10049 },
10050 },
10051 },
10052 },
10053 },
10054 },
10055 expectedErrors: []validationMatch{
10056 invalid("spec.validation.openAPIV3Schema.properties[value].properties[subfield].x-kubernetes-validations[0].rule"),
10057 unsupported("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-map-type"),
10058 },
10059 },
10060 {
10061 name: "forbid setting optionalOldSelf to true if oldSelf is not used",
10062 opts: validationOptions{requireStructuralSchema: true},
10063 input: apiextensions.CustomResourceValidation{
10064 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
10065 Type: "object",
10066 Properties: map[string]apiextensions.JSONSchemaProps{
10067 "value": {
10068 Type: "string",
10069 MaxLength: int64ptr(10),
10070 XValidations: apiextensions.ValidationRules{
10071 {Rule: `self == "foo"`, OptionalOldSelf: ptr.To(true)},
10072 },
10073 },
10074 },
10075 },
10076 },
10077 expectedErrors: []validationMatch{
10078 invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].optionalOldSelf"),
10079 },
10080 },
10081 {
10082 name: "forbid setting optionalOldSelf to false if oldSelf is not used",
10083 opts: validationOptions{requireStructuralSchema: true},
10084 input: apiextensions.CustomResourceValidation{
10085 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
10086 Type: "object",
10087 Properties: map[string]apiextensions.JSONSchemaProps{
10088 "value": {
10089 Type: "string",
10090 MaxLength: int64ptr(10),
10091 XValidations: apiextensions.ValidationRules{
10092 {Rule: `self == "foo"`, OptionalOldSelf: ptr.To(false)},
10093 },
10094 },
10095 },
10096 },
10097 },
10098 expectedErrors: []validationMatch{
10099 invalid("spec.validation.openAPIV3Schema.properties[value].x-kubernetes-validations[0].optionalOldSelf"),
10100 },
10101 },
10102 }
10103 for _, tt := range tests {
10104 t.Run(tt.name, func(t *testing.T) {
10105 ctx := context.TODO()
10106 if tt.opts.celEnvironmentSet == nil {
10107 tt.opts.celEnvironmentSet = environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
10108 }
10109 got := validateCustomResourceDefinitionValidation(ctx, &tt.input, tt.statusEnabled, tt.opts, field.NewPath("spec", "validation"))
10110
10111 seenErrs := make([]bool, len(got))
10112
10113 for _, expectedError := range tt.expectedErrors {
10114 found := false
10115 for i, err := range got {
10116 if expectedError.matches(err) && !seenErrs[i] {
10117 found = true
10118 seenErrs[i] = true
10119 break
10120 }
10121 }
10122
10123 if !found {
10124 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), got)
10125 }
10126 }
10127
10128 for i, seen := range seenErrs {
10129 if !seen {
10130 t.Errorf("unexpected error: %v", got[i])
10131 }
10132 }
10133 })
10134 }
10135 }
10136
10137 func TestSchemaHasDefaults(t *testing.T) {
10138 scheme := runtime.NewScheme()
10139 codecs := serializer.NewCodecFactory(scheme)
10140 if err := apiextensions.AddToScheme(scheme); err != nil {
10141 t.Fatal(err)
10142 }
10143
10144 seed := rand.Int63()
10145 t.Logf("seed: %d", seed)
10146 fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs)
10147 f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs)
10148
10149 for i := 0; i < 10000; i++ {
10150
10151 schema := &apiextensions.JSONSchemaProps{}
10152 f.Fuzz(schema)
10153
10154 v1beta1Schema := &apiextensionsv1beta1.JSONSchemaProps{}
10155 if err := apiextensionsv1beta1.Convert_apiextensions_JSONSchemaProps_To_v1beta1_JSONSchemaProps(schema, v1beta1Schema, nil); err != nil {
10156 t.Fatal(err)
10157 }
10158
10159 bs, err := json.Marshal(v1beta1Schema)
10160 if err != nil {
10161 t.Fatal(err)
10162 }
10163
10164 expected := strings.Contains(strings.Replace(string(bs), `"default":null`, `"deleted":null`, -1), `"default":`)
10165 if got := schemaHasDefaults(schema); got != expected {
10166 t.Errorf("expected %v, got %v for: %s", expected, got, string(bs))
10167 }
10168 }
10169 }
10170
10171 func TestValidateCustomResourceDefinitionStoredVersions(t *testing.T) {
10172 tests := []struct {
10173 name string
10174 versions []string
10175 storageVersion string
10176 storedVersions []string
10177 errors []validationMatch
10178 }{
10179 {
10180 name: "one version",
10181 versions: []string{"v1"},
10182 storageVersion: "v1",
10183 storedVersions: []string{"v1"},
10184 },
10185 {
10186 name: "no stored version",
10187 versions: []string{"v1"},
10188 storageVersion: "v1",
10189 storedVersions: []string{},
10190 errors: []validationMatch{
10191 invalid("status", "storedVersions").contains("Invalid value: []string{}: must have at least one stored version"),
10192 },
10193 },
10194 {
10195 name: "many versions",
10196 versions: []string{"v1alpha", "v1beta1", "v1"},
10197 storageVersion: "v1",
10198 storedVersions: []string{"v1alpha", "v1"},
10199 },
10200 {
10201 name: "missing stored versions",
10202 versions: []string{"v1beta1", "v1"},
10203 storageVersion: "v1",
10204 storedVersions: []string{"v1alpha", "v1beta1", "v1"},
10205 errors: []validationMatch{
10206 invalidIndex(0, "status", "storedVersions").contains("Invalid value: \"v1alpha\": must appear in spec.versions"),
10207 },
10208 },
10209 {
10210 name: "missing storage versions",
10211 versions: []string{"v1alpha", "v1beta1", "v1"},
10212 storageVersion: "v1",
10213 storedVersions: []string{"v1alpha", "v1beta1"},
10214 errors: []validationMatch{
10215 invalid("status", "storedVersions").contains("Invalid value: []string{\"v1alpha\", \"v1beta1\"}: must have the storage version v1"),
10216 },
10217 },
10218 }
10219
10220 for _, tc := range tests {
10221 crd := &apiextensions.CustomResourceDefinition{
10222 ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com", ResourceVersion: "1"},
10223 Spec: apiextensions.CustomResourceDefinitionSpec{
10224 Group: "group.com",
10225 Scope: "Cluster",
10226 Names: apiextensions.CustomResourceDefinitionNames{Plural: "plural", Singular: "singular", Kind: "Plural", ListKind: "PluralList"},
10227 },
10228 Status: apiextensions.CustomResourceDefinitionStatus{StoredVersions: tc.storedVersions},
10229 }
10230 for _, version := range tc.versions {
10231 v := apiextensions.CustomResourceDefinitionVersion{Name: version}
10232 if tc.storageVersion == version {
10233 v.Storage = true
10234 }
10235 crd.Spec.Versions = append(crd.Spec.Versions, v)
10236 }
10237
10238 t.Run(tc.name, func(t *testing.T) {
10239 errs := ValidateCustomResourceDefinitionStoredVersions(crd.Status.StoredVersions, crd.Spec.Versions, field.NewPath("status", "storedVersions"))
10240 seenErrs := make([]bool, len(errs))
10241
10242 for _, expectedError := range tc.errors {
10243 found := false
10244 for i, err := range errs {
10245 if expectedError.matches(err) && !seenErrs[i] {
10246 found = true
10247 seenErrs[i] = true
10248 break
10249 }
10250 }
10251
10252 if !found {
10253 t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
10254 }
10255 }
10256 for i, seen := range seenErrs {
10257 if !seen {
10258 t.Errorf("unexpected error: %v", errs[i])
10259 }
10260 }
10261 })
10262 }
10263 }
10264
10265 func BenchmarkSchemaHas(b *testing.B) {
10266 scheme := runtime.NewScheme()
10267 codecs := serializer.NewCodecFactory(scheme)
10268 if err := apiextensions.AddToScheme(scheme); err != nil {
10269 b.Fatal(err)
10270 }
10271 fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs)
10272 seed := int64(5577006791947779410)
10273 f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs)
10274
10275 schema := &apiextensions.JSONSchemaProps{}
10276 f.NilChance(0).NumElements(10, 10).MaxDepth(10).Fuzz(schema)
10277
10278 b.ReportAllocs()
10279 b.ResetTimer()
10280 for i := 0; i < b.N; i++ {
10281 if SchemaHas(schema, func(_ *apiextensions.JSONSchemaProps) bool {
10282 return false
10283 }) {
10284 b.Errorf("Function returned true")
10285 }
10286 }
10287 }
10288
10289 var example = apiextensions.JSON(`"This is an example"`)
10290
10291 var validValidationSchema = &apiextensions.JSONSchemaProps{
10292 Description: "This is a description",
10293 Type: "object",
10294 Format: "date-time",
10295 Title: "This is a title",
10296 Maximum: float64Ptr(10),
10297 ExclusiveMaximum: true,
10298 Minimum: float64Ptr(5),
10299 ExclusiveMinimum: true,
10300 MaxLength: int64Ptr(10),
10301 MinLength: int64Ptr(5),
10302 Pattern: "^[a-z]$",
10303 MaxItems: int64Ptr(10),
10304 MinItems: int64Ptr(5),
10305 MultipleOf: float64Ptr(3),
10306 Required: []string{"spec", "status"},
10307 Properties: map[string]apiextensions.JSONSchemaProps{
10308 "spec": {
10309 Type: "object",
10310 Items: &apiextensions.JSONSchemaPropsOrArray{
10311 Schema: &apiextensions.JSONSchemaProps{
10312 Description: "This is a schema nested under Items",
10313 Type: "string",
10314 },
10315 },
10316 },
10317 "status": {
10318 Type: "object",
10319 },
10320 },
10321 ExternalDocs: &apiextensions.ExternalDocumentation{
10322 Description: "This is an external documentation description",
10323 },
10324 Example: &example,
10325 }
10326
10327 var validUnstructuralValidationSchema = &apiextensions.JSONSchemaProps{
10328 Description: "This is a description",
10329 Type: "object",
10330 Format: "date-time",
10331 Title: "This is a title",
10332 Maximum: float64Ptr(10),
10333 ExclusiveMaximum: true,
10334 Minimum: float64Ptr(5),
10335 ExclusiveMinimum: true,
10336 MaxLength: int64Ptr(10),
10337 MinLength: int64Ptr(5),
10338 Pattern: "^[a-z]$",
10339 MaxItems: int64Ptr(10),
10340 MinItems: int64Ptr(5),
10341 MultipleOf: float64Ptr(3),
10342 Required: []string{"spec", "status"},
10343 Items: &apiextensions.JSONSchemaPropsOrArray{
10344 Schema: &apiextensions.JSONSchemaProps{
10345 Description: "This is a schema nested under Items",
10346 },
10347 },
10348 Properties: map[string]apiextensions.JSONSchemaProps{
10349 "spec": {},
10350 "status": {},
10351 },
10352 ExternalDocs: &apiextensions.ExternalDocumentation{
10353 Description: "This is an external documentation description",
10354 },
10355 Example: &example,
10356 }
10357
10358 func float64Ptr(f float64) *float64 {
10359 return &f
10360 }
10361
10362 func int64Ptr(f int64) *int64 {
10363 return &f
10364 }
10365
10366 func jsonPtr(x interface{}) *apiextensions.JSON {
10367 ret := apiextensions.JSON(x)
10368 return &ret
10369 }
10370
10371 func jsonSlice(l ...interface{}) []apiextensions.JSON {
10372 if len(l) == 0 {
10373 return nil
10374 }
10375 ret := make([]apiextensions.JSON, 0, len(l))
10376 for _, x := range l {
10377 ret = append(ret, x)
10378 }
10379 return ret
10380 }
10381
10382 func Test_validateDeprecationWarning(t *testing.T) {
10383 tests := []struct {
10384 name string
10385
10386 deprecated bool
10387 warning *string
10388
10389 want []string
10390 }{
10391 {
10392 name: "not deprecated, nil warning",
10393 deprecated: false,
10394 warning: nil,
10395 want: nil,
10396 },
10397
10398 {
10399 name: "not deprecated, empty warning",
10400 deprecated: false,
10401 warning: pointer.StringPtr(""),
10402 want: []string{"can only be set for deprecated versions"},
10403 },
10404 {
10405 name: "not deprecated, set warning",
10406 deprecated: false,
10407 warning: pointer.StringPtr("foo"),
10408 want: []string{"can only be set for deprecated versions"},
10409 },
10410
10411 {
10412 name: "utf-8",
10413 deprecated: true,
10414 warning: pointer.StringPtr("Iñtërnâtiônàlizætiøn,💝🐹🌇⛔"),
10415 want: nil,
10416 },
10417 {
10418 name: "long warning",
10419 deprecated: true,
10420 warning: pointer.StringPtr(strings.Repeat("x", 256)),
10421 want: nil,
10422 },
10423
10424 {
10425 name: "too long warning",
10426 deprecated: true,
10427 warning: pointer.StringPtr(strings.Repeat("x", 257)),
10428 want: []string{"must be <= 256 characters long"},
10429 },
10430 {
10431 name: "newline",
10432 deprecated: true,
10433 warning: pointer.StringPtr("Test message\nfoo"),
10434 want: []string{"must only contain printable UTF-8 characters; non-printable character found at index 12"},
10435 },
10436 {
10437 name: "non-printable character",
10438 deprecated: true,
10439 warning: pointer.StringPtr("Test message\u0008"),
10440 want: []string{"must only contain printable UTF-8 characters; non-printable character found at index 12"},
10441 },
10442 {
10443 name: "null character",
10444 deprecated: true,
10445 warning: pointer.StringPtr("Test message\u0000"),
10446 want: []string{"must only contain printable UTF-8 characters; non-printable character found at index 12"},
10447 },
10448 {
10449 name: "non-utf-8",
10450 deprecated: true,
10451 warning: pointer.StringPtr("Test message\xc5foo"),
10452 want: []string{"must only contain printable UTF-8 characters"},
10453 },
10454 }
10455 for _, tt := range tests {
10456 t.Run(tt.name, func(t *testing.T) {
10457 if got := validateDeprecationWarning(tt.deprecated, tt.warning); !reflect.DeepEqual(got, tt.want) {
10458 t.Errorf("validateDeprecationWarning() = %v, want %v", got, tt.want)
10459 }
10460 })
10461 }
10462 }
10463
10464 func genMapSchema() *apiextensions.JSONSchemaProps {
10465 return &apiextensions.JSONSchemaProps{
10466 Type: "object",
10467 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
10468 Schema: &apiextensions.JSONSchemaProps{
10469 Type: "string",
10470 },
10471 },
10472 }
10473 }
10474
10475 func withMaxProperties(mapSchema *apiextensions.JSONSchemaProps, maxProps *int64) *apiextensions.JSONSchemaProps {
10476 mapSchema.MaxProperties = maxProps
10477 return mapSchema
10478 }
10479
10480 func genArraySchema() *apiextensions.JSONSchemaProps {
10481 return &apiextensions.JSONSchemaProps{
10482 Type: "array",
10483 }
10484 }
10485
10486 func withMaxItems(arraySchema *apiextensions.JSONSchemaProps, maxItems *int64) *apiextensions.JSONSchemaProps {
10487 arraySchema.MaxItems = maxItems
10488 return arraySchema
10489 }
10490
10491 func genObjectSchema() *apiextensions.JSONSchemaProps {
10492 return &apiextensions.JSONSchemaProps{
10493 Type: "object",
10494 }
10495 }
10496
10497 func TestCostInfo(t *testing.T) {
10498 tests := []struct {
10499 name string
10500 schema []*apiextensions.JSONSchemaProps
10501 expectedMaxCardinality *uint64
10502 }{
10503 {
10504 name: "object",
10505 schema: []*apiextensions.JSONSchemaProps{
10506 genObjectSchema(),
10507 },
10508 expectedMaxCardinality: uint64ptr(1),
10509 },
10510 {
10511 name: "array",
10512 schema: []*apiextensions.JSONSchemaProps{
10513 withMaxItems(genArraySchema(), int64ptr(5)),
10514 },
10515 expectedMaxCardinality: uint64ptr(5),
10516 },
10517 {
10518 name: "unbounded array",
10519 schema: []*apiextensions.JSONSchemaProps{genArraySchema()},
10520 expectedMaxCardinality: nil,
10521 },
10522 {
10523 name: "map",
10524 schema: []*apiextensions.JSONSchemaProps{withMaxProperties(genMapSchema(), int64ptr(10))},
10525 expectedMaxCardinality: uint64ptr(10),
10526 },
10527 {
10528 name: "unbounded map",
10529 schema: []*apiextensions.JSONSchemaProps{
10530 genMapSchema(),
10531 },
10532 expectedMaxCardinality: nil,
10533 },
10534 {
10535 name: "array inside map",
10536 schema: []*apiextensions.JSONSchemaProps{
10537 withMaxProperties(genMapSchema(), int64ptr(5)),
10538 withMaxItems(genArraySchema(), int64ptr(5)),
10539 },
10540 expectedMaxCardinality: uint64ptr(25),
10541 },
10542 {
10543 name: "unbounded array inside bounded map",
10544 schema: []*apiextensions.JSONSchemaProps{
10545 withMaxProperties(genMapSchema(), int64ptr(5)),
10546 genArraySchema(),
10547 },
10548 expectedMaxCardinality: nil,
10549 },
10550 {
10551 name: "object inside array",
10552 schema: []*apiextensions.JSONSchemaProps{
10553 withMaxItems(genArraySchema(), int64ptr(3)),
10554 genObjectSchema(),
10555 },
10556 expectedMaxCardinality: uint64ptr(3),
10557 },
10558 {
10559 name: "map inside object inside array",
10560 schema: []*apiextensions.JSONSchemaProps{
10561 withMaxItems(genArraySchema(), int64ptr(2)),
10562 genObjectSchema(),
10563 withMaxProperties(genMapSchema(), int64ptr(4)),
10564 },
10565 expectedMaxCardinality: uint64ptr(8),
10566 },
10567 {
10568 name: "integer overflow bounds check",
10569 schema: []*apiextensions.JSONSchemaProps{
10570 withMaxItems(genArraySchema(), int64ptr(math.MaxInt)),
10571 withMaxItems(genArraySchema(), int64ptr(100)),
10572 },
10573 expectedMaxCardinality: uint64ptr(math.MaxUint),
10574 },
10575 }
10576 for _, tt := range tests {
10577 t.Run(tt.name, func(t *testing.T) {
10578
10579 schemas := append(tt.schema, &apiextensions.JSONSchemaProps{Type: "string"})
10580 curCostInfo := RootCELContext(schemas[0])
10581 for i := 1; i < len(schemas); i++ {
10582 curCostInfo = curCostInfo.childContext(schemas[i], nil)
10583 }
10584 if tt.expectedMaxCardinality == nil && curCostInfo.MaxCardinality == nil {
10585
10586 } else if tt.expectedMaxCardinality == nil && curCostInfo.MaxCardinality != nil {
10587 t.Errorf("expected unbounded cardinality (got %d)", curCostInfo.MaxCardinality)
10588 } else if tt.expectedMaxCardinality != nil && curCostInfo.MaxCardinality == nil {
10589 t.Errorf("expected bounded cardinality of %d but got unbounded cardinality", tt.expectedMaxCardinality)
10590 } else if *tt.expectedMaxCardinality != *curCostInfo.MaxCardinality {
10591 t.Errorf("wrong cardinality (expected %d, got %d)", *tt.expectedMaxCardinality, curCostInfo.MaxCardinality)
10592 }
10593 })
10594 }
10595 }
10596
10597 func TestCelContext(t *testing.T) {
10598 tests := []struct {
10599 name string
10600 schema *apiextensions.JSONSchemaProps
10601 }{
10602 {
10603 name: "verify that schemas are converted only once and then reused",
10604 schema: &apiextensions.JSONSchemaProps{
10605 Type: "object",
10606 XValidations: []apiextensions.ValidationRule{{Rule: "self.size() < 100"}},
10607 AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
10608 Schema: &apiextensions.JSONSchemaProps{
10609 Type: "array",
10610 Items: &apiextensions.JSONSchemaPropsOrArray{
10611 Schema: &apiextensions.JSONSchemaProps{
10612 Type: "object",
10613 XValidations: []apiextensions.ValidationRule{{Rule: "has(self.field)"}},
10614 Properties: map[string]apiextensions.JSONSchemaProps{
10615 "field": {
10616 XValidations: []apiextensions.ValidationRule{{Rule: "self.startsWith('abc')"}},
10617 Type: "string",
10618 },
10619 },
10620 },
10621 },
10622 },
10623 },
10624 },
10625 },
10626 }
10627 for _, tt := range tests {
10628 t.Run(tt.name, func(t *testing.T) {
10629
10630 conversionCount := 0
10631 converter := func(schema *apiextensions.JSONSchemaProps, isRoot bool) (*CELTypeInfo, error) {
10632 conversionCount++
10633 return defaultConverter(schema, isRoot)
10634 }
10635 celContext := RootCELContext(tt.schema)
10636 celContext.converter = converter
10637 opts := validationOptions{
10638 celEnvironmentSet: environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()),
10639 }
10640 openAPIV3Schema := &specStandardValidatorV3{
10641 allowDefaults: opts.allowDefaults,
10642 disallowDefaultsReason: opts.disallowDefaultsReason,
10643 requireValidPropertyType: opts.requireValidPropertyType,
10644 }
10645 errors := ValidateCustomResourceDefinitionOpenAPISchema(tt.schema, field.NewPath("openAPIV3Schema"), openAPIV3Schema, true, &opts, celContext).AllErrors()
10646 if len(errors) != 0 {
10647 t.Errorf("Expected no validate errors but got %v", errors)
10648 }
10649 if conversionCount != 1 {
10650 t.Errorf("Expected 1 conversion to be performed by cel context during schema traversal but observed %d conversions", conversionCount)
10651 }
10652 })
10653 }
10654 }
10655
10656 func TestPerCRDEstimatedCost(t *testing.T) {
10657 tests := []struct {
10658 name string
10659 costs []uint64
10660 expectedExpensive []uint64
10661 expectedTotal uint64
10662 }{
10663 {
10664 name: "no costs",
10665 costs: []uint64{},
10666 expectedExpensive: []uint64{},
10667 expectedTotal: uint64(0),
10668 },
10669 {
10670 name: "one cost",
10671 costs: []uint64{1000000},
10672 expectedExpensive: []uint64{1000000},
10673 expectedTotal: uint64(1000000),
10674 },
10675 {
10676 name: "one cost, ignored",
10677 costs: []uint64{900000},
10678 expectedExpensive: []uint64{},
10679 expectedTotal: uint64(900000),
10680 },
10681 {
10682 name: "2 costs",
10683 costs: []uint64{5000000, 25000000},
10684 expectedExpensive: []uint64{25000000, 5000000},
10685 expectedTotal: uint64(30000000),
10686 },
10687 {
10688 name: "3 costs, one ignored",
10689 costs: []uint64{5000000, 25000000, 900000},
10690 expectedExpensive: []uint64{25000000, 5000000},
10691 expectedTotal: uint64(30900000),
10692 },
10693 {
10694 name: "4 costs",
10695 costs: []uint64{16000000, 50000000, 34000000, 50000000},
10696 expectedExpensive: []uint64{50000000, 50000000, 34000000, 16000000},
10697 expectedTotal: uint64(150000000),
10698 },
10699 {
10700 name: "5 costs, one trimmed, one ignored",
10701 costs: []uint64{16000000, 50000000, 900000, 34000000, 50000000, 50000001},
10702 expectedExpensive: []uint64{50000001, 50000000, 50000000, 34000000},
10703 expectedTotal: uint64(200900001),
10704 },
10705 {
10706 name: "costs do not overflow",
10707 costs: []uint64{math.MaxUint64 / 2, math.MaxUint64 / 2, 1, 10, 100, 1000},
10708 expectedExpensive: []uint64{math.MaxUint64 / 2, math.MaxUint64 / 2},
10709 expectedTotal: uint64(math.MaxUint64),
10710 },
10711 }
10712 for _, tt := range tests {
10713 t.Run(tt.name, func(t *testing.T) {
10714 crdCost := TotalCost{}
10715 for _, cost := range tt.costs {
10716 crdCost.ObserveExpressionCost(nil, cost)
10717 }
10718 if len(crdCost.MostExpensive) != len(tt.expectedExpensive) {
10719 t.Fatalf("expected %d largest costs but got %d: %v", len(tt.expectedExpensive), len(crdCost.MostExpensive), crdCost.MostExpensive)
10720 }
10721 for i, expensive := range crdCost.MostExpensive {
10722 if tt.expectedExpensive[i] != expensive.Cost {
10723 t.Errorf("expected largest cost of %d at index %d but got %d", tt.expectedExpensive[i], i, expensive.Cost)
10724 }
10725 }
10726 if tt.expectedTotal != crdCost.Total {
10727 t.Errorf("expected total cost of %d but got %d", tt.expectedTotal, crdCost.Total)
10728 }
10729 })
10730 }
10731 }
10732
10733 func int64ptr(i int64) *int64 {
10734 return &i
10735 }
10736
View as plain text