...

Source file src/k8s.io/kubernetes/test/integration/apiserver/field_validation_test.go

Documentation: k8s.io/kubernetes/test/integration/apiserver

     1  /*
     2  Copyright 2021 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 apiserver
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"flag"
    23  	"fmt"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    29  	apiextensionsclient "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/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/client-go/dynamic"
    36  	clientset "k8s.io/client-go/kubernetes"
    37  	"k8s.io/client-go/rest"
    38  	"k8s.io/klog/v2"
    39  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    40  
    41  	"k8s.io/kubernetes/test/integration/framework"
    42  )
    43  
    44  var (
    45  	invalidBodyJSON = `
    46  	{
    47  		"apiVersion": "apps/v1",
    48  		"kind": "Deployment",
    49  		"metadata": {
    50  			"name": "dupename",
    51  			"name": "%s",
    52  			"labels": {"app": "nginx"},
    53  			"unknownMeta": "metaVal"
    54  		},
    55  		"spec": {
    56  			"unknown1": "val1",
    57  			"unknownDupe": "valDupe",
    58  			"unknownDupe": "valDupe2",
    59  			"paused": true,
    60  			"paused": false,
    61  			"selector": {
    62  				"matchLabels": {
    63  					"app": "nginx"
    64  				}
    65  			},
    66  			"template": {
    67  				"metadata": {
    68  					"labels": {
    69  						"app": "nginx"
    70  					}
    71  				},
    72  				"spec": {
    73  					"containers": [{
    74  						"name":  "nginx",
    75  						"image": "nginx:latest",
    76  						"unknownNested": "val1",
    77  						"imagePullPolicy": "Always",
    78  						"imagePullPolicy": "Never"
    79  					}]
    80  				}
    81  			}
    82  		}
    83  	}
    84  		`
    85  	validBodyJSON = `
    86  {
    87  	"apiVersion": "apps/v1",
    88  	"kind": "Deployment",
    89  	"metadata": {
    90  		"name": "%s",
    91  		"labels": {"app": "nginx"},
    92  		"annotations": {"a1": "foo", "a2": "bar"}
    93  	},
    94  	"spec": {
    95  		"selector": {
    96  			"matchLabels": {
    97  				"app": "nginx"
    98  			}
    99  		},
   100  		"template": {
   101  			"metadata": {
   102  				"labels": {
   103  					"app": "nginx"
   104  				}
   105  			},
   106  			"spec": {
   107  				"containers": [{
   108  					"name":  "nginx",
   109  					"image": "nginx:latest",
   110  					"imagePullPolicy": "Always"
   111  				}]
   112  			}
   113  		},
   114  		"replicas": 2
   115  	}
   116  }`
   117  
   118  	invalidBodyYAML = `apiVersion: apps/v1
   119  kind: Deployment
   120  metadata:
   121    name: dupename
   122    name: %s
   123    unknownMeta: metaVal
   124    labels:
   125      app: nginx
   126  spec:
   127    unknown1: val1
   128    unknownDupe: valDupe
   129    unknownDupe: valDupe2
   130    paused: true
   131    paused: false
   132    selector:
   133      matchLabels:
   134        app: nginx
   135    template:
   136      metadata:
   137        labels:
   138          app: nginx
   139      spec:
   140        containers:
   141        - name: nginx
   142          image: nginx:latest
   143          unknownNested: val1
   144          imagePullPolicy: Always
   145          imagePullPolicy: Never`
   146  
   147  	validBodyYAML = `apiVersion: apps/v1
   148  kind: Deployment
   149  metadata:
   150    name: %s
   151    labels:
   152      app: nginx
   153    annotations:
   154      a1: foo
   155      a2: bar
   156  spec:
   157    replicas: 2
   158    paused: true
   159    selector:
   160      matchLabels:
   161        app: nginx
   162    template:
   163      metadata:
   164        labels:
   165          app: nginx
   166      spec:
   167        containers:
   168        - name: nginx
   169          image: nginx:latest
   170          imagePullPolicy: Always`
   171  
   172  	applyInvalidBody = `{
   173  		"apiVersion": "apps/v1",
   174  		"kind": "Deployment",
   175  		"metadata": {
   176  			"name": "%s",
   177  			"labels": {"app": "nginx"}
   178  		},
   179  		"spec": {
   180  			"paused": false,
   181  			"paused": true,
   182  			"selector": {
   183  				"matchLabels": {
   184  					"app": "nginx"
   185  				}
   186  			},
   187  			"template": {
   188  				"metadata": {
   189  					"labels": {
   190  						"app": "nginx"
   191  					}
   192  				},
   193  				"spec": {
   194  					"containers": [{
   195  						"name":  "nginx",
   196  						"image": "nginx:latest",
   197  						"imagePullPolicy": "Never",
   198  						"imagePullPolicy": "Always"
   199  					}]
   200  				}
   201  			}
   202  		}
   203  	}`
   204  	applyValidBody = `
   205  {
   206  	"apiVersion": "apps/v1",
   207  	"kind": "Deployment",
   208  	"metadata": {
   209  		"name": "%s",
   210  		"labels": {"app": "nginx"},
   211  		"annotations": {"a1": "foo", "a2": "bar"}
   212  	},
   213  	"spec": {
   214  		"selector": {
   215  			"matchLabels": {
   216  				"app": "nginx"
   217  			}
   218  		},
   219  		"template": {
   220  			"metadata": {
   221  				"labels": {
   222  					"app": "nginx"
   223  				}
   224  			},
   225  			"spec": {
   226  				"containers": [{
   227  					"name":  "nginx",
   228  					"image": "nginx:latest",
   229  					"imagePullPolicy": "Always"
   230  				}]
   231  			}
   232  		},
   233  		"replicas": 3
   234  	}
   235  }`
   236  	crdInvalidBody = `
   237  {
   238  	"apiVersion": "%s",
   239  	"kind": "%s",
   240  	"metadata": {
   241  		"name": "dupename",
   242  		"name": "%s",
   243  		"unknownMeta": "metaVal",
   244  		"resourceVersion": "%s"
   245  	},
   246  	"spec": {
   247  		"unknown1": "val1",
   248  		"unknownDupe": "valDupe",
   249  		"unknownDupe": "valDupe2",
   250  		"knownField1": "val1",
   251  		"knownField1": "val2",
   252  			"ports": [{
   253  				"name": "portName",
   254  				"containerPort": 8080,
   255  				"protocol": "TCP",
   256  				"hostPort": 8081,
   257  				"hostPort": 8082,
   258  				"unknownNested": "val"
   259  			}],
   260  		"embeddedObj": {
   261  			"apiVersion": "v1",
   262  			"kind": "ConfigMap",
   263  			"metadata": {
   264  				"name": "my-cm",
   265  				"namespace": "my-ns",
   266  				"unknownEmbeddedMeta": "foo"
   267  			}
   268  		}
   269  	}
   270  }`
   271  
   272  	crdValidBody = `
   273  {
   274  	"apiVersion": "%s",
   275  	"kind": "%s",
   276  	"metadata": {
   277  		"name": "%s",
   278  		"resourceVersion": "%s"
   279  	},
   280  	"spec": {
   281  		"knownField1": "val1",
   282  			"ports": [{
   283  				"name": "portName",
   284  				"containerPort": 8080,
   285  				"protocol": "TCP",
   286  				"hostPort": 8081
   287  			}],
   288  		"embeddedObj": {
   289  			"apiVersion": "v1",
   290  			"kind": "ConfigMap",
   291  			"metadata": {
   292  				"name": "my-cm"
   293  			}
   294  		}
   295  	}
   296  }
   297  	`
   298  
   299  	crdInvalidBodyYAML = `
   300  apiVersion: "%s"
   301  kind: "%s"
   302  metadata:
   303    name: dupename
   304    name: "%s"
   305    resourceVersion: "%s"
   306    unknownMeta: metaVal
   307  spec:
   308    unknown1: val1
   309    unknownDupe: valDupe
   310    unknownDupe: valDupe2
   311    knownField1: val1
   312    knownField1: val2
   313    ports:
   314    - name: portName
   315      containerPort: 8080
   316      protocol: TCP
   317      hostPort: 8081
   318      hostPort: 8082
   319      unknownNested: val
   320    embeddedObj:
   321      apiVersion: v1
   322      kind: ConfigMap
   323      metadata:
   324        name: my-cm
   325        namespace: my-ns
   326        unknownEmbeddedMeta: foo`
   327  
   328  	crdValidBodyYAML = `
   329  apiVersion: "%s"
   330  kind: "%s"
   331  metadata:
   332    name: "%s"
   333    resourceVersion: "%s"
   334  spec:
   335    knownField1: val1
   336    ports:
   337    - name: portName
   338      containerPort: 8080
   339      protocol: TCP
   340      hostPort: 8081
   341    embeddedObj:
   342      apiVersion: v1
   343      kind: ConfigMap
   344      metadata:
   345        name: my-cm
   346        namespace: my-ns`
   347  
   348  	crdApplyInvalidBody = `
   349  {
   350  	"apiVersion": "%s",
   351  	"kind": "%s",
   352  	"metadata": {
   353  		"name": "%s"
   354  	},
   355  	"spec": {
   356  		"knownField1": "val1",
   357  		"knownField1": "val2",
   358  		"ports": [{
   359  			"name": "portName",
   360  			"containerPort": 8080,
   361  			"protocol": "TCP",
   362  			"hostPort": 8081,
   363  			"hostPort": 8082
   364  		}],
   365  		"embeddedObj": {
   366  			"apiVersion": "v1",
   367  			"kind": "ConfigMap",
   368  			"metadata": {
   369  				"name": "my-cm",
   370  				"namespace": "my-ns"
   371  			}
   372  		}
   373  	}
   374  }`
   375  
   376  	crdApplyValidBody = `
   377  {
   378  	"apiVersion": "%s",
   379  	"kind": "%s",
   380  	"metadata": {
   381  		"name": "%s"
   382  	},
   383  	"spec": {
   384  		"knownField1": "val1",
   385  		"ports": [{
   386  			"name": "portName",
   387  			"containerPort": 8080,
   388  			"protocol": "TCP",
   389  			"hostPort": 8082
   390  		}],
   391  		"embeddedObj": {
   392  			"apiVersion": "v1",
   393  			"kind": "ConfigMap",
   394  			"metadata": {
   395  				"name": "my-cm",
   396  				"namespace": "my-ns"
   397  			}
   398  		}
   399  	}
   400  }`
   401  
   402  	crdApplyValidBody2 = `
   403  {
   404  	"apiVersion": "%s",
   405  	"kind": "%s",
   406  	"metadata": {
   407  		"name": "%s"
   408  	},
   409  	"spec": {
   410  		"knownField1": "val2",
   411  		"ports": [{
   412  			"name": "portName",
   413  			"containerPort": 8080,
   414  			"protocol": "TCP",
   415  			"hostPort": 8083
   416  		}],
   417  		"embeddedObj": {
   418  			"apiVersion": "v1",
   419  			"kind": "ConfigMap",
   420  			"metadata": {
   421  				"name": "my-cm",
   422  				"namespace": "my-ns"
   423  			}
   424  		}
   425  	}
   426  }`
   427  
   428  	crdApplyFinalizerBody = `
   429  {
   430  	"apiVersion": "%s",
   431  	"kind": "%s",
   432  	"metadata": {
   433  		"name": "%s",
   434  		"finalizers": %s
   435  	},
   436  	"spec": {
   437  		"knownField1": "val1",
   438  		"ports": [{
   439  			"name": "portName",
   440  			"containerPort": 8080,
   441  			"protocol": "TCP",
   442  			"hostPort": 8082
   443  		}],
   444  		"embeddedObj": {
   445  			"apiVersion": "v1",
   446  			"kind": "ConfigMap",
   447  			"metadata": {
   448  				"name": "my-cm",
   449  				"namespace": "my-ns"
   450  			}
   451  		}
   452  	}
   453  }`
   454  
   455  	patchYAMLBody = `
   456  apiVersion: %s
   457  kind: %s
   458  metadata:
   459    name: %s
   460    finalizers:
   461    - test/finalizer
   462  spec:
   463    cronSpec: "* * * * */5"
   464    ports:
   465    - name: x
   466      containerPort: 80
   467      protocol: TCP
   468  `
   469  
   470  	crdSchemaBase = `
   471  {
   472  		"openAPIV3Schema": {
   473  			"type": "object",
   474  			"properties": {
   475  				"spec": {
   476  					"type": "object",
   477  					%s
   478  					"properties": {
   479  						"cronSpec": {
   480  							"type": "string",
   481  							"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
   482  						},
   483  						"knownField1": {
   484  							"type": "string"
   485  						},
   486  						"embeddedObj": {
   487  							"x-kubernetes-embedded-resource": true,
   488  							"type": "object",
   489  							"properties": {
   490  								"apiversion": {
   491  									"type": "string"
   492  								},
   493  								"kind": {
   494  									"type": "string"
   495  								},
   496  								"metadata": {
   497  									"type": "object"
   498  								}
   499  							}
   500  						},
   501  						"ports": {
   502  							"type": "array",
   503  							"x-kubernetes-list-map-keys": [
   504  								"containerPort",
   505  								"protocol"
   506  							],
   507  							"x-kubernetes-list-type": "map",
   508  							"items": {
   509  								"properties": {
   510  									"containerPort": {
   511  										"format": "int32",
   512  										"type": "integer"
   513  									},
   514  									"hostIP": {
   515  										"type": "string"
   516  									},
   517  									"hostPort": {
   518  										"format": "int32",
   519  										"type": "integer"
   520  									},
   521  									"name": {
   522  										"type": "string"
   523  									},
   524  									"protocol": {
   525  										"type": "string"
   526  									}
   527  								},
   528  								"required": [
   529  									"containerPort",
   530  									"protocol"
   531  								],
   532  								"type": "object"
   533  							}
   534  						}
   535  					}
   536  				}
   537  			}
   538  		}
   539  	}
   540  	`
   541  )
   542  
   543  func TestFieldValidation(t *testing.T) {
   544  	server, err := kubeapiservertesting.StartTestServer(t, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
   545  	if err != nil {
   546  		t.Fatal(err)
   547  	}
   548  	config := server.ClientConfig
   549  	defer server.TearDownFn()
   550  
   551  	// don't log warnings, tests inspect them in the responses directly
   552  	config.WarningHandler = rest.NoWarnings{}
   553  
   554  	schemaCRD := setupCRD(t, config, "schema.example.com", false)
   555  	schemaGVR := schema.GroupVersionResource{
   556  		Group:    schemaCRD.Spec.Group,
   557  		Version:  schemaCRD.Spec.Versions[0].Name,
   558  		Resource: schemaCRD.Spec.Names.Plural,
   559  	}
   560  	schemaGVK := schema.GroupVersionKind{
   561  		Group:   schemaCRD.Spec.Group,
   562  		Version: schemaCRD.Spec.Versions[0].Name,
   563  		Kind:    schemaCRD.Spec.Names.Kind,
   564  	}
   565  
   566  	schemalessCRD := setupCRD(t, config, "schemaless.example.com", true)
   567  	schemalessGVR := schema.GroupVersionResource{
   568  		Group:    schemalessCRD.Spec.Group,
   569  		Version:  schemalessCRD.Spec.Versions[0].Name,
   570  		Resource: schemalessCRD.Spec.Names.Plural,
   571  	}
   572  	schemalessGVK := schema.GroupVersionKind{
   573  		Group:   schemalessCRD.Spec.Group,
   574  		Version: schemalessCRD.Spec.Versions[0].Name,
   575  		Kind:    schemalessCRD.Spec.Names.Kind,
   576  	}
   577  
   578  	client := clientset.NewForConfigOrDie(config)
   579  	rest := client.Discovery().RESTClient()
   580  
   581  	t.Run("Post", func(t *testing.T) { testFieldValidationPost(t, client) })
   582  	t.Run("Put", func(t *testing.T) { testFieldValidationPut(t, client) })
   583  	t.Run("PatchTyped", func(t *testing.T) { testFieldValidationPatchTyped(t, client) })
   584  	t.Run("SMP", func(t *testing.T) { testFieldValidationSMP(t, client) })
   585  	t.Run("ApplyCreate", func(t *testing.T) { testFieldValidationApplyCreate(t, client) })
   586  	t.Run("ApplyUpdate", func(t *testing.T) { testFieldValidationApplyUpdate(t, client) })
   587  
   588  	t.Run("PostCRD", func(t *testing.T) { testFieldValidationPostCRD(t, rest, schemaGVK, schemaGVR) })
   589  	t.Run("PutCRD", func(t *testing.T) { testFieldValidationPutCRD(t, rest, schemaGVK, schemaGVR) })
   590  	t.Run("PatchCRD", func(t *testing.T) { testFieldValidationPatchCRD(t, rest, schemaGVK, schemaGVR) })
   591  	t.Run("ApplyCreateCRD", func(t *testing.T) { testFieldValidationApplyCreateCRD(t, rest, schemaGVK, schemaGVR) })
   592  	t.Run("ApplyUpdateCRD", func(t *testing.T) { testFieldValidationApplyUpdateCRD(t, rest, schemaGVK, schemaGVR) })
   593  
   594  	t.Run("PostCRDSchemaless", func(t *testing.T) { testFieldValidationPostCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
   595  	t.Run("PutCRDSchemaless", func(t *testing.T) { testFieldValidationPutCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
   596  	t.Run("PatchCRDSchemaless", func(t *testing.T) { testFieldValidationPatchCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
   597  	t.Run("ApplyCreateCRDSchemaless", func(t *testing.T) { testFieldValidationApplyCreateCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
   598  	t.Run("ApplyUpdateCRDSchemaless", func(t *testing.T) { testFieldValidationApplyUpdateCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
   599  	t.Run("testFinalizerValidationApplyCreateCRD", func(t *testing.T) {
   600  		testFinalizerValidationApplyCreateAndUpdateCRD(t, rest, schemalessGVK, schemalessGVR)
   601  	})
   602  }
   603  
   604  // testFieldValidationPost tests POST requests containing unknown fields with
   605  // strict and non-strict field validation.
   606  func testFieldValidationPost(t *testing.T, client clientset.Interface) {
   607  	var testcases = []struct {
   608  		name                   string
   609  		bodyBase               string
   610  		opts                   metav1.CreateOptions
   611  		contentType            string
   612  		strictDecodingError    string
   613  		strictDecodingWarnings []string
   614  	}{
   615  		{
   616  			name: "post-strict-validation",
   617  			opts: metav1.CreateOptions{
   618  				FieldValidation: "Strict",
   619  			},
   620  			bodyBase:            invalidBodyJSON,
   621  			strictDecodingError: `strict decoding error: duplicate field "metadata.name", unknown field "metadata.unknownMeta", unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
   622  		},
   623  		{
   624  			name: "post-warn-validation",
   625  			opts: metav1.CreateOptions{
   626  				FieldValidation: "Warn",
   627  			},
   628  			bodyBase: invalidBodyJSON,
   629  			strictDecodingWarnings: []string{
   630  				`duplicate field "metadata.name"`,
   631  				`unknown field "metadata.unknownMeta"`,
   632  				`unknown field "spec.unknown1"`,
   633  				`unknown field "spec.unknownDupe"`,
   634  				`duplicate field "spec.paused"`,
   635  				// note: fields that are both unknown
   636  				// and duplicated will only be detected
   637  				// as unknown for typed resources.
   638  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   639  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
   640  			},
   641  		},
   642  		{
   643  			name: "post-ignore-validation",
   644  			opts: metav1.CreateOptions{
   645  				FieldValidation: "Ignore",
   646  			},
   647  			bodyBase: invalidBodyJSON,
   648  		},
   649  		{
   650  			name:     "post-no-validation",
   651  			bodyBase: invalidBodyJSON,
   652  			strictDecodingWarnings: []string{
   653  				`duplicate field "metadata.name"`,
   654  				`unknown field "metadata.unknownMeta"`,
   655  				`unknown field "spec.unknown1"`,
   656  				`unknown field "spec.unknownDupe"`,
   657  				`duplicate field "spec.paused"`,
   658  				// note: fields that are both unknown
   659  				// and duplicated will only be detected
   660  				// as unknown for typed resources.
   661  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   662  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
   663  			},
   664  		},
   665  		{
   666  			name: "post-strict-validation-yaml",
   667  			opts: metav1.CreateOptions{
   668  				FieldValidation: "Strict",
   669  			},
   670  			bodyBase:    invalidBodyYAML,
   671  			contentType: "application/yaml",
   672  			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
   673    line 5: key "name" already set in map
   674    line 12: key "unknownDupe" already set in map
   675    line 14: key "paused" already set in map
   676    line 28: key "imagePullPolicy" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
   677  		},
   678  		{
   679  			name: "post-warn-validation-yaml",
   680  			opts: metav1.CreateOptions{
   681  				FieldValidation: "Warn",
   682  			},
   683  			bodyBase:    invalidBodyYAML,
   684  			contentType: "application/yaml",
   685  			strictDecodingWarnings: []string{
   686  				`line 5: key "name" already set in map`,
   687  				`line 12: key "unknownDupe" already set in map`,
   688  				`line 14: key "paused" already set in map`,
   689  				`line 28: key "imagePullPolicy" already set in map`,
   690  				`unknown field "metadata.unknownMeta"`,
   691  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   692  				`unknown field "spec.unknown1"`,
   693  				`unknown field "spec.unknownDupe"`,
   694  			},
   695  		},
   696  		{
   697  			name: "post-ignore-validation-yaml",
   698  			opts: metav1.CreateOptions{
   699  				FieldValidation: "Ignore",
   700  			},
   701  			bodyBase:    invalidBodyYAML,
   702  			contentType: "application/yaml",
   703  		},
   704  		{
   705  			name:        "post-no-validation-yaml",
   706  			bodyBase:    invalidBodyYAML,
   707  			contentType: "application/yaml",
   708  			strictDecodingWarnings: []string{
   709  				`line 5: key "name" already set in map`,
   710  				`line 12: key "unknownDupe" already set in map`,
   711  				`line 14: key "paused" already set in map`,
   712  				`line 28: key "imagePullPolicy" already set in map`,
   713  				`unknown field "metadata.unknownMeta"`,
   714  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   715  				`unknown field "spec.unknown1"`,
   716  				`unknown field "spec.unknownDupe"`,
   717  			},
   718  		},
   719  	}
   720  
   721  	for _, tc := range testcases {
   722  		t.Run(tc.name, func(t *testing.T) {
   723  			klog.Warningf("running tc named: %s", tc.name)
   724  			body := []byte(fmt.Sprintf(tc.bodyBase, fmt.Sprintf("test-deployment-%s", tc.name)))
   725  			req := client.CoreV1().RESTClient().Post().
   726  				AbsPath("/apis/apps/v1").
   727  				Namespace("default").
   728  				Resource("deployments").
   729  				SetHeader("Content-Type", tc.contentType).
   730  				VersionedParams(&tc.opts, metav1.ParameterCodec)
   731  			result := req.Body(body).Do(context.TODO())
   732  			if result.Error() == nil && tc.strictDecodingError != "" {
   733  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
   734  			}
   735  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
   736  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
   737  			}
   738  
   739  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
   740  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
   741  			}
   742  			for i, strictWarn := range tc.strictDecodingWarnings {
   743  				if strictWarn != result.Warnings()[i].Text {
   744  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
   745  				}
   746  
   747  			}
   748  		})
   749  	}
   750  }
   751  
   752  // testFieldValidationPut tests PUT requests
   753  // that update existing objects with unknown fields
   754  // for both strict and non-strict field validation.
   755  func testFieldValidationPut(t *testing.T, client clientset.Interface) {
   756  	deployName := "test-deployment-put"
   757  	postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
   758  
   759  	if _, err := client.CoreV1().RESTClient().Post().
   760  		AbsPath("/apis/apps/v1").
   761  		Namespace("default").
   762  		Resource("deployments").
   763  		Body(postBody).
   764  		DoRaw(context.TODO()); err != nil {
   765  		t.Fatalf("failed to create initial deployment: %v", err)
   766  	}
   767  
   768  	var testcases = []struct {
   769  		name                   string
   770  		opts                   metav1.UpdateOptions
   771  		putBodyBase            string
   772  		contentType            string
   773  		strictDecodingError    string
   774  		strictDecodingWarnings []string
   775  	}{
   776  		{
   777  			name: "put-strict-validation",
   778  			opts: metav1.UpdateOptions{
   779  				FieldValidation: "Strict",
   780  			},
   781  			putBodyBase:         invalidBodyJSON,
   782  			strictDecodingError: `strict decoding error: duplicate field "metadata.name", unknown field "metadata.unknownMeta", unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
   783  		},
   784  		{
   785  			name: "put-warn-validation",
   786  			opts: metav1.UpdateOptions{
   787  				FieldValidation: "Warn",
   788  			},
   789  			putBodyBase: invalidBodyJSON,
   790  			strictDecodingWarnings: []string{
   791  				`duplicate field "metadata.name"`,
   792  				`unknown field "metadata.unknownMeta"`,
   793  				`unknown field "spec.unknown1"`,
   794  				`unknown field "spec.unknownDupe"`,
   795  				`duplicate field "spec.paused"`,
   796  				// note: fields that are both unknown
   797  				// and duplicated will only be detected
   798  				// as unknown for typed resources.
   799  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   800  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
   801  			},
   802  		},
   803  		{
   804  			name: "put-ignore-validation",
   805  			opts: metav1.UpdateOptions{
   806  				FieldValidation: "Ignore",
   807  			},
   808  			putBodyBase: invalidBodyJSON,
   809  		},
   810  		{
   811  			name:        "put-no-validation",
   812  			putBodyBase: invalidBodyJSON,
   813  			strictDecodingWarnings: []string{
   814  				`duplicate field "metadata.name"`,
   815  				`unknown field "metadata.unknownMeta"`,
   816  				`unknown field "spec.unknown1"`,
   817  				`unknown field "spec.unknownDupe"`,
   818  				`duplicate field "spec.paused"`,
   819  				// note: fields that are both unknown
   820  				// and duplicated will only be detected
   821  				// as unknown for typed resources.
   822  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   823  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
   824  			},
   825  		},
   826  		{
   827  			name: "put-strict-validation-yaml",
   828  			opts: metav1.UpdateOptions{
   829  				FieldValidation: "Strict",
   830  			},
   831  			putBodyBase: invalidBodyYAML,
   832  			contentType: "application/yaml",
   833  			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
   834    line 5: key "name" already set in map
   835    line 12: key "unknownDupe" already set in map
   836    line 14: key "paused" already set in map
   837    line 28: key "imagePullPolicy" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
   838  		},
   839  		{
   840  			name: "put-warn-validation-yaml",
   841  			opts: metav1.UpdateOptions{
   842  				FieldValidation: "Warn",
   843  			},
   844  			putBodyBase: invalidBodyYAML,
   845  			contentType: "application/yaml",
   846  			strictDecodingWarnings: []string{
   847  				`line 5: key "name" already set in map`,
   848  				`line 12: key "unknownDupe" already set in map`,
   849  				`line 14: key "paused" already set in map`,
   850  				`line 28: key "imagePullPolicy" already set in map`,
   851  				`unknown field "metadata.unknownMeta"`,
   852  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   853  				`unknown field "spec.unknown1"`,
   854  				`unknown field "spec.unknownDupe"`,
   855  			},
   856  		},
   857  		{
   858  			name: "put-ignore-validation-yaml",
   859  			opts: metav1.UpdateOptions{
   860  				FieldValidation: "Ignore",
   861  			},
   862  			putBodyBase: invalidBodyYAML,
   863  			contentType: "application/yaml",
   864  		},
   865  		{
   866  			name:        "put-no-validation-yaml",
   867  			putBodyBase: invalidBodyYAML,
   868  			contentType: "application/yaml",
   869  			strictDecodingWarnings: []string{
   870  				`line 5: key "name" already set in map`,
   871  				`line 12: key "unknownDupe" already set in map`,
   872  				`line 14: key "paused" already set in map`,
   873  				`line 28: key "imagePullPolicy" already set in map`,
   874  				`unknown field "metadata.unknownMeta"`,
   875  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
   876  				`unknown field "spec.unknown1"`,
   877  				`unknown field "spec.unknownDupe"`,
   878  			},
   879  		},
   880  	}
   881  
   882  	for _, tc := range testcases {
   883  		t.Run(tc.name, func(t *testing.T) {
   884  			putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
   885  			req := client.CoreV1().RESTClient().Put().
   886  				AbsPath("/apis/apps/v1").
   887  				Namespace("default").
   888  				Resource("deployments").
   889  				SetHeader("Content-Type", tc.contentType).
   890  				Name(deployName).
   891  				VersionedParams(&tc.opts, metav1.ParameterCodec)
   892  			result := req.Body([]byte(putBody)).Do(context.TODO())
   893  			if result.Error() == nil && tc.strictDecodingError != "" {
   894  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
   895  			}
   896  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
   897  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
   898  			}
   899  
   900  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
   901  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
   902  			}
   903  			for i, strictWarn := range tc.strictDecodingWarnings {
   904  				if strictWarn != result.Warnings()[i].Text {
   905  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
   906  				}
   907  
   908  			}
   909  		})
   910  	}
   911  }
   912  
   913  // testFieldValidationPatchTyped tests merge-patch and json-patch requests containing unknown fields with
   914  // strict and non-strict field validation for typed objects.
   915  func testFieldValidationPatchTyped(t *testing.T, client clientset.Interface) {
   916  	deployName := "test-deployment-patch-typed"
   917  	postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
   918  
   919  	if _, err := client.CoreV1().RESTClient().Post().
   920  		AbsPath("/apis/apps/v1").
   921  		Namespace("default").
   922  		Resource("deployments").
   923  		Body(postBody).
   924  		DoRaw(context.TODO()); err != nil {
   925  		t.Fatalf("failed to create initial deployment: %v", err)
   926  	}
   927  
   928  	mergePatchBody := `
   929  {
   930  	"spec": {
   931  		"unknown1": "val1",
   932  		"unknownDupe": "valDupe",
   933  		"unknownDupe": "valDupe2",
   934  		"paused": true,
   935  		"paused": false,
   936  		"template": {
   937  			"spec": {
   938  				"containers": [{
   939  					"name": "nginx",
   940  					"image": "nginx:latest",
   941  					"unknownNested": "val1",
   942  					"imagePullPolicy": "Always",
   943  					"imagePullPolicy": "Never"
   944  				}]
   945  			}
   946  		}
   947  	}
   948  }
   949  	`
   950  	jsonPatchBody := `
   951  			[
   952  				{"op": "add", "path": "/spec/unknown1", "value": "val1", "foo":"bar"},
   953  				{"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val1"},
   954  				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
   955  				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
   956  				{"op": "add", "path": "/spec/paused", "value": true},
   957  				{"op": "add", "path": "/spec/paused", "value": false},
   958  				{"op": "add", "path": "/spec/template/spec/containers/0/unknownNested", "value": "val1"},
   959  				{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Always"},
   960  				{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"}
   961  			]
   962  			`
   963  	// non-conflicting mergePatch has issues with the patch (duplicate fields),
   964  	// but doesn't conflict with the existing object it's being patched to
   965  	nonconflictingMergePatchBody := `
   966  {
   967  	"spec": {
   968  		"paused": true,
   969  		"paused": false,
   970  		"template": {
   971  			"spec": {
   972  				"containers": [{
   973  					"name": "nginx",
   974  					"image": "nginx:latest",
   975  					"imagePullPolicy": "Always",
   976  					"imagePullPolicy": "Never"
   977  				}]
   978  			}
   979  		}
   980  	}
   981  }
   982  			`
   983  	var testcases = []struct {
   984  		name                   string
   985  		opts                   metav1.PatchOptions
   986  		patchType              types.PatchType
   987  		body                   string
   988  		strictDecodingError    string
   989  		strictDecodingWarnings []string
   990  	}{
   991  		{
   992  			name: "merge-patch-strict-validation",
   993  			opts: metav1.PatchOptions{
   994  				FieldValidation: "Strict",
   995  			},
   996  			patchType:           types.MergePatchType,
   997  			body:                mergePatchBody,
   998  			strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
   999  		},
  1000  		{
  1001  			name: "merge-patch-warn-validation",
  1002  			opts: metav1.PatchOptions{
  1003  				FieldValidation: "Warn",
  1004  			},
  1005  			patchType: types.MergePatchType,
  1006  			body:      mergePatchBody,
  1007  			strictDecodingWarnings: []string{
  1008  				`duplicate field "spec.unknownDupe"`,
  1009  				`duplicate field "spec.paused"`,
  1010  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1011  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
  1012  				`unknown field "spec.unknown1"`,
  1013  				`unknown field "spec.unknownDupe"`,
  1014  			},
  1015  		},
  1016  		{
  1017  			name: "merge-patch-ignore-validation",
  1018  			opts: metav1.PatchOptions{
  1019  				FieldValidation: "Ignore",
  1020  			},
  1021  			patchType: types.MergePatchType,
  1022  			body:      mergePatchBody,
  1023  		},
  1024  		{
  1025  			name:      "merge-patch-no-validation",
  1026  			patchType: types.MergePatchType,
  1027  			body:      mergePatchBody,
  1028  			strictDecodingWarnings: []string{
  1029  				`duplicate field "spec.unknownDupe"`,
  1030  				`duplicate field "spec.paused"`,
  1031  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1032  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
  1033  				`unknown field "spec.unknown1"`,
  1034  				`unknown field "spec.unknownDupe"`,
  1035  			},
  1036  		},
  1037  		{
  1038  			name:      "json-patch-strict-validation",
  1039  			patchType: types.JSONPatchType,
  1040  			opts: metav1.PatchOptions{
  1041  				FieldValidation: "Strict",
  1042  			},
  1043  			body:                jsonPatchBody,
  1044  			strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknown3", unknown field "spec.unknownDupe"`,
  1045  		},
  1046  		{
  1047  			name:      "json-patch-warn-validation",
  1048  			patchType: types.JSONPatchType,
  1049  			opts: metav1.PatchOptions{
  1050  				FieldValidation: "Warn",
  1051  			},
  1052  			body: jsonPatchBody,
  1053  			strictDecodingWarnings: []string{
  1054  				// note: duplicate fields in the patch itself
  1055  				// are dropped by the
  1056  				// evanphx/json-patch library and is expected.
  1057  				// Duplicate fields in the json patch ops
  1058  				// themselves can be detected though
  1059  				`json patch unknown field "[0].foo"`,
  1060  				`json patch duplicate field "[1].path"`,
  1061  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
  1062  				`unknown field "spec.unknown1"`,
  1063  				`unknown field "spec.unknown3"`,
  1064  				`unknown field "spec.unknownDupe"`,
  1065  			},
  1066  		},
  1067  		{
  1068  			name:      "json-patch-ignore-validation",
  1069  			patchType: types.JSONPatchType,
  1070  			opts: metav1.PatchOptions{
  1071  				FieldValidation: "Ignore",
  1072  			},
  1073  			body: jsonPatchBody,
  1074  		},
  1075  		{
  1076  			name:      "json-patch-no-validation",
  1077  			patchType: types.JSONPatchType,
  1078  			body:      jsonPatchBody,
  1079  			strictDecodingWarnings: []string{
  1080  				// note: duplicate fields in the patch itself
  1081  				// are dropped by the
  1082  				// evanphx/json-patch library and is expected.
  1083  				// Duplicate fields in the json patch ops
  1084  				// themselves can be detected though
  1085  				`json patch unknown field "[0].foo"`,
  1086  				`json patch duplicate field "[1].path"`,
  1087  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
  1088  				`unknown field "spec.unknown1"`,
  1089  				`unknown field "spec.unknown3"`,
  1090  				`unknown field "spec.unknownDupe"`,
  1091  			},
  1092  		},
  1093  		{
  1094  			name: "nonconflicting-merge-patch-strict-validation",
  1095  			opts: metav1.PatchOptions{
  1096  				FieldValidation: "Strict",
  1097  			},
  1098  			patchType:           types.MergePatchType,
  1099  			body:                nonconflictingMergePatchBody,
  1100  			strictDecodingError: `strict decoding error: duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1101  		},
  1102  		{
  1103  			name: "nonconflicting-merge-patch-warn-validation",
  1104  			opts: metav1.PatchOptions{
  1105  				FieldValidation: "Warn",
  1106  			},
  1107  			patchType: types.MergePatchType,
  1108  			body:      nonconflictingMergePatchBody,
  1109  			strictDecodingWarnings: []string{
  1110  				`duplicate field "spec.paused"`,
  1111  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1112  			},
  1113  		},
  1114  		{
  1115  			name: "nonconflicting-merge-patch-ignore-validation",
  1116  			opts: metav1.PatchOptions{
  1117  				FieldValidation: "Ignore",
  1118  			},
  1119  			patchType: types.MergePatchType,
  1120  			body:      nonconflictingMergePatchBody,
  1121  		},
  1122  		{
  1123  			name:      "nonconflicting-merge-patch-no-validation",
  1124  			patchType: types.MergePatchType,
  1125  			body:      nonconflictingMergePatchBody,
  1126  			strictDecodingWarnings: []string{
  1127  				`duplicate field "spec.paused"`,
  1128  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1129  			},
  1130  		},
  1131  	}
  1132  
  1133  	for _, tc := range testcases {
  1134  		t.Run(tc.name, func(t *testing.T) {
  1135  			req := client.CoreV1().RESTClient().Patch(tc.patchType).
  1136  				AbsPath("/apis/apps/v1").
  1137  				Namespace("default").
  1138  				Resource("deployments").
  1139  				Name(deployName).
  1140  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1141  			result := req.Body([]byte(tc.body)).Do(context.TODO())
  1142  			if result.Error() == nil && tc.strictDecodingError != "" {
  1143  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1144  			}
  1145  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1146  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1147  			}
  1148  
  1149  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1150  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1151  			}
  1152  			for i, strictWarn := range tc.strictDecodingWarnings {
  1153  				if strictWarn != result.Warnings()[i].Text {
  1154  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1155  				}
  1156  
  1157  			}
  1158  		})
  1159  	}
  1160  }
  1161  
  1162  // testFieldValidationSMP tests that attempting a strategic-merge-patch
  1163  // with unknown fields errors out when fieldValidation is strict,
  1164  // but succeeds when fieldValidation is ignored.
  1165  func testFieldValidationSMP(t *testing.T, client clientset.Interface) {
  1166  	// non-conflicting SMP has issues with the patch (duplicate fields),
  1167  	// but doesn't conflict with the existing object it's being patched to
  1168  	nonconflictingSMPBody := `
  1169  	{
  1170  		"spec": {
  1171  			"paused": true,
  1172  			"paused": false,
  1173  			"selector": {
  1174  				"matchLabels": {
  1175  					"app": "nginx"
  1176  				}
  1177  			},
  1178  			"template": {
  1179  				"metadata": {
  1180  					"labels": {
  1181  						"app": "nginx"
  1182  					}
  1183  				},
  1184  				"spec": {
  1185  					"containers": [{
  1186  						"name": "nginx",
  1187  						"imagePullPolicy": "Always",
  1188  						"imagePullPolicy": "Never"
  1189  					}]
  1190  				}
  1191  			}
  1192  		}
  1193  	}
  1194  	`
  1195  
  1196  	smpBody := `
  1197  	{
  1198  		"spec": {
  1199  			"unknown1": "val1",
  1200  			"unknownDupe": "valDupe",
  1201  			"unknownDupe": "valDupe2",
  1202  			"paused": true,
  1203  			"paused": false,
  1204  			"selector": {
  1205  				"matchLabels": {
  1206  					"app": "nginx"
  1207  				}
  1208  			},
  1209  			"template": {
  1210  				"metadata": {
  1211  					"labels": {
  1212  						"app": "nginx"
  1213  					}
  1214  				},
  1215  				"spec": {
  1216  					"containers": [{
  1217  						"name": "nginx",
  1218  						"unknownNested": "val1",
  1219  						"imagePullPolicy": "Always",
  1220  						"imagePullPolicy": "Never"
  1221  					}]
  1222  				}
  1223  			}
  1224  		}
  1225  	}
  1226  	`
  1227  	var testcases = []struct {
  1228  		name                   string
  1229  		opts                   metav1.PatchOptions
  1230  		body                   string
  1231  		strictDecodingError    string
  1232  		strictDecodingWarnings []string
  1233  	}{
  1234  		{
  1235  			name: "smp-strict-validation",
  1236  			opts: metav1.PatchOptions{
  1237  				FieldValidation: "Strict",
  1238  			},
  1239  			body:                smpBody,
  1240  			strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
  1241  		},
  1242  		{
  1243  			name: "smp-warn-validation",
  1244  			opts: metav1.PatchOptions{
  1245  				FieldValidation: "Warn",
  1246  			},
  1247  			body: smpBody,
  1248  			strictDecodingWarnings: []string{
  1249  				`duplicate field "spec.unknownDupe"`,
  1250  				`duplicate field "spec.paused"`,
  1251  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1252  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
  1253  				`unknown field "spec.unknown1"`,
  1254  				`unknown field "spec.unknownDupe"`,
  1255  			},
  1256  		},
  1257  		{
  1258  			name: "smp-ignore-validation",
  1259  			opts: metav1.PatchOptions{
  1260  				FieldValidation: "Ignore",
  1261  			},
  1262  			body: smpBody,
  1263  		},
  1264  		{
  1265  			name: "smp-no-validation",
  1266  			body: smpBody,
  1267  			strictDecodingWarnings: []string{
  1268  				`duplicate field "spec.unknownDupe"`,
  1269  				`duplicate field "spec.paused"`,
  1270  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1271  				`unknown field "spec.template.spec.containers[0].unknownNested"`,
  1272  				`unknown field "spec.unknown1"`,
  1273  				`unknown field "spec.unknownDupe"`,
  1274  			},
  1275  		},
  1276  		{
  1277  			name: "nonconflicting-smp-strict-validation",
  1278  			opts: metav1.PatchOptions{
  1279  				FieldValidation: "Strict",
  1280  			},
  1281  			body:                nonconflictingSMPBody,
  1282  			strictDecodingError: `strict decoding error: duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1283  		},
  1284  		{
  1285  			name: "nonconflicting-smp-warn-validation",
  1286  			opts: metav1.PatchOptions{
  1287  				FieldValidation: "Warn",
  1288  			},
  1289  			body: nonconflictingSMPBody,
  1290  			strictDecodingWarnings: []string{
  1291  				`duplicate field "spec.paused"`,
  1292  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1293  			},
  1294  		},
  1295  		{
  1296  			name: "nonconflicting-smp-ignore-validation",
  1297  			opts: metav1.PatchOptions{
  1298  				FieldValidation: "Ignore",
  1299  			},
  1300  			body: nonconflictingSMPBody,
  1301  		},
  1302  		{
  1303  			name: "nonconflicting-smp-no-validation",
  1304  			body: nonconflictingSMPBody,
  1305  			strictDecodingWarnings: []string{
  1306  				`duplicate field "spec.paused"`,
  1307  				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
  1308  			},
  1309  		},
  1310  	}
  1311  
  1312  	for _, tc := range testcases {
  1313  		t.Run(tc.name, func(t *testing.T) {
  1314  			body := []byte(fmt.Sprintf(validBodyJSON, tc.name))
  1315  			_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  1316  				AbsPath("/apis/apps/v1").
  1317  				Namespace("default").
  1318  				Resource("deployments").
  1319  				Name(tc.name).
  1320  				Param("fieldManager", "apply_test").
  1321  				Body(body).
  1322  				Do(context.TODO()).
  1323  				Get()
  1324  			if err != nil {
  1325  				t.Fatalf("Failed to create object using Apply patch: %v", err)
  1326  			}
  1327  
  1328  			req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
  1329  				AbsPath("/apis/apps/v1").
  1330  				Namespace("default").
  1331  				Resource("deployments").
  1332  				Name(tc.name).
  1333  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1334  			result := req.Body([]byte(tc.body)).Do(context.TODO())
  1335  			if result.Error() == nil && tc.strictDecodingError != "" {
  1336  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1337  			}
  1338  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1339  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1340  			}
  1341  
  1342  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1343  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1344  			}
  1345  
  1346  			for i, strictWarn := range tc.strictDecodingWarnings {
  1347  				if strictWarn != result.Warnings()[i].Text {
  1348  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1349  				}
  1350  
  1351  			}
  1352  		})
  1353  	}
  1354  }
  1355  
  1356  // testFieldValidationApplyCreate tests apply patch requests containing unknown fields
  1357  // on newly created objects, with strict and non-strict field validation.
  1358  func testFieldValidationApplyCreate(t *testing.T, client clientset.Interface) {
  1359  	var testcases = []struct {
  1360  		name                   string
  1361  		opts                   metav1.PatchOptions
  1362  		strictDecodingError    string
  1363  		strictDecodingWarnings []string
  1364  	}{
  1365  		{
  1366  			name: "strict-validation",
  1367  			opts: metav1.PatchOptions{
  1368  				FieldValidation: "Strict",
  1369  				FieldManager:    "mgr",
  1370  			},
  1371  			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
  1372    line 10: key "paused" already set in map
  1373    line 27: key "imagePullPolicy" already set in map`,
  1374  		},
  1375  		{
  1376  			name: "warn-validation",
  1377  			opts: metav1.PatchOptions{
  1378  				FieldValidation: "Warn",
  1379  				FieldManager:    "mgr",
  1380  			},
  1381  			strictDecodingWarnings: []string{
  1382  				`line 10: key "paused" already set in map`,
  1383  				`line 27: key "imagePullPolicy" already set in map`,
  1384  			},
  1385  		},
  1386  		{
  1387  			name: "ignore-validation",
  1388  			opts: metav1.PatchOptions{
  1389  				FieldValidation: "Ignore",
  1390  				FieldManager:    "mgr",
  1391  			},
  1392  		},
  1393  		{
  1394  			name: "no-validation",
  1395  			opts: metav1.PatchOptions{
  1396  				FieldManager: "mgr",
  1397  			},
  1398  			strictDecodingWarnings: []string{
  1399  				`line 10: key "paused" already set in map`,
  1400  				`line 27: key "imagePullPolicy" already set in map`,
  1401  			},
  1402  		},
  1403  	}
  1404  
  1405  	for _, tc := range testcases {
  1406  		t.Run(tc.name, func(t *testing.T) {
  1407  			name := fmt.Sprintf("apply-create-deployment-%s", tc.name)
  1408  			body := []byte(fmt.Sprintf(applyInvalidBody, name))
  1409  			req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  1410  				AbsPath("/apis/apps/v1").
  1411  				Namespace("default").
  1412  				Resource("deployments").
  1413  				Name(name).
  1414  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1415  			result := req.Body(body).Do(context.TODO())
  1416  			if result.Error() == nil && tc.strictDecodingError != "" {
  1417  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1418  			}
  1419  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1420  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1421  			}
  1422  
  1423  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1424  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1425  			}
  1426  			for i, strictWarn := range tc.strictDecodingWarnings {
  1427  				if strictWarn != result.Warnings()[i].Text {
  1428  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1429  				}
  1430  
  1431  			}
  1432  		})
  1433  	}
  1434  }
  1435  
  1436  // testFieldValidationApplyUpdate tests apply patch requests containing unknown fields
  1437  // on apply requests to existing objects, with strict and non-strict field validation.
  1438  func testFieldValidationApplyUpdate(t *testing.T, client clientset.Interface) {
  1439  	var testcases = []struct {
  1440  		name                   string
  1441  		opts                   metav1.PatchOptions
  1442  		strictDecodingError    string
  1443  		strictDecodingWarnings []string
  1444  	}{
  1445  		{
  1446  			name: "strict-validation",
  1447  			opts: metav1.PatchOptions{
  1448  				FieldValidation: "Strict",
  1449  				FieldManager:    "mgr",
  1450  			},
  1451  			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
  1452    line 10: key "paused" already set in map
  1453    line 27: key "imagePullPolicy" already set in map`,
  1454  		},
  1455  		{
  1456  			name: "warn-validation",
  1457  			opts: metav1.PatchOptions{
  1458  				FieldValidation: "Warn",
  1459  				FieldManager:    "mgr",
  1460  			},
  1461  			strictDecodingWarnings: []string{
  1462  				`line 10: key "paused" already set in map`,
  1463  				`line 27: key "imagePullPolicy" already set in map`,
  1464  			},
  1465  		},
  1466  		{
  1467  			name: "ignore-validation",
  1468  			opts: metav1.PatchOptions{
  1469  				FieldValidation: "Ignore",
  1470  				FieldManager:    "mgr",
  1471  			},
  1472  		},
  1473  		{
  1474  			name: "no-validation",
  1475  			opts: metav1.PatchOptions{
  1476  				FieldManager: "mgr",
  1477  			},
  1478  			strictDecodingWarnings: []string{
  1479  				`line 10: key "paused" already set in map`,
  1480  				`line 27: key "imagePullPolicy" already set in map`,
  1481  			},
  1482  		},
  1483  	}
  1484  
  1485  	for _, tc := range testcases {
  1486  		t.Run(tc.name, func(t *testing.T) {
  1487  			name := fmt.Sprintf("apply-update-deployment-%s", tc.name)
  1488  			createBody := []byte(fmt.Sprintf(validBodyJSON, name))
  1489  			createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  1490  				AbsPath("/apis/apps/v1").
  1491  				Namespace("default").
  1492  				Resource("deployments").
  1493  				Name(name).
  1494  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1495  			createResult := createReq.Body(createBody).Do(context.TODO())
  1496  			if createResult.Error() != nil {
  1497  				t.Fatalf("unexpected apply create err: %v", createResult.Error())
  1498  			}
  1499  
  1500  			updateBody := []byte(fmt.Sprintf(applyInvalidBody, name))
  1501  			updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  1502  				AbsPath("/apis/apps/v1").
  1503  				Namespace("default").
  1504  				Resource("deployments").
  1505  				Name(name).
  1506  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1507  			result := updateReq.Body(updateBody).Do(context.TODO())
  1508  			if result.Error() == nil && tc.strictDecodingError != "" {
  1509  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1510  			}
  1511  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1512  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1513  			}
  1514  
  1515  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1516  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1517  			}
  1518  			for i, strictWarn := range tc.strictDecodingWarnings {
  1519  				if strictWarn != result.Warnings()[i].Text {
  1520  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1521  				}
  1522  
  1523  			}
  1524  		})
  1525  	}
  1526  }
  1527  
  1528  // testFieldValidationPostCRD tests that server-side schema validation
  1529  // works for CRD create requests for CRDs with schemas
  1530  func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  1531  	var testcases = []struct {
  1532  		name                   string
  1533  		opts                   metav1.PatchOptions
  1534  		body                   string
  1535  		contentType            string
  1536  		strictDecodingError    string
  1537  		strictDecodingWarnings []string
  1538  	}{
  1539  		{
  1540  			name: "crd-post-strict-validation",
  1541  			opts: metav1.PatchOptions{
  1542  				FieldValidation: "Strict",
  1543  			},
  1544  			body:                crdInvalidBody,
  1545  			strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1546  		},
  1547  		{
  1548  			name: "crd-post-warn-validation",
  1549  			opts: metav1.PatchOptions{
  1550  				FieldValidation: "Warn",
  1551  			},
  1552  			body: crdInvalidBody,
  1553  			strictDecodingWarnings: []string{
  1554  				`duplicate field "metadata.name"`,
  1555  				`duplicate field "spec.unknownDupe"`,
  1556  				`duplicate field "spec.knownField1"`,
  1557  				`duplicate field "spec.ports[0].hostPort"`,
  1558  				`unknown field "metadata.unknownMeta"`,
  1559  				`unknown field "spec.ports[0].unknownNested"`,
  1560  				`unknown field "spec.unknown1"`,
  1561  				`unknown field "spec.unknownDupe"`,
  1562  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1563  			},
  1564  		},
  1565  		{
  1566  			name: "crd-post-ignore-validation",
  1567  			opts: metav1.PatchOptions{
  1568  				FieldValidation: "Ignore",
  1569  			},
  1570  			body: crdInvalidBody,
  1571  		},
  1572  		{
  1573  			name: "crd-post-no-validation",
  1574  			body: crdInvalidBody,
  1575  			strictDecodingWarnings: []string{
  1576  				`duplicate field "metadata.name"`,
  1577  				`duplicate field "spec.unknownDupe"`,
  1578  				`duplicate field "spec.knownField1"`,
  1579  				`duplicate field "spec.ports[0].hostPort"`,
  1580  				`unknown field "metadata.unknownMeta"`,
  1581  				`unknown field "spec.ports[0].unknownNested"`,
  1582  				`unknown field "spec.unknown1"`,
  1583  				`unknown field "spec.unknownDupe"`,
  1584  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1585  			},
  1586  		},
  1587  		{
  1588  			name: "crd-post-strict-validation-yaml",
  1589  			opts: metav1.PatchOptions{
  1590  				FieldValidation: "Strict",
  1591  			},
  1592  			body:        crdInvalidBodyYAML,
  1593  			contentType: "application/yaml",
  1594  			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
  1595    line 6: key "name" already set in map
  1596    line 12: key "unknownDupe" already set in map
  1597    line 14: key "knownField1" already set in map
  1598    line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1599  		},
  1600  		{
  1601  			name: "crd-post-warn-validation-yaml",
  1602  			opts: metav1.PatchOptions{
  1603  				FieldValidation: "Warn",
  1604  			},
  1605  			body:        crdInvalidBodyYAML,
  1606  			contentType: "application/yaml",
  1607  			strictDecodingWarnings: []string{
  1608  				`line 6: key "name" already set in map`,
  1609  				`line 12: key "unknownDupe" already set in map`,
  1610  				`line 14: key "knownField1" already set in map`,
  1611  				`line 20: key "hostPort" already set in map`,
  1612  				`unknown field "metadata.unknownMeta"`,
  1613  				`unknown field "spec.ports[0].unknownNested"`,
  1614  				`unknown field "spec.unknown1"`,
  1615  				`unknown field "spec.unknownDupe"`,
  1616  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1617  			},
  1618  		},
  1619  		{
  1620  			name: "crd-post-ignore-validation-yaml",
  1621  			opts: metav1.PatchOptions{
  1622  				FieldValidation: "Ignore",
  1623  			},
  1624  			body:        crdInvalidBodyYAML,
  1625  			contentType: "application/yaml",
  1626  		},
  1627  		{
  1628  			name:        "crd-post-no-validation-yaml",
  1629  			body:        crdInvalidBodyYAML,
  1630  			contentType: "application/yaml",
  1631  			strictDecodingWarnings: []string{
  1632  				`line 6: key "name" already set in map`,
  1633  				`line 12: key "unknownDupe" already set in map`,
  1634  				`line 14: key "knownField1" already set in map`,
  1635  				`line 20: key "hostPort" already set in map`,
  1636  				`unknown field "metadata.unknownMeta"`,
  1637  				`unknown field "spec.ports[0].unknownNested"`,
  1638  				`unknown field "spec.unknown1"`,
  1639  				`unknown field "spec.unknownDupe"`,
  1640  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1641  			},
  1642  		},
  1643  	}
  1644  	for _, tc := range testcases {
  1645  		t.Run(tc.name, func(t *testing.T) {
  1646  			klog.Warningf("running tc named: %s", tc.name)
  1647  			kind := gvk.Kind
  1648  			apiVersion := gvk.Group + "/" + gvk.Version
  1649  
  1650  			// create the CR as specified by the test case
  1651  			jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, tc.name))
  1652  			req := rest.Post().
  1653  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  1654  				SetHeader("Content-Type", tc.contentType).
  1655  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1656  			result := req.Body([]byte(jsonBody)).Do(context.TODO())
  1657  			if result.Error() == nil && tc.strictDecodingError != "" {
  1658  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1659  			}
  1660  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1661  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1662  			}
  1663  
  1664  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1665  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1666  			}
  1667  
  1668  			for i, strictWarn := range tc.strictDecodingWarnings {
  1669  				if strictWarn != result.Warnings()[i].Text {
  1670  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1671  				}
  1672  
  1673  			}
  1674  		})
  1675  	}
  1676  }
  1677  
  1678  // testFieldValidationPostCRDSchemaless tests that server-side schema validation
  1679  // works for CRD create requests for CRDs that have schemas
  1680  // with x-kubernetes-preserve-unknown-field set
  1681  func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  1682  	var testcases = []struct {
  1683  		name                   string
  1684  		opts                   metav1.PatchOptions
  1685  		body                   string
  1686  		contentType            string
  1687  		strictDecodingError    string
  1688  		strictDecodingWarnings []string
  1689  	}{
  1690  		{
  1691  			name: "schemaless-crd-post-strict-validation",
  1692  			opts: metav1.PatchOptions{
  1693  				FieldValidation: "Strict",
  1694  			},
  1695  			body:                crdInvalidBody,
  1696  			strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1697  		},
  1698  		{
  1699  			name: "schemaless-crd-post-warn-validation",
  1700  			opts: metav1.PatchOptions{
  1701  				FieldValidation: "Warn",
  1702  			},
  1703  			body: crdInvalidBody,
  1704  			strictDecodingWarnings: []string{
  1705  				`duplicate field "metadata.name"`,
  1706  				`duplicate field "spec.unknownDupe"`,
  1707  				`duplicate field "spec.knownField1"`,
  1708  				`duplicate field "spec.ports[0].hostPort"`,
  1709  				`unknown field "metadata.unknownMeta"`,
  1710  				`unknown field "spec.ports[0].unknownNested"`,
  1711  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1712  			},
  1713  		},
  1714  		{
  1715  			name: "schemaless-crd-post-ignore-validation",
  1716  			opts: metav1.PatchOptions{
  1717  				FieldValidation: "Ignore",
  1718  			},
  1719  			body: crdInvalidBody,
  1720  		},
  1721  		{
  1722  			name: "schemaless-crd-post-no-validation",
  1723  			body: crdInvalidBody,
  1724  			strictDecodingWarnings: []string{
  1725  				`duplicate field "metadata.name"`,
  1726  				`duplicate field "spec.unknownDupe"`,
  1727  				`duplicate field "spec.knownField1"`,
  1728  				`duplicate field "spec.ports[0].hostPort"`,
  1729  				`unknown field "metadata.unknownMeta"`,
  1730  				`unknown field "spec.ports[0].unknownNested"`,
  1731  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1732  			},
  1733  		},
  1734  		{
  1735  			name: "schemaless-crd-post-strict-validation-yaml",
  1736  			opts: metav1.PatchOptions{
  1737  				FieldValidation: "Strict",
  1738  			},
  1739  			body:        crdInvalidBodyYAML,
  1740  			contentType: "application/yaml",
  1741  			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
  1742    line 6: key "name" already set in map
  1743    line 12: key "unknownDupe" already set in map
  1744    line 14: key "knownField1" already set in map
  1745    line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1746  		},
  1747  		{
  1748  			name: "schemaless-crd-post-warn-validation-yaml",
  1749  			opts: metav1.PatchOptions{
  1750  				FieldValidation: "Warn",
  1751  			},
  1752  			body:        crdInvalidBodyYAML,
  1753  			contentType: "application/yaml",
  1754  			strictDecodingWarnings: []string{
  1755  				`line 6: key "name" already set in map`,
  1756  				`line 12: key "unknownDupe" already set in map`,
  1757  				`line 14: key "knownField1" already set in map`,
  1758  				`line 20: key "hostPort" already set in map`,
  1759  				`unknown field "metadata.unknownMeta"`,
  1760  				`unknown field "spec.ports[0].unknownNested"`,
  1761  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1762  			},
  1763  		},
  1764  		{
  1765  			name: "schemaless-crd-post-ignore-validation-yaml",
  1766  			opts: metav1.PatchOptions{
  1767  				FieldValidation: "Ignore",
  1768  			},
  1769  			body:        crdInvalidBodyYAML,
  1770  			contentType: "application/yaml",
  1771  		},
  1772  		{
  1773  			name:        "schemaless-crd-post-no-validation-yaml",
  1774  			body:        crdInvalidBodyYAML,
  1775  			contentType: "application/yaml",
  1776  			strictDecodingWarnings: []string{
  1777  				`line 6: key "name" already set in map`,
  1778  				`line 12: key "unknownDupe" already set in map`,
  1779  				`line 14: key "knownField1" already set in map`,
  1780  				`line 20: key "hostPort" already set in map`,
  1781  				`unknown field "metadata.unknownMeta"`,
  1782  				`unknown field "spec.ports[0].unknownNested"`,
  1783  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1784  			},
  1785  		},
  1786  	}
  1787  	for _, tc := range testcases {
  1788  		t.Run(tc.name, func(t *testing.T) {
  1789  
  1790  			kind := gvk.Kind
  1791  			apiVersion := gvk.Group + "/" + gvk.Version
  1792  
  1793  			// create the CR as specified by the test case
  1794  			jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, tc.name))
  1795  			req := rest.Post().
  1796  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  1797  				SetHeader("Content-Type", tc.contentType).
  1798  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1799  			result := req.Body([]byte(jsonBody)).Do(context.TODO())
  1800  			if result.Error() == nil && tc.strictDecodingError != "" {
  1801  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1802  			}
  1803  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1804  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1805  			}
  1806  
  1807  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1808  				t.Logf("expected:")
  1809  				for _, w := range tc.strictDecodingWarnings {
  1810  					t.Logf("\t%v", w)
  1811  				}
  1812  				t.Logf("got:")
  1813  				for _, w := range result.Warnings() {
  1814  					t.Logf("\t%v", w.Text)
  1815  				}
  1816  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1817  			}
  1818  
  1819  			for i, strictWarn := range tc.strictDecodingWarnings {
  1820  				if strictWarn != result.Warnings()[i].Text {
  1821  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1822  				}
  1823  
  1824  			}
  1825  		})
  1826  	}
  1827  }
  1828  
  1829  // testFieldValidationPutCRD tests that server-side schema validation
  1830  // works for CRD update requests for CRDs with schemas.
  1831  func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  1832  	var testcases = []struct {
  1833  		name                   string
  1834  		opts                   metav1.PatchOptions
  1835  		putBody                string
  1836  		contentType            string
  1837  		strictDecodingError    string
  1838  		strictDecodingWarnings []string
  1839  	}{
  1840  		{
  1841  			name: "crd-put-strict-validation",
  1842  			opts: metav1.PatchOptions{
  1843  				FieldValidation: "Strict",
  1844  			},
  1845  			putBody:             crdInvalidBody,
  1846  			strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1847  		},
  1848  		{
  1849  			name: "crd-put-warn-validation",
  1850  			opts: metav1.PatchOptions{
  1851  				FieldValidation: "Warn",
  1852  			},
  1853  			putBody: crdInvalidBody,
  1854  			strictDecodingWarnings: []string{
  1855  				`duplicate field "metadata.name"`,
  1856  				`duplicate field "spec.unknownDupe"`,
  1857  				`duplicate field "spec.knownField1"`,
  1858  				`duplicate field "spec.ports[0].hostPort"`,
  1859  				`unknown field "metadata.unknownMeta"`,
  1860  				`unknown field "spec.ports[0].unknownNested"`,
  1861  				`unknown field "spec.unknown1"`,
  1862  				`unknown field "spec.unknownDupe"`,
  1863  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1864  			},
  1865  		},
  1866  		{
  1867  			name: "crd-put-ignore-validation",
  1868  			opts: metav1.PatchOptions{
  1869  				FieldValidation: "Ignore",
  1870  			},
  1871  			putBody: crdInvalidBody,
  1872  		},
  1873  		{
  1874  			name:    "crd-put-no-validation",
  1875  			putBody: crdInvalidBody,
  1876  			strictDecodingWarnings: []string{
  1877  				`duplicate field "metadata.name"`,
  1878  				`duplicate field "spec.unknownDupe"`,
  1879  				`duplicate field "spec.knownField1"`,
  1880  				`duplicate field "spec.ports[0].hostPort"`,
  1881  				`unknown field "metadata.unknownMeta"`,
  1882  				`unknown field "spec.ports[0].unknownNested"`,
  1883  				`unknown field "spec.unknown1"`,
  1884  				`unknown field "spec.unknownDupe"`,
  1885  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1886  			},
  1887  		},
  1888  		{
  1889  			name: "crd-put-strict-validation-yaml",
  1890  			opts: metav1.PatchOptions{
  1891  				FieldValidation: "Strict",
  1892  			},
  1893  			putBody:     crdInvalidBodyYAML,
  1894  			contentType: "application/yaml",
  1895  			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
  1896    line 6: key "name" already set in map
  1897    line 12: key "unknownDupe" already set in map
  1898    line 14: key "knownField1" already set in map
  1899    line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1900  		},
  1901  		{
  1902  			name: "crd-put-warn-validation-yaml",
  1903  			opts: metav1.PatchOptions{
  1904  				FieldValidation: "Warn",
  1905  			},
  1906  			putBody:     crdInvalidBodyYAML,
  1907  			contentType: "application/yaml",
  1908  			strictDecodingWarnings: []string{
  1909  				`line 6: key "name" already set in map`,
  1910  				`line 12: key "unknownDupe" already set in map`,
  1911  				`line 14: key "knownField1" already set in map`,
  1912  				`line 20: key "hostPort" already set in map`,
  1913  				`unknown field "metadata.unknownMeta"`,
  1914  				`unknown field "spec.ports[0].unknownNested"`,
  1915  				`unknown field "spec.unknown1"`,
  1916  				`unknown field "spec.unknownDupe"`,
  1917  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1918  			},
  1919  		},
  1920  		{
  1921  			name: "crd-put-ignore-validation-yaml",
  1922  			opts: metav1.PatchOptions{
  1923  				FieldValidation: "Ignore",
  1924  			},
  1925  			putBody:     crdInvalidBodyYAML,
  1926  			contentType: "application/yaml",
  1927  		},
  1928  		{
  1929  			name:        "crd-put-no-validation-yaml",
  1930  			putBody:     crdInvalidBodyYAML,
  1931  			contentType: "application/yaml",
  1932  			strictDecodingWarnings: []string{
  1933  				`line 6: key "name" already set in map`,
  1934  				`line 12: key "unknownDupe" already set in map`,
  1935  				`line 14: key "knownField1" already set in map`,
  1936  				`line 20: key "hostPort" already set in map`,
  1937  				`unknown field "metadata.unknownMeta"`,
  1938  				`unknown field "spec.ports[0].unknownNested"`,
  1939  				`unknown field "spec.unknown1"`,
  1940  				`unknown field "spec.unknownDupe"`,
  1941  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  1942  			},
  1943  		},
  1944  	}
  1945  	for _, tc := range testcases {
  1946  		t.Run(tc.name, func(t *testing.T) {
  1947  			kind := gvk.Kind
  1948  			apiVersion := gvk.Group + "/" + gvk.Version
  1949  
  1950  			// create the CR as specified by the test case
  1951  			jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, tc.name))
  1952  			postReq := rest.Post().
  1953  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  1954  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1955  			postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
  1956  			if err != nil {
  1957  				t.Fatalf("unexpeted error on CR creation: %v", err)
  1958  			}
  1959  			postUnstructured := &unstructured.Unstructured{}
  1960  			if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
  1961  				t.Fatalf("unexpeted error unmarshalling created CR: %v", err)
  1962  			}
  1963  
  1964  			// update the CR as specified by the test case
  1965  			putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, tc.name, postUnstructured.GetResourceVersion()))
  1966  			putReq := rest.Put().
  1967  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  1968  				Name(tc.name).
  1969  				SetHeader("Content-Type", tc.contentType).
  1970  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  1971  			result := putReq.Body([]byte(putBody)).Do(context.TODO())
  1972  			if result.Error() == nil && tc.strictDecodingError != "" {
  1973  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  1974  			}
  1975  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  1976  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  1977  			}
  1978  
  1979  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  1980  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  1981  			}
  1982  
  1983  			for i, strictWarn := range tc.strictDecodingWarnings {
  1984  				if strictWarn != result.Warnings()[i].Text {
  1985  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  1986  				}
  1987  
  1988  			}
  1989  		})
  1990  	}
  1991  }
  1992  
  1993  // testFieldValidationPutCRDSchemaless tests that server-side schema validation
  1994  // works for CRD update requests for CRDs that have schemas
  1995  // with x-kubernetes-preserve-unknown-field set
  1996  func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  1997  	var testcases = []struct {
  1998  		name                   string
  1999  		opts                   metav1.PatchOptions
  2000  		putBody                string
  2001  		contentType            string
  2002  		strictDecodingError    string
  2003  		strictDecodingWarnings []string
  2004  	}{
  2005  		{
  2006  			name: "schemaless-crd-put-strict-validation",
  2007  			opts: metav1.PatchOptions{
  2008  				FieldValidation: "Strict",
  2009  			},
  2010  			putBody:             crdInvalidBody,
  2011  			strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  2012  		},
  2013  		{
  2014  			name: "schemaless-crd-put-warn-validation",
  2015  			opts: metav1.PatchOptions{
  2016  				FieldValidation: "Warn",
  2017  			},
  2018  			putBody: crdInvalidBody,
  2019  			strictDecodingWarnings: []string{
  2020  				`duplicate field "metadata.name"`,
  2021  				`duplicate field "spec.unknownDupe"`,
  2022  				`duplicate field "spec.knownField1"`,
  2023  				`duplicate field "spec.ports[0].hostPort"`,
  2024  				`unknown field "metadata.unknownMeta"`,
  2025  				`unknown field "spec.ports[0].unknownNested"`,
  2026  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  2027  			},
  2028  		},
  2029  		{
  2030  			name: "schemaless-crd-put-ignore-validation",
  2031  			opts: metav1.PatchOptions{
  2032  				FieldValidation: "Ignore",
  2033  			},
  2034  			putBody: crdInvalidBody,
  2035  		},
  2036  		{
  2037  			name:    "schemaless-crd-put-no-validation",
  2038  			putBody: crdInvalidBody,
  2039  			strictDecodingWarnings: []string{
  2040  				`duplicate field "metadata.name"`,
  2041  				`duplicate field "spec.unknownDupe"`,
  2042  				`duplicate field "spec.knownField1"`,
  2043  				`duplicate field "spec.ports[0].hostPort"`,
  2044  				`unknown field "metadata.unknownMeta"`,
  2045  				`unknown field "spec.ports[0].unknownNested"`,
  2046  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  2047  			},
  2048  		},
  2049  		{
  2050  			name: "schemaless-crd-put-strict-validation-yaml",
  2051  			opts: metav1.PatchOptions{
  2052  				FieldValidation: "Strict",
  2053  			},
  2054  			putBody:     crdInvalidBodyYAML,
  2055  			contentType: "application/yaml",
  2056  			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
  2057    line 6: key "name" already set in map
  2058    line 12: key "unknownDupe" already set in map
  2059    line 14: key "knownField1" already set in map
  2060    line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  2061  		},
  2062  		{
  2063  			name: "schemaless-crd-put-warn-validation-yaml",
  2064  			opts: metav1.PatchOptions{
  2065  				FieldValidation: "Warn",
  2066  			},
  2067  			putBody:     crdInvalidBodyYAML,
  2068  			contentType: "application/yaml",
  2069  			strictDecodingWarnings: []string{
  2070  				`line 6: key "name" already set in map`,
  2071  				`line 12: key "unknownDupe" already set in map`,
  2072  				`line 14: key "knownField1" already set in map`,
  2073  				`line 20: key "hostPort" already set in map`,
  2074  				`unknown field "metadata.unknownMeta"`,
  2075  				`unknown field "spec.ports[0].unknownNested"`,
  2076  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  2077  			},
  2078  		},
  2079  		{
  2080  			name: "schemaless-crd-put-ignore-validation-yaml",
  2081  			opts: metav1.PatchOptions{
  2082  				FieldValidation: "Ignore",
  2083  			},
  2084  			putBody:     crdInvalidBodyYAML,
  2085  			contentType: "application/yaml",
  2086  		},
  2087  		{
  2088  			name:        "schemaless-crd-put-no-validation-yaml",
  2089  			putBody:     crdInvalidBodyYAML,
  2090  			contentType: "application/yaml",
  2091  			strictDecodingWarnings: []string{
  2092  				`line 6: key "name" already set in map`,
  2093  				`line 12: key "unknownDupe" already set in map`,
  2094  				`line 14: key "knownField1" already set in map`,
  2095  				`line 20: key "hostPort" already set in map`,
  2096  				`unknown field "metadata.unknownMeta"`,
  2097  				`unknown field "spec.ports[0].unknownNested"`,
  2098  				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
  2099  			},
  2100  		},
  2101  	}
  2102  	for _, tc := range testcases {
  2103  		t.Run(tc.name, func(t *testing.T) {
  2104  			kind := gvk.Kind
  2105  			apiVersion := gvk.Group + "/" + gvk.Version
  2106  
  2107  			// create the CR as specified by the test case
  2108  			jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, tc.name))
  2109  			postReq := rest.Post().
  2110  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2111  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2112  			postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
  2113  			if err != nil {
  2114  				t.Fatalf("unexpeted error on CR creation: %v", err)
  2115  			}
  2116  			postUnstructured := &unstructured.Unstructured{}
  2117  			if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
  2118  				t.Fatalf("unexpeted error unmarshalling created CR: %v", err)
  2119  			}
  2120  
  2121  			// update the CR as specified by the test case
  2122  			putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, tc.name, postUnstructured.GetResourceVersion()))
  2123  			putReq := rest.Put().
  2124  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2125  				Name(tc.name).
  2126  				SetHeader("Content-Type", tc.contentType).
  2127  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2128  			result := putReq.Body([]byte(putBody)).Do(context.TODO())
  2129  			if result.Error() == nil && tc.strictDecodingError != "" {
  2130  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2131  			}
  2132  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2133  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2134  			}
  2135  
  2136  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2137  				t.Logf("expected:")
  2138  				for _, w := range tc.strictDecodingWarnings {
  2139  					t.Logf("\t%v", w)
  2140  				}
  2141  				t.Logf("got:")
  2142  				for _, w := range result.Warnings() {
  2143  					t.Logf("\t%v", w.Text)
  2144  				}
  2145  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2146  			}
  2147  
  2148  			for i, strictWarn := range tc.strictDecodingWarnings {
  2149  				if strictWarn != result.Warnings()[i].Text {
  2150  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2151  				}
  2152  
  2153  			}
  2154  		})
  2155  	}
  2156  }
  2157  
  2158  // testFieldValidationPatchCRD tests that server-side schema validation
  2159  // works for jsonpatch and mergepatch requests
  2160  // for custom resources that have schemas.
  2161  func testFieldValidationPatchCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2162  	patchYAMLBody := `
  2163  apiVersion: %s
  2164  kind: %s
  2165  metadata:
  2166    name: %s
  2167    finalizers:
  2168    - test/finalizer
  2169  spec:
  2170    cronSpec: "* * * * */5"
  2171    ports:
  2172    - name: x
  2173      containerPort: 80
  2174      protocol: TCP`
  2175  
  2176  	mergePatchBody := `
  2177  {
  2178  	"spec": {
  2179  		"unknown1": "val1",
  2180  		"unknownDupe": "valDupe",
  2181  		"unknownDupe": "valDupe2",
  2182  		"knownField1": "val1",
  2183  		"knownField1": "val2",
  2184  			"ports": [{
  2185  				"name": "portName",
  2186  				"containerPort": 8080,
  2187  				"protocol": "TCP",
  2188  				"hostPort": 8081,
  2189  				"hostPort": 8082,
  2190  				"unknownNested": "val"
  2191  			}]
  2192  	}
  2193  }
  2194  	`
  2195  	jsonPatchBody := `
  2196  			[
  2197  				{"op": "add", "path": "/spec/unknown1", "value": "val1", "foo": "bar"},
  2198  				{"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val2"},
  2199  				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
  2200  				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
  2201  				{"op": "add", "path": "/spec/knownField1", "value": "val1"},
  2202  				{"op": "add", "path": "/spec/knownField1", "value": "val2"},
  2203  				{"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
  2204  				{"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
  2205  				{"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
  2206  				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081},
  2207  				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8082},
  2208  				{"op": "add", "path": "/spec/ports/0/unknownNested", "value": "val"}
  2209  			]
  2210  			`
  2211  	var testcases = []struct {
  2212  		name                   string
  2213  		patchType              types.PatchType
  2214  		opts                   metav1.PatchOptions
  2215  		body                   string
  2216  		strictDecodingError    string
  2217  		strictDecodingWarnings []string
  2218  	}{
  2219  		{
  2220  			name:      "crd-merge-patch-strict-validation",
  2221  			patchType: types.MergePatchType,
  2222  			opts: metav1.PatchOptions{
  2223  				FieldValidation: "Strict",
  2224  			},
  2225  			body:                mergePatchBody,
  2226  			strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
  2227  		},
  2228  		{
  2229  			name:      "crd-merge-patch-warn-validation",
  2230  			patchType: types.MergePatchType,
  2231  			opts: metav1.PatchOptions{
  2232  				FieldValidation: "Warn",
  2233  			},
  2234  			body: mergePatchBody,
  2235  			strictDecodingWarnings: []string{
  2236  				`duplicate field "spec.unknownDupe"`,
  2237  				`duplicate field "spec.knownField1"`,
  2238  				`duplicate field "spec.ports[0].hostPort"`,
  2239  				`unknown field "spec.ports[0].unknownNested"`,
  2240  				`unknown field "spec.unknown1"`,
  2241  				`unknown field "spec.unknownDupe"`,
  2242  			},
  2243  		},
  2244  		{
  2245  			name:      "crd-merge-patch-ignore-validation",
  2246  			patchType: types.MergePatchType,
  2247  			opts: metav1.PatchOptions{
  2248  				FieldValidation: "Ignore",
  2249  			},
  2250  			body: mergePatchBody,
  2251  		},
  2252  		{
  2253  			name:      "crd-merge-patch-no-validation",
  2254  			patchType: types.MergePatchType,
  2255  			body:      mergePatchBody,
  2256  			strictDecodingWarnings: []string{
  2257  				`duplicate field "spec.unknownDupe"`,
  2258  				`duplicate field "spec.knownField1"`,
  2259  				`duplicate field "spec.ports[0].hostPort"`,
  2260  				`unknown field "spec.ports[0].unknownNested"`,
  2261  				`unknown field "spec.unknown1"`,
  2262  				`unknown field "spec.unknownDupe"`,
  2263  			},
  2264  		},
  2265  		{
  2266  			name:      "crd-json-patch-strict-validation",
  2267  			patchType: types.JSONPatchType,
  2268  			opts: metav1.PatchOptions{
  2269  				FieldValidation: "Strict",
  2270  			},
  2271  			body: jsonPatchBody,
  2272  			// note: duplicate fields in the patch itself
  2273  			// are dropped by the
  2274  			// evanphx/json-patch library and is expected.
  2275  			// Duplicate fields in the json patch ops
  2276  			// themselves can be detected though
  2277  			strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknown3", unknown field "spec.unknownDupe"`,
  2278  		},
  2279  		{
  2280  			name:      "crd-json-patch-warn-validation",
  2281  			patchType: types.JSONPatchType,
  2282  			opts: metav1.PatchOptions{
  2283  				FieldValidation: "Warn",
  2284  			},
  2285  			body: jsonPatchBody,
  2286  			strictDecodingWarnings: []string{
  2287  				// note: duplicate fields in the patch itself
  2288  				// are dropped by the
  2289  				// evanphx/json-patch library and is expected.
  2290  				// Duplicate fields in the json patch ops
  2291  				// themselves can be detected though
  2292  				`json patch unknown field "[0].foo"`,
  2293  				`json patch duplicate field "[1].path"`,
  2294  				`unknown field "spec.ports[0].unknownNested"`,
  2295  				`unknown field "spec.unknown1"`,
  2296  				`unknown field "spec.unknown3"`,
  2297  				`unknown field "spec.unknownDupe"`,
  2298  			},
  2299  		},
  2300  		{
  2301  			name:      "crd-json-patch-ignore-validation",
  2302  			patchType: types.JSONPatchType,
  2303  			opts: metav1.PatchOptions{
  2304  				FieldValidation: "Ignore",
  2305  			},
  2306  			body: jsonPatchBody,
  2307  		},
  2308  		{
  2309  			name:      "crd-json-patch-no-validation",
  2310  			patchType: types.JSONPatchType,
  2311  			body:      jsonPatchBody,
  2312  			strictDecodingWarnings: []string{
  2313  				// note: duplicate fields in the patch itself
  2314  				// are dropped by the
  2315  				// evanphx/json-patch library and is expected.
  2316  				// Duplicate fields in the json patch ops
  2317  				// themselves can be detected though
  2318  				`json patch unknown field "[0].foo"`,
  2319  				`json patch duplicate field "[1].path"`,
  2320  				`unknown field "spec.ports[0].unknownNested"`,
  2321  				`unknown field "spec.unknown1"`,
  2322  				`unknown field "spec.unknown3"`,
  2323  				`unknown field "spec.unknownDupe"`,
  2324  			},
  2325  		},
  2326  	}
  2327  	for _, tc := range testcases {
  2328  		t.Run(tc.name, func(t *testing.T) {
  2329  			kind := gvk.Kind
  2330  			apiVersion := gvk.Group + "/" + gvk.Version
  2331  			// create a CR
  2332  			yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, tc.name))
  2333  			createResult, err := rest.Patch(types.ApplyPatchType).
  2334  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2335  				Name(tc.name).
  2336  				Param("fieldManager", "apply_test").
  2337  				Body(yamlBody).
  2338  				DoRaw(context.TODO())
  2339  			if err != nil {
  2340  				t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
  2341  			}
  2342  
  2343  			// patch the CR as specified by the test case
  2344  			req := rest.Patch(tc.patchType).
  2345  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2346  				Name(tc.name).
  2347  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2348  			result := req.Body([]byte(tc.body)).Do(context.TODO())
  2349  			if result.Error() == nil && tc.strictDecodingError != "" {
  2350  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2351  			}
  2352  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2353  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2354  			}
  2355  
  2356  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2357  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2358  			}
  2359  
  2360  			for i, strictWarn := range tc.strictDecodingWarnings {
  2361  				if strictWarn != result.Warnings()[i].Text {
  2362  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2363  				}
  2364  
  2365  			}
  2366  		})
  2367  	}
  2368  }
  2369  
  2370  // testFieldValidationPatchCRDSchemaless tests that server-side schema validation
  2371  // works for jsonpatch and mergepatch requests
  2372  // for custom resources that have schemas
  2373  // with x-kubernetes-preserve-unknown-field set
  2374  func testFieldValidationPatchCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2375  	mergePatchBody := `
  2376  {
  2377  	"spec": {
  2378  		"unknown1": "val1",
  2379  		"unknownDupe": "valDupe",
  2380  		"unknownDupe": "valDupe2",
  2381  		"knownField1": "val1",
  2382  		"knownField1": "val2",
  2383  			"ports": [{
  2384  				"name": "portName",
  2385  				"containerPort": 8080,
  2386  				"protocol": "TCP",
  2387  				"hostPort": 8081,
  2388  				"hostPort": 8082,
  2389  				"unknownNested": "val"
  2390  			}]
  2391  	}
  2392  }
  2393  	`
  2394  	jsonPatchBody := `
  2395  			[
  2396  				{"op": "add", "path": "/spec/unknown1", "value": "val1", "foo": "bar"},
  2397  				{"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val2"},
  2398  				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
  2399  				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
  2400  				{"op": "add", "path": "/spec/knownField1", "value": "val1"},
  2401  				{"op": "add", "path": "/spec/knownField1", "value": "val2"},
  2402  				{"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
  2403  				{"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
  2404  				{"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
  2405  				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081},
  2406  				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8082},
  2407  				{"op": "add", "path": "/spec/ports/0/unknownNested", "value": "val"}
  2408  			]
  2409  			`
  2410  	var testcases = []struct {
  2411  		name                   string
  2412  		patchType              types.PatchType
  2413  		opts                   metav1.PatchOptions
  2414  		body                   string
  2415  		strictDecodingError    string
  2416  		strictDecodingWarnings []string
  2417  	}{
  2418  		{
  2419  			name:      "schemaless-crd-merge-patch-strict-validation",
  2420  			patchType: types.MergePatchType,
  2421  			opts: metav1.PatchOptions{
  2422  				FieldValidation: "Strict",
  2423  			},
  2424  			body:                mergePatchBody,
  2425  			strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested"`,
  2426  		},
  2427  		{
  2428  			name:      "schemaless-crd-merge-patch-warn-validation",
  2429  			patchType: types.MergePatchType,
  2430  			opts: metav1.PatchOptions{
  2431  				FieldValidation: "Warn",
  2432  			},
  2433  			body: mergePatchBody,
  2434  			strictDecodingWarnings: []string{
  2435  				`duplicate field "spec.unknownDupe"`,
  2436  				`duplicate field "spec.knownField1"`,
  2437  				`duplicate field "spec.ports[0].hostPort"`,
  2438  				`unknown field "spec.ports[0].unknownNested"`,
  2439  			},
  2440  		},
  2441  		{
  2442  			name:      "schemaless-crd-merge-patch-ignore-validation",
  2443  			patchType: types.MergePatchType,
  2444  			opts: metav1.PatchOptions{
  2445  				FieldValidation: "Ignore",
  2446  			},
  2447  			body: mergePatchBody,
  2448  		},
  2449  		{
  2450  			name:      "schemaless-crd-merge-patch-no-validation",
  2451  			patchType: types.MergePatchType,
  2452  			body:      mergePatchBody,
  2453  			strictDecodingWarnings: []string{
  2454  				`duplicate field "spec.unknownDupe"`,
  2455  				`duplicate field "spec.knownField1"`,
  2456  				`duplicate field "spec.ports[0].hostPort"`,
  2457  				`unknown field "spec.ports[0].unknownNested"`,
  2458  			},
  2459  		},
  2460  		{
  2461  			name:      "schemaless-crd-json-patch-strict-validation",
  2462  			patchType: types.JSONPatchType,
  2463  			opts: metav1.PatchOptions{
  2464  				FieldValidation: "Strict",
  2465  			},
  2466  			body: jsonPatchBody,
  2467  			// note: duplicate fields in the patch itself
  2468  			// are dropped by the
  2469  			// evanphx/json-patch library and is expected.
  2470  			// Duplicate fields in the json patch ops
  2471  			// themselves can be detected though
  2472  			strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.ports[0].unknownNested"`,
  2473  		},
  2474  		{
  2475  			name:      "schemaless-crd-json-patch-warn-validation",
  2476  			patchType: types.JSONPatchType,
  2477  			opts: metav1.PatchOptions{
  2478  				FieldValidation: "Warn",
  2479  			},
  2480  			body: jsonPatchBody,
  2481  			strictDecodingWarnings: []string{
  2482  				// note: duplicate fields in the patch itself
  2483  				// are dropped by the
  2484  				// evanphx/json-patch library and is expected.
  2485  				// Duplicate fields in the json patch ops
  2486  				// themselves can be detected though
  2487  				`json patch unknown field "[0].foo"`,
  2488  				`json patch duplicate field "[1].path"`,
  2489  				`unknown field "spec.ports[0].unknownNested"`,
  2490  			},
  2491  		},
  2492  		{
  2493  			name:      "schemaless-crd-json-patch-ignore-validation",
  2494  			patchType: types.JSONPatchType,
  2495  			opts: metav1.PatchOptions{
  2496  				FieldValidation: "Ignore",
  2497  			},
  2498  			body: jsonPatchBody,
  2499  		},
  2500  		{
  2501  			name:      "schemaless-crd-json-patch-no-validation",
  2502  			patchType: types.JSONPatchType,
  2503  			body:      jsonPatchBody,
  2504  			strictDecodingWarnings: []string{
  2505  				// note: duplicate fields in the patch itself
  2506  				// are dropped by the
  2507  				// evanphx/json-patch library and is expected.
  2508  				// Duplicate fields in the json patch ops
  2509  				// themselves can be detected though
  2510  				`json patch unknown field "[0].foo"`,
  2511  				`json patch duplicate field "[1].path"`,
  2512  				`unknown field "spec.ports[0].unknownNested"`,
  2513  			},
  2514  		},
  2515  	}
  2516  	for _, tc := range testcases {
  2517  		t.Run(tc.name, func(t *testing.T) {
  2518  			kind := gvk.Kind
  2519  			apiVersion := gvk.Group + "/" + gvk.Version
  2520  			// create a CR
  2521  			yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, tc.name))
  2522  			createResult, err := rest.Patch(types.ApplyPatchType).
  2523  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2524  				Name(tc.name).
  2525  				Param("fieldManager", "apply_test").
  2526  				Body(yamlBody).
  2527  				DoRaw(context.TODO())
  2528  			if err != nil {
  2529  				t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
  2530  			}
  2531  
  2532  			// patch the CR as specified by the test case
  2533  			req := rest.Patch(tc.patchType).
  2534  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2535  				Name(tc.name).
  2536  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2537  			result := req.Body([]byte(tc.body)).Do(context.TODO())
  2538  			if result.Error() == nil && tc.strictDecodingError != "" {
  2539  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2540  			}
  2541  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2542  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2543  			}
  2544  
  2545  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2546  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2547  			}
  2548  
  2549  			for i, strictWarn := range tc.strictDecodingWarnings {
  2550  				if strictWarn != result.Warnings()[i].Text {
  2551  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2552  				}
  2553  
  2554  			}
  2555  		})
  2556  	}
  2557  }
  2558  
  2559  // testFieldValidationApplyCreateCRD tests apply patch requests containing duplicate fields
  2560  // on newly created objects, for CRDs that have schemas
  2561  // Note that even prior to server-side validation, unknown fields were treated as
  2562  // errors in apply-patch and are not tested here.
  2563  func testFieldValidationApplyCreateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2564  	var testcases = []struct {
  2565  		name                   string
  2566  		opts                   metav1.PatchOptions
  2567  		strictDecodingError    string
  2568  		strictDecodingWarnings []string
  2569  	}{
  2570  		{
  2571  			name: "strict-validation",
  2572  			opts: metav1.PatchOptions{
  2573  				FieldValidation: "Strict",
  2574  				FieldManager:    "mgr",
  2575  			},
  2576  			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
  2577    line 10: key "knownField1" already set in map
  2578    line 16: key "hostPort" already set in map`,
  2579  		},
  2580  		{
  2581  			name: "warn-validation",
  2582  			opts: metav1.PatchOptions{
  2583  				FieldValidation: "Warn",
  2584  				FieldManager:    "mgr",
  2585  			},
  2586  			strictDecodingWarnings: []string{
  2587  				`line 10: key "knownField1" already set in map`,
  2588  				`line 16: key "hostPort" already set in map`,
  2589  			},
  2590  		},
  2591  		{
  2592  			name: "ignore-validation",
  2593  			opts: metav1.PatchOptions{
  2594  				FieldValidation: "Ignore",
  2595  				FieldManager:    "mgr",
  2596  			},
  2597  		},
  2598  		{
  2599  			name: "no-validation",
  2600  			opts: metav1.PatchOptions{
  2601  				FieldManager: "mgr",
  2602  			},
  2603  			strictDecodingWarnings: []string{
  2604  				`line 10: key "knownField1" already set in map`,
  2605  				`line 16: key "hostPort" already set in map`,
  2606  			},
  2607  		},
  2608  	}
  2609  
  2610  	for _, tc := range testcases {
  2611  		t.Run(tc.name, func(t *testing.T) {
  2612  			kind := gvk.Kind
  2613  			apiVersion := gvk.Group + "/" + gvk.Version
  2614  
  2615  			// create the CR as specified by the test case
  2616  			name := fmt.Sprintf("apply-create-crd-%s", tc.name)
  2617  			applyCreateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
  2618  
  2619  			req := rest.Patch(types.ApplyPatchType).
  2620  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2621  				Name(name).
  2622  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2623  			result := req.Body(applyCreateBody).Do(context.TODO())
  2624  			if result.Error() == nil && tc.strictDecodingError != "" {
  2625  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2626  			}
  2627  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2628  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2629  			}
  2630  
  2631  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2632  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2633  			}
  2634  			for i, strictWarn := range tc.strictDecodingWarnings {
  2635  				if strictWarn != result.Warnings()[i].Text {
  2636  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2637  				}
  2638  
  2639  			}
  2640  		})
  2641  	}
  2642  }
  2643  
  2644  // testFieldValidationApplyCreateCRDSchemaless tests apply patch requests containing duplicate fields
  2645  // on newly created objects, for CRDs that have schemas
  2646  // with x-kubernetes-preserve-unknown-field set
  2647  // Note that even prior to server-side validation, unknown fields were treated as
  2648  // errors in apply-patch and are not tested here.
  2649  func testFieldValidationApplyCreateCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2650  	var testcases = []struct {
  2651  		name                   string
  2652  		opts                   metav1.PatchOptions
  2653  		strictDecodingError    string
  2654  		strictDecodingWarnings []string
  2655  	}{
  2656  		{
  2657  			name: "schemaless-strict-validation",
  2658  			opts: metav1.PatchOptions{
  2659  				FieldValidation: "Strict",
  2660  				FieldManager:    "mgr",
  2661  			},
  2662  			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
  2663    line 10: key "knownField1" already set in map
  2664    line 16: key "hostPort" already set in map`,
  2665  		},
  2666  		{
  2667  			name: "schemaless-warn-validation",
  2668  			opts: metav1.PatchOptions{
  2669  				FieldValidation: "Warn",
  2670  				FieldManager:    "mgr",
  2671  			},
  2672  			strictDecodingWarnings: []string{
  2673  				`line 10: key "knownField1" already set in map`,
  2674  				`line 16: key "hostPort" already set in map`,
  2675  			},
  2676  		},
  2677  		{
  2678  			name: "schemaless-ignore-validation",
  2679  			opts: metav1.PatchOptions{
  2680  				FieldValidation: "Ignore",
  2681  				FieldManager:    "mgr",
  2682  			},
  2683  		},
  2684  		{
  2685  			name: "schemaless-no-validation",
  2686  			opts: metav1.PatchOptions{
  2687  				FieldManager: "mgr",
  2688  			},
  2689  			strictDecodingWarnings: []string{
  2690  				`line 10: key "knownField1" already set in map`,
  2691  				`line 16: key "hostPort" already set in map`,
  2692  			},
  2693  		},
  2694  	}
  2695  
  2696  	for _, tc := range testcases {
  2697  		t.Run(tc.name, func(t *testing.T) {
  2698  			kind := gvk.Kind
  2699  			apiVersion := gvk.Group + "/" + gvk.Version
  2700  
  2701  			// create the CR as specified by the test case
  2702  			name := fmt.Sprintf("apply-create-crd-schemaless-%s", tc.name)
  2703  			applyCreateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
  2704  
  2705  			req := rest.Patch(types.ApplyPatchType).
  2706  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2707  				Name(name).
  2708  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2709  			result := req.Body(applyCreateBody).Do(context.TODO())
  2710  			if result.Error() == nil && tc.strictDecodingError != "" {
  2711  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2712  			}
  2713  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2714  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2715  			}
  2716  
  2717  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2718  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2719  			}
  2720  			for i, strictWarn := range tc.strictDecodingWarnings {
  2721  				if strictWarn != result.Warnings()[i].Text {
  2722  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2723  				}
  2724  
  2725  			}
  2726  		})
  2727  	}
  2728  }
  2729  
  2730  // testFieldValidationApplyUpdateCRD tests apply patch requests containing duplicate fields
  2731  // on existing objects, for CRDs with schemas
  2732  // Note that even prior to server-side validation, unknown fields were treated as
  2733  // errors in apply-patch and are not tested here.
  2734  func testFieldValidationApplyUpdateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2735  	var testcases = []struct {
  2736  		name                   string
  2737  		opts                   metav1.PatchOptions
  2738  		strictDecodingError    string
  2739  		strictDecodingWarnings []string
  2740  	}{
  2741  		{
  2742  			name: "strict-validation",
  2743  			opts: metav1.PatchOptions{
  2744  				FieldValidation: "Strict",
  2745  				FieldManager:    "mgr",
  2746  			},
  2747  			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
  2748    line 10: key "knownField1" already set in map
  2749    line 16: key "hostPort" already set in map`,
  2750  		},
  2751  		{
  2752  			name: "warn-validation",
  2753  			opts: metav1.PatchOptions{
  2754  				FieldValidation: "Warn",
  2755  				FieldManager:    "mgr",
  2756  			},
  2757  			strictDecodingWarnings: []string{
  2758  				`line 10: key "knownField1" already set in map`,
  2759  				`line 16: key "hostPort" already set in map`,
  2760  			},
  2761  		},
  2762  		{
  2763  			name: "ignore-validation",
  2764  			opts: metav1.PatchOptions{
  2765  				FieldValidation: "Ignore",
  2766  				FieldManager:    "mgr",
  2767  			},
  2768  		},
  2769  		{
  2770  			name: "no-validation",
  2771  			opts: metav1.PatchOptions{
  2772  				FieldManager: "mgr",
  2773  			},
  2774  			strictDecodingWarnings: []string{
  2775  				`line 10: key "knownField1" already set in map`,
  2776  				`line 16: key "hostPort" already set in map`,
  2777  			},
  2778  		},
  2779  	}
  2780  
  2781  	for _, tc := range testcases {
  2782  		t.Run(tc.name, func(t *testing.T) {
  2783  			kind := gvk.Kind
  2784  			apiVersion := gvk.Group + "/" + gvk.Version
  2785  
  2786  			// create the CR as specified by the test case
  2787  			name := fmt.Sprintf("apply-update-crd-%s", tc.name)
  2788  			applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
  2789  			createReq := rest.Patch(types.ApplyPatchType).
  2790  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2791  				Name(name).
  2792  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2793  			createResult := createReq.Body(applyCreateBody).Do(context.TODO())
  2794  			if createResult.Error() != nil {
  2795  				t.Fatalf("unexpected apply create err: %v", createResult.Error())
  2796  			}
  2797  
  2798  			applyUpdateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
  2799  			updateReq := rest.Patch(types.ApplyPatchType).
  2800  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2801  				Name(name).
  2802  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2803  			result := updateReq.Body(applyUpdateBody).Do(context.TODO())
  2804  			if result.Error() == nil && tc.strictDecodingError != "" {
  2805  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2806  			}
  2807  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2808  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2809  			}
  2810  
  2811  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2812  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2813  			}
  2814  			for i, strictWarn := range tc.strictDecodingWarnings {
  2815  				if strictWarn != result.Warnings()[i].Text {
  2816  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2817  				}
  2818  
  2819  			}
  2820  		})
  2821  	}
  2822  }
  2823  
  2824  // testFieldValidationApplyUpdateCRDSchemaless tests apply patch requests containing duplicate fields
  2825  // on existing objects, for CRDs with schemas
  2826  // with x-kubernetes-preserve-unknown-field set
  2827  // Note that even prior to server-side validation, unknown fields were treated as
  2828  // errors in apply-patch and are not tested here.
  2829  func testFieldValidationApplyUpdateCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2830  	var testcases = []struct {
  2831  		name                   string
  2832  		opts                   metav1.PatchOptions
  2833  		strictDecodingError    string
  2834  		strictDecodingWarnings []string
  2835  	}{
  2836  		{
  2837  			name: "schemaless-strict-validation",
  2838  			opts: metav1.PatchOptions{
  2839  				FieldValidation: "Strict",
  2840  				FieldManager:    "mgr",
  2841  			},
  2842  			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
  2843    line 10: key "knownField1" already set in map
  2844    line 16: key "hostPort" already set in map`,
  2845  		},
  2846  		{
  2847  			name: "schemaless-warn-validation",
  2848  			opts: metav1.PatchOptions{
  2849  				FieldValidation: "Warn",
  2850  				FieldManager:    "mgr",
  2851  			},
  2852  			strictDecodingWarnings: []string{
  2853  				`line 10: key "knownField1" already set in map`,
  2854  				`line 16: key "hostPort" already set in map`,
  2855  			},
  2856  		},
  2857  		{
  2858  			name: "schemaless-ignore-validation",
  2859  			opts: metav1.PatchOptions{
  2860  				FieldValidation: "Ignore",
  2861  				FieldManager:    "mgr",
  2862  			},
  2863  		},
  2864  		{
  2865  			name: "schemaless-no-validation",
  2866  			opts: metav1.PatchOptions{
  2867  				FieldManager: "mgr",
  2868  			},
  2869  			strictDecodingWarnings: []string{
  2870  				`line 10: key "knownField1" already set in map`,
  2871  				`line 16: key "hostPort" already set in map`,
  2872  			},
  2873  		},
  2874  	}
  2875  
  2876  	for _, tc := range testcases {
  2877  		t.Run(tc.name, func(t *testing.T) {
  2878  			kind := gvk.Kind
  2879  			apiVersion := gvk.Group + "/" + gvk.Version
  2880  
  2881  			// create the CR as specified by the test case
  2882  			name := fmt.Sprintf("apply-update-crd-schemaless-%s", tc.name)
  2883  			applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
  2884  			createReq := rest.Patch(types.ApplyPatchType).
  2885  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2886  				Name(name).
  2887  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2888  			createResult := createReq.Body(applyCreateBody).Do(context.TODO())
  2889  			if createResult.Error() != nil {
  2890  				t.Fatalf("unexpected apply create err: %v", createResult.Error())
  2891  			}
  2892  
  2893  			applyUpdateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
  2894  			updateReq := rest.Patch(types.ApplyPatchType).
  2895  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2896  				Name(name).
  2897  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2898  			result := updateReq.Body(applyUpdateBody).Do(context.TODO())
  2899  
  2900  			if result.Error() == nil && tc.strictDecodingError != "" {
  2901  				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
  2902  			}
  2903  			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
  2904  				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
  2905  			}
  2906  
  2907  			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
  2908  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
  2909  			}
  2910  			for i, strictWarn := range tc.strictDecodingWarnings {
  2911  				if strictWarn != result.Warnings()[i].Text {
  2912  					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
  2913  				}
  2914  
  2915  			}
  2916  		})
  2917  	}
  2918  }
  2919  
  2920  func testFinalizerValidationApplyCreateAndUpdateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  2921  	var testcases = []struct {
  2922  		name                 string
  2923  		finalizer            []string
  2924  		updatedFinalizer     []string
  2925  		opts                 metav1.PatchOptions
  2926  		expectUpdateWarnings []string
  2927  		expectCreateWarnings []string
  2928  	}{
  2929  		{
  2930  			name:      "create-crd-with-invalid-finalizer",
  2931  			finalizer: []string{"invalid-finalizer"},
  2932  			expectCreateWarnings: []string{
  2933  				`metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
  2934  			},
  2935  		},
  2936  		{
  2937  			name:      "create-crd-with-valid-finalizer",
  2938  			finalizer: []string{"kubernetes.io/valid-finalizer"},
  2939  		},
  2940  		{
  2941  			name:             "update-crd-with-invalid-finalizer",
  2942  			finalizer:        []string{"invalid-finalizer"},
  2943  			updatedFinalizer: []string{"another-invalid-finalizer"},
  2944  			expectCreateWarnings: []string{
  2945  				`metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
  2946  			},
  2947  			expectUpdateWarnings: []string{
  2948  				`metadata.finalizers: "another-invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
  2949  			},
  2950  		},
  2951  		{
  2952  			name:             "update-crd-with-valid-finalizer",
  2953  			finalizer:        []string{"kubernetes.io/valid-finalizer"},
  2954  			updatedFinalizer: []string{"kubernetes.io/another-valid-finalizer"},
  2955  		},
  2956  		{
  2957  			name:             "update-crd-with-valid-finalizer-leaving-an-existing-invalid-finalizer",
  2958  			finalizer:        []string{"invalid-finalizer"},
  2959  			updatedFinalizer: []string{"kubernetes.io/another-valid-finalizer"},
  2960  			expectCreateWarnings: []string{
  2961  				`metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
  2962  			},
  2963  		},
  2964  	}
  2965  
  2966  	for _, tc := range testcases {
  2967  		t.Run(tc.name, func(t *testing.T) {
  2968  			kind := gvk.Kind
  2969  			apiVersion := gvk.Group + "/" + gvk.Version
  2970  
  2971  			// create the CR as specified by the test case
  2972  			name := fmt.Sprintf("apply-create-crd-%s", tc.name)
  2973  			finalizerVal, _ := json.Marshal(tc.finalizer)
  2974  			applyCreateBody := []byte(fmt.Sprintf(crdApplyFinalizerBody, apiVersion, kind, name, finalizerVal))
  2975  
  2976  			req := rest.Patch(types.ApplyPatchType).
  2977  				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  2978  				Name(name).
  2979  				Param("fieldManager", "apply_test").
  2980  				VersionedParams(&tc.opts, metav1.ParameterCodec)
  2981  			result := req.Body(applyCreateBody).Do(context.TODO())
  2982  			if result.Error() != nil {
  2983  				t.Fatalf("unexpected error: %v", result.Error())
  2984  			}
  2985  
  2986  			if len(result.Warnings()) != len(tc.expectCreateWarnings) {
  2987  				for _, r := range result.Warnings() {
  2988  					t.Logf("received warning: %v", r)
  2989  				}
  2990  				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.expectCreateWarnings), len(result.Warnings()))
  2991  			}
  2992  			for i, expectedWarning := range tc.expectCreateWarnings {
  2993  				if expectedWarning != result.Warnings()[i].Text {
  2994  					t.Fatalf("expected warning: %s, got warning: %s", expectedWarning, result.Warnings()[i].Text)
  2995  				}
  2996  			}
  2997  
  2998  			if len(tc.updatedFinalizer) != 0 {
  2999  				finalizerVal, _ := json.Marshal(tc.updatedFinalizer)
  3000  				applyUpdateBody := []byte(fmt.Sprintf(crdApplyFinalizerBody, apiVersion, kind, name, finalizerVal))
  3001  				updateReq := rest.Patch(types.ApplyPatchType).
  3002  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  3003  					Name(name).
  3004  					Param("fieldManager", "apply_test").
  3005  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3006  				result = updateReq.Body(applyUpdateBody).Do(context.TODO())
  3007  
  3008  				if result.Error() != nil {
  3009  					t.Fatalf("unexpected error: %v", result.Error())
  3010  				}
  3011  
  3012  				if len(result.Warnings()) != len(tc.expectUpdateWarnings) {
  3013  					t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.expectUpdateWarnings), len(result.Warnings()))
  3014  				}
  3015  				for i, expectedWarning := range tc.expectUpdateWarnings {
  3016  					if expectedWarning != result.Warnings()[i].Text {
  3017  						t.Fatalf("expected warning: %s, got warning: %s", expectedWarning, result.Warnings()[i].Text)
  3018  					}
  3019  				}
  3020  			}
  3021  		})
  3022  	}
  3023  }
  3024  
  3025  func setupCRD(t testing.TB, config *rest.Config, apiGroup string, schemaless bool) *apiextensionsv1.CustomResourceDefinition {
  3026  	apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  3027  	if err != nil {
  3028  		t.Fatal(err)
  3029  	}
  3030  	dynamicClient, err := dynamic.NewForConfig(config)
  3031  	if err != nil {
  3032  		t.Fatal(err)
  3033  	}
  3034  
  3035  	preserveUnknownFields := ""
  3036  	if schemaless {
  3037  		preserveUnknownFields = `"x-kubernetes-preserve-unknown-fields": true,`
  3038  	}
  3039  	crdSchema := fmt.Sprintf(crdSchemaBase, preserveUnknownFields)
  3040  
  3041  	// create the CRD
  3042  	crd := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
  3043  
  3044  	// adjust the API group
  3045  	crd.Name = crd.Spec.Names.Plural + "." + apiGroup
  3046  	crd.Spec.Group = apiGroup
  3047  
  3048  	var c apiextensionsv1.CustomResourceValidation
  3049  	err = json.Unmarshal([]byte(crdSchema), &c)
  3050  	if err != nil {
  3051  		t.Fatal(err)
  3052  	}
  3053  	//crd.Spec.PreserveUnknownFields = false
  3054  	for i := range crd.Spec.Versions {
  3055  		crd.Spec.Versions[i].Schema = &c
  3056  	}
  3057  	// install the CRD
  3058  	crd, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
  3059  	if err != nil {
  3060  		t.Fatal(err)
  3061  	}
  3062  
  3063  	return crd
  3064  }
  3065  
  3066  func BenchmarkFieldValidation(b *testing.B) {
  3067  	flag.Lookup("v").Value.Set("0")
  3068  	server, err := kubeapiservertesting.StartTestServer(b, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
  3069  	if err != nil {
  3070  		b.Fatal(err)
  3071  	}
  3072  	config := server.ClientConfig
  3073  	defer server.TearDownFn()
  3074  
  3075  	// don't log warnings, tests inspect them in the responses directly
  3076  	config.WarningHandler = rest.NoWarnings{}
  3077  
  3078  	client := clientset.NewForConfigOrDie(config)
  3079  
  3080  	schemaCRD := setupCRD(b, config, "schema.example.com", false)
  3081  	schemaGVR := schema.GroupVersionResource{
  3082  		Group:    schemaCRD.Spec.Group,
  3083  		Version:  schemaCRD.Spec.Versions[0].Name,
  3084  		Resource: schemaCRD.Spec.Names.Plural,
  3085  	}
  3086  	schemaGVK := schema.GroupVersionKind{
  3087  		Group:   schemaCRD.Spec.Group,
  3088  		Version: schemaCRD.Spec.Versions[0].Name,
  3089  		Kind:    schemaCRD.Spec.Names.Kind,
  3090  	}
  3091  
  3092  	schemalessCRD := setupCRD(b, config, "schemaless.example.com", true)
  3093  	schemalessGVR := schema.GroupVersionResource{
  3094  		Group:    schemalessCRD.Spec.Group,
  3095  		Version:  schemalessCRD.Spec.Versions[0].Name,
  3096  		Resource: schemalessCRD.Spec.Names.Plural,
  3097  	}
  3098  	schemalessGVK := schema.GroupVersionKind{
  3099  		Group:   schemalessCRD.Spec.Group,
  3100  		Version: schemalessCRD.Spec.Versions[0].Name,
  3101  		Kind:    schemalessCRD.Spec.Names.Kind,
  3102  	}
  3103  
  3104  	rest := client.Discovery().RESTClient()
  3105  
  3106  	b.Run("Post", func(b *testing.B) { benchFieldValidationPost(b, client) })
  3107  	b.Run("Put", func(b *testing.B) { benchFieldValidationPut(b, client) })
  3108  	b.Run("PatchTyped", func(b *testing.B) { benchFieldValidationPatchTyped(b, client) })
  3109  	b.Run("SMP", func(b *testing.B) { benchFieldValidationSMP(b, client) })
  3110  	b.Run("ApplyCreate", func(b *testing.B) { benchFieldValidationApplyCreate(b, client) })
  3111  	b.Run("ApplyUpdate", func(b *testing.B) { benchFieldValidationApplyUpdate(b, client) })
  3112  
  3113  	b.Run("PostCRD", func(b *testing.B) { benchFieldValidationPostCRD(b, rest, schemaGVK, schemaGVR) })
  3114  	b.Run("PutCRD", func(b *testing.B) { benchFieldValidationPutCRD(b, rest, schemaGVK, schemaGVR) })
  3115  	b.Run("PatchCRD", func(b *testing.B) { benchFieldValidationPatchCRD(b, rest, schemaGVK, schemaGVR) })
  3116  	b.Run("ApplyCreateCRD", func(b *testing.B) { benchFieldValidationApplyCreateCRD(b, rest, schemaGVK, schemaGVR) })
  3117  	b.Run("ApplyUpdateCRD", func(b *testing.B) { benchFieldValidationApplyUpdateCRD(b, rest, schemaGVK, schemaGVR) })
  3118  
  3119  	b.Run("PostCRDSchemaless", func(b *testing.B) { benchFieldValidationPostCRD(b, rest, schemalessGVK, schemalessGVR) })
  3120  	b.Run("PutCRDSchemaless", func(b *testing.B) { benchFieldValidationPutCRD(b, rest, schemalessGVK, schemalessGVR) })
  3121  	b.Run("PatchCRDSchemaless", func(b *testing.B) { benchFieldValidationPatchCRD(b, rest, schemalessGVK, schemalessGVR) })
  3122  	b.Run("ApplyCreateCRDSchemaless", func(b *testing.B) { benchFieldValidationApplyCreateCRD(b, rest, schemalessGVK, schemalessGVR) })
  3123  	b.Run("ApplyUpdateCRDSchemaless", func(b *testing.B) { benchFieldValidationApplyUpdateCRD(b, rest, schemalessGVK, schemalessGVR) })
  3124  
  3125  }
  3126  
  3127  func benchFieldValidationPost(b *testing.B, client clientset.Interface) {
  3128  	var benchmarks = []struct {
  3129  		name        string
  3130  		bodyBase    string
  3131  		opts        metav1.CreateOptions
  3132  		contentType string
  3133  	}{
  3134  		{
  3135  			name: "post-strict-validation",
  3136  			opts: metav1.CreateOptions{
  3137  				FieldValidation: "Strict",
  3138  			},
  3139  			bodyBase: validBodyJSON,
  3140  		},
  3141  		{
  3142  			name: "post-warn-validation",
  3143  			opts: metav1.CreateOptions{
  3144  				FieldValidation: "Warn",
  3145  			},
  3146  			bodyBase: validBodyJSON,
  3147  		},
  3148  		{
  3149  			name: "post-ignore-validation",
  3150  			opts: metav1.CreateOptions{
  3151  				FieldValidation: "Ignore",
  3152  			},
  3153  			bodyBase: validBodyJSON,
  3154  		},
  3155  		{
  3156  			name: "post-strict-validation-yaml",
  3157  			opts: metav1.CreateOptions{
  3158  				FieldValidation: "Strict",
  3159  			},
  3160  			bodyBase:    validBodyYAML,
  3161  			contentType: "application/yaml",
  3162  		},
  3163  		{
  3164  			name: "post-warn-validation-yaml",
  3165  			opts: metav1.CreateOptions{
  3166  				FieldValidation: "Warn",
  3167  			},
  3168  			bodyBase:    validBodyYAML,
  3169  			contentType: "application/yaml",
  3170  		},
  3171  		{
  3172  			name: "post-ignore-validation-yaml",
  3173  			opts: metav1.CreateOptions{
  3174  				FieldValidation: "Ignore",
  3175  			},
  3176  			bodyBase:    validBodyYAML,
  3177  			contentType: "application/yaml",
  3178  		},
  3179  	}
  3180  
  3181  	for _, bm := range benchmarks {
  3182  		b.Run(bm.name, func(b *testing.B) {
  3183  			b.ResetTimer()
  3184  			b.ReportAllocs()
  3185  			for n := 0; n < b.N; n++ {
  3186  				body := []byte(fmt.Sprintf(bm.bodyBase, fmt.Sprintf("test-deployment-%s-%d-%d-%d", bm.name, n, b.N, time.Now().UnixNano())))
  3187  				req := client.CoreV1().RESTClient().Post().
  3188  					AbsPath("/apis/apps/v1").
  3189  					Namespace("default").
  3190  					Resource("deployments").
  3191  					SetHeader("Content-Type", bm.contentType).
  3192  					VersionedParams(&bm.opts, metav1.ParameterCodec)
  3193  				result := req.Body(body).Do(context.TODO())
  3194  				if result.Error() != nil {
  3195  					b.Fatalf("unexpected request err: %v", result.Error())
  3196  				}
  3197  			}
  3198  		})
  3199  	}
  3200  }
  3201  
  3202  func benchFieldValidationPut(b *testing.B, client clientset.Interface) {
  3203  	var testcases = []struct {
  3204  		name        string
  3205  		opts        metav1.UpdateOptions
  3206  		putBodyBase string
  3207  		contentType string
  3208  	}{
  3209  		{
  3210  			name: "put-strict-validation",
  3211  			opts: metav1.UpdateOptions{
  3212  				FieldValidation: "Strict",
  3213  			},
  3214  			putBodyBase: validBodyJSON,
  3215  		},
  3216  		{
  3217  			name: "put-warn-validation",
  3218  			opts: metav1.UpdateOptions{
  3219  				FieldValidation: "Warn",
  3220  			},
  3221  			putBodyBase: validBodyJSON,
  3222  		},
  3223  		{
  3224  			name: "put-ignore-validation",
  3225  			opts: metav1.UpdateOptions{
  3226  				FieldValidation: "Ignore",
  3227  			},
  3228  			putBodyBase: validBodyJSON,
  3229  		},
  3230  		{
  3231  			name: "put-strict-validation-yaml",
  3232  			opts: metav1.UpdateOptions{
  3233  				FieldValidation: "Strict",
  3234  			},
  3235  			putBodyBase: validBodyYAML,
  3236  			contentType: "application/yaml",
  3237  		},
  3238  		{
  3239  			name: "put-warn-validation-yaml",
  3240  			opts: metav1.UpdateOptions{
  3241  				FieldValidation: "Warn",
  3242  			},
  3243  			putBodyBase: validBodyYAML,
  3244  			contentType: "application/yaml",
  3245  		},
  3246  		{
  3247  			name: "put-ignore-validation-yaml",
  3248  			opts: metav1.UpdateOptions{
  3249  				FieldValidation: "Ignore",
  3250  			},
  3251  			putBodyBase: validBodyYAML,
  3252  			contentType: "application/yaml",
  3253  		},
  3254  	}
  3255  
  3256  	for _, tc := range testcases {
  3257  		b.Run(tc.name, func(b *testing.B) {
  3258  			names := make([]string, b.N)
  3259  			for n := 0; n < b.N; n++ {
  3260  				deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3261  				names[n] = deployName
  3262  				postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
  3263  
  3264  				if _, err := client.CoreV1().RESTClient().Post().
  3265  					AbsPath("/apis/apps/v1").
  3266  					Namespace("default").
  3267  					Resource("deployments").
  3268  					Body(postBody).
  3269  					DoRaw(context.TODO()); err != nil {
  3270  					b.Fatalf("failed to create initial deployment: %v", err)
  3271  				}
  3272  
  3273  			}
  3274  			b.ResetTimer()
  3275  			b.ReportAllocs()
  3276  			for n := 0; n < b.N; n++ {
  3277  				deployName := names[n]
  3278  				putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
  3279  				req := client.CoreV1().RESTClient().Put().
  3280  					AbsPath("/apis/apps/v1").
  3281  					Namespace("default").
  3282  					Resource("deployments").
  3283  					SetHeader("Content-Type", tc.contentType).
  3284  					Name(deployName).
  3285  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3286  				result := req.Body([]byte(putBody)).Do(context.TODO())
  3287  				if result.Error() != nil {
  3288  					b.Fatalf("unexpected request err: %v", result.Error())
  3289  				}
  3290  			}
  3291  		})
  3292  	}
  3293  }
  3294  
  3295  func benchFieldValidationPatchTyped(b *testing.B, client clientset.Interface) {
  3296  	mergePatchBodyValid := `
  3297  {
  3298  	"spec": {
  3299  		"paused": false,
  3300  		"template": {
  3301  			"spec": {
  3302  				"containers": [{
  3303  					"name": "nginx",
  3304  					"image": "nginx:latest",
  3305  					"imagePullPolicy": "Always"
  3306  				}]
  3307  			}
  3308  		},
  3309  		"replicas": 2
  3310  	}
  3311  }
  3312  	`
  3313  
  3314  	jsonPatchBodyValid := `
  3315  			[
  3316  				{"op": "add", "path": "/spec/paused", "value": true},
  3317  				{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"},
  3318  				{"op": "add", "path": "/spec/replicas", "value": 2}
  3319  			]
  3320  			`
  3321  
  3322  	var testcases = []struct {
  3323  		name      string
  3324  		opts      metav1.PatchOptions
  3325  		patchType types.PatchType
  3326  		body      string
  3327  	}{
  3328  		{
  3329  			name: "merge-patch-strict-validation",
  3330  			opts: metav1.PatchOptions{
  3331  				FieldValidation: "Strict",
  3332  			},
  3333  			patchType: types.MergePatchType,
  3334  			body:      mergePatchBodyValid,
  3335  		},
  3336  		{
  3337  			name: "merge-patch-warn-validation",
  3338  			opts: metav1.PatchOptions{
  3339  				FieldValidation: "Warn",
  3340  			},
  3341  			patchType: types.MergePatchType,
  3342  			body:      mergePatchBodyValid,
  3343  		},
  3344  		{
  3345  			name: "merge-patch-ignore-validation",
  3346  			opts: metav1.PatchOptions{
  3347  				FieldValidation: "Ignore",
  3348  			},
  3349  			patchType: types.MergePatchType,
  3350  			body:      mergePatchBodyValid,
  3351  		},
  3352  		{
  3353  			name:      "json-patch-strict-validation",
  3354  			patchType: types.JSONPatchType,
  3355  			opts: metav1.PatchOptions{
  3356  				FieldValidation: "Strict",
  3357  			},
  3358  			body: jsonPatchBodyValid,
  3359  		},
  3360  		{
  3361  			name:      "json-patch-warn-validation",
  3362  			patchType: types.JSONPatchType,
  3363  			opts: metav1.PatchOptions{
  3364  				FieldValidation: "Warn",
  3365  			},
  3366  			body: jsonPatchBodyValid,
  3367  		},
  3368  		{
  3369  			name:      "json-patch-ignore-validation",
  3370  			patchType: types.JSONPatchType,
  3371  			opts: metav1.PatchOptions{
  3372  				FieldValidation: "Ignore",
  3373  			},
  3374  			body: jsonPatchBodyValid,
  3375  		},
  3376  	}
  3377  
  3378  	for _, tc := range testcases {
  3379  		b.Run(tc.name, func(b *testing.B) {
  3380  			names := make([]string, b.N)
  3381  			for n := 0; n < b.N; n++ {
  3382  				deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3383  				names[n] = deployName
  3384  				postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
  3385  
  3386  				if _, err := client.CoreV1().RESTClient().Post().
  3387  					AbsPath("/apis/apps/v1").
  3388  					Namespace("default").
  3389  					Resource("deployments").
  3390  					Body(postBody).
  3391  					DoRaw(context.TODO()); err != nil {
  3392  					b.Fatalf("failed to create initial deployment: %v", err)
  3393  				}
  3394  			}
  3395  			b.ResetTimer()
  3396  			b.ReportAllocs()
  3397  			for n := 0; n < b.N; n++ {
  3398  				deployName := names[n]
  3399  				req := client.CoreV1().RESTClient().Patch(tc.patchType).
  3400  					AbsPath("/apis/apps/v1").
  3401  					Namespace("default").
  3402  					Resource("deployments").
  3403  					Name(deployName).
  3404  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3405  				result := req.Body([]byte(tc.body)).Do(context.TODO())
  3406  				if result.Error() != nil {
  3407  					b.Fatalf("unexpected request err: %v", result.Error())
  3408  				}
  3409  			}
  3410  
  3411  		})
  3412  	}
  3413  }
  3414  
  3415  func benchFieldValidationSMP(b *testing.B, client clientset.Interface) {
  3416  	smpBodyValid := `
  3417  	{
  3418  		"spec": {
  3419  			"replicas": 3,
  3420  			"paused": false,
  3421  			"selector": {
  3422  				"matchLabels": {
  3423  					"app": "nginx"
  3424  				}
  3425  			},
  3426  			"template": {
  3427  				"metadata": {
  3428  					"labels": {
  3429  						"app": "nginx"
  3430  					}
  3431  				},
  3432  				"spec": {
  3433  					"containers": [{
  3434  						"name": "nginx",
  3435  						"imagePullPolicy": "Never"
  3436  					}]
  3437  				}
  3438  			}
  3439  		}
  3440  	}
  3441  	`
  3442  	var testcases = []struct {
  3443  		name string
  3444  		opts metav1.PatchOptions
  3445  		body string
  3446  	}{
  3447  		{
  3448  			name: "smp-strict-validation",
  3449  			opts: metav1.PatchOptions{
  3450  				FieldValidation: "Strict",
  3451  			},
  3452  			body: smpBodyValid,
  3453  		},
  3454  		{
  3455  			name: "smp-warn-validation",
  3456  			opts: metav1.PatchOptions{
  3457  				FieldValidation: "Warn",
  3458  			},
  3459  			body: smpBodyValid,
  3460  		},
  3461  		{
  3462  			name: "smp-ignore-validation",
  3463  			opts: metav1.PatchOptions{
  3464  				FieldValidation: "Ignore",
  3465  			},
  3466  			body: smpBodyValid,
  3467  		},
  3468  	}
  3469  
  3470  	for _, tc := range testcases {
  3471  		b.Run(tc.name, func(b *testing.B) {
  3472  			names := make([]string, b.N)
  3473  			for n := 0; n < b.N; n++ {
  3474  				name := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3475  				names[n] = name
  3476  				body := []byte(fmt.Sprintf(validBodyJSON, name))
  3477  				_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  3478  					AbsPath("/apis/apps/v1").
  3479  					Namespace("default").
  3480  					Resource("deployments").
  3481  					Name(name).
  3482  					Param("fieldManager", "apply_test").
  3483  					Body(body).
  3484  					Do(context.TODO()).
  3485  					Get()
  3486  				if err != nil {
  3487  					b.Fatalf("Failed to create object using Apply patch: %v", err)
  3488  				}
  3489  			}
  3490  			b.ResetTimer()
  3491  			b.ReportAllocs()
  3492  			for n := 0; n < b.N; n++ {
  3493  				name := names[n]
  3494  				req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
  3495  					AbsPath("/apis/apps/v1").
  3496  					Namespace("default").
  3497  					Resource("deployments").
  3498  					Name(name).
  3499  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3500  				result := req.Body([]byte(tc.body)).Do(context.TODO())
  3501  				if result.Error() != nil {
  3502  					b.Fatalf("unexpected request err: %v", result.Error())
  3503  				}
  3504  			}
  3505  		})
  3506  	}
  3507  
  3508  }
  3509  
  3510  func benchFieldValidationApplyCreate(b *testing.B, client clientset.Interface) {
  3511  	var testcases = []struct {
  3512  		name string
  3513  		opts metav1.PatchOptions
  3514  	}{
  3515  		{
  3516  			name: "strict-validation",
  3517  			opts: metav1.PatchOptions{
  3518  				FieldValidation: "Strict",
  3519  				FieldManager:    "mgr",
  3520  			},
  3521  		},
  3522  		{
  3523  			name: "warn-validation",
  3524  			opts: metav1.PatchOptions{
  3525  				FieldValidation: "Warn",
  3526  				FieldManager:    "mgr",
  3527  			},
  3528  		},
  3529  		{
  3530  			name: "ignore-validation",
  3531  			opts: metav1.PatchOptions{
  3532  				FieldValidation: "Ignore",
  3533  				FieldManager:    "mgr",
  3534  			},
  3535  		},
  3536  	}
  3537  
  3538  	for _, tc := range testcases {
  3539  		b.Run(tc.name, func(b *testing.B) {
  3540  			b.ResetTimer()
  3541  			b.ReportAllocs()
  3542  			for n := 0; n < b.N; n++ {
  3543  				name := fmt.Sprintf("apply-create-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3544  				body := []byte(fmt.Sprintf(validBodyJSON, name))
  3545  				req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  3546  					AbsPath("/apis/apps/v1").
  3547  					Namespace("default").
  3548  					Resource("deployments").
  3549  					Name(name).
  3550  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3551  				result := req.Body(body).Do(context.TODO())
  3552  				if result.Error() != nil {
  3553  					b.Fatalf("unexpected request err: %v", result.Error())
  3554  				}
  3555  			}
  3556  		})
  3557  	}
  3558  }
  3559  
  3560  func benchFieldValidationApplyUpdate(b *testing.B, client clientset.Interface) {
  3561  	var testcases = []struct {
  3562  		name string
  3563  		opts metav1.PatchOptions
  3564  	}{
  3565  		{
  3566  			name: "strict-validation",
  3567  			opts: metav1.PatchOptions{
  3568  				FieldValidation: "Strict",
  3569  				FieldManager:    "mgr",
  3570  			},
  3571  		},
  3572  		{
  3573  			name: "warn-validation",
  3574  			opts: metav1.PatchOptions{
  3575  				FieldValidation: "Warn",
  3576  				FieldManager:    "mgr",
  3577  			},
  3578  		},
  3579  		{
  3580  			name: "ignore-validation",
  3581  			opts: metav1.PatchOptions{
  3582  				FieldValidation: "Ignore",
  3583  				FieldManager:    "mgr",
  3584  			},
  3585  		},
  3586  	}
  3587  
  3588  	for _, tc := range testcases {
  3589  		b.Run(tc.name, func(b *testing.B) {
  3590  			names := make([]string, b.N)
  3591  			for n := 0; n < b.N; n++ {
  3592  				name := fmt.Sprintf("apply-update-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3593  				names[n] = name
  3594  				createBody := []byte(fmt.Sprintf(validBodyJSON, name))
  3595  				createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  3596  					AbsPath("/apis/apps/v1").
  3597  					Namespace("default").
  3598  					Resource("deployments").
  3599  					Name(name).
  3600  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3601  				createResult := createReq.Body(createBody).Do(context.TODO())
  3602  				if createResult.Error() != nil {
  3603  					b.Fatalf("unexpected apply create err: %v", createResult.Error())
  3604  				}
  3605  			}
  3606  			b.ResetTimer()
  3607  			b.ReportAllocs()
  3608  			for n := 0; n < b.N; n++ {
  3609  				name := names[n]
  3610  				updateBody := []byte(fmt.Sprintf(applyValidBody, name))
  3611  				updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  3612  					AbsPath("/apis/apps/v1").
  3613  					Namespace("default").
  3614  					Resource("deployments").
  3615  					Name(name).
  3616  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3617  				result := updateReq.Body(updateBody).Do(context.TODO())
  3618  				if result.Error() != nil {
  3619  					b.Fatalf("unexpected request err: %v", result.Error())
  3620  				}
  3621  			}
  3622  		})
  3623  	}
  3624  }
  3625  
  3626  func benchFieldValidationPostCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  3627  	var testcases = []struct {
  3628  		name        string
  3629  		opts        metav1.PatchOptions
  3630  		body        string
  3631  		contentType string
  3632  	}{
  3633  		{
  3634  			name: "crd-post-strict-validation",
  3635  			opts: metav1.PatchOptions{
  3636  				FieldValidation: "Strict",
  3637  			},
  3638  			body: crdValidBody,
  3639  		},
  3640  		{
  3641  			name: "crd-post-warn-validation",
  3642  			opts: metav1.PatchOptions{
  3643  				FieldValidation: "Warn",
  3644  			},
  3645  			body: crdValidBody,
  3646  		},
  3647  		{
  3648  			name: "crd-post-ignore-validation",
  3649  			opts: metav1.PatchOptions{
  3650  				FieldValidation: "Ignore",
  3651  			},
  3652  			body: crdValidBody,
  3653  		},
  3654  		{
  3655  			name: "crd-post-no-validation",
  3656  			body: crdValidBody,
  3657  		},
  3658  		{
  3659  			name: "crd-post-strict-validation-yaml",
  3660  			opts: metav1.PatchOptions{
  3661  				FieldValidation: "Strict",
  3662  			},
  3663  			body:        crdValidBodyYAML,
  3664  			contentType: "application/yaml",
  3665  		},
  3666  		{
  3667  			name: "crd-post-warn-validation-yaml",
  3668  			opts: metav1.PatchOptions{
  3669  				FieldValidation: "Warn",
  3670  			},
  3671  			body:        crdValidBodyYAML,
  3672  			contentType: "application/yaml",
  3673  		},
  3674  		{
  3675  			name: "crd-post-ignore-validation-yaml",
  3676  			opts: metav1.PatchOptions{
  3677  				FieldValidation: "Ignore",
  3678  			},
  3679  			body:        crdValidBodyYAML,
  3680  			contentType: "application/yaml",
  3681  		},
  3682  		{
  3683  			name:        "crd-post-no-validation-yaml",
  3684  			body:        crdValidBodyYAML,
  3685  			contentType: "application/yaml",
  3686  		},
  3687  	}
  3688  	for _, tc := range testcases {
  3689  		b.Run(tc.name, func(b *testing.B) {
  3690  			b.ResetTimer()
  3691  			b.ReportAllocs()
  3692  			for n := 0; n < b.N; n++ {
  3693  				kind := gvk.Kind
  3694  				apiVersion := gvk.Group + "/" + gvk.Version
  3695  
  3696  				// create the CR as specified by the test case
  3697  				jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())))
  3698  				req := rest.Post().
  3699  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  3700  					SetHeader("Content-Type", tc.contentType).
  3701  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3702  				result := req.Body([]byte(jsonBody)).Do(context.TODO())
  3703  
  3704  				if result.Error() != nil {
  3705  					b.Fatalf("unexpected post err: %v", result.Error())
  3706  				}
  3707  			}
  3708  		})
  3709  	}
  3710  }
  3711  
  3712  func benchFieldValidationPutCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  3713  	var testcases = []struct {
  3714  		name        string
  3715  		opts        metav1.PatchOptions
  3716  		putBody     string
  3717  		contentType string
  3718  	}{
  3719  		{
  3720  			name: "crd-put-strict-validation",
  3721  			opts: metav1.PatchOptions{
  3722  				FieldValidation: "Strict",
  3723  			},
  3724  			putBody: crdValidBody,
  3725  		},
  3726  		{
  3727  			name: "crd-put-warn-validation",
  3728  			opts: metav1.PatchOptions{
  3729  				FieldValidation: "Warn",
  3730  			},
  3731  			putBody: crdValidBody,
  3732  		},
  3733  		{
  3734  			name: "crd-put-ignore-validation",
  3735  			opts: metav1.PatchOptions{
  3736  				FieldValidation: "Ignore",
  3737  			},
  3738  			putBody: crdValidBody,
  3739  		},
  3740  		{
  3741  			name:    "crd-put-no-validation",
  3742  			putBody: crdValidBody,
  3743  		},
  3744  		{
  3745  			name: "crd-put-strict-validation-yaml",
  3746  			opts: metav1.PatchOptions{
  3747  				FieldValidation: "Strict",
  3748  			},
  3749  			putBody:     crdValidBodyYAML,
  3750  			contentType: "application/yaml",
  3751  		},
  3752  		{
  3753  			name: "crd-put-warn-validation-yaml",
  3754  			opts: metav1.PatchOptions{
  3755  				FieldValidation: "Warn",
  3756  			},
  3757  			putBody:     crdValidBodyYAML,
  3758  			contentType: "application/yaml",
  3759  		},
  3760  		{
  3761  			name: "crd-put-ignore-validation-yaml",
  3762  			opts: metav1.PatchOptions{
  3763  				FieldValidation: "Ignore",
  3764  			},
  3765  			putBody:     crdValidBodyYAML,
  3766  			contentType: "application/yaml",
  3767  		},
  3768  		{
  3769  			name:        "crd-put-no-validation-yaml",
  3770  			putBody:     crdValidBodyYAML,
  3771  			contentType: "application/yaml",
  3772  		},
  3773  	}
  3774  	for _, tc := range testcases {
  3775  		b.Run(tc.name, func(b *testing.B) {
  3776  			kind := gvk.Kind
  3777  			apiVersion := gvk.Group + "/" + gvk.Version
  3778  			names := make([]string, b.N)
  3779  			resourceVersions := make([]string, b.N)
  3780  			for n := 0; n < b.N; n++ {
  3781  				deployName := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3782  				names[n] = deployName
  3783  
  3784  				// create the CR as specified by the test case
  3785  				jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, deployName))
  3786  				postReq := rest.Post().
  3787  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  3788  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3789  				postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
  3790  				if err != nil {
  3791  					b.Fatalf("unexpeted error on CR creation: %v", err)
  3792  				}
  3793  				postUnstructured := &unstructured.Unstructured{}
  3794  				if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
  3795  					b.Fatalf("unexpeted error unmarshalling created CR: %v", err)
  3796  				}
  3797  				resourceVersions[n] = postUnstructured.GetResourceVersion()
  3798  			}
  3799  			b.ResetTimer()
  3800  			b.ReportAllocs()
  3801  			for n := 0; n < b.N; n++ {
  3802  				// update the CR as specified by the test case
  3803  				putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, names[n], resourceVersions[n]))
  3804  				putReq := rest.Put().
  3805  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  3806  					Name(names[n]).
  3807  					SetHeader("Content-Type", tc.contentType).
  3808  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3809  				result := putReq.Body([]byte(putBody)).Do(context.TODO())
  3810  				if result.Error() != nil {
  3811  					b.Fatalf("unexpected put err: %v", result.Error())
  3812  				}
  3813  			}
  3814  		})
  3815  	}
  3816  }
  3817  
  3818  func benchFieldValidationPatchCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  3819  	patchYAMLBody := `
  3820  apiVersion: %s
  3821  kind: %s
  3822  metadata:
  3823    name: %s
  3824    finalizers:
  3825    - test/finalizer
  3826  spec:
  3827    cronSpec: "* * * * */5"
  3828    ports:
  3829    - name: x
  3830      containerPort: 80
  3831      protocol: TCP`
  3832  
  3833  	mergePatchBody := `
  3834  {
  3835  	"spec": {
  3836  		"knownField1": "val1",
  3837  			"ports": [{
  3838  				"name": "portName",
  3839  				"containerPort": 8080,
  3840  				"protocol": "TCP",
  3841  				"hostPort": 8081
  3842  			}]
  3843  	}
  3844  }
  3845  	`
  3846  	jsonPatchBody := `
  3847  			[
  3848  				{"op": "add", "path": "/spec/knownField1", "value": "val1"},
  3849  				{"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
  3850  				{"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
  3851  				{"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
  3852  				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081}
  3853  			]
  3854  			`
  3855  	var testcases = []struct {
  3856  		name      string
  3857  		patchType types.PatchType
  3858  		opts      metav1.PatchOptions
  3859  		body      string
  3860  	}{
  3861  		{
  3862  			name:      "crd-merge-patch-strict-validation",
  3863  			patchType: types.MergePatchType,
  3864  			opts: metav1.PatchOptions{
  3865  				FieldValidation: "Strict",
  3866  			},
  3867  			body: mergePatchBody,
  3868  		},
  3869  		{
  3870  			name:      "crd-merge-patch-warn-validation",
  3871  			patchType: types.MergePatchType,
  3872  			opts: metav1.PatchOptions{
  3873  				FieldValidation: "Warn",
  3874  			},
  3875  			body: mergePatchBody,
  3876  		},
  3877  		{
  3878  			name:      "crd-merge-patch-ignore-validation",
  3879  			patchType: types.MergePatchType,
  3880  			opts: metav1.PatchOptions{
  3881  				FieldValidation: "Ignore",
  3882  			},
  3883  			body: mergePatchBody,
  3884  		},
  3885  		{
  3886  			name:      "crd-merge-patch-no-validation",
  3887  			patchType: types.MergePatchType,
  3888  			body:      mergePatchBody,
  3889  		},
  3890  		{
  3891  			name:      "crd-json-patch-strict-validation",
  3892  			patchType: types.JSONPatchType,
  3893  			opts: metav1.PatchOptions{
  3894  				FieldValidation: "Strict",
  3895  			},
  3896  			body: jsonPatchBody,
  3897  		},
  3898  		{
  3899  			name:      "crd-json-patch-warn-validation",
  3900  			patchType: types.JSONPatchType,
  3901  			opts: metav1.PatchOptions{
  3902  				FieldValidation: "Warn",
  3903  			},
  3904  			body: jsonPatchBody,
  3905  		},
  3906  		{
  3907  			name:      "crd-json-patch-ignore-validation",
  3908  			patchType: types.JSONPatchType,
  3909  			opts: metav1.PatchOptions{
  3910  				FieldValidation: "Ignore",
  3911  			},
  3912  			body: jsonPatchBody,
  3913  		},
  3914  		{
  3915  			name:      "crd-json-patch-no-validation",
  3916  			patchType: types.JSONPatchType,
  3917  			body:      jsonPatchBody,
  3918  		},
  3919  	}
  3920  	for _, tc := range testcases {
  3921  		b.Run(tc.name, func(b *testing.B) {
  3922  			kind := gvk.Kind
  3923  			apiVersion := gvk.Group + "/" + gvk.Version
  3924  			names := make([]string, b.N)
  3925  			for n := 0; n < b.N; n++ {
  3926  				deployName := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  3927  				names[n] = deployName
  3928  
  3929  				// create a CR
  3930  				yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, deployName))
  3931  				createResult, err := rest.Patch(types.ApplyPatchType).
  3932  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  3933  					Name(deployName).
  3934  					Param("fieldManager", "apply_test").
  3935  					Body(yamlBody).
  3936  					DoRaw(context.TODO())
  3937  				if err != nil {
  3938  					b.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
  3939  				}
  3940  			}
  3941  			b.ResetTimer()
  3942  			b.ReportAllocs()
  3943  			for n := 0; n < b.N; n++ {
  3944  				// patch the CR as specified by the test case
  3945  				req := rest.Patch(tc.patchType).
  3946  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  3947  					Name(names[n]).
  3948  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  3949  				result := req.Body([]byte(tc.body)).Do(context.TODO())
  3950  				if result.Error() != nil {
  3951  					b.Fatalf("unexpected patch err: %v", result.Error())
  3952  				}
  3953  			}
  3954  
  3955  		})
  3956  	}
  3957  }
  3958  
  3959  func benchFieldValidationApplyCreateCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  3960  	var testcases = []struct {
  3961  		name string
  3962  		opts metav1.PatchOptions
  3963  	}{
  3964  		{
  3965  			name: "strict-validation",
  3966  			opts: metav1.PatchOptions{
  3967  				FieldValidation: "Strict",
  3968  				FieldManager:    "mgr",
  3969  			},
  3970  		},
  3971  		{
  3972  			name: "warn-validation",
  3973  			opts: metav1.PatchOptions{
  3974  				FieldValidation: "Warn",
  3975  				FieldManager:    "mgr",
  3976  			},
  3977  		},
  3978  		{
  3979  			name: "ignore-validation",
  3980  			opts: metav1.PatchOptions{
  3981  				FieldValidation: "Ignore",
  3982  				FieldManager:    "mgr",
  3983  			},
  3984  		},
  3985  		{
  3986  			name: "no-validation",
  3987  			opts: metav1.PatchOptions{
  3988  				FieldManager: "mgr",
  3989  			},
  3990  		},
  3991  	}
  3992  
  3993  	for _, tc := range testcases {
  3994  		b.Run(tc.name, func(b *testing.B) {
  3995  			b.ResetTimer()
  3996  			b.ReportAllocs()
  3997  			for n := 0; n < b.N; n++ {
  3998  				kind := gvk.Kind
  3999  				apiVersion := gvk.Group + "/" + gvk.Version
  4000  				name := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  4001  
  4002  				// create the CR as specified by the test case
  4003  				applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
  4004  
  4005  				req := rest.Patch(types.ApplyPatchType).
  4006  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  4007  					Name(name).
  4008  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  4009  				result := req.Body(applyCreateBody).Do(context.TODO())
  4010  				if result.Error() != nil {
  4011  					b.Fatalf("unexpected apply err: %v", result.Error())
  4012  				}
  4013  
  4014  			}
  4015  		})
  4016  	}
  4017  }
  4018  
  4019  func benchFieldValidationApplyUpdateCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
  4020  	var testcases = []struct {
  4021  		name string
  4022  		opts metav1.PatchOptions
  4023  	}{
  4024  		{
  4025  			name: "strict-validation",
  4026  			opts: metav1.PatchOptions{
  4027  				FieldValidation: "Strict",
  4028  				FieldManager:    "mgr",
  4029  			},
  4030  		},
  4031  		{
  4032  			name: "warn-validation",
  4033  			opts: metav1.PatchOptions{
  4034  				FieldValidation: "Warn",
  4035  				FieldManager:    "mgr",
  4036  			},
  4037  		},
  4038  		{
  4039  			name: "ignore-validation",
  4040  			opts: metav1.PatchOptions{
  4041  				FieldValidation: "Ignore",
  4042  				FieldManager:    "mgr",
  4043  			},
  4044  		},
  4045  		{
  4046  			name: "no-validation",
  4047  			opts: metav1.PatchOptions{
  4048  				FieldManager: "mgr",
  4049  			},
  4050  		},
  4051  	}
  4052  
  4053  	for _, tc := range testcases {
  4054  		b.Run(tc.name, func(b *testing.B) {
  4055  			kind := gvk.Kind
  4056  			apiVersion := gvk.Group + "/" + gvk.Version
  4057  			names := make([]string, b.N)
  4058  
  4059  			for n := 0; n < b.N; n++ {
  4060  				names[n] = fmt.Sprintf("apply-update-crd-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
  4061  				applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, names[n]))
  4062  				createReq := rest.Patch(types.ApplyPatchType).
  4063  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  4064  					Name(names[n]).
  4065  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  4066  				createResult := createReq.Body(applyCreateBody).Do(context.TODO())
  4067  				if createResult.Error() != nil {
  4068  					b.Fatalf("unexpected apply create err: %v", createResult.Error())
  4069  				}
  4070  			}
  4071  			b.ResetTimer()
  4072  			b.ReportAllocs()
  4073  			for n := 0; n < b.N; n++ {
  4074  				applyUpdateBody := []byte(fmt.Sprintf(crdApplyValidBody2, apiVersion, kind, names[n]))
  4075  				updateReq := rest.Patch(types.ApplyPatchType).
  4076  					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
  4077  					Name(names[n]).
  4078  					VersionedParams(&tc.opts, metav1.ParameterCodec)
  4079  				result := updateReq.Body(applyUpdateBody).Do(context.TODO())
  4080  
  4081  				if result.Error() != nil {
  4082  					b.Fatalf("unexpected apply err: %v", result.Error())
  4083  				}
  4084  			}
  4085  
  4086  		})
  4087  	}
  4088  }
  4089  

View as plain text