mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			4089 lines
		
	
	
		
			125 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			4089 lines
		
	
	
		
			125 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2021 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package apiserver
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
						|
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 | 
						|
	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/client-go/dynamic"
 | 
						|
	clientset "k8s.io/client-go/kubernetes"
 | 
						|
	"k8s.io/client-go/rest"
 | 
						|
	"k8s.io/klog/v2"
 | 
						|
	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
 | 
						|
 | 
						|
	"k8s.io/kubernetes/test/integration/framework"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	invalidBodyJSON = `
 | 
						|
	{
 | 
						|
		"apiVersion": "apps/v1",
 | 
						|
		"kind": "Deployment",
 | 
						|
		"metadata": {
 | 
						|
			"name": "dupename",
 | 
						|
			"name": "%s",
 | 
						|
			"labels": {"app": "nginx"},
 | 
						|
			"unknownMeta": "metaVal"
 | 
						|
		},
 | 
						|
		"spec": {
 | 
						|
			"unknown1": "val1",
 | 
						|
			"unknownDupe": "valDupe",
 | 
						|
			"unknownDupe": "valDupe2",
 | 
						|
			"paused": true,
 | 
						|
			"paused": false,
 | 
						|
			"selector": {
 | 
						|
				"matchLabels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"template": {
 | 
						|
				"metadata": {
 | 
						|
					"labels": {
 | 
						|
						"app": "nginx"
 | 
						|
					}
 | 
						|
				},
 | 
						|
				"spec": {
 | 
						|
					"containers": [{
 | 
						|
						"name":  "nginx",
 | 
						|
						"image": "nginx:latest",
 | 
						|
						"unknownNested": "val1",
 | 
						|
						"imagePullPolicy": "Always",
 | 
						|
						"imagePullPolicy": "Never"
 | 
						|
					}]
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
		`
 | 
						|
	validBodyJSON = `
 | 
						|
{
 | 
						|
	"apiVersion": "apps/v1",
 | 
						|
	"kind": "Deployment",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s",
 | 
						|
		"labels": {"app": "nginx"},
 | 
						|
		"annotations": {"a1": "foo", "a2": "bar"}
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"selector": {
 | 
						|
			"matchLabels": {
 | 
						|
				"app": "nginx"
 | 
						|
			}
 | 
						|
		},
 | 
						|
		"template": {
 | 
						|
			"metadata": {
 | 
						|
				"labels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"spec": {
 | 
						|
				"containers": [{
 | 
						|
					"name":  "nginx",
 | 
						|
					"image": "nginx:latest",
 | 
						|
					"imagePullPolicy": "Always"
 | 
						|
				}]
 | 
						|
			}
 | 
						|
		},
 | 
						|
		"replicas": 2
 | 
						|
	}
 | 
						|
}`
 | 
						|
 | 
						|
	invalidBodyYAML = `apiVersion: apps/v1
 | 
						|
kind: Deployment
 | 
						|
metadata:
 | 
						|
  name: dupename
 | 
						|
  name: %s
 | 
						|
  unknownMeta: metaVal
 | 
						|
  labels:
 | 
						|
    app: nginx
 | 
						|
spec:
 | 
						|
  unknown1: val1
 | 
						|
  unknownDupe: valDupe
 | 
						|
  unknownDupe: valDupe2
 | 
						|
  paused: true
 | 
						|
  paused: false
 | 
						|
  selector:
 | 
						|
    matchLabels:
 | 
						|
      app: nginx
 | 
						|
  template:
 | 
						|
    metadata:
 | 
						|
      labels:
 | 
						|
        app: nginx
 | 
						|
    spec:
 | 
						|
      containers:
 | 
						|
      - name: nginx
 | 
						|
        image: nginx:latest
 | 
						|
        unknownNested: val1
 | 
						|
        imagePullPolicy: Always
 | 
						|
        imagePullPolicy: Never`
 | 
						|
 | 
						|
	validBodyYAML = `apiVersion: apps/v1
 | 
						|
kind: Deployment
 | 
						|
metadata:
 | 
						|
  name: %s
 | 
						|
  labels:
 | 
						|
    app: nginx
 | 
						|
  annotations:
 | 
						|
    a1: foo
 | 
						|
    a2: bar
 | 
						|
spec:
 | 
						|
  replicas: 2
 | 
						|
  paused: true
 | 
						|
  selector:
 | 
						|
    matchLabels:
 | 
						|
      app: nginx
 | 
						|
  template:
 | 
						|
    metadata:
 | 
						|
      labels:
 | 
						|
        app: nginx
 | 
						|
    spec:
 | 
						|
      containers:
 | 
						|
      - name: nginx
 | 
						|
        image: nginx:latest
 | 
						|
        imagePullPolicy: Always`
 | 
						|
 | 
						|
	applyInvalidBody = `{
 | 
						|
		"apiVersion": "apps/v1",
 | 
						|
		"kind": "Deployment",
 | 
						|
		"metadata": {
 | 
						|
			"name": "%s",
 | 
						|
			"labels": {"app": "nginx"}
 | 
						|
		},
 | 
						|
		"spec": {
 | 
						|
			"paused": false,
 | 
						|
			"paused": true,
 | 
						|
			"selector": {
 | 
						|
				"matchLabels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"template": {
 | 
						|
				"metadata": {
 | 
						|
					"labels": {
 | 
						|
						"app": "nginx"
 | 
						|
					}
 | 
						|
				},
 | 
						|
				"spec": {
 | 
						|
					"containers": [{
 | 
						|
						"name":  "nginx",
 | 
						|
						"image": "nginx:latest",
 | 
						|
						"imagePullPolicy": "Never",
 | 
						|
						"imagePullPolicy": "Always"
 | 
						|
					}]
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}`
 | 
						|
	applyValidBody = `
 | 
						|
{
 | 
						|
	"apiVersion": "apps/v1",
 | 
						|
	"kind": "Deployment",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s",
 | 
						|
		"labels": {"app": "nginx"},
 | 
						|
		"annotations": {"a1": "foo", "a2": "bar"}
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"selector": {
 | 
						|
			"matchLabels": {
 | 
						|
				"app": "nginx"
 | 
						|
			}
 | 
						|
		},
 | 
						|
		"template": {
 | 
						|
			"metadata": {
 | 
						|
				"labels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"spec": {
 | 
						|
				"containers": [{
 | 
						|
					"name":  "nginx",
 | 
						|
					"image": "nginx:latest",
 | 
						|
					"imagePullPolicy": "Always"
 | 
						|
				}]
 | 
						|
			}
 | 
						|
		},
 | 
						|
		"replicas": 3
 | 
						|
	}
 | 
						|
}`
 | 
						|
	crdInvalidBody = `
 | 
						|
{
 | 
						|
	"apiVersion": "%s",
 | 
						|
	"kind": "%s",
 | 
						|
	"metadata": {
 | 
						|
		"name": "dupename",
 | 
						|
		"name": "%s",
 | 
						|
		"unknownMeta": "metaVal",
 | 
						|
		"resourceVersion": "%s"
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"unknown1": "val1",
 | 
						|
		"unknownDupe": "valDupe",
 | 
						|
		"unknownDupe": "valDupe2",
 | 
						|
		"knownField1": "val1",
 | 
						|
		"knownField1": "val2",
 | 
						|
			"ports": [{
 | 
						|
				"name": "portName",
 | 
						|
				"containerPort": 8080,
 | 
						|
				"protocol": "TCP",
 | 
						|
				"hostPort": 8081,
 | 
						|
				"hostPort": 8082,
 | 
						|
				"unknownNested": "val"
 | 
						|
			}],
 | 
						|
		"embeddedObj": {
 | 
						|
			"apiVersion": "v1",
 | 
						|
			"kind": "ConfigMap",
 | 
						|
			"metadata": {
 | 
						|
				"name": "my-cm",
 | 
						|
				"namespace": "my-ns",
 | 
						|
				"unknownEmbeddedMeta": "foo"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}`
 | 
						|
 | 
						|
	crdValidBody = `
 | 
						|
{
 | 
						|
	"apiVersion": "%s",
 | 
						|
	"kind": "%s",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s",
 | 
						|
		"resourceVersion": "%s"
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"knownField1": "val1",
 | 
						|
			"ports": [{
 | 
						|
				"name": "portName",
 | 
						|
				"containerPort": 8080,
 | 
						|
				"protocol": "TCP",
 | 
						|
				"hostPort": 8081
 | 
						|
			}],
 | 
						|
		"embeddedObj": {
 | 
						|
			"apiVersion": "v1",
 | 
						|
			"kind": "ConfigMap",
 | 
						|
			"metadata": {
 | 
						|
				"name": "my-cm"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
	`
 | 
						|
 | 
						|
	crdInvalidBodyYAML = `
 | 
						|
apiVersion: "%s"
 | 
						|
kind: "%s"
 | 
						|
metadata:
 | 
						|
  name: dupename
 | 
						|
  name: "%s"
 | 
						|
  resourceVersion: "%s"
 | 
						|
  unknownMeta: metaVal
 | 
						|
spec:
 | 
						|
  unknown1: val1
 | 
						|
  unknownDupe: valDupe
 | 
						|
  unknownDupe: valDupe2
 | 
						|
  knownField1: val1
 | 
						|
  knownField1: val2
 | 
						|
  ports:
 | 
						|
  - name: portName
 | 
						|
    containerPort: 8080
 | 
						|
    protocol: TCP
 | 
						|
    hostPort: 8081
 | 
						|
    hostPort: 8082
 | 
						|
    unknownNested: val
 | 
						|
  embeddedObj:
 | 
						|
    apiVersion: v1
 | 
						|
    kind: ConfigMap
 | 
						|
    metadata:
 | 
						|
      name: my-cm
 | 
						|
      namespace: my-ns
 | 
						|
      unknownEmbeddedMeta: foo`
 | 
						|
 | 
						|
	crdValidBodyYAML = `
 | 
						|
apiVersion: "%s"
 | 
						|
kind: "%s"
 | 
						|
metadata:
 | 
						|
  name: "%s"
 | 
						|
  resourceVersion: "%s"
 | 
						|
spec:
 | 
						|
  knownField1: val1
 | 
						|
  ports:
 | 
						|
  - name: portName
 | 
						|
    containerPort: 8080
 | 
						|
    protocol: TCP
 | 
						|
    hostPort: 8081
 | 
						|
  embeddedObj:
 | 
						|
    apiVersion: v1
 | 
						|
    kind: ConfigMap
 | 
						|
    metadata:
 | 
						|
      name: my-cm
 | 
						|
      namespace: my-ns`
 | 
						|
 | 
						|
	crdApplyInvalidBody = `
 | 
						|
{
 | 
						|
	"apiVersion": "%s",
 | 
						|
	"kind": "%s",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s"
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"knownField1": "val1",
 | 
						|
		"knownField1": "val2",
 | 
						|
		"ports": [{
 | 
						|
			"name": "portName",
 | 
						|
			"containerPort": 8080,
 | 
						|
			"protocol": "TCP",
 | 
						|
			"hostPort": 8081,
 | 
						|
			"hostPort": 8082
 | 
						|
		}],
 | 
						|
		"embeddedObj": {
 | 
						|
			"apiVersion": "v1",
 | 
						|
			"kind": "ConfigMap",
 | 
						|
			"metadata": {
 | 
						|
				"name": "my-cm",
 | 
						|
				"namespace": "my-ns"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}`
 | 
						|
 | 
						|
	crdApplyValidBody = `
 | 
						|
{
 | 
						|
	"apiVersion": "%s",
 | 
						|
	"kind": "%s",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s"
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"knownField1": "val1",
 | 
						|
		"ports": [{
 | 
						|
			"name": "portName",
 | 
						|
			"containerPort": 8080,
 | 
						|
			"protocol": "TCP",
 | 
						|
			"hostPort": 8082
 | 
						|
		}],
 | 
						|
		"embeddedObj": {
 | 
						|
			"apiVersion": "v1",
 | 
						|
			"kind": "ConfigMap",
 | 
						|
			"metadata": {
 | 
						|
				"name": "my-cm",
 | 
						|
				"namespace": "my-ns"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}`
 | 
						|
 | 
						|
	crdApplyValidBody2 = `
 | 
						|
{
 | 
						|
	"apiVersion": "%s",
 | 
						|
	"kind": "%s",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s"
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"knownField1": "val2",
 | 
						|
		"ports": [{
 | 
						|
			"name": "portName",
 | 
						|
			"containerPort": 8080,
 | 
						|
			"protocol": "TCP",
 | 
						|
			"hostPort": 8083
 | 
						|
		}],
 | 
						|
		"embeddedObj": {
 | 
						|
			"apiVersion": "v1",
 | 
						|
			"kind": "ConfigMap",
 | 
						|
			"metadata": {
 | 
						|
				"name": "my-cm",
 | 
						|
				"namespace": "my-ns"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}`
 | 
						|
 | 
						|
	crdApplyFinalizerBody = `
 | 
						|
{
 | 
						|
	"apiVersion": "%s",
 | 
						|
	"kind": "%s",
 | 
						|
	"metadata": {
 | 
						|
		"name": "%s",
 | 
						|
		"finalizers": %s
 | 
						|
	},
 | 
						|
	"spec": {
 | 
						|
		"knownField1": "val1",
 | 
						|
		"ports": [{
 | 
						|
			"name": "portName",
 | 
						|
			"containerPort": 8080,
 | 
						|
			"protocol": "TCP",
 | 
						|
			"hostPort": 8082
 | 
						|
		}],
 | 
						|
		"embeddedObj": {
 | 
						|
			"apiVersion": "v1",
 | 
						|
			"kind": "ConfigMap",
 | 
						|
			"metadata": {
 | 
						|
				"name": "my-cm",
 | 
						|
				"namespace": "my-ns"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}`
 | 
						|
 | 
						|
	patchYAMLBody = `
 | 
						|
apiVersion: %s
 | 
						|
kind: %s
 | 
						|
metadata:
 | 
						|
  name: %s
 | 
						|
  finalizers:
 | 
						|
  - test/finalizer
 | 
						|
spec:
 | 
						|
  cronSpec: "* * * * */5"
 | 
						|
  ports:
 | 
						|
  - name: x
 | 
						|
    containerPort: 80
 | 
						|
    protocol: TCP
 | 
						|
`
 | 
						|
 | 
						|
	crdSchemaBase = `
 | 
						|
{
 | 
						|
		"openAPIV3Schema": {
 | 
						|
			"type": "object",
 | 
						|
			"properties": {
 | 
						|
				"spec": {
 | 
						|
					"type": "object",
 | 
						|
					%s
 | 
						|
					"properties": {
 | 
						|
						"cronSpec": {
 | 
						|
							"type": "string",
 | 
						|
							"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
 | 
						|
						},
 | 
						|
						"knownField1": {
 | 
						|
							"type": "string"
 | 
						|
						},
 | 
						|
						"embeddedObj": {
 | 
						|
							"x-kubernetes-embedded-resource": true,
 | 
						|
							"type": "object",
 | 
						|
							"properties": {
 | 
						|
								"apiversion": {
 | 
						|
									"type": "string"
 | 
						|
								},
 | 
						|
								"kind": {
 | 
						|
									"type": "string"
 | 
						|
								},
 | 
						|
								"metadata": {
 | 
						|
									"type": "object"
 | 
						|
								}
 | 
						|
							}
 | 
						|
						},
 | 
						|
						"ports": {
 | 
						|
							"type": "array",
 | 
						|
							"x-kubernetes-list-map-keys": [
 | 
						|
								"containerPort",
 | 
						|
								"protocol"
 | 
						|
							],
 | 
						|
							"x-kubernetes-list-type": "map",
 | 
						|
							"items": {
 | 
						|
								"properties": {
 | 
						|
									"containerPort": {
 | 
						|
										"format": "int32",
 | 
						|
										"type": "integer"
 | 
						|
									},
 | 
						|
									"hostIP": {
 | 
						|
										"type": "string"
 | 
						|
									},
 | 
						|
									"hostPort": {
 | 
						|
										"format": "int32",
 | 
						|
										"type": "integer"
 | 
						|
									},
 | 
						|
									"name": {
 | 
						|
										"type": "string"
 | 
						|
									},
 | 
						|
									"protocol": {
 | 
						|
										"type": "string"
 | 
						|
									}
 | 
						|
								},
 | 
						|
								"required": [
 | 
						|
									"containerPort",
 | 
						|
									"protocol"
 | 
						|
								],
 | 
						|
								"type": "object"
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	`
 | 
						|
)
 | 
						|
 | 
						|
func TestFieldValidation(t *testing.T) {
 | 
						|
	server, err := kubeapiservertesting.StartTestServer(t, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	config := server.ClientConfig
 | 
						|
	defer server.TearDownFn()
 | 
						|
 | 
						|
	// don't log warnings, tests inspect them in the responses directly
 | 
						|
	config.WarningHandler = rest.NoWarnings{}
 | 
						|
 | 
						|
	schemaCRD := setupCRD(t, config, "schema.example.com", false)
 | 
						|
	schemaGVR := schema.GroupVersionResource{
 | 
						|
		Group:    schemaCRD.Spec.Group,
 | 
						|
		Version:  schemaCRD.Spec.Versions[0].Name,
 | 
						|
		Resource: schemaCRD.Spec.Names.Plural,
 | 
						|
	}
 | 
						|
	schemaGVK := schema.GroupVersionKind{
 | 
						|
		Group:   schemaCRD.Spec.Group,
 | 
						|
		Version: schemaCRD.Spec.Versions[0].Name,
 | 
						|
		Kind:    schemaCRD.Spec.Names.Kind,
 | 
						|
	}
 | 
						|
 | 
						|
	schemalessCRD := setupCRD(t, config, "schemaless.example.com", true)
 | 
						|
	schemalessGVR := schema.GroupVersionResource{
 | 
						|
		Group:    schemalessCRD.Spec.Group,
 | 
						|
		Version:  schemalessCRD.Spec.Versions[0].Name,
 | 
						|
		Resource: schemalessCRD.Spec.Names.Plural,
 | 
						|
	}
 | 
						|
	schemalessGVK := schema.GroupVersionKind{
 | 
						|
		Group:   schemalessCRD.Spec.Group,
 | 
						|
		Version: schemalessCRD.Spec.Versions[0].Name,
 | 
						|
		Kind:    schemalessCRD.Spec.Names.Kind,
 | 
						|
	}
 | 
						|
 | 
						|
	client := clientset.NewForConfigOrDie(config)
 | 
						|
	rest := client.Discovery().RESTClient()
 | 
						|
 | 
						|
	t.Run("Post", func(t *testing.T) { testFieldValidationPost(t, client) })
 | 
						|
	t.Run("Put", func(t *testing.T) { testFieldValidationPut(t, client) })
 | 
						|
	t.Run("PatchTyped", func(t *testing.T) { testFieldValidationPatchTyped(t, client) })
 | 
						|
	t.Run("SMP", func(t *testing.T) { testFieldValidationSMP(t, client) })
 | 
						|
	t.Run("ApplyCreate", func(t *testing.T) { testFieldValidationApplyCreate(t, client) })
 | 
						|
	t.Run("ApplyUpdate", func(t *testing.T) { testFieldValidationApplyUpdate(t, client) })
 | 
						|
 | 
						|
	t.Run("PostCRD", func(t *testing.T) { testFieldValidationPostCRD(t, rest, schemaGVK, schemaGVR) })
 | 
						|
	t.Run("PutCRD", func(t *testing.T) { testFieldValidationPutCRD(t, rest, schemaGVK, schemaGVR) })
 | 
						|
	t.Run("PatchCRD", func(t *testing.T) { testFieldValidationPatchCRD(t, rest, schemaGVK, schemaGVR) })
 | 
						|
	t.Run("ApplyCreateCRD", func(t *testing.T) { testFieldValidationApplyCreateCRD(t, rest, schemaGVK, schemaGVR) })
 | 
						|
	t.Run("ApplyUpdateCRD", func(t *testing.T) { testFieldValidationApplyUpdateCRD(t, rest, schemaGVK, schemaGVR) })
 | 
						|
 | 
						|
	t.Run("PostCRDSchemaless", func(t *testing.T) { testFieldValidationPostCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	t.Run("PutCRDSchemaless", func(t *testing.T) { testFieldValidationPutCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	t.Run("PatchCRDSchemaless", func(t *testing.T) { testFieldValidationPatchCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	t.Run("ApplyCreateCRDSchemaless", func(t *testing.T) { testFieldValidationApplyCreateCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	t.Run("ApplyUpdateCRDSchemaless", func(t *testing.T) { testFieldValidationApplyUpdateCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	t.Run("testFinalizerValidationApplyCreateCRD", func(t *testing.T) {
 | 
						|
		testFinalizerValidationApplyCreateAndUpdateCRD(t, rest, schemalessGVK, schemalessGVR)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPost tests POST requests containing unknown fields with
 | 
						|
// strict and non-strict field validation.
 | 
						|
func testFieldValidationPost(t *testing.T, client clientset.Interface) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		bodyBase               string
 | 
						|
		opts                   metav1.CreateOptions
 | 
						|
		contentType            string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "post-strict-validation",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			bodyBase:            invalidBodyJSON,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-warn-validation",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			bodyBase: invalidBodyJSON,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "metadata.name"`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.unknown1"`,
 | 
						|
				`unknown field "spec.unknownDupe"`,
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				// note: fields that are both unknown
 | 
						|
				// and duplicated will only be detected
 | 
						|
				// as unknown for typed resources.
 | 
						|
				`unknown field "spec.template.spec.containers[0].unknownNested"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-ignore-validation",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			bodyBase: invalidBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "post-no-validation",
 | 
						|
			bodyBase: invalidBodyJSON,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "metadata.name"`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.unknown1"`,
 | 
						|
				`unknown field "spec.unknownDupe"`,
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				// note: fields that are both unknown
 | 
						|
				// and duplicated will only be detected
 | 
						|
				// as unknown for typed resources.
 | 
						|
				`unknown field "spec.template.spec.containers[0].unknownNested"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-strict-validation-yaml",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			bodyBase:    invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
 | 
						|
  line 5: key "name" already set in map
 | 
						|
  line 12: key "unknownDupe" already set in map
 | 
						|
  line 14: key "paused" already set in map
 | 
						|
  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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-warn-validation-yaml",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			bodyBase:    invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 5: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "paused" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-ignore-validation-yaml",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			bodyBase:    invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "post-no-validation-yaml",
 | 
						|
			bodyBase:    invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 5: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "paused" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			klog.Warningf("running tc named: %s", tc.name)
 | 
						|
			body := []byte(fmt.Sprintf(tc.bodyBase, fmt.Sprintf("test-deployment-%s", tc.name)))
 | 
						|
			req := client.CoreV1().RESTClient().Post().
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				SetHeader("Content-Type", tc.contentType).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body(body).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPut tests PUT requests
 | 
						|
// that update existing objects with unknown fields
 | 
						|
// for both strict and non-strict field validation.
 | 
						|
func testFieldValidationPut(t *testing.T, client clientset.Interface) {
 | 
						|
	deployName := "test-deployment-put"
 | 
						|
	postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
 | 
						|
 | 
						|
	if _, err := client.CoreV1().RESTClient().Post().
 | 
						|
		AbsPath("/apis/apps/v1").
 | 
						|
		Namespace("default").
 | 
						|
		Resource("deployments").
 | 
						|
		Body(postBody).
 | 
						|
		DoRaw(context.TODO()); err != nil {
 | 
						|
		t.Fatalf("failed to create initial deployment: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.UpdateOptions
 | 
						|
		putBodyBase            string
 | 
						|
		contentType            string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "put-strict-validation",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBodyBase:         invalidBodyJSON,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-warn-validation",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBodyBase: invalidBodyJSON,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "metadata.name"`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.unknown1"`,
 | 
						|
				`unknown field "spec.unknownDupe"`,
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				// note: fields that are both unknown
 | 
						|
				// and duplicated will only be detected
 | 
						|
				// as unknown for typed resources.
 | 
						|
				`unknown field "spec.template.spec.containers[0].unknownNested"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-ignore-validation",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBodyBase: invalidBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "put-no-validation",
 | 
						|
			putBodyBase: invalidBodyJSON,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "metadata.name"`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.unknown1"`,
 | 
						|
				`unknown field "spec.unknownDupe"`,
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				// note: fields that are both unknown
 | 
						|
				// and duplicated will only be detected
 | 
						|
				// as unknown for typed resources.
 | 
						|
				`unknown field "spec.template.spec.containers[0].unknownNested"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-strict-validation-yaml",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBodyBase: invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
 | 
						|
  line 5: key "name" already set in map
 | 
						|
  line 12: key "unknownDupe" already set in map
 | 
						|
  line 14: key "paused" already set in map
 | 
						|
  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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-warn-validation-yaml",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBodyBase: invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 5: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "paused" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-ignore-validation-yaml",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBodyBase: invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "put-no-validation-yaml",
 | 
						|
			putBodyBase: invalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 5: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "paused" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
 | 
						|
			req := client.CoreV1().RESTClient().Put().
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				SetHeader("Content-Type", tc.contentType).
 | 
						|
				Name(deployName).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(putBody)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPatchTyped tests merge-patch and json-patch requests containing unknown fields with
 | 
						|
// strict and non-strict field validation for typed objects.
 | 
						|
func testFieldValidationPatchTyped(t *testing.T, client clientset.Interface) {
 | 
						|
	deployName := "test-deployment-patch-typed"
 | 
						|
	postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
 | 
						|
 | 
						|
	if _, err := client.CoreV1().RESTClient().Post().
 | 
						|
		AbsPath("/apis/apps/v1").
 | 
						|
		Namespace("default").
 | 
						|
		Resource("deployments").
 | 
						|
		Body(postBody).
 | 
						|
		DoRaw(context.TODO()); err != nil {
 | 
						|
		t.Fatalf("failed to create initial deployment: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	mergePatchBody := `
 | 
						|
{
 | 
						|
	"spec": {
 | 
						|
		"unknown1": "val1",
 | 
						|
		"unknownDupe": "valDupe",
 | 
						|
		"unknownDupe": "valDupe2",
 | 
						|
		"paused": true,
 | 
						|
		"paused": false,
 | 
						|
		"template": {
 | 
						|
			"spec": {
 | 
						|
				"containers": [{
 | 
						|
					"name": "nginx",
 | 
						|
					"image": "nginx:latest",
 | 
						|
					"unknownNested": "val1",
 | 
						|
					"imagePullPolicy": "Always",
 | 
						|
					"imagePullPolicy": "Never"
 | 
						|
				}]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
	`
 | 
						|
	jsonPatchBody := `
 | 
						|
			[
 | 
						|
				{"op": "add", "path": "/spec/unknown1", "value": "val1", "foo":"bar"},
 | 
						|
				{"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val1"},
 | 
						|
				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
 | 
						|
				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
 | 
						|
				{"op": "add", "path": "/spec/paused", "value": true},
 | 
						|
				{"op": "add", "path": "/spec/paused", "value": false},
 | 
						|
				{"op": "add", "path": "/spec/template/spec/containers/0/unknownNested", "value": "val1"},
 | 
						|
				{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Always"},
 | 
						|
				{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"}
 | 
						|
			]
 | 
						|
			`
 | 
						|
	// non-conflicting mergePatch has issues with the patch (duplicate fields),
 | 
						|
	// but doesn't conflict with the existing object it's being patched to
 | 
						|
	nonconflictingMergePatchBody := `
 | 
						|
{
 | 
						|
	"spec": {
 | 
						|
		"paused": true,
 | 
						|
		"paused": false,
 | 
						|
		"template": {
 | 
						|
			"spec": {
 | 
						|
				"containers": [{
 | 
						|
					"name": "nginx",
 | 
						|
					"image": "nginx:latest",
 | 
						|
					"imagePullPolicy": "Always",
 | 
						|
					"imagePullPolicy": "Never"
 | 
						|
				}]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
			`
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		patchType              types.PatchType
 | 
						|
		body                   string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "merge-patch-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			patchType:           types.MergePatchType,
 | 
						|
			body:                mergePatchBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "merge-patch-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "merge-patch-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "merge-patch-no-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-strict-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                jsonPatchBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-warn-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				// note: duplicate fields in the patch itself
 | 
						|
				// are dropped by the
 | 
						|
				// evanphx/json-patch library and is expected.
 | 
						|
				// Duplicate fields in the json patch ops
 | 
						|
				// themselves can be detected though
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-ignore-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-no-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			body:      jsonPatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				// note: duplicate fields in the patch itself
 | 
						|
				// are dropped by the
 | 
						|
				// evanphx/json-patch library and is expected.
 | 
						|
				// Duplicate fields in the json patch ops
 | 
						|
				// themselves can be detected though
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-merge-patch-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			patchType:           types.MergePatchType,
 | 
						|
			body:                nonconflictingMergePatchBody,
 | 
						|
			strictDecodingError: `strict decoding error: duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-merge-patch-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      nonconflictingMergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-merge-patch-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      nonconflictingMergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "nonconflicting-merge-patch-no-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      nonconflictingMergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			req := client.CoreV1().RESTClient().Patch(tc.patchType).
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				Name(deployName).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationSMP tests that attempting a strategic-merge-patch
 | 
						|
// with unknown fields errors out when fieldValidation is strict,
 | 
						|
// but succeeds when fieldValidation is ignored.
 | 
						|
func testFieldValidationSMP(t *testing.T, client clientset.Interface) {
 | 
						|
	// non-conflicting SMP has issues with the patch (duplicate fields),
 | 
						|
	// but doesn't conflict with the existing object it's being patched to
 | 
						|
	nonconflictingSMPBody := `
 | 
						|
	{
 | 
						|
		"spec": {
 | 
						|
			"paused": true,
 | 
						|
			"paused": false,
 | 
						|
			"selector": {
 | 
						|
				"matchLabels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"template": {
 | 
						|
				"metadata": {
 | 
						|
					"labels": {
 | 
						|
						"app": "nginx"
 | 
						|
					}
 | 
						|
				},
 | 
						|
				"spec": {
 | 
						|
					"containers": [{
 | 
						|
						"name": "nginx",
 | 
						|
						"imagePullPolicy": "Always",
 | 
						|
						"imagePullPolicy": "Never"
 | 
						|
					}]
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	`
 | 
						|
 | 
						|
	smpBody := `
 | 
						|
	{
 | 
						|
		"spec": {
 | 
						|
			"unknown1": "val1",
 | 
						|
			"unknownDupe": "valDupe",
 | 
						|
			"unknownDupe": "valDupe2",
 | 
						|
			"paused": true,
 | 
						|
			"paused": false,
 | 
						|
			"selector": {
 | 
						|
				"matchLabels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"template": {
 | 
						|
				"metadata": {
 | 
						|
					"labels": {
 | 
						|
						"app": "nginx"
 | 
						|
					}
 | 
						|
				},
 | 
						|
				"spec": {
 | 
						|
					"containers": [{
 | 
						|
						"name": "nginx",
 | 
						|
						"unknownNested": "val1",
 | 
						|
						"imagePullPolicy": "Always",
 | 
						|
						"imagePullPolicy": "Never"
 | 
						|
					}]
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	`
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		body                   string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "smp-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                smpBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "smp-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: smpBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "smp-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: smpBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "smp-no-validation",
 | 
						|
			body: smpBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-smp-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                nonconflictingSMPBody,
 | 
						|
			strictDecodingError: `strict decoding error: duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-smp-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: nonconflictingSMPBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-smp-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: nonconflictingSMPBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "nonconflicting-smp-no-validation",
 | 
						|
			body: nonconflictingSMPBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "spec.paused"`,
 | 
						|
				`duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			body := []byte(fmt.Sprintf(validBodyJSON, tc.name))
 | 
						|
			_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				Name(tc.name).
 | 
						|
				Param("fieldManager", "apply_test").
 | 
						|
				Body(body).
 | 
						|
				Do(context.TODO()).
 | 
						|
				Get()
 | 
						|
			if err != nil {
 | 
						|
				t.Fatalf("Failed to create object using Apply patch: %v", err)
 | 
						|
			}
 | 
						|
 | 
						|
			req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				Name(tc.name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationApplyCreate tests apply patch requests containing unknown fields
 | 
						|
// on newly created objects, with strict and non-strict field validation.
 | 
						|
func testFieldValidationApplyCreate(t *testing.T, client clientset.Interface) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
 | 
						|
  line 10: key "paused" already set in map
 | 
						|
  line 27: key "imagePullPolicy" already set in map`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "paused" already set in map`,
 | 
						|
				`line 27: key "imagePullPolicy" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "paused" already set in map`,
 | 
						|
				`line 27: key "imagePullPolicy" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			name := fmt.Sprintf("apply-create-deployment-%s", tc.name)
 | 
						|
			body := []byte(fmt.Sprintf(applyInvalidBody, name))
 | 
						|
			req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body(body).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationApplyUpdate tests apply patch requests containing unknown fields
 | 
						|
// on apply requests to existing objects, with strict and non-strict field validation.
 | 
						|
func testFieldValidationApplyUpdate(t *testing.T, client clientset.Interface) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
 | 
						|
  line 10: key "paused" already set in map
 | 
						|
  line 27: key "imagePullPolicy" already set in map`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "paused" already set in map`,
 | 
						|
				`line 27: key "imagePullPolicy" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "paused" already set in map`,
 | 
						|
				`line 27: key "imagePullPolicy" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			name := fmt.Sprintf("apply-update-deployment-%s", tc.name)
 | 
						|
			createBody := []byte(fmt.Sprintf(validBodyJSON, name))
 | 
						|
			createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			createResult := createReq.Body(createBody).Do(context.TODO())
 | 
						|
			if createResult.Error() != nil {
 | 
						|
				t.Fatalf("unexpected apply create err: %v", createResult.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			updateBody := []byte(fmt.Sprintf(applyInvalidBody, name))
 | 
						|
			updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis/apps/v1").
 | 
						|
				Namespace("default").
 | 
						|
				Resource("deployments").
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := updateReq.Body(updateBody).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPostCRD tests that server-side schema validation
 | 
						|
// works for CRD create requests for CRDs with schemas
 | 
						|
func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		body                   string
 | 
						|
		contentType            string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "crd-post-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                crdInvalidBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: crdInvalidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-no-validation",
 | 
						|
			body: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-strict-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
 | 
						|
  line 6: key "name" already set in map
 | 
						|
  line 12: key "unknownDupe" already set in map
 | 
						|
  line 14: key "knownField1" already set in map
 | 
						|
  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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-warn-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-ignore-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "crd-post-no-validation-yaml",
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			klog.Warningf("running tc named: %s", tc.name)
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, tc.name))
 | 
						|
			req := rest.Post().
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				SetHeader("Content-Type", tc.contentType).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(jsonBody)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPostCRDSchemaless tests that server-side schema validation
 | 
						|
// works for CRD create requests for CRDs that have schemas
 | 
						|
// with x-kubernetes-preserve-unknown-field set
 | 
						|
func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		body                   string
 | 
						|
		contentType            string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                crdInvalidBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: crdInvalidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-no-validation",
 | 
						|
			body: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-strict-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
 | 
						|
  line 6: key "name" already set in map
 | 
						|
  line 12: key "unknownDupe" already set in map
 | 
						|
  line 14: key "knownField1" already set in map
 | 
						|
  line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-warn-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`line 20: key "hostPort" already set in map`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-post-ignore-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "schemaless-crd-post-no-validation-yaml",
 | 
						|
			body:        crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`line 20: key "hostPort" already set in map`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, tc.name))
 | 
						|
			req := rest.Post().
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				SetHeader("Content-Type", tc.contentType).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(jsonBody)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Logf("expected:")
 | 
						|
				for _, w := range tc.strictDecodingWarnings {
 | 
						|
					t.Logf("\t%v", w)
 | 
						|
				}
 | 
						|
				t.Logf("got:")
 | 
						|
				for _, w := range result.Warnings() {
 | 
						|
					t.Logf("\t%v", w.Text)
 | 
						|
				}
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPutCRD tests that server-side schema validation
 | 
						|
// works for CRD update requests for CRDs with schemas.
 | 
						|
func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		putBody                string
 | 
						|
		contentType            string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "crd-put-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBody:             crdInvalidBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBody: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBody: crdInvalidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:    "crd-put-no-validation",
 | 
						|
			putBody: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-strict-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
 | 
						|
  line 6: key "name" already set in map
 | 
						|
  line 12: key "unknownDupe" already set in map
 | 
						|
  line 14: key "knownField1" already set in map
 | 
						|
  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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-warn-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-ignore-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "crd-put-no-validation-yaml",
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, tc.name))
 | 
						|
			postReq := rest.Post().
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
 | 
						|
			if err != nil {
 | 
						|
				t.Fatalf("unexpeted error on CR creation: %v", err)
 | 
						|
			}
 | 
						|
			postUnstructured := &unstructured.Unstructured{}
 | 
						|
			if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
 | 
						|
				t.Fatalf("unexpeted error unmarshalling created CR: %v", err)
 | 
						|
			}
 | 
						|
 | 
						|
			// update the CR as specified by the test case
 | 
						|
			putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, tc.name, postUnstructured.GetResourceVersion()))
 | 
						|
			putReq := rest.Put().
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(tc.name).
 | 
						|
				SetHeader("Content-Type", tc.contentType).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := putReq.Body([]byte(putBody)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPutCRDSchemaless tests that server-side schema validation
 | 
						|
// works for CRD update requests for CRDs that have schemas
 | 
						|
// with x-kubernetes-preserve-unknown-field set
 | 
						|
func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		putBody                string
 | 
						|
		contentType            string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-put-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBody:             crdInvalidBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-put-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBody: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-put-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBody: crdInvalidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:    "schemaless-crd-put-no-validation",
 | 
						|
			putBody: crdInvalidBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-put-strict-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingError: `strict decoding error: yaml: unmarshal errors:
 | 
						|
  line 6: key "name" already set in map
 | 
						|
  line 12: key "unknownDupe" already set in map
 | 
						|
  line 14: key "knownField1" already set in map
 | 
						|
  line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-put-warn-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`line 20: key "hostPort" already set in map`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-crd-put-ignore-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "schemaless-crd-put-no-validation-yaml",
 | 
						|
			putBody:     crdInvalidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 6: key "name" already set in map`,
 | 
						|
				`line 12: key "unknownDupe" already set in map`,
 | 
						|
				`line 14: key "knownField1" already set in map`,
 | 
						|
				`line 20: key "hostPort" already set in map`,
 | 
						|
				`unknown field "metadata.unknownMeta"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
				`unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, tc.name))
 | 
						|
			postReq := rest.Post().
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
 | 
						|
			if err != nil {
 | 
						|
				t.Fatalf("unexpeted error on CR creation: %v", err)
 | 
						|
			}
 | 
						|
			postUnstructured := &unstructured.Unstructured{}
 | 
						|
			if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
 | 
						|
				t.Fatalf("unexpeted error unmarshalling created CR: %v", err)
 | 
						|
			}
 | 
						|
 | 
						|
			// update the CR as specified by the test case
 | 
						|
			putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, tc.name, postUnstructured.GetResourceVersion()))
 | 
						|
			putReq := rest.Put().
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(tc.name).
 | 
						|
				SetHeader("Content-Type", tc.contentType).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := putReq.Body([]byte(putBody)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Logf("expected:")
 | 
						|
				for _, w := range tc.strictDecodingWarnings {
 | 
						|
					t.Logf("\t%v", w)
 | 
						|
				}
 | 
						|
				t.Logf("got:")
 | 
						|
				for _, w := range result.Warnings() {
 | 
						|
					t.Logf("\t%v", w.Text)
 | 
						|
				}
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPatchCRD tests that server-side schema validation
 | 
						|
// works for jsonpatch and mergepatch requests
 | 
						|
// for custom resources that have schemas.
 | 
						|
func testFieldValidationPatchCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	patchYAMLBody := `
 | 
						|
apiVersion: %s
 | 
						|
kind: %s
 | 
						|
metadata:
 | 
						|
  name: %s
 | 
						|
  finalizers:
 | 
						|
  - test/finalizer
 | 
						|
spec:
 | 
						|
  cronSpec: "* * * * */5"
 | 
						|
  ports:
 | 
						|
  - name: x
 | 
						|
    containerPort: 80
 | 
						|
    protocol: TCP`
 | 
						|
 | 
						|
	mergePatchBody := `
 | 
						|
{
 | 
						|
	"spec": {
 | 
						|
		"unknown1": "val1",
 | 
						|
		"unknownDupe": "valDupe",
 | 
						|
		"unknownDupe": "valDupe2",
 | 
						|
		"knownField1": "val1",
 | 
						|
		"knownField1": "val2",
 | 
						|
			"ports": [{
 | 
						|
				"name": "portName",
 | 
						|
				"containerPort": 8080,
 | 
						|
				"protocol": "TCP",
 | 
						|
				"hostPort": 8081,
 | 
						|
				"hostPort": 8082,
 | 
						|
				"unknownNested": "val"
 | 
						|
			}]
 | 
						|
	}
 | 
						|
}
 | 
						|
	`
 | 
						|
	jsonPatchBody := `
 | 
						|
			[
 | 
						|
				{"op": "add", "path": "/spec/unknown1", "value": "val1", "foo": "bar"},
 | 
						|
				{"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val2"},
 | 
						|
				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
 | 
						|
				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
 | 
						|
				{"op": "add", "path": "/spec/knownField1", "value": "val1"},
 | 
						|
				{"op": "add", "path": "/spec/knownField1", "value": "val2"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8082},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/unknownNested", "value": "val"}
 | 
						|
			]
 | 
						|
			`
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		patchType              types.PatchType
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		body                   string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-strict-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                mergePatchBody,
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-warn-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-ignore-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-no-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-strict-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
			// note: duplicate fields in the patch itself
 | 
						|
			// are dropped by the
 | 
						|
			// evanphx/json-patch library and is expected.
 | 
						|
			// Duplicate fields in the json patch ops
 | 
						|
			// themselves can be detected though
 | 
						|
			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"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-warn-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				// note: duplicate fields in the patch itself
 | 
						|
				// are dropped by the
 | 
						|
				// evanphx/json-patch library and is expected.
 | 
						|
				// Duplicate fields in the json patch ops
 | 
						|
				// themselves can be detected though
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-ignore-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-no-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			body:      jsonPatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				// note: duplicate fields in the patch itself
 | 
						|
				// are dropped by the
 | 
						|
				// evanphx/json-patch library and is expected.
 | 
						|
				// Duplicate fields in the json patch ops
 | 
						|
				// themselves can be detected though
 | 
						|
				`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"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
			// create a CR
 | 
						|
			yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, tc.name))
 | 
						|
			createResult, err := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(tc.name).
 | 
						|
				Param("fieldManager", "apply_test").
 | 
						|
				Body(yamlBody).
 | 
						|
				DoRaw(context.TODO())
 | 
						|
			if err != nil {
 | 
						|
				t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
 | 
						|
			}
 | 
						|
 | 
						|
			// patch the CR as specified by the test case
 | 
						|
			req := rest.Patch(tc.patchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(tc.name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationPatchCRDSchemaless tests that server-side schema validation
 | 
						|
// works for jsonpatch and mergepatch requests
 | 
						|
// for custom resources that have schemas
 | 
						|
// with x-kubernetes-preserve-unknown-field set
 | 
						|
func testFieldValidationPatchCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	mergePatchBody := `
 | 
						|
{
 | 
						|
	"spec": {
 | 
						|
		"unknown1": "val1",
 | 
						|
		"unknownDupe": "valDupe",
 | 
						|
		"unknownDupe": "valDupe2",
 | 
						|
		"knownField1": "val1",
 | 
						|
		"knownField1": "val2",
 | 
						|
			"ports": [{
 | 
						|
				"name": "portName",
 | 
						|
				"containerPort": 8080,
 | 
						|
				"protocol": "TCP",
 | 
						|
				"hostPort": 8081,
 | 
						|
				"hostPort": 8082,
 | 
						|
				"unknownNested": "val"
 | 
						|
			}]
 | 
						|
	}
 | 
						|
}
 | 
						|
	`
 | 
						|
	jsonPatchBody := `
 | 
						|
			[
 | 
						|
				{"op": "add", "path": "/spec/unknown1", "value": "val1", "foo": "bar"},
 | 
						|
				{"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val2"},
 | 
						|
				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
 | 
						|
				{"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
 | 
						|
				{"op": "add", "path": "/spec/knownField1", "value": "val1"},
 | 
						|
				{"op": "add", "path": "/spec/knownField1", "value": "val2"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8082},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/unknownNested", "value": "val"}
 | 
						|
			]
 | 
						|
			`
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		patchType              types.PatchType
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		body                   string
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-merge-patch-strict-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:                mergePatchBody,
 | 
						|
			strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-merge-patch-warn-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "spec.unknownDupe"`,
 | 
						|
				`duplicate field "spec.knownField1"`,
 | 
						|
				`duplicate field "spec.ports[0].hostPort"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-merge-patch-ignore-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-merge-patch-no-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`duplicate field "spec.unknownDupe"`,
 | 
						|
				`duplicate field "spec.knownField1"`,
 | 
						|
				`duplicate field "spec.ports[0].hostPort"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-json-patch-strict-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
			// note: duplicate fields in the patch itself
 | 
						|
			// are dropped by the
 | 
						|
			// evanphx/json-patch library and is expected.
 | 
						|
			// Duplicate fields in the json patch ops
 | 
						|
			// themselves can be detected though
 | 
						|
			strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.ports[0].unknownNested"`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-json-patch-warn-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				// note: duplicate fields in the patch itself
 | 
						|
				// are dropped by the
 | 
						|
				// evanphx/json-patch library and is expected.
 | 
						|
				// Duplicate fields in the json patch ops
 | 
						|
				// themselves can be detected though
 | 
						|
				`json patch unknown field "[0].foo"`,
 | 
						|
				`json patch duplicate field "[1].path"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-json-patch-ignore-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "schemaless-crd-json-patch-no-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			body:      jsonPatchBody,
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				// note: duplicate fields in the patch itself
 | 
						|
				// are dropped by the
 | 
						|
				// evanphx/json-patch library and is expected.
 | 
						|
				// Duplicate fields in the json patch ops
 | 
						|
				// themselves can be detected though
 | 
						|
				`json patch unknown field "[0].foo"`,
 | 
						|
				`json patch duplicate field "[1].path"`,
 | 
						|
				`unknown field "spec.ports[0].unknownNested"`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
			// create a CR
 | 
						|
			yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, tc.name))
 | 
						|
			createResult, err := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(tc.name).
 | 
						|
				Param("fieldManager", "apply_test").
 | 
						|
				Body(yamlBody).
 | 
						|
				DoRaw(context.TODO())
 | 
						|
			if err != nil {
 | 
						|
				t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
 | 
						|
			}
 | 
						|
 | 
						|
			// patch the CR as specified by the test case
 | 
						|
			req := rest.Patch(tc.patchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(tc.name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationApplyCreateCRD tests apply patch requests containing duplicate fields
 | 
						|
// on newly created objects, for CRDs that have schemas
 | 
						|
// Note that even prior to server-side validation, unknown fields were treated as
 | 
						|
// errors in apply-patch and are not tested here.
 | 
						|
func testFieldValidationApplyCreateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
 | 
						|
  line 10: key "knownField1" already set in map
 | 
						|
  line 16: key "hostPort" already set in map`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			name := fmt.Sprintf("apply-create-crd-%s", tc.name)
 | 
						|
			applyCreateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
 | 
						|
 | 
						|
			req := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body(applyCreateBody).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationApplyCreateCRDSchemaless tests apply patch requests containing duplicate fields
 | 
						|
// on newly created objects, for CRDs that have schemas
 | 
						|
// with x-kubernetes-preserve-unknown-field set
 | 
						|
// Note that even prior to server-side validation, unknown fields were treated as
 | 
						|
// errors in apply-patch and are not tested here.
 | 
						|
func testFieldValidationApplyCreateCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "schemaless-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
 | 
						|
  line 10: key "knownField1" already set in map
 | 
						|
  line 16: key "hostPort" already set in map`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			name := fmt.Sprintf("apply-create-crd-schemaless-%s", tc.name)
 | 
						|
			applyCreateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
 | 
						|
 | 
						|
			req := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body(applyCreateBody).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationApplyUpdateCRD tests apply patch requests containing duplicate fields
 | 
						|
// on existing objects, for CRDs with schemas
 | 
						|
// Note that even prior to server-side validation, unknown fields were treated as
 | 
						|
// errors in apply-patch and are not tested here.
 | 
						|
func testFieldValidationApplyUpdateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
 | 
						|
  line 10: key "knownField1" already set in map
 | 
						|
  line 16: key "hostPort" already set in map`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			name := fmt.Sprintf("apply-update-crd-%s", tc.name)
 | 
						|
			applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
 | 
						|
			createReq := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			createResult := createReq.Body(applyCreateBody).Do(context.TODO())
 | 
						|
			if createResult.Error() != nil {
 | 
						|
				t.Fatalf("unexpected apply create err: %v", createResult.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			applyUpdateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
 | 
						|
			updateReq := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := updateReq.Body(applyUpdateBody).Do(context.TODO())
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// testFieldValidationApplyUpdateCRDSchemaless tests apply patch requests containing duplicate fields
 | 
						|
// on existing objects, for CRDs with schemas
 | 
						|
// with x-kubernetes-preserve-unknown-field set
 | 
						|
// Note that even prior to server-side validation, unknown fields were treated as
 | 
						|
// errors in apply-patch and are not tested here.
 | 
						|
func testFieldValidationApplyUpdateCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                   string
 | 
						|
		opts                   metav1.PatchOptions
 | 
						|
		strictDecodingError    string
 | 
						|
		strictDecodingWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "schemaless-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
 | 
						|
  line 10: key "knownField1" already set in map
 | 
						|
  line 16: key "hostPort" already set in map`,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "schemaless-no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
			strictDecodingWarnings: []string{
 | 
						|
				`line 10: key "knownField1" already set in map`,
 | 
						|
				`line 16: key "hostPort" already set in map`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			name := fmt.Sprintf("apply-update-crd-schemaless-%s", tc.name)
 | 
						|
			applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
 | 
						|
			createReq := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			createResult := createReq.Body(applyCreateBody).Do(context.TODO())
 | 
						|
			if createResult.Error() != nil {
 | 
						|
				t.Fatalf("unexpected apply create err: %v", createResult.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			applyUpdateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
 | 
						|
			updateReq := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := updateReq.Body(applyUpdateBody).Do(context.TODO())
 | 
						|
 | 
						|
			if result.Error() == nil && tc.strictDecodingError != "" {
 | 
						|
				t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
 | 
						|
			}
 | 
						|
			if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
 | 
						|
				t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, strictWarn := range tc.strictDecodingWarnings {
 | 
						|
				if strictWarn != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testFinalizerValidationApplyCreateAndUpdateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name                 string
 | 
						|
		finalizer            []string
 | 
						|
		updatedFinalizer     []string
 | 
						|
		opts                 metav1.PatchOptions
 | 
						|
		expectUpdateWarnings []string
 | 
						|
		expectCreateWarnings []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:      "create-crd-with-invalid-finalizer",
 | 
						|
			finalizer: []string{"invalid-finalizer"},
 | 
						|
			expectCreateWarnings: []string{
 | 
						|
				`metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "create-crd-with-valid-finalizer",
 | 
						|
			finalizer: []string{"kubernetes.io/valid-finalizer"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:             "update-crd-with-invalid-finalizer",
 | 
						|
			finalizer:        []string{"invalid-finalizer"},
 | 
						|
			updatedFinalizer: []string{"another-invalid-finalizer"},
 | 
						|
			expectCreateWarnings: []string{
 | 
						|
				`metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
 | 
						|
			},
 | 
						|
			expectUpdateWarnings: []string{
 | 
						|
				`metadata.finalizers: "another-invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:             "update-crd-with-valid-finalizer",
 | 
						|
			finalizer:        []string{"kubernetes.io/valid-finalizer"},
 | 
						|
			updatedFinalizer: []string{"kubernetes.io/another-valid-finalizer"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:             "update-crd-with-valid-finalizer-leaving-an-existing-invalid-finalizer",
 | 
						|
			finalizer:        []string{"invalid-finalizer"},
 | 
						|
			updatedFinalizer: []string{"kubernetes.io/another-valid-finalizer"},
 | 
						|
			expectCreateWarnings: []string{
 | 
						|
				`metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.name, func(t *testing.T) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
			// create the CR as specified by the test case
 | 
						|
			name := fmt.Sprintf("apply-create-crd-%s", tc.name)
 | 
						|
			finalizerVal, _ := json.Marshal(tc.finalizer)
 | 
						|
			applyCreateBody := []byte(fmt.Sprintf(crdApplyFinalizerBody, apiVersion, kind, name, finalizerVal))
 | 
						|
 | 
						|
			req := rest.Patch(types.ApplyPatchType).
 | 
						|
				AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
				Name(name).
 | 
						|
				Param("fieldManager", "apply_test").
 | 
						|
				VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
			result := req.Body(applyCreateBody).Do(context.TODO())
 | 
						|
			if result.Error() != nil {
 | 
						|
				t.Fatalf("unexpected error: %v", result.Error())
 | 
						|
			}
 | 
						|
 | 
						|
			if len(result.Warnings()) != len(tc.expectCreateWarnings) {
 | 
						|
				for _, r := range result.Warnings() {
 | 
						|
					t.Logf("received warning: %v", r)
 | 
						|
				}
 | 
						|
				t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.expectCreateWarnings), len(result.Warnings()))
 | 
						|
			}
 | 
						|
			for i, expectedWarning := range tc.expectCreateWarnings {
 | 
						|
				if expectedWarning != result.Warnings()[i].Text {
 | 
						|
					t.Fatalf("expected warning: %s, got warning: %s", expectedWarning, result.Warnings()[i].Text)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if len(tc.updatedFinalizer) != 0 {
 | 
						|
				finalizerVal, _ := json.Marshal(tc.updatedFinalizer)
 | 
						|
				applyUpdateBody := []byte(fmt.Sprintf(crdApplyFinalizerBody, apiVersion, kind, name, finalizerVal))
 | 
						|
				updateReq := rest.Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(name).
 | 
						|
					Param("fieldManager", "apply_test").
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result = updateReq.Body(applyUpdateBody).Do(context.TODO())
 | 
						|
 | 
						|
				if result.Error() != nil {
 | 
						|
					t.Fatalf("unexpected error: %v", result.Error())
 | 
						|
				}
 | 
						|
 | 
						|
				if len(result.Warnings()) != len(tc.expectUpdateWarnings) {
 | 
						|
					t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.expectUpdateWarnings), len(result.Warnings()))
 | 
						|
				}
 | 
						|
				for i, expectedWarning := range tc.expectUpdateWarnings {
 | 
						|
					if expectedWarning != result.Warnings()[i].Text {
 | 
						|
						t.Fatalf("expected warning: %s, got warning: %s", expectedWarning, result.Warnings()[i].Text)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func setupCRD(t testing.TB, config *rest.Config, apiGroup string, schemaless bool) *apiextensionsv1.CustomResourceDefinition {
 | 
						|
	apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	dynamicClient, err := dynamic.NewForConfig(config)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	preserveUnknownFields := ""
 | 
						|
	if schemaless {
 | 
						|
		preserveUnknownFields = `"x-kubernetes-preserve-unknown-fields": true,`
 | 
						|
	}
 | 
						|
	crdSchema := fmt.Sprintf(crdSchemaBase, preserveUnknownFields)
 | 
						|
 | 
						|
	// create the CRD
 | 
						|
	crd := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
 | 
						|
 | 
						|
	// adjust the API group
 | 
						|
	crd.Name = crd.Spec.Names.Plural + "." + apiGroup
 | 
						|
	crd.Spec.Group = apiGroup
 | 
						|
 | 
						|
	var c apiextensionsv1.CustomResourceValidation
 | 
						|
	err = json.Unmarshal([]byte(crdSchema), &c)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	//crd.Spec.PreserveUnknownFields = false
 | 
						|
	for i := range crd.Spec.Versions {
 | 
						|
		crd.Spec.Versions[i].Schema = &c
 | 
						|
	}
 | 
						|
	// install the CRD
 | 
						|
	crd, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return crd
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkFieldValidation(b *testing.B) {
 | 
						|
	flag.Lookup("v").Value.Set("0")
 | 
						|
	server, err := kubeapiservertesting.StartTestServer(b, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
 | 
						|
	if err != nil {
 | 
						|
		b.Fatal(err)
 | 
						|
	}
 | 
						|
	config := server.ClientConfig
 | 
						|
	defer server.TearDownFn()
 | 
						|
 | 
						|
	// don't log warnings, tests inspect them in the responses directly
 | 
						|
	config.WarningHandler = rest.NoWarnings{}
 | 
						|
 | 
						|
	client := clientset.NewForConfigOrDie(config)
 | 
						|
 | 
						|
	schemaCRD := setupCRD(b, config, "schema.example.com", false)
 | 
						|
	schemaGVR := schema.GroupVersionResource{
 | 
						|
		Group:    schemaCRD.Spec.Group,
 | 
						|
		Version:  schemaCRD.Spec.Versions[0].Name,
 | 
						|
		Resource: schemaCRD.Spec.Names.Plural,
 | 
						|
	}
 | 
						|
	schemaGVK := schema.GroupVersionKind{
 | 
						|
		Group:   schemaCRD.Spec.Group,
 | 
						|
		Version: schemaCRD.Spec.Versions[0].Name,
 | 
						|
		Kind:    schemaCRD.Spec.Names.Kind,
 | 
						|
	}
 | 
						|
 | 
						|
	schemalessCRD := setupCRD(b, config, "schemaless.example.com", true)
 | 
						|
	schemalessGVR := schema.GroupVersionResource{
 | 
						|
		Group:    schemalessCRD.Spec.Group,
 | 
						|
		Version:  schemalessCRD.Spec.Versions[0].Name,
 | 
						|
		Resource: schemalessCRD.Spec.Names.Plural,
 | 
						|
	}
 | 
						|
	schemalessGVK := schema.GroupVersionKind{
 | 
						|
		Group:   schemalessCRD.Spec.Group,
 | 
						|
		Version: schemalessCRD.Spec.Versions[0].Name,
 | 
						|
		Kind:    schemalessCRD.Spec.Names.Kind,
 | 
						|
	}
 | 
						|
 | 
						|
	rest := client.Discovery().RESTClient()
 | 
						|
 | 
						|
	b.Run("Post", func(b *testing.B) { benchFieldValidationPost(b, client) })
 | 
						|
	b.Run("Put", func(b *testing.B) { benchFieldValidationPut(b, client) })
 | 
						|
	b.Run("PatchTyped", func(b *testing.B) { benchFieldValidationPatchTyped(b, client) })
 | 
						|
	b.Run("SMP", func(b *testing.B) { benchFieldValidationSMP(b, client) })
 | 
						|
	b.Run("ApplyCreate", func(b *testing.B) { benchFieldValidationApplyCreate(b, client) })
 | 
						|
	b.Run("ApplyUpdate", func(b *testing.B) { benchFieldValidationApplyUpdate(b, client) })
 | 
						|
 | 
						|
	b.Run("PostCRD", func(b *testing.B) { benchFieldValidationPostCRD(b, rest, schemaGVK, schemaGVR) })
 | 
						|
	b.Run("PutCRD", func(b *testing.B) { benchFieldValidationPutCRD(b, rest, schemaGVK, schemaGVR) })
 | 
						|
	b.Run("PatchCRD", func(b *testing.B) { benchFieldValidationPatchCRD(b, rest, schemaGVK, schemaGVR) })
 | 
						|
	b.Run("ApplyCreateCRD", func(b *testing.B) { benchFieldValidationApplyCreateCRD(b, rest, schemaGVK, schemaGVR) })
 | 
						|
	b.Run("ApplyUpdateCRD", func(b *testing.B) { benchFieldValidationApplyUpdateCRD(b, rest, schemaGVK, schemaGVR) })
 | 
						|
 | 
						|
	b.Run("PostCRDSchemaless", func(b *testing.B) { benchFieldValidationPostCRD(b, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	b.Run("PutCRDSchemaless", func(b *testing.B) { benchFieldValidationPutCRD(b, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	b.Run("PatchCRDSchemaless", func(b *testing.B) { benchFieldValidationPatchCRD(b, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	b.Run("ApplyCreateCRDSchemaless", func(b *testing.B) { benchFieldValidationApplyCreateCRD(b, rest, schemalessGVK, schemalessGVR) })
 | 
						|
	b.Run("ApplyUpdateCRDSchemaless", func(b *testing.B) { benchFieldValidationApplyUpdateCRD(b, rest, schemalessGVK, schemalessGVR) })
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationPost(b *testing.B, client clientset.Interface) {
 | 
						|
	var benchmarks = []struct {
 | 
						|
		name        string
 | 
						|
		bodyBase    string
 | 
						|
		opts        metav1.CreateOptions
 | 
						|
		contentType string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "post-strict-validation",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			bodyBase: validBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-warn-validation",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			bodyBase: validBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-ignore-validation",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			bodyBase: validBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-strict-validation-yaml",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			bodyBase:    validBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-warn-validation-yaml",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			bodyBase:    validBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "post-ignore-validation-yaml",
 | 
						|
			opts: metav1.CreateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			bodyBase:    validBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, bm := range benchmarks {
 | 
						|
		b.Run(bm.name, func(b *testing.B) {
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				body := []byte(fmt.Sprintf(bm.bodyBase, fmt.Sprintf("test-deployment-%s-%d-%d-%d", bm.name, n, b.N, time.Now().UnixNano())))
 | 
						|
				req := client.CoreV1().RESTClient().Post().
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					SetHeader("Content-Type", bm.contentType).
 | 
						|
					VersionedParams(&bm.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body(body).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected request err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationPut(b *testing.B, client clientset.Interface) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name        string
 | 
						|
		opts        metav1.UpdateOptions
 | 
						|
		putBodyBase string
 | 
						|
		contentType string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "put-strict-validation",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBodyBase: validBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-warn-validation",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBodyBase: validBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-ignore-validation",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBodyBase: validBodyJSON,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-strict-validation-yaml",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBodyBase: validBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-warn-validation-yaml",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBodyBase: validBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "put-ignore-validation-yaml",
 | 
						|
			opts: metav1.UpdateOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBodyBase: validBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			names := make([]string, b.N)
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				names[n] = deployName
 | 
						|
				postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
 | 
						|
 | 
						|
				if _, err := client.CoreV1().RESTClient().Post().
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Body(postBody).
 | 
						|
					DoRaw(context.TODO()); err != nil {
 | 
						|
					b.Fatalf("failed to create initial deployment: %v", err)
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				deployName := names[n]
 | 
						|
				putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
 | 
						|
				req := client.CoreV1().RESTClient().Put().
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					SetHeader("Content-Type", tc.contentType).
 | 
						|
					Name(deployName).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body([]byte(putBody)).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected request err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationPatchTyped(b *testing.B, client clientset.Interface) {
 | 
						|
	mergePatchBodyValid := `
 | 
						|
{
 | 
						|
	"spec": {
 | 
						|
		"paused": false,
 | 
						|
		"template": {
 | 
						|
			"spec": {
 | 
						|
				"containers": [{
 | 
						|
					"name": "nginx",
 | 
						|
					"image": "nginx:latest",
 | 
						|
					"imagePullPolicy": "Always"
 | 
						|
				}]
 | 
						|
			}
 | 
						|
		},
 | 
						|
		"replicas": 2
 | 
						|
	}
 | 
						|
}
 | 
						|
	`
 | 
						|
 | 
						|
	jsonPatchBodyValid := `
 | 
						|
			[
 | 
						|
				{"op": "add", "path": "/spec/paused", "value": true},
 | 
						|
				{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"},
 | 
						|
				{"op": "add", "path": "/spec/replicas", "value": 2}
 | 
						|
			]
 | 
						|
			`
 | 
						|
 | 
						|
	var testcases = []struct {
 | 
						|
		name      string
 | 
						|
		opts      metav1.PatchOptions
 | 
						|
		patchType types.PatchType
 | 
						|
		body      string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "merge-patch-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "merge-patch-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "merge-patch-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-strict-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: jsonPatchBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-warn-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: jsonPatchBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "json-patch-ignore-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: jsonPatchBodyValid,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			names := make([]string, b.N)
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				names[n] = deployName
 | 
						|
				postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
 | 
						|
 | 
						|
				if _, err := client.CoreV1().RESTClient().Post().
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Body(postBody).
 | 
						|
					DoRaw(context.TODO()); err != nil {
 | 
						|
					b.Fatalf("failed to create initial deployment: %v", err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				deployName := names[n]
 | 
						|
				req := client.CoreV1().RESTClient().Patch(tc.patchType).
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Name(deployName).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected request err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationSMP(b *testing.B, client clientset.Interface) {
 | 
						|
	smpBodyValid := `
 | 
						|
	{
 | 
						|
		"spec": {
 | 
						|
			"replicas": 3,
 | 
						|
			"paused": false,
 | 
						|
			"selector": {
 | 
						|
				"matchLabels": {
 | 
						|
					"app": "nginx"
 | 
						|
				}
 | 
						|
			},
 | 
						|
			"template": {
 | 
						|
				"metadata": {
 | 
						|
					"labels": {
 | 
						|
						"app": "nginx"
 | 
						|
					}
 | 
						|
				},
 | 
						|
				"spec": {
 | 
						|
					"containers": [{
 | 
						|
						"name": "nginx",
 | 
						|
						"imagePullPolicy": "Never"
 | 
						|
					}]
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	`
 | 
						|
	var testcases = []struct {
 | 
						|
		name string
 | 
						|
		opts metav1.PatchOptions
 | 
						|
		body string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "smp-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: smpBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "smp-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: smpBodyValid,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "smp-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: smpBodyValid,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			names := make([]string, b.N)
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				name := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				names[n] = name
 | 
						|
				body := []byte(fmt.Sprintf(validBodyJSON, name))
 | 
						|
				_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Name(name).
 | 
						|
					Param("fieldManager", "apply_test").
 | 
						|
					Body(body).
 | 
						|
					Do(context.TODO()).
 | 
						|
					Get()
 | 
						|
				if err != nil {
 | 
						|
					b.Fatalf("Failed to create object using Apply patch: %v", err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				name := names[n]
 | 
						|
				req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Name(name).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected request err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationApplyCreate(b *testing.B, client clientset.Interface) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name string
 | 
						|
		opts metav1.PatchOptions
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				name := fmt.Sprintf("apply-create-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				body := []byte(fmt.Sprintf(validBodyJSON, name))
 | 
						|
				req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Name(name).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body(body).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected request err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationApplyUpdate(b *testing.B, client clientset.Interface) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name string
 | 
						|
		opts metav1.PatchOptions
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			names := make([]string, b.N)
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				name := fmt.Sprintf("apply-update-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				names[n] = name
 | 
						|
				createBody := []byte(fmt.Sprintf(validBodyJSON, name))
 | 
						|
				createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Name(name).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				createResult := createReq.Body(createBody).Do(context.TODO())
 | 
						|
				if createResult.Error() != nil {
 | 
						|
					b.Fatalf("unexpected apply create err: %v", createResult.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				name := names[n]
 | 
						|
				updateBody := []byte(fmt.Sprintf(applyValidBody, name))
 | 
						|
				updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis/apps/v1").
 | 
						|
					Namespace("default").
 | 
						|
					Resource("deployments").
 | 
						|
					Name(name).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := updateReq.Body(updateBody).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected request err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationPostCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name        string
 | 
						|
		opts        metav1.PatchOptions
 | 
						|
		body        string
 | 
						|
		contentType string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "crd-post-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-no-validation",
 | 
						|
			body: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-strict-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body:        crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-warn-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body:        crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-post-ignore-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body:        crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "crd-post-no-validation-yaml",
 | 
						|
			body:        crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				kind := gvk.Kind
 | 
						|
				apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
 | 
						|
				// create the CR as specified by the test case
 | 
						|
				jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())))
 | 
						|
				req := rest.Post().
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					SetHeader("Content-Type", tc.contentType).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body([]byte(jsonBody)).Do(context.TODO())
 | 
						|
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected post err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationPutCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name        string
 | 
						|
		opts        metav1.PatchOptions
 | 
						|
		putBody     string
 | 
						|
		contentType string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "crd-put-strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBody: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBody: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBody: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:    "crd-put-no-validation",
 | 
						|
			putBody: crdValidBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-strict-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			putBody:     crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-warn-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			putBody:     crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "crd-put-ignore-validation-yaml",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			putBody:     crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:        "crd-put-no-validation-yaml",
 | 
						|
			putBody:     crdValidBodyYAML,
 | 
						|
			contentType: "application/yaml",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
			names := make([]string, b.N)
 | 
						|
			resourceVersions := make([]string, b.N)
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				deployName := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				names[n] = deployName
 | 
						|
 | 
						|
				// create the CR as specified by the test case
 | 
						|
				jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, deployName))
 | 
						|
				postReq := rest.Post().
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
 | 
						|
				if err != nil {
 | 
						|
					b.Fatalf("unexpeted error on CR creation: %v", err)
 | 
						|
				}
 | 
						|
				postUnstructured := &unstructured.Unstructured{}
 | 
						|
				if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
 | 
						|
					b.Fatalf("unexpeted error unmarshalling created CR: %v", err)
 | 
						|
				}
 | 
						|
				resourceVersions[n] = postUnstructured.GetResourceVersion()
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				// update the CR as specified by the test case
 | 
						|
				putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, names[n], resourceVersions[n]))
 | 
						|
				putReq := rest.Put().
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(names[n]).
 | 
						|
					SetHeader("Content-Type", tc.contentType).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := putReq.Body([]byte(putBody)).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected put err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationPatchCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	patchYAMLBody := `
 | 
						|
apiVersion: %s
 | 
						|
kind: %s
 | 
						|
metadata:
 | 
						|
  name: %s
 | 
						|
  finalizers:
 | 
						|
  - test/finalizer
 | 
						|
spec:
 | 
						|
  cronSpec: "* * * * */5"
 | 
						|
  ports:
 | 
						|
  - name: x
 | 
						|
    containerPort: 80
 | 
						|
    protocol: TCP`
 | 
						|
 | 
						|
	mergePatchBody := `
 | 
						|
{
 | 
						|
	"spec": {
 | 
						|
		"knownField1": "val1",
 | 
						|
			"ports": [{
 | 
						|
				"name": "portName",
 | 
						|
				"containerPort": 8080,
 | 
						|
				"protocol": "TCP",
 | 
						|
				"hostPort": 8081
 | 
						|
			}]
 | 
						|
	}
 | 
						|
}
 | 
						|
	`
 | 
						|
	jsonPatchBody := `
 | 
						|
			[
 | 
						|
				{"op": "add", "path": "/spec/knownField1", "value": "val1"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
 | 
						|
				{"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081}
 | 
						|
			]
 | 
						|
			`
 | 
						|
	var testcases = []struct {
 | 
						|
		name      string
 | 
						|
		patchType types.PatchType
 | 
						|
		opts      metav1.PatchOptions
 | 
						|
		body      string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-strict-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-warn-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-ignore-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-merge-patch-no-validation",
 | 
						|
			patchType: types.MergePatchType,
 | 
						|
			body:      mergePatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-strict-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-warn-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-ignore-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
			},
 | 
						|
			body: jsonPatchBody,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:      "crd-json-patch-no-validation",
 | 
						|
			patchType: types.JSONPatchType,
 | 
						|
			body:      jsonPatchBody,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
			names := make([]string, b.N)
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				deployName := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				names[n] = deployName
 | 
						|
 | 
						|
				// create a CR
 | 
						|
				yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, deployName))
 | 
						|
				createResult, err := rest.Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(deployName).
 | 
						|
					Param("fieldManager", "apply_test").
 | 
						|
					Body(yamlBody).
 | 
						|
					DoRaw(context.TODO())
 | 
						|
				if err != nil {
 | 
						|
					b.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
 | 
						|
				}
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				// patch the CR as specified by the test case
 | 
						|
				req := rest.Patch(tc.patchType).
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(names[n]).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body([]byte(tc.body)).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected patch err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationApplyCreateCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name string
 | 
						|
		opts metav1.PatchOptions
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				kind := gvk.Kind
 | 
						|
				apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
				name := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
 | 
						|
				// create the CR as specified by the test case
 | 
						|
				applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
 | 
						|
 | 
						|
				req := rest.Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(name).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := req.Body(applyCreateBody).Do(context.TODO())
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected apply err: %v", result.Error())
 | 
						|
				}
 | 
						|
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func benchFieldValidationApplyUpdateCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
 | 
						|
	var testcases = []struct {
 | 
						|
		name string
 | 
						|
		opts metav1.PatchOptions
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "strict-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Strict",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "warn-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Warn",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "ignore-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldValidation: "Ignore",
 | 
						|
				FieldManager:    "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no-validation",
 | 
						|
			opts: metav1.PatchOptions{
 | 
						|
				FieldManager: "mgr",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tc := range testcases {
 | 
						|
		b.Run(tc.name, func(b *testing.B) {
 | 
						|
			kind := gvk.Kind
 | 
						|
			apiVersion := gvk.Group + "/" + gvk.Version
 | 
						|
			names := make([]string, b.N)
 | 
						|
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				names[n] = fmt.Sprintf("apply-update-crd-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
 | 
						|
				applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, names[n]))
 | 
						|
				createReq := rest.Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(names[n]).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				createResult := createReq.Body(applyCreateBody).Do(context.TODO())
 | 
						|
				if createResult.Error() != nil {
 | 
						|
					b.Fatalf("unexpected apply create err: %v", createResult.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
			b.ResetTimer()
 | 
						|
			b.ReportAllocs()
 | 
						|
			for n := 0; n < b.N; n++ {
 | 
						|
				applyUpdateBody := []byte(fmt.Sprintf(crdApplyValidBody2, apiVersion, kind, names[n]))
 | 
						|
				updateReq := rest.Patch(types.ApplyPatchType).
 | 
						|
					AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
 | 
						|
					Name(names[n]).
 | 
						|
					VersionedParams(&tc.opts, metav1.ParameterCodec)
 | 
						|
				result := updateReq.Body(applyUpdateBody).Do(context.TODO())
 | 
						|
 | 
						|
				if result.Error() != nil {
 | 
						|
					b.Fatalf("unexpected apply err: %v", result.Error())
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |