...

Source file src/k8s.io/kubernetes/test/e2e/apimachinery/field_validation.go

Documentation: k8s.io/kubernetes/test/e2e/apimachinery

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apimachinery
    18  
    19  import (
    20  	// ensure libs have a chance to initialize
    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  	/*
    60  		Release: v1.27
    61  		Testname: Server side field validation, typed object
    62  		Description: It should reject the request if a typed object has unknown or duplicate fields.
    63  	*/
    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  	/*
   113  		Release: v1.27
   114  		Testname: Server side field validation, typed unknown metadata
   115  		Description: It should reject the request if a typed object has unknown fields in the metadata.
   116  	*/
   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  	/*
   164  		Release: v1.27
   165  		Testname: Server side field validation, valid CR with validation schema
   166  		Description: When a CRD has a validation schema, it should succeed when a valid CR is applied.
   167  	*/
   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  	/*
   285  		Release: v1.27
   286  		Testname: Server side field validation, unknown fields CR no validation schema
   287  		Description: When a CRD does not have a validation schema, it should succeed when a CR with unknown fields is applied.
   288  	*/
   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  	/*
   346  		Release: v1.27
   347  		Testname: Server side field validation, unknown fields CR fails validation
   348  		Description: When a CRD does have a validation schema, it should reject CRs with unknown fields.
   349  	*/
   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  	/*
   469  		Release: v1.27
   470  		Testname: Server side field validation, unknown metadata
   471  		Description: The server should reject CRs with unknown metadata fields in both the root and embedded objects
   472  		of a CR.
   473  	*/
   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  	/*
   618  		Release: v1.27
   619  		Testname: Server side field validation, CR duplicates
   620  		Description: The server should reject CRs with duplicate fields even when preserving unknown fields.
   621  	*/
   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