1
16
17 package apimachinery
18
19 import (
20
21 "context"
22 "encoding/json"
23 "fmt"
24 "strings"
25
26 "github.com/onsi/ginkgo/v2"
27 _ "github.com/stretchr/testify/assert"
28 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
29 apiextensionclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
30 "k8s.io/apiextensions-apiserver/test/integration/fixtures"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/types"
33 "k8s.io/client-go/dynamic"
34 clientset "k8s.io/client-go/kubernetes"
35 "k8s.io/klog/v2"
36 "k8s.io/kubernetes/test/e2e/framework"
37 admissionapi "k8s.io/pod-security-admission/api"
38 )
39
40 var _ = SIGDescribe("FieldValidation", func() {
41 f := framework.NewDefaultFramework("field-validation")
42 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
43
44 var client clientset.Interface
45 var ns string
46
47 ginkgo.BeforeEach(func() {
48 client = f.ClientSet
49 ns = f.Namespace.Name
50 })
51
52 ginkgo.AfterEach(func(ctx context.Context) {
53 _ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment", metav1.DeleteOptions{})
54 _ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-unset", metav1.DeleteOptions{})
55 _ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-map-item-removal", metav1.DeleteOptions{})
56 _ = client.CoreV1().Pods(ns).Delete(ctx, "test-pod", metav1.DeleteOptions{})
57 })
58
59
64 framework.ConformanceIt("should detect unknown and duplicate fields of a typed object", func(ctx context.Context) {
65 ginkgo.By("apply creating a deployment")
66 invalidMetaDeployment := `{
67 "apiVersion": "apps/v1",
68 "kind": "Deployment",
69 "metadata": {
70 "name": "my-dep",
71 "labels": {"app": "nginx"}
72 },
73 "spec": {
74 "unknownField": "foo",
75 "replicas": 2,
76 "replicas": 3,
77 "selector": {
78 "matchLabels": {
79 "app": "nginx"
80 }
81 },
82 "template": {
83 "metadata": {
84 "labels": {
85 "app": "nginx"
86 }
87 },
88 "spec": {
89 "containers": [{
90 "name": "nginx",
91 "image": "nginx:latest"
92 }]
93 }
94 }
95 }
96 }`
97 _, err := client.CoreV1().RESTClient().Post().
98 AbsPath("/apis/apps/v1").
99 Namespace(ns).
100 Resource("deployments").
101 Param("fieldManager", "field_validation_mgr").
102 Param("fieldValidation", "Strict").
103 Body([]byte(invalidMetaDeployment)).
104 Do(ctx).
105 Get()
106 if !(strings.Contains(err.Error(), `strict decoding error: unknown field "spec.unknownField", duplicate field "spec.replicas"`)) {
107 framework.Failf("error missing unknown/duplicate field field, got: %v", err)
108 }
109
110 })
111
112
117 framework.ConformanceIt("should detect unknown metadata fields of a typed object", func(ctx context.Context) {
118 ginkgo.By("apply creating a deployment")
119 invalidMetaDeployment := `{
120 "apiVersion": "apps/v1",
121 "kind": "Deployment",
122 "metadata": {
123 "name": "my-dep",
124 "unknownMeta": "foo",
125 "labels": {"app": "nginx"}
126 },
127 "spec": {
128 "selector": {
129 "matchLabels": {
130 "app": "nginx"
131 }
132 },
133 "template": {
134 "metadata": {
135 "labels": {
136 "app": "nginx"
137 }
138 },
139 "spec": {
140 "containers": [{
141 "name": "nginx",
142 "image": "nginx:latest"
143 }]
144 }
145 }
146 }
147 }`
148 _, err := client.CoreV1().RESTClient().Post().
149 AbsPath("/apis/apps/v1").
150 Namespace(ns).
151 Resource("deployments").
152 Param("fieldManager", "field_validation_mgr").
153 Param("fieldValidation", "Strict").
154 Body([]byte(invalidMetaDeployment)).
155 Do(ctx).
156 Get()
157 if !(strings.Contains(err.Error(), `strict decoding error: unknown field "metadata.unknownMeta"`)) {
158 framework.Failf("error missing unknown metadata field, got: %v", err)
159 }
160
161 })
162
163
168 framework.ConformanceIt("should create/apply a valid CR for CRD with validation schema", func(ctx context.Context) {
169 config, err := framework.LoadConfig()
170 if err != nil {
171 framework.Failf("%s", err)
172 }
173 apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
174 if err != nil {
175 framework.Failf("%s", err)
176 }
177 dynamicClient, err := dynamic.NewForConfig(config)
178 if err != nil {
179 framework.Failf("%s", err)
180 }
181
182 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
183
184 var c apiextensionsv1.CustomResourceValidation
185 err = json.Unmarshal([]byte(`{
186 "openAPIV3Schema": {
187 "type": "object",
188 "properties": {
189 "spec": {
190 "type": "object",
191 "properties": {
192 "foo": {
193 "type": "string"
194 },
195 "cronSpec": {
196 "type": "string",
197 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
198 },
199 "ports": {
200 "type": "array",
201 "x-kubernetes-list-map-keys": [
202 "containerPort",
203 "protocol"
204 ],
205 "x-kubernetes-list-type": "map",
206 "items": {
207 "properties": {
208 "containerPort": {
209 "format": "int32",
210 "type": "integer"
211 },
212 "hostIP": {
213 "type": "string"
214 },
215 "hostPort": {
216 "format": "int32",
217 "type": "integer"
218 },
219 "name": {
220 "type": "string"
221 },
222 "protocol": {
223 "type": "string"
224 }
225 },
226 "required": [
227 "containerPort",
228 "protocol"
229 ],
230 "type": "object"
231 }
232 }
233 }
234 }
235 }
236 }
237 }`), &c)
238 if err != nil {
239 framework.Failf("%v", err)
240 }
241 for i := range noxuDefinition.Spec.Versions {
242 noxuDefinition.Spec.Versions[i].Schema = &c
243 }
244
245 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
246 if err != nil {
247 framework.Failf("cannot create crd %s", err)
248 }
249
250 defer func() {
251 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
252 framework.ExpectNoError(err, "deleting CustomResourceDefinition")
253 }()
254
255 kind := noxuDefinition.Spec.Names.Kind
256 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
257 name := "mytest"
258
259 rest := apiExtensionClient.Discovery().RESTClient()
260 yamlBody := []byte(fmt.Sprintf(`
261 apiVersion: %s
262 kind: %s
263 metadata:
264 name: %s
265 spec:
266 foo: foo1
267 cronSpec: "* * * * */5"
268 ports:
269 - name: x
270 containerPort: 80
271 protocol: TCP`, apiVersion, kind, name))
272 _, err = rest.Patch(types.ApplyPatchType).
273 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
274 Name(name).
275 Param("fieldManager", "field_validation_mgr").
276 Param("fieldValidation", "Strict").
277 Body(yamlBody).
278 DoRaw(ctx)
279 if err != nil {
280 framework.Failf("%v", err)
281 }
282 })
283
284
289 framework.ConformanceIt("should create/apply a CR with unknown fields for CRD with no validation schema", func(ctx context.Context) {
290 config, err := framework.LoadConfig()
291 if err != nil {
292 framework.Failf("%s", err)
293 }
294 apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
295 if err != nil {
296 framework.Failf("%s", err)
297 }
298 dynamicClient, err := dynamic.NewForConfig(config)
299 if err != nil {
300 framework.Failf("%s", err)
301 }
302
303 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
304
305 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
306 if err != nil {
307 framework.Failf("cannot create crd %s", err)
308 }
309
310 defer func() {
311 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
312 framework.ExpectNoError(err, "deleting CustomResourceDefinition")
313 }()
314
315 kind := noxuDefinition.Spec.Names.Kind
316 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
317 name := "mytest"
318
319 rest := apiExtensionClient.Discovery().RESTClient()
320 yamlBody := []byte(fmt.Sprintf(`
321 apiVersion: %s
322 kind: %s
323 metadata:
324 name: %s
325 spec:
326 unknown: uk1
327 cronSpec: "* * * * */5"
328 ports:
329 - name: x
330 containerPort: 80
331 protocol: TCP`, apiVersion, kind, name))
332 _, err = rest.Patch(types.ApplyPatchType).
333 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
334 Name(name).
335 Param("fieldManager", "field_validation_mgr").
336 Param("fieldValidation", "Strict").
337 Body(yamlBody).
338 DoRaw(ctx)
339 if err != nil {
340 framework.Failf("%v", err)
341 }
342
343 })
344
345
350 framework.ConformanceIt("should create/apply an invalid CR with extra properties for CRD with validation schema", func(ctx context.Context) {
351 config, err := framework.LoadConfig()
352 if err != nil {
353 framework.Failf("%s", err)
354 }
355 apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
356 if err != nil {
357 framework.Failf("%s", err)
358 }
359 dynamicClient, err := dynamic.NewForConfig(config)
360 if err != nil {
361 framework.Failf("%s", err)
362 }
363
364 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
365
366 var c apiextensionsv1.CustomResourceValidation
367 err = json.Unmarshal([]byte(`{
368 "openAPIV3Schema": {
369 "type": "object",
370 "properties": {
371 "spec": {
372 "type": "object",
373 "properties": {
374 "foo": {
375 "type": "string"
376 },
377 "cronSpec": {
378 "type": "string",
379 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
380 },
381 "ports": {
382 "type": "array",
383 "x-kubernetes-list-map-keys": [
384 "containerPort",
385 "protocol"
386 ],
387 "x-kubernetes-list-type": "map",
388 "items": {
389 "properties": {
390 "containerPort": {
391 "format": "int32",
392 "type": "integer"
393 },
394 "hostIP": {
395 "type": "string"
396 },
397 "hostPort": {
398 "format": "int32",
399 "type": "integer"
400 },
401 "name": {
402 "type": "string"
403 },
404 "protocol": {
405 "type": "string"
406 }
407 },
408 "required": [
409 "containerPort",
410 "protocol"
411 ],
412 "type": "object"
413 }
414 }
415 }
416 }
417 }
418 }
419 }`), &c)
420 if err != nil {
421 framework.Failf("%v", err)
422 }
423 klog.Warningf("props: %v\n", c.OpenAPIV3Schema)
424 for i := range noxuDefinition.Spec.Versions {
425 noxuDefinition.Spec.Versions[i].Schema = &c
426 }
427
428 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
429 if err != nil {
430 framework.Failf("cannot create crd %s", err)
431 }
432
433 defer func() {
434 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
435 framework.ExpectNoError(err, "deleting CustomResourceDefinition")
436 }()
437
438 kind := noxuDefinition.Spec.Names.Kind
439 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
440 name := "mytest"
441
442 rest := apiExtensionClient.Discovery().RESTClient()
443 yamlBody := []byte(fmt.Sprintf(`
444 apiVersion: %s
445 kind: %s
446 metadata:
447 name: %s
448 unknownField: unknown
449 spec:
450 foo: foo1
451 cronSpec: "* * * * */5"
452 ports:
453 - name: x
454 containerPort: 80
455 protocol: TCP`, apiVersion, kind, name))
456 result, err := rest.Patch(types.ApplyPatchType).
457 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
458 Name(name).
459 Param("fieldManager", "field_validation_mgr").
460 Param("fieldValidation", "Strict").
461 Body(yamlBody).
462 DoRaw(ctx)
463 if !(strings.Contains(string(result), `.unknownField: field not declared in schema`)) {
464 framework.Failf("error missing unknown field: %v:\n%v", err, string(result))
465 }
466 })
467
468
474 framework.ConformanceIt("should detect unknown metadata fields in both the root and embedded object of a CR", func(ctx context.Context) {
475 config, err := framework.LoadConfig()
476 if err != nil {
477 framework.Failf("%s", err)
478 }
479 apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
480 if err != nil {
481 framework.Failf("%s", err)
482 }
483 dynamicClient, err := dynamic.NewForConfig(config)
484 if err != nil {
485 framework.Failf("%s", err)
486 }
487
488 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
489
490 var c apiextensionsv1.CustomResourceValidation
491 err = json.Unmarshal([]byte(`{
492 "openAPIV3Schema": {
493 "type": "object",
494 "properties": {
495 "spec": {
496 "type": "object",
497 "x-kubernetes-preserve-unknown-fields": true,
498 "properties": {
499 "template": {
500 "type": "object",
501 "x-kubernetes-embedded-resource": true,
502 "properties": {
503 "metadata": {
504 "type": "object",
505 "properties": {
506 "name": {
507 "type": "string"
508 }
509 }
510 },
511 "spec": {
512 "type": "object"
513 }
514 }
515
516 },
517 "foo": {
518 "type": "string"
519 },
520 "cronSpec": {
521 "type": "string",
522 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
523 },
524 "ports": {
525 "type": "array",
526 "x-kubernetes-list-map-keys": [
527 "containerPort",
528 "protocol"
529 ],
530 "x-kubernetes-list-type": "map",
531 "items": {
532 "properties": {
533 "containerPort": {
534 "format": "int32",
535 "type": "integer"
536 },
537 "hostIP": {
538 "type": "string"
539 },
540 "hostPort": {
541 "format": "int32",
542 "type": "integer"
543 },
544 "name": {
545 "type": "string"
546 },
547 "protocol": {
548 "type": "string"
549 }
550 },
551 "required": [
552 "containerPort",
553 "protocol"
554 ],
555 "type": "object"
556 }
557 }
558 }
559 }
560 }
561 }
562 }`), &c)
563 if err != nil {
564 framework.Failf("%v", err)
565 }
566 for i := range noxuDefinition.Spec.Versions {
567 noxuDefinition.Spec.Versions[i].Schema = &c
568 }
569
570 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
571 if err != nil {
572 framework.Failf("cannot create crd %s", err)
573 }
574
575 defer func() {
576 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
577 framework.ExpectNoError(err, "deleting CustomResourceDefinition")
578 }()
579
580 kind := noxuDefinition.Spec.Names.Kind
581 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
582 name := "mytest"
583
584 rest := apiExtensionClient.Discovery().RESTClient()
585 yamlBody := []byte(fmt.Sprintf(`
586 apiVersion: %s
587 kind: %s
588 metadata:
589 name: %s
590 unknownMeta: unknown
591 spec:
592 template:
593 apiversion: foo/v1
594 kind: Sub
595 metadata:
596 unknownSubMeta: unknown
597 name: subobject
598 namespace: %s
599 foo: foo1
600 cronSpec: "* * * * */5"
601 ports:
602 - name: x
603 containerPort: 80
604 protocol: TCP`, apiVersion, kind, name, ns))
605 result, err := rest.Patch(types.ApplyPatchType).
606 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
607 Name(name).
608 Param("fieldManager", "field_validation_mgr").
609 Param("fieldValidation", "Strict").
610 Body(yamlBody).
611 DoRaw(ctx)
612 if !(strings.Contains(string(result), `.spec.template.metadata.unknownSubMeta: field not declared in schema`) || strings.Contains(string(result), `.metadata.unknownMeta: field not declared in schema`)) {
613 framework.Failf("error missing duplicate field: %v:\n%v", err, string(result))
614 }
615 })
616
617
622 framework.ConformanceIt("should detect duplicates in a CR when preserving unknown fields", func(ctx context.Context) {
623 config, err := framework.LoadConfig()
624 if err != nil {
625 framework.Failf("%s", err)
626 }
627 apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
628 if err != nil {
629 framework.Failf("%s", err)
630 }
631 dynamicClient, err := dynamic.NewForConfig(config)
632 if err != nil {
633 framework.Failf("%s", err)
634 }
635
636 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
637
638 var c apiextensionsv1.CustomResourceValidation
639 err = json.Unmarshal([]byte(`{
640 "openAPIV3Schema": {
641 "type": "object",
642 "properties": {
643 "spec": {
644 "type": "object",
645 "x-kubernetes-preserve-unknown-fields": true,
646 "properties": {
647 "foo": {
648 "type": "string"
649 },
650 "cronSpec": {
651 "type": "string",
652 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
653 },
654 "ports": {
655 "type": "array",
656 "x-kubernetes-list-map-keys": [
657 "containerPort",
658 "protocol"
659 ],
660 "x-kubernetes-list-type": "map",
661 "items": {
662 "properties": {
663 "containerPort": {
664 "format": "int32",
665 "type": "integer"
666 },
667 "hostIP": {
668 "type": "string"
669 },
670 "hostPort": {
671 "format": "int32",
672 "type": "integer"
673 },
674 "name": {
675 "type": "string"
676 },
677 "protocol": {
678 "type": "string"
679 }
680 },
681 "required": [
682 "containerPort",
683 "protocol"
684 ],
685 "type": "object"
686 }
687 }
688 }
689 }
690 }
691 }
692 }`), &c)
693 if err != nil {
694 framework.Failf("%s", err)
695 }
696 for i := range noxuDefinition.Spec.Versions {
697 noxuDefinition.Spec.Versions[i].Schema = &c
698 }
699
700 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
701 if err != nil {
702 framework.Failf("cannot create crd %s", err)
703 }
704
705 defer func() {
706 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
707 framework.ExpectNoError(err, "deleting CustomResourceDefinition")
708 }()
709
710 kind := noxuDefinition.Spec.Names.Kind
711 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
712 name := "mytest"
713
714 rest := apiExtensionClient.Discovery().RESTClient()
715 yamlBody := []byte(fmt.Sprintf(`
716 apiVersion: %s
717 kind: %s
718 metadata:
719 name: %s
720 spec:
721 unknown: uk1
722 foo: foo1
723 foo: foo2
724 cronSpec: "* * * * */5"
725 ports:
726 - name: x
727 containerPort: 80
728 protocol: TCP`, apiVersion, kind, name))
729 result, err := rest.Patch(types.ApplyPatchType).
730 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
731 Name(name).
732 Param("fieldManager", "field_validation_mgr").
733 Param("fieldValidation", "Strict").
734 Body(yamlBody).
735 DoRaw(ctx)
736 if !(strings.Contains(string(result), `line 9: key \"foo\" already set in map`)) {
737 framework.Failf("error missing duplicate field: %v:\n%v", err, string(result))
738 }
739 })
740 })
741
View as plain text