mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	Add integration tests for CRD validation rules feature
This commit is contained in:
		
							
								
								
									
										497
									
								
								test/integration/apiserver/crd_validation_expressions_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										497
									
								
								test/integration/apiserver/crd_validation_expressions_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,497 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package apiserver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
				
			||||||
 | 
						apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
				
			||||||
 | 
						"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 | 
				
			||||||
 | 
						"k8s.io/apiextensions-apiserver/test/integration/fixtures"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/json"
 | 
				
			||||||
 | 
						genericfeatures "k8s.io/apiserver/pkg/features"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/storage/names"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						"k8s.io/client-go/dynamic"
 | 
				
			||||||
 | 
						featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
 | 
						apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/test/integration/framework"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestCustomResourceValidatorsWithDisabledFeatureGate test that x-kubernetes-validations work as expected when the
 | 
				
			||||||
 | 
					// feature gate is disabled.
 | 
				
			||||||
 | 
					func TestCustomResourceValidatorsWithDisabledFeatureGate(t *testing.T) {
 | 
				
			||||||
 | 
						defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CustomResourceValidationExpressions, false)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiExtensionClient, err := clientset.NewForConfig(config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dynamicClient, err := dynamic.NewForConfig(config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("x-kubernetes-validations fields MUST be dropped from CRDs that are created when feature gate is disabled", func(t *testing.T) {
 | 
				
			||||||
 | 
							schemaWithFeatureGateOff := crdWithSchema(t, "WithFeatureGateOff", structuralSchemaWithValidators)
 | 
				
			||||||
 | 
							crdWithFeatureGateOff, err := fixtures.CreateNewV1CustomResourceDefinition(schemaWithFeatureGateOff, apiExtensionClient, dynamicClient)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							s := crdWithFeatureGateOff.Spec.Versions[0].Schema.OpenAPIV3Schema
 | 
				
			||||||
 | 
							if len(s.XValidations) != 0 {
 | 
				
			||||||
 | 
								t.Errorf("Expected CRD to have no x-kubernetes-validatons rules but got: %v", s.XValidations)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestCustomResourceValidators tests x-kubernetes-validations compile and validate as expected when the feature gate
 | 
				
			||||||
 | 
					// is enabled.
 | 
				
			||||||
 | 
					func TestCustomResourceValidators(t *testing.T) {
 | 
				
			||||||
 | 
						defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CustomResourceValidationExpressions, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer server.TearDownFn()
 | 
				
			||||||
 | 
						config := server.ClientConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						apiExtensionClient, err := clientset.NewForConfig(config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dynamicClient, err := dynamic.NewForConfig(config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("Structural schema", func(t *testing.T) {
 | 
				
			||||||
 | 
							structuralWithValidators := crdWithSchema(t, "Structural", structuralSchemaWithValidators)
 | 
				
			||||||
 | 
							crd, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							gvr := schema.GroupVersionResource{
 | 
				
			||||||
 | 
								Group:    crd.Spec.Group,
 | 
				
			||||||
 | 
								Version:  crd.Spec.Versions[0].Name,
 | 
				
			||||||
 | 
								Resource: crd.Spec.Names.Plural,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							crClient := dynamicClient.Resource(gvr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("CRD creation MUST allow data that is valid according to x-kubernetes-validations", func(t *testing.T) {
 | 
				
			||||||
 | 
								name1 := names.SimpleNameGenerator.GenerateName("cr-1")
 | 
				
			||||||
 | 
								_, err = crClient.Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{
 | 
				
			||||||
 | 
									"apiVersion": gvr.Group + "/" + gvr.Version,
 | 
				
			||||||
 | 
									"kind":       crd.Spec.Names.Kind,
 | 
				
			||||||
 | 
									"metadata": map[string]interface{}{
 | 
				
			||||||
 | 
										"name": name1,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"spec": map[string]interface{}{
 | 
				
			||||||
 | 
										"x":     int64(2),
 | 
				
			||||||
 | 
										"y":     int64(2),
 | 
				
			||||||
 | 
										"limit": int64(123),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}}, metav1.CreateOptions{})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Errorf("Failed to create custom resource: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							t.Run("custom resource create and update MUST NOT allow data that is invalid according to x-kubernetes-validations if the feature gate is enabled", func(t *testing.T) {
 | 
				
			||||||
 | 
								name1 := names.SimpleNameGenerator.GenerateName("cr-1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// a spec create that is invalid MUST fail validation
 | 
				
			||||||
 | 
								cr := &unstructured.Unstructured{Object: map[string]interface{}{
 | 
				
			||||||
 | 
									"apiVersion": gvr.Group + "/" + gvr.Version,
 | 
				
			||||||
 | 
									"kind":       crd.Spec.Names.Kind,
 | 
				
			||||||
 | 
									"metadata": map[string]interface{}{
 | 
				
			||||||
 | 
										"name": name1,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"spec": map[string]interface{}{
 | 
				
			||||||
 | 
										"x": int64(-1),
 | 
				
			||||||
 | 
										"y": int64(0),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// a spec create that is invalid MUST fail validation
 | 
				
			||||||
 | 
								_, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{})
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									t.Fatal("Expected create of invalid custom resource to fail")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									if !strings.Contains(err.Error(), "failed rule: self.spec.x + self.spec.y") {
 | 
				
			||||||
 | 
										t.Fatalf("Expected error to contain %s but got %v", "failed rule: self.spec.x + self.spec.y", err.Error())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// a spec create that is valid MUST pass validation
 | 
				
			||||||
 | 
								cr.Object["spec"] = map[string]interface{}{
 | 
				
			||||||
 | 
									"x":     int64(2),
 | 
				
			||||||
 | 
									"y":     int64(2),
 | 
				
			||||||
 | 
									"extra": "anything?",
 | 
				
			||||||
 | 
									"floatMap": map[string]interface{}{
 | 
				
			||||||
 | 
										"key1": 0.2,
 | 
				
			||||||
 | 
										"key2": 0.3,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"assocList": []interface{}{
 | 
				
			||||||
 | 
										map[string]interface{}{
 | 
				
			||||||
 | 
											"k": "a",
 | 
				
			||||||
 | 
											"v": "1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"limit": nil,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								cr, err := crClient.Create(context.TODO(), cr, metav1.CreateOptions{})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("Unexpected error creating custom resource: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// spec updates that are invalid MUST fail validation
 | 
				
			||||||
 | 
								cases := []struct {
 | 
				
			||||||
 | 
									name string
 | 
				
			||||||
 | 
									spec map[string]interface{}
 | 
				
			||||||
 | 
								}{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										name: "spec vs. status default value",
 | 
				
			||||||
 | 
										spec: map[string]interface{}{
 | 
				
			||||||
 | 
											"x": 3,
 | 
				
			||||||
 | 
											"y": -4,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										name: "nested string field",
 | 
				
			||||||
 | 
										spec: map[string]interface{}{
 | 
				
			||||||
 | 
											"extra": "something",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										name: "nested array",
 | 
				
			||||||
 | 
										spec: map[string]interface{}{
 | 
				
			||||||
 | 
											"floatMap": map[string]interface{}{
 | 
				
			||||||
 | 
												"key1": 0.1,
 | 
				
			||||||
 | 
												"key2": 0.2,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										name: "nested associative list",
 | 
				
			||||||
 | 
										spec: map[string]interface{}{
 | 
				
			||||||
 | 
											"assocList": []interface{}{
 | 
				
			||||||
 | 
												map[string]interface{}{
 | 
				
			||||||
 | 
													"k": "a",
 | 
				
			||||||
 | 
													"v": "2",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for _, tc := range cases {
 | 
				
			||||||
 | 
									t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
										cr.Object["spec"] = tc.spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										_, err = crClient.Update(context.TODO(), cr, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
										if err == nil {
 | 
				
			||||||
 | 
											t.Fatal("Expected invalid update of custom resource to fail")
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											if !strings.Contains(err.Error(), "failed rule") {
 | 
				
			||||||
 | 
												t.Fatalf("Expected error to contain %s but got %v", "failed rule", err.Error())
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// a status update that is invalid MUST fail validation
 | 
				
			||||||
 | 
								cr.Object["status"] = map[string]interface{}{
 | 
				
			||||||
 | 
									"z": int64(5),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_, err = crClient.UpdateStatus(context.TODO(), cr, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									t.Fatal("Expected invalid update of custom resource status to fail")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									if !strings.Contains(err.Error(), "failed rule: self.spec.x + self.spec.y") {
 | 
				
			||||||
 | 
										t.Fatalf("Expected error to contain %s but got %v", "failed rule: self.spec.x + self.spec.y", err.Error())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// a status update this is valid MUST pass validation
 | 
				
			||||||
 | 
								cr.Object["status"] = map[string]interface{}{
 | 
				
			||||||
 | 
									"z": int64(3),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, err = crClient.UpdateStatus(context.TODO(), cr, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatalf("Unexpected error updating custom resource status: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("CRD writes MUST fail for a non-structural schema containing x-kubernetes-validations", func(t *testing.T) {
 | 
				
			||||||
 | 
							// The only way for a non-structural schema to exist is for it to already be persisted in etcd as a non-structural CRD.
 | 
				
			||||||
 | 
							nonStructuralCRD, err := fixtures.CreateCRDUsingRemovedAPI(server.EtcdClient, server.EtcdStoragePrefix, nonStructuralCrdWithValidations(), apiExtensionClient, dynamicClient)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Unexpected error non-structural CRD by writing directly to etcd: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Double check that the schema is non-structural
 | 
				
			||||||
 | 
							crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), nonStructuralCRD.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Unexpected error: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							nonStructural := false
 | 
				
			||||||
 | 
							for _, c := range crd.Status.Conditions {
 | 
				
			||||||
 | 
								if c.Type == apiextensionsv1.NonStructuralSchema {
 | 
				
			||||||
 | 
									nonStructural = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !nonStructural {
 | 
				
			||||||
 | 
								t.Fatal("Expected CRD to be non-structural")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							//Try to change it
 | 
				
			||||||
 | 
							crd.Spec.Versions[0].Schema.OpenAPIV3Schema.XValidations = apiextensionsv1.ValidationRules{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Rule: "has(self.foo)",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								t.Fatal("Expected error")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("CRD creation MUST fail if a x-kubernetes-validations rule accesses a metadata field other than name", func(t *testing.T) {
 | 
				
			||||||
 | 
							structuralWithValidators := crdWithSchema(t, "InvalidStructuralMetadata", structuralSchemaWithInvalidMetadataValidators)
 | 
				
			||||||
 | 
							_, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								t.Error("Expected error creating custom resource but got none")
 | 
				
			||||||
 | 
							} else if !strings.Contains(err.Error(), "undefined field 'labels'") {
 | 
				
			||||||
 | 
								t.Errorf("Expected error to contain %s but got %v", "undefined field 'labels'", err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("CRD creation MUST pass if a x-kubernetes-validations rule accesses metadata.name", func(t *testing.T) {
 | 
				
			||||||
 | 
							structuralWithValidators := crdWithSchema(t, "ValidStructuralMetadata", structuralSchemaWithValidMetadataValidators)
 | 
				
			||||||
 | 
							_, err := fixtures.CreateNewV1CustomResourceDefinition(structuralWithValidators, apiExtensionClient, dynamicClient)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Error("Unexpected error creating custom resource but metadata validation rule")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func nonStructuralCrdWithValidations() *apiextensionsv1beta1.CustomResourceDefinition {
 | 
				
			||||||
 | 
						return &apiextensionsv1beta1.CustomResourceDefinition{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "foos.nonstructural.cr.bar.com",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
								Group:   "nonstructural.cr.bar.com",
 | 
				
			||||||
 | 
								Version: "v1",
 | 
				
			||||||
 | 
								Scope:   apiextensionsv1beta1.NamespaceScoped,
 | 
				
			||||||
 | 
								Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
									Plural: "foos",
 | 
				
			||||||
 | 
									Kind:   "Foo",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Validation: &apiextensionsv1beta1.CustomResourceValidation{
 | 
				
			||||||
 | 
									OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
 | 
				
			||||||
 | 
										Type: "object",
 | 
				
			||||||
 | 
										Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
 | 
				
			||||||
 | 
											"foo": {},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func crdWithSchema(t *testing.T, kind string, schemaJson []byte) *apiextensionsv1.CustomResourceDefinition {
 | 
				
			||||||
 | 
						plural := strings.ToLower(kind) + "s"
 | 
				
			||||||
 | 
						var c apiextensionsv1.CustomResourceValidation
 | 
				
			||||||
 | 
						err := json.Unmarshal(schemaJson, &c)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &apiextensionsv1.CustomResourceDefinition{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s.mygroup.example.com", plural)},
 | 
				
			||||||
 | 
							Spec: apiextensionsv1.CustomResourceDefinitionSpec{
 | 
				
			||||||
 | 
								Group: "mygroup.example.com",
 | 
				
			||||||
 | 
								Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
 | 
				
			||||||
 | 
									Name:    "v1beta1",
 | 
				
			||||||
 | 
									Served:  true,
 | 
				
			||||||
 | 
									Storage: true,
 | 
				
			||||||
 | 
									Schema:  &c,
 | 
				
			||||||
 | 
									Subresources: &apiextensionsv1.CustomResourceSubresources{
 | 
				
			||||||
 | 
										Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								Names: apiextensionsv1.CustomResourceDefinitionNames{
 | 
				
			||||||
 | 
									Plural: plural,
 | 
				
			||||||
 | 
									Kind:   kind,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Scope: apiextensionsv1.ClusterScoped,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var structuralSchemaWithValidators = []byte(`
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "openAPIV3Schema": {
 | 
				
			||||||
 | 
					    "description": "CRD with CEL validators",
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
						"x-kubernetes-validations": [
 | 
				
			||||||
 | 
						  {
 | 
				
			||||||
 | 
							"rule": "self.spec.x + self.spec.y >= (has(self.status) ? self.status.z : 0)"
 | 
				
			||||||
 | 
						  }
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					    "properties": {
 | 
				
			||||||
 | 
					      "spec": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
					          "x": {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
								"default": 0
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "y": {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
								"default": 0
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "extra": {
 | 
				
			||||||
 | 
								"type": "string",
 | 
				
			||||||
 | 
								"x-kubernetes-validations": [
 | 
				
			||||||
 | 
								  {
 | 
				
			||||||
 | 
									"rule": "self.startsWith('anything')"
 | 
				
			||||||
 | 
								  }
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
							  "floatMap": {
 | 
				
			||||||
 | 
								"type": "object",
 | 
				
			||||||
 | 
								"additionalProperties": { "type": "number" },
 | 
				
			||||||
 | 
								"x-kubernetes-validations": [
 | 
				
			||||||
 | 
								  {
 | 
				
			||||||
 | 
									"rule": "self.all(k, self[k] >= 0.2)"
 | 
				
			||||||
 | 
								  }
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
							  "assocList": {
 | 
				
			||||||
 | 
								"type": "array",
 | 
				
			||||||
 | 
								"items": {
 | 
				
			||||||
 | 
								  "type": "object",
 | 
				
			||||||
 | 
								  "properties": {
 | 
				
			||||||
 | 
								    "k": { "type": "string" },
 | 
				
			||||||
 | 
								    "v": { "type": "string" }
 | 
				
			||||||
 | 
								  },
 | 
				
			||||||
 | 
								  "required": ["k"]
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"x-kubernetes-list-type": "map",
 | 
				
			||||||
 | 
								"x-kubernetes-list-map-keys": ["k"],
 | 
				
			||||||
 | 
								"x-kubernetes-validations": [
 | 
				
			||||||
 | 
								  {
 | 
				
			||||||
 | 
									"rule": "self.exists(e, e.k == 'a' && e.v == '1')"
 | 
				
			||||||
 | 
								  }
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "limit": {
 | 
				
			||||||
 | 
								"nullable": true,
 | 
				
			||||||
 | 
								"x-kubernetes-validations": [
 | 
				
			||||||
 | 
								  {
 | 
				
			||||||
 | 
									"rule": "type(self) == int && self == 123"
 | 
				
			||||||
 | 
								  }
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"x-kubernetes-int-or-string": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "status": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
							"properties": {
 | 
				
			||||||
 | 
					          "z": {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
								"default": 0
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var structuralSchemaWithValidMetadataValidators = []byte(`
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "openAPIV3Schema": {
 | 
				
			||||||
 | 
					    "description": "CRD with CEL validators",
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
						"x-kubernetes-validations": [
 | 
				
			||||||
 | 
						  {
 | 
				
			||||||
 | 
							"rule": "self.metadata.name.size() > 3"
 | 
				
			||||||
 | 
						  }
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					    "properties": {
 | 
				
			||||||
 | 
						  "metadata": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
							  "name": { "type": "string" }
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "spec": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {}
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "status": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {}
 | 
				
			||||||
 | 
						  }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var structuralSchemaWithInvalidMetadataValidators = []byte(`
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "openAPIV3Schema": {
 | 
				
			||||||
 | 
					    "description": "CRD with CEL validators",
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
						"x-kubernetes-validations": [
 | 
				
			||||||
 | 
						  {
 | 
				
			||||||
 | 
							"rule": "self.metadata.labels.size() > 0"
 | 
				
			||||||
 | 
						  }
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					    "properties": {
 | 
				
			||||||
 | 
						  "metadata": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {
 | 
				
			||||||
 | 
							  "name": { "type": "string" }
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "spec": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {}
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "status": {
 | 
				
			||||||
 | 
					        "type": "object",
 | 
				
			||||||
 | 
					        "properties": {}
 | 
				
			||||||
 | 
						  }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}`)
 | 
				
			||||||
		Reference in New Issue
	
	Block a user