k8s.io/kubernetes@v1.29.3/test/integration/etcd/data.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package etcd
    18  
    19  import (
    20  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    21  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/runtime/schema"
    24  
    25  	"k8s.io/kubernetes/test/utils/image"
    26  )
    27  
    28  // GetEtcdStorageData returns etcd data for all persisted objects.
    29  // It is exported so that it can be reused across multiple tests.
    30  // It returns a new map on every invocation to prevent different tests from mutating shared state.
    31  func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
    32  	return GetEtcdStorageDataForNamespace("etcdstoragepathtestnamespace")
    33  }
    34  
    35  // GetEtcdStorageDataForNamespace returns etcd data for all persisted objects.
    36  // It is exported so that it can be reused across multiple tests.
    37  // It returns a new map on every invocation to prevent different tests from mutating shared state.
    38  // Namespaced objects keys are computed for the specified namespace.
    39  func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionResource]StorageData {
    40  	image := image.GetE2EImage(image.BusyBox)
    41  	etcdStorageData := map[schema.GroupVersionResource]StorageData{
    42  		// k8s.io/kubernetes/pkg/api/v1
    43  		gvr("", "v1", "configmaps"): {
    44  			Stub:             `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
    45  			ExpectedEtcdPath: "/registry/configmaps/" + namespace + "/cm1",
    46  		},
    47  		gvr("", "v1", "services"): {
    48  			Stub:             `{"metadata": {"name": "service1"}, "spec": {"type": "LoadBalancer", "ports": [{"port": 10000, "targetPort": 11000}], "selector": {"test": "data"}}}`,
    49  			ExpectedEtcdPath: "/registry/services/specs/" + namespace + "/service1",
    50  		},
    51  		gvr("", "v1", "podtemplates"): {
    52  			Stub:             `{"metadata": {"name": "pt1name"}, "template": {"metadata": {"labels": {"pt": "01"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container9"}]}}}`,
    53  			ExpectedEtcdPath: "/registry/podtemplates/" + namespace + "/pt1name",
    54  		},
    55  		gvr("", "v1", "pods"): {
    56  			Stub:             `{"metadata": {"name": "pod1"}, "spec": {"containers": [{"image": "` + image + `", "name": "container7", "resources": {"limits": {"cpu": "1M"}, "requests": {"cpu": "1M"}}}]}}`,
    57  			ExpectedEtcdPath: "/registry/pods/" + namespace + "/pod1",
    58  		},
    59  		gvr("", "v1", "endpoints"): {
    60  			Stub:             `{"metadata": {"name": "ep1name"}, "subsets": [{"addresses": [{"hostname": "bar-001", "ip": "192.168.3.1"}], "ports": [{"port": 8000}]}]}`,
    61  			ExpectedEtcdPath: "/registry/services/endpoints/" + namespace + "/ep1name",
    62  		},
    63  		gvr("", "v1", "resourcequotas"): {
    64  			Stub:             `{"metadata": {"name": "rq1name"}, "spec": {"hard": {"cpu": "5M"}}}`,
    65  			ExpectedEtcdPath: "/registry/resourcequotas/" + namespace + "/rq1name",
    66  		},
    67  		gvr("", "v1", "limitranges"): {
    68  			Stub:             `{"metadata": {"name": "lr1name"}, "spec": {"limits": [{"type": "Pod"}]}}`,
    69  			ExpectedEtcdPath: "/registry/limitranges/" + namespace + "/lr1name",
    70  		},
    71  		gvr("", "v1", "namespaces"): {
    72  			Stub:             `{"metadata": {"name": "namespace1"}, "spec": {"finalizers": ["kubernetes"]}}`,
    73  			ExpectedEtcdPath: "/registry/namespaces/namespace1",
    74  		},
    75  		gvr("", "v1", "nodes"): {
    76  			Stub:             `{"metadata": {"name": "node1"}, "spec": {"unschedulable": true}}`,
    77  			ExpectedEtcdPath: "/registry/minions/node1",
    78  		},
    79  		gvr("", "v1", "persistentvolumes"): {
    80  			Stub:             `{"metadata": {"name": "pv1name"}, "spec": {"accessModes": ["ReadWriteOnce"], "capacity": {"storage": "3M"}, "hostPath": {"path": "/tmp/test/"}}}`,
    81  			ExpectedEtcdPath: "/registry/persistentvolumes/pv1name",
    82  		},
    83  		gvr("", "v1", "events"): {
    84  			Stub:             `{"involvedObject": {"namespace": "` + namespace + `"}, "message": "some data here", "metadata": {"name": "event1"}}`,
    85  			ExpectedEtcdPath: "/registry/events/" + namespace + "/event1",
    86  		},
    87  		gvr("", "v1", "persistentvolumeclaims"): {
    88  			Stub:             `{"metadata": {"name": "pvc1"}, "spec": {"accessModes": ["ReadWriteOnce"], "resources": {"limits": {"storage": "1M"}, "requests": {"storage": "2M"}}, "selector": {"matchLabels": {"pvc": "stuff"}}}}`,
    89  			ExpectedEtcdPath: "/registry/persistentvolumeclaims/" + namespace + "/pvc1",
    90  		},
    91  		gvr("", "v1", "serviceaccounts"): {
    92  			Stub:             `{"metadata": {"name": "sa1name"}, "secrets": [{"name": "secret00"}]}`,
    93  			ExpectedEtcdPath: "/registry/serviceaccounts/" + namespace + "/sa1name",
    94  		},
    95  		gvr("", "v1", "secrets"): {
    96  			Stub:             `{"data": {"key": "ZGF0YSBmaWxl"}, "metadata": {"name": "secret1"}}`,
    97  			ExpectedEtcdPath: "/registry/secrets/" + namespace + "/secret1",
    98  		},
    99  		gvr("", "v1", "replicationcontrollers"): {
   100  			Stub:             `{"metadata": {"name": "rc1"}, "spec": {"selector": {"new": "stuff"}, "template": {"metadata": {"labels": {"new": "stuff"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container8"}]}}}}`,
   101  			ExpectedEtcdPath: "/registry/controllers/" + namespace + "/rc1",
   102  		},
   103  		// --
   104  
   105  		// k8s.io/kubernetes/pkg/apis/apps/v1
   106  		gvr("apps", "v1", "daemonsets"): {
   107  			Stub:             `{"metadata": {"name": "ds6"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container6"}]}}}}`,
   108  			ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds6",
   109  		},
   110  		gvr("apps", "v1", "deployments"): {
   111  			Stub:             `{"metadata": {"name": "deployment4"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container6"}]}}}}`,
   112  			ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment4",
   113  		},
   114  		gvr("apps", "v1", "statefulsets"): {
   115  			Stub:             `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
   116  			ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3",
   117  		},
   118  		gvr("apps", "v1", "replicasets"): {
   119  			Stub:             `{"metadata": {"name": "rs3"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container4"}]}}}}`,
   120  			ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs3",
   121  		},
   122  		gvr("apps", "v1", "controllerrevisions"): {
   123  			Stub:             `{"metadata":{"name":"crs3"},"data":{"name":"abc","namespace":"default","creationTimestamp":null,"Spec":{"Replicas":0,"Selector":{"matchLabels":{"foo":"bar"}},"Template":{"creationTimestamp":null,"labels":{"foo":"bar"},"Spec":{"Volumes":null,"InitContainers":null,"Containers":null,"RestartPolicy":"Always","TerminationGracePeriodSeconds":null,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"","AutomountServiceAccountToken":null,"NodeName":"","SecurityContext":null,"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"","Tolerations":null,"HostAliases":null}},"VolumeClaimTemplates":null,"ServiceName":""},"Status":{"ObservedGeneration":null,"Replicas":0}},"revision":0}`,
   124  			ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs3",
   125  		},
   126  		// --
   127  
   128  		// k8s.io/kubernetes/pkg/apis/autoscaling/v1
   129  		gvr("autoscaling", "v1", "horizontalpodautoscalers"): {
   130  			Stub:             `{"metadata": {"name": "hpa2"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
   131  			ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa2",
   132  			ExpectedGVK:      gvkP("autoscaling", "v2", "HorizontalPodAutoscaler"),
   133  		},
   134  		// --
   135  
   136  		// k8s.io/kubernetes/pkg/apis/autoscaling/v2
   137  		gvr("autoscaling", "v2", "horizontalpodautoscalers"): {
   138  			Stub:             `{"metadata": {"name": "hpa4"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
   139  			ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa4",
   140  		},
   141  		// --
   142  
   143  		// k8s.io/kubernetes/pkg/apis/batch/v1
   144  		gvr("batch", "v1", "jobs"): {
   145  			Stub:             `{"metadata": {"name": "job1"}, "spec": {"manualSelector": true, "selector": {"matchLabels": {"controller-uid": "uid1"}}, "template": {"metadata": {"labels": {"controller-uid": "uid1"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container1"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}`,
   146  			ExpectedEtcdPath: "/registry/jobs/" + namespace + "/job1",
   147  		},
   148  		gvr("batch", "v1", "cronjobs"): {
   149  			Stub:             `{"metadata": {"name": "cjv1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "` + image + `", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
   150  			ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv1",
   151  		},
   152  		// --
   153  
   154  		// k8s.io/kubernetes/pkg/apis/certificates/v1
   155  		gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): {
   156  			Stub:             `{"metadata": {"name": "csr2"}, "spec": {"signerName":"example.com/signer", "usages":["any"], "request": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQnlqQ0NBVE1DQVFBd2dZa3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saApNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01STXdFUVlEVlFRS0V3cEhiMjluYkdVZ1NXNWpNUjh3CkhRWURWUVFMRXhaSmJtWnZjbTFoZEdsdmJpQlVaV05vYm05c2IyZDVNUmN3RlFZRFZRUURFdzUzZDNjdVoyOXYKWjJ4bExtTnZiVENCbnpBTkJna3Foa2lHOXcwQkFRRUZBQU9CalFBd2dZa0NnWUVBcFp0WUpDSEo0VnBWWEhmVgpJbHN0UVRsTzRxQzAzaGpYK1prUHl2ZFlkMVE0K3FiQWVUd1htQ1VLWUhUaFZSZDVhWFNxbFB6eUlCd2llTVpyCldGbFJRZGRaMUl6WEFsVlJEV3dBbzYwS2VjcWVBWG5uVUsrNWZYb1RJL1VnV3NocmU4dEoreC9UTUhhUUtSL0oKY0lXUGhxYVFoc0p1elpidkFkR0E4MEJMeGRNQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUlobAo0UHZGcStlN2lwQVJnSTVaTStHWng2bXBDejQ0RFRvMEprd2ZSRGYrQnRyc2FDMHE2OGVUZjJYaFlPc3E0ZmtIClEwdUEwYVZvZzNmNWlKeENhM0hwNWd4YkpRNnpWNmtKMFRFc3VhYU9oRWtvOXNkcENvUE9uUkJtMmkvWFJEMkQKNmlOaDhmOHowU2hHc0ZxakRnRkh5RjNvK2xVeWorVUM2SDFRVzdibgotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0="}}`,
   157  			ExpectedEtcdPath: "/registry/certificatesigningrequests/csr2",
   158  		},
   159  		// --
   160  
   161  		// k8s.io/kubernetes/pkg/apis/certificates/v1alpha1
   162  		gvr("certificates.k8s.io", "v1alpha1", "clustertrustbundles"): {
   163  			Stub:             `{"metadata": {"name": "example.com:signer:abc"}, "spec": {"signerName":"example.com/signer", "trustBundle": "-----BEGIN CERTIFICATE-----\nMIIBBDCBt6ADAgECAgEAMAUGAytlcDAQMQ4wDAYDVQQDEwVyb290MTAiGA8wMDAx\nMDEwMTAwMDAwMFoYDzAwMDEwMTAxMDAwMDAwWjAQMQ4wDAYDVQQDEwVyb290MTAq\nMAUGAytlcAMhAF2MoFeGa97gK2NGT1h6p1/a1GlMXAXbcjI/OShyIobPozIwMDAP\nBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTWDdK2CNQiHqRjPaAWYPPtIykQgjAF\nBgMrZXADQQCtom9WGl7m2SAa4tXM9Soo/mbInBsRhn187BMoqTAHInHchKup5/3y\nl1tYJSZZsEXnXrCvw2qLCBNif6+2YYgE\n-----END CERTIFICATE-----\n"}}`,
   164  			ExpectedEtcdPath: "/registry/clustertrustbundles/example.com:signer:abc",
   165  		},
   166  		// --
   167  
   168  		// k8s.io/kubernetes/pkg/apis/coordination/v1
   169  		gvr("coordination.k8s.io", "v1", "leases"): {
   170  			Stub:             `{"metadata": {"name": "leasev1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
   171  			ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1",
   172  		},
   173  		// --
   174  
   175  		// k8s.io/kubernetes/pkg/apis/discovery/v1
   176  		gvr("discovery.k8s.io", "v1", "endpointslices"): {
   177  			Stub:             `{"metadata": {"name": "slicev1"}, "addressType": "IPv4", "protocol": "TCP", "ports": [], "endpoints": []}`,
   178  			ExpectedEtcdPath: "/registry/endpointslices/" + namespace + "/slicev1",
   179  			ExpectedGVK:      gvkP("discovery.k8s.io", "v1beta1", "EndpointSlice"),
   180  		},
   181  		// --
   182  
   183  		// k8s.io/kubernetes/pkg/apis/events/v1
   184  		gvr("events.k8s.io", "v1", "events"): {
   185  			Stub:             `{"metadata": {"name": "event3"}, "regarding": {"namespace": "` + namespace + `"}, "note": "some data here", "eventTime": "2017-08-09T15:04:05.000000Z", "reportingInstance": "node-xyz", "reportingController": "k8s.io/my-controller", "action": "DidNothing", "reason": "Laziness", "type": "Normal"}`,
   186  			ExpectedEtcdPath: "/registry/events/" + namespace + "/event3",
   187  			ExpectedGVK:      gvkP("", "v1", "Event"),
   188  		},
   189  		// --
   190  
   191  		// k8s.io/kubernetes/pkg/apis/networking/v1
   192  		gvr("networking.k8s.io", "v1", "ingresses"): {
   193  			Stub:             `{"metadata": {"name": "ingress3"}, "spec": {"defaultBackend": {"service":{"name":"service", "port":{"number": 5000}}}}}`,
   194  			ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress3",
   195  		},
   196  		gvr("networking.k8s.io", "v1", "ingressclasses"): {
   197  			Stub:             `{"metadata": {"name": "ingressclass3"}, "spec": {"controller": "example.com/controller"}}`,
   198  			ExpectedEtcdPath: "/registry/ingressclasses/ingressclass3",
   199  		},
   200  		gvr("networking.k8s.io", "v1", "networkpolicies"): {
   201  			Stub:             `{"metadata": {"name": "np2"}, "spec": {"podSelector": {"matchLabels": {"e": "f"}}}}`,
   202  			ExpectedEtcdPath: "/registry/networkpolicies/" + namespace + "/np2",
   203  		},
   204  		// --
   205  
   206  		// k8s.io/kubernetes/pkg/apis/networking/v1alpha1
   207  		gvr("networking.k8s.io", "v1alpha1", "ipaddresses"): {
   208  			Stub:             `{"metadata": {"name": "192.168.1.2"}, "spec": {"parentRef": {"resource": "services","name": "test", "namespace": "ns"}}}`,
   209  			ExpectedEtcdPath: "/registry/ipaddresses/192.168.1.2",
   210  		},
   211  		// --
   212  
   213  		// k8s.io/kubernetes/pkg/apis/networking/v1alpha1
   214  		gvr("networking.k8s.io", "v1alpha1", "servicecidrs"): {
   215  			Stub:             `{"metadata": {"name": "range1"}, "spec": {"cidrs": ["192.168.0.0/16","fd00:1::/120"]}}`,
   216  			ExpectedEtcdPath: "/registry/servicecidrs/range1",
   217  		},
   218  		// --
   219  
   220  		// k8s.io/kubernetes/pkg/apis/policy/v1
   221  		gvr("policy", "v1", "poddisruptionbudgets"): {
   222  			Stub:             `{"metadata": {"name": "pdbv1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
   223  			ExpectedEtcdPath: "/registry/poddisruptionbudgets/" + namespace + "/pdbv1",
   224  		},
   225  		// --
   226  
   227  		// k8s.io/kubernetes/pkg/apis/storage/v1alpha1
   228  		gvr("storage.k8s.io", "v1alpha1", "csistoragecapacities"): {
   229  			Stub:             `{"metadata": {"name": "csc-12345-1"}, "storageClassName": "sc1"}`,
   230  			ExpectedEtcdPath: "/registry/csistoragecapacities/" + namespace + "/csc-12345-1",
   231  			ExpectedGVK:      gvkP("storage.k8s.io", "v1", "CSIStorageCapacity"),
   232  		},
   233  		// --
   234  
   235  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta1
   236  		gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "flowschemas"): {
   237  			Stub:             `{"metadata": {"name": "va2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   238  			ExpectedEtcdPath: "/registry/flowschemas/va2",
   239  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "FlowSchema"),
   240  		},
   241  		// --
   242  
   243  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta1
   244  		gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "prioritylevelconfigurations"): {
   245  			Stub:             `{"metadata": {"name": "conf2"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   246  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf2",
   247  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "PriorityLevelConfiguration"),
   248  		},
   249  		// --
   250  
   251  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta2
   252  		gvr("flowcontrol.apiserver.k8s.io", "v1beta2", "flowschemas"): {
   253  			Stub:             `{"metadata": {"name": "fs-1"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   254  			ExpectedEtcdPath: "/registry/flowschemas/fs-1",
   255  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "FlowSchema"),
   256  		},
   257  		// --
   258  
   259  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta2
   260  		gvr("flowcontrol.apiserver.k8s.io", "v1beta2", "prioritylevelconfigurations"): {
   261  			Stub:             `{"metadata": {"name": "conf3"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   262  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf3",
   263  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "PriorityLevelConfiguration"),
   264  		},
   265  		// --
   266  
   267  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3
   268  		gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "flowschemas"): {
   269  			Stub:             `{"metadata": {"name": "fs-2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   270  			ExpectedEtcdPath: "/registry/flowschemas/fs-2",
   271  		},
   272  		// --
   273  
   274  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1beta3
   275  		gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "prioritylevelconfigurations"): {
   276  			Stub:             `{"metadata": {"name": "conf4"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   277  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf4",
   278  		},
   279  		// --
   280  
   281  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1
   282  		gvr("flowcontrol.apiserver.k8s.io", "v1", "flowschemas"): {
   283  			Stub:             `{"metadata": {"name": "fs-3"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
   284  			ExpectedEtcdPath: "/registry/flowschemas/fs-3",
   285  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "FlowSchema"),
   286  		},
   287  		// --
   288  
   289  		// k8s.io/kubernetes/pkg/apis/flowcontrol/v1
   290  		gvr("flowcontrol.apiserver.k8s.io", "v1", "prioritylevelconfigurations"): {
   291  			Stub:             `{"metadata": {"name": "conf5"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
   292  			ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf5",
   293  			ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "PriorityLevelConfiguration"),
   294  		},
   295  		// --
   296  
   297  		// k8s.io/kubernetes/pkg/apis/storage/v1
   298  		gvr("storage.k8s.io", "v1", "volumeattachments"): {
   299  			Stub:             `{"metadata": {"name": "va3"}, "spec": {"attacher": "gce", "nodeName": "localhost", "source": {"persistentVolumeName": "pv3"}}}`,
   300  			ExpectedEtcdPath: "/registry/volumeattachments/va3",
   301  		},
   302  		// --
   303  
   304  		// k8s.io/kubernetes/pkg/apis/storage/v1alpha1
   305  		gvr("storage.k8s.io", "v1alpha1", "volumeattributesclasses"): {
   306  			Stub:             `{"metadata": {"name": "vac1"}, "driverName": "example.com/driver", "parameters": {"foo": "bar"}}`,
   307  			ExpectedEtcdPath: "/registry/volumeattributesclasses/vac1",
   308  		},
   309  		// --
   310  
   311  		// k8s.io/kubernetes/pkg/apis/storage/v1beta1
   312  		gvr("storage.k8s.io", "v1beta1", "csistoragecapacities"): {
   313  			Stub:             `{"metadata": {"name": "csc-12345-2"}, "storageClassName": "sc1"}`,
   314  			ExpectedEtcdPath: "/registry/csistoragecapacities/" + namespace + "/csc-12345-2",
   315  			ExpectedGVK:      gvkP("storage.k8s.io", "v1", "CSIStorageCapacity"),
   316  		},
   317  		// --
   318  
   319  		// k8s.io/kubernetes/pkg/apis/storage/v1
   320  		gvr("storage.k8s.io", "v1", "storageclasses"): {
   321  			Stub:             `{"metadata": {"name": "sc2"}, "provisioner": "aws"}`,
   322  			ExpectedEtcdPath: "/registry/storageclasses/sc2",
   323  		},
   324  		gvr("storage.k8s.io", "v1", "csistoragecapacities"): {
   325  			Stub:             `{"metadata": {"name": "csc-12345-3"}, "storageClassName": "sc1"}`,
   326  			ExpectedEtcdPath: "/registry/csistoragecapacities/" + namespace + "/csc-12345-3",
   327  		},
   328  		// --
   329  
   330  		// k8s.io/kubernetes/pkg/apis/rbac/v1
   331  		gvr("rbac.authorization.k8s.io", "v1", "roles"): {
   332  			Stub:             `{"metadata": {"name": "role3"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
   333  			ExpectedEtcdPath: "/registry/roles/" + namespace + "/role3",
   334  		},
   335  		gvr("rbac.authorization.k8s.io", "v1", "clusterroles"): {
   336  			Stub:             `{"metadata": {"name": "crole3"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
   337  			ExpectedEtcdPath: "/registry/clusterroles/crole3",
   338  		},
   339  		gvr("rbac.authorization.k8s.io", "v1", "rolebindings"): {
   340  			Stub:             `{"metadata": {"name": "roleb3"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
   341  			ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb3",
   342  		},
   343  		gvr("rbac.authorization.k8s.io", "v1", "clusterrolebindings"): {
   344  			Stub:             `{"metadata": {"name": "croleb3"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
   345  			ExpectedEtcdPath: "/registry/clusterrolebindings/croleb3",
   346  		},
   347  		// --
   348  
   349  		// k8s.io/kubernetes/pkg/apis/admissionregistration/v1
   350  		gvr("admissionregistration.k8s.io", "v1", "validatingwebhookconfigurations"): {
   351  			Stub:             `{"metadata":{"name":"hook2","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"ns","name":"n"},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore","sideEffects":"None","admissionReviewVersions":["v1beta1"]}]}`,
   352  			ExpectedEtcdPath: "/registry/validatingwebhookconfigurations/hook2",
   353  		},
   354  		gvr("admissionregistration.k8s.io", "v1", "mutatingwebhookconfigurations"): {
   355  			Stub:             `{"metadata":{"name":"hook2","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"ns","name":"n"},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore","sideEffects":"None","admissionReviewVersions":["v1beta1"]}]}`,
   356  			ExpectedEtcdPath: "/registry/mutatingwebhookconfigurations/hook2",
   357  		},
   358  		// --
   359  
   360  		// k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1
   361  		gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): {
   362  			Stub:             `{"metadata":{"name":"vap1b1","creationTimestamp":null},"spec":{"paramKind":{"apiVersion":"test.example.com/v1","kind":"Example"},"matchConstraints":{"resourceRules": [{"resourceNames": ["fakeName"], "apiGroups":["apps"],"apiVersions":["v1"],"operations":["CREATE", "UPDATE"], "resources":["deployments"]}]},"validations":[{"expression":"object.spec.replicas <= params.maxReplicas","message":"Too many replicas"}]}}`,
   363  			ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1b1",
   364  		},
   365  		gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicybindings"): {
   366  			Stub:             `{"metadata":{"name":"pb1b1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com","parameterNotFoundAction":"Deny"},"validationActions":["Deny"]}}`,
   367  			ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1b1",
   368  		},
   369  		// --
   370  
   371  		// k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1
   372  		gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): {
   373  			Stub:             `{"metadata":{"name":"vap1","creationTimestamp":null},"spec":{"paramKind":{"apiVersion":"test.example.com/v1","kind":"Example"},"matchConstraints":{"resourceRules": [{"resourceNames": ["fakeName"], "apiGroups":["apps"],"apiVersions":["v1"],"operations":["CREATE", "UPDATE"], "resources":["deployments"]}]},"validations":[{"expression":"object.spec.replicas <= params.maxReplicas","message":"Too many replicas"}]}}`,
   374  			ExpectedEtcdPath: "/registry/validatingadmissionpolicies/vap1",
   375  			ExpectedGVK:      gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingAdmissionPolicy"),
   376  		},
   377  		gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicybindings"): {
   378  			Stub:             `{"metadata":{"name":"pb1","creationTimestamp":null},"spec":{"policyName":"replicalimit-policy.example.com","paramRef":{"name":"replica-limit-test.example.com"},"validationActions":["Deny"]}}`,
   379  			ExpectedEtcdPath: "/registry/validatingadmissionpolicybindings/pb1",
   380  			ExpectedGVK:      gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingAdmissionPolicyBinding"),
   381  		},
   382  		// --
   383  
   384  		// k8s.io/kubernetes/pkg/apis/scheduling/v1
   385  		gvr("scheduling.k8s.io", "v1", "priorityclasses"): {
   386  			Stub:             `{"metadata":{"name":"pc3"},"Value":1000}`,
   387  			ExpectedEtcdPath: "/registry/priorityclasses/pc3",
   388  		},
   389  		// --
   390  
   391  		// k8s.io/kube-aggregator/pkg/apis/apiregistration/v1
   392  		// depends on aggregator using the same ungrouped RESTOptionsGetter as the kube apiserver, not SimpleRestOptionsFactory in aggregator.go
   393  		gvr("apiregistration.k8s.io", "v1", "apiservices"): {
   394  			Stub:             `{"metadata": {"name": "as2.foo.com"}, "spec": {"group": "foo.com", "version": "as2", "groupPriorityMinimum":100, "versionPriority":10}}`,
   395  			ExpectedEtcdPath: "/registry/apiregistration.k8s.io/apiservices/as2.foo.com",
   396  		},
   397  		// --
   398  
   399  		// k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
   400  		gvr("apiextensions.k8s.io", "v1", "customresourcedefinitions"): {
   401  			Stub: `{"metadata": {"name": "openshiftwebconsoleconfigs.webconsole2.operator.openshift.io"},"spec": {` +
   402  				`"scope": "Cluster","group": "webconsole2.operator.openshift.io",` +
   403  				`"versions": [{"name":"v1alpha1","storage":true,"served":true,"schema":{"openAPIV3Schema":{"type":"object"}}}],` +
   404  				`"names": {"kind": "OpenShiftWebConsoleConfig","plural": "openshiftwebconsoleconfigs","singular": "openshiftwebconsoleconfig"}}}`,
   405  			ExpectedEtcdPath: "/registry/apiextensions.k8s.io/customresourcedefinitions/openshiftwebconsoleconfigs.webconsole2.operator.openshift.io",
   406  			ExpectedGVK:      gvkP("apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition"),
   407  		},
   408  		gvr("cr.bar.com", "v1", "foos"): {
   409  			Stub:             `{"kind": "Foo", "apiVersion": "cr.bar.com/v1", "metadata": {"name": "cr1foo"}, "color": "blue"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   410  			ExpectedEtcdPath: "/registry/cr.bar.com/foos/" + namespace + "/cr1foo",
   411  		},
   412  		gvr("custom.fancy.com", "v2", "pants"): {
   413  			Stub:             `{"kind": "Pant", "apiVersion": "custom.fancy.com/v2", "metadata": {"name": "cr2pant"}, "isFancy": true}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   414  			ExpectedEtcdPath: "/registry/custom.fancy.com/pants/cr2pant",
   415  		},
   416  		gvr("awesome.bears.com", "v1", "pandas"): {
   417  			Stub:             `{"kind": "Panda", "apiVersion": "awesome.bears.com/v1", "metadata": {"name": "cr3panda"}, "spec":{"replicas": 100}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   418  			ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr3panda",
   419  		},
   420  		gvr("awesome.bears.com", "v3", "pandas"): {
   421  			Stub:             `{"kind": "Panda", "apiVersion": "awesome.bears.com/v3", "metadata": {"name": "cr4panda"}, "spec":{"replicas": 300}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   422  			ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
   423  			ExpectedGVK:      gvkP("awesome.bears.com", "v1", "Panda"),
   424  		},
   425  		gvr("random.numbers.com", "v1", "integers"): {
   426  			Stub:             `{"kind": "Integer", "apiVersion": "random.numbers.com/v1", "metadata": {"name": "fortytwo"}, "value": 42, "garbage": "oiujnasdf"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
   427  			ExpectedEtcdPath: "/registry/random.numbers.com/integers/fortytwo",
   428  		},
   429  		// --
   430  
   431  		// k8s.io/kubernetes/pkg/apis/node/v1
   432  		gvr("node.k8s.io", "v1", "runtimeclasses"): {
   433  			Stub:             `{"metadata": {"name": "rc3"}, "handler": "h3"}`,
   434  			ExpectedEtcdPath: "/registry/runtimeclasses/rc3",
   435  		},
   436  		// --
   437  
   438  		// k8s.io/kubernetes/pkg/apis/resource/v1alpha2
   439  		gvr("resource.k8s.io", "v1alpha2", "resourceclasses"): {
   440  			Stub:             `{"metadata": {"name": "class1name"}, "driverName": "example.com"}`,
   441  			ExpectedEtcdPath: "/registry/resourceclasses/class1name",
   442  		},
   443  		gvr("resource.k8s.io", "v1alpha2", "resourceclaims"): {
   444  			Stub:             `{"metadata": {"name": "claim1name"}, "spec": {"resourceClassName": "class1name", "allocationMode": "WaitForFirstConsumer"}}`,
   445  			ExpectedEtcdPath: "/registry/resourceclaims/" + namespace + "/claim1name",
   446  		},
   447  		gvr("resource.k8s.io", "v1alpha2", "resourceclaimtemplates"): {
   448  			Stub:             `{"metadata": {"name": "claimtemplate1name"}, "spec": {"spec": {"resourceClassName": "class1name", "allocationMode": "WaitForFirstConsumer"}}}`,
   449  			ExpectedEtcdPath: "/registry/resourceclaimtemplates/" + namespace + "/claimtemplate1name",
   450  		},
   451  		gvr("resource.k8s.io", "v1alpha2", "podschedulingcontexts"): {
   452  			Stub:             `{"metadata": {"name": "pod1name"}, "spec": {"selectedNode": "node1name", "potentialNodes": ["node1name", "node2name"]}}`,
   453  			ExpectedEtcdPath: "/registry/podschedulingcontexts/" + namespace + "/pod1name",
   454  		},
   455  		// --
   456  
   457  		// k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1
   458  		gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {
   459  			Stub:             `{"metadata":{"name":"sv1.test"},"spec":{}}`,
   460  			ExpectedEtcdPath: "/registry/storageversions/sv1.test",
   461  		},
   462  		// --
   463  
   464  	}
   465  
   466  	// add csinodes
   467  	// k8s.io/kubernetes/pkg/apis/storage/v1
   468  	etcdStorageData[gvr("storage.k8s.io", "v1", "csinodes")] = StorageData{
   469  		Stub:             `{"metadata": {"name": "csini2"}, "spec": {"drivers": [{"name": "test-driver", "nodeID": "localhost", "topologyKeys": ["company.com/zone1", "company.com/zone2"]}]}}`,
   470  		ExpectedEtcdPath: "/registry/csinodes/csini2",
   471  	}
   472  
   473  	// add csidrivers
   474  	// k8s.io/kubernetes/pkg/apis/storage/v1
   475  	etcdStorageData[gvr("storage.k8s.io", "v1", "csidrivers")] = StorageData{
   476  		Stub:             `{"metadata": {"name": "csid2"}, "spec": {"attachRequired": true, "podInfoOnMount": true}}`,
   477  		ExpectedEtcdPath: "/registry/csidrivers/csid2",
   478  	}
   479  
   480  	return etcdStorageData
   481  }
   482  
   483  // StorageData contains information required to create an object and verify its storage in etcd
   484  // It must be paired with a specific resource
   485  type StorageData struct {
   486  	Stub             string                   // Valid JSON stub to use during create
   487  	Prerequisites    []Prerequisite           // Optional, ordered list of JSON objects to create before stub
   488  	ExpectedEtcdPath string                   // Expected location of object in etcd, do not use any variables, constants, etc to derive this value - always supply the full raw string
   489  	ExpectedGVK      *schema.GroupVersionKind // The GVK that we expect this object to be stored as - leave this nil to use the default
   490  }
   491  
   492  // Prerequisite contains information required to create a resource (but not verify it)
   493  type Prerequisite struct {
   494  	GvrData schema.GroupVersionResource
   495  	Stub    string
   496  }
   497  
   498  // GetCustomResourceDefinitionData returns the resource definitions that back the custom resources
   499  // included in GetEtcdStorageData.  They should be created using CreateTestCRDs before running any tests.
   500  // We can switch this to v1 CRDs based on transitive call site analysis.
   501  // Call sites:
   502  // 1. TestDedupOwnerReferences - beta doesn't matter
   503  // 2. TestWebhookAdmissionWithWatchCache/TestWebhookAdmissionWithoutWatchCache - beta doesn't matter
   504  // 3. TestApplyStatus - the version fields don't matter.  Pruning isn't checked, just ownership.
   505  // 4. TestDryRun - versions and pruning don't matter
   506  // 5. TestStorageVersionBootstrap - versions and pruning don't matter.
   507  // 6. TestEtcdStoragePath - beta doesn't matter
   508  // 7. TestCrossGroupStorage - beta doesn't matter
   509  // 8. TestOverlappingCustomResourceCustomResourceDefinition - beta doesn't matter
   510  // 9. TestOverlappingCustomResourceAPIService - beta doesn't matter
   511  func GetCustomResourceDefinitionData() []*apiextensionsv1.CustomResourceDefinition {
   512  	return []*apiextensionsv1.CustomResourceDefinition{
   513  		// namespaced
   514  		{
   515  			ObjectMeta: metav1.ObjectMeta{
   516  				Name: "foos.cr.bar.com",
   517  			},
   518  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   519  				Group: "cr.bar.com",
   520  				Scope: apiextensionsv1.NamespaceScoped,
   521  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   522  					Plural: "foos",
   523  					Kind:   "Foo",
   524  				},
   525  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   526  					{
   527  						Name:    "v1",
   528  						Served:  true,
   529  						Storage: true,
   530  						Schema:  fixtures.AllowAllSchema(),
   531  					},
   532  				},
   533  			},
   534  		},
   535  		// cluster scoped
   536  		{
   537  			ObjectMeta: metav1.ObjectMeta{
   538  				Name: "pants.custom.fancy.com",
   539  			},
   540  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   541  				Group: "custom.fancy.com",
   542  				Scope: apiextensionsv1.ClusterScoped,
   543  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   544  					Plural: "pants",
   545  					Kind:   "Pant",
   546  				},
   547  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   548  					{
   549  						Name:    "v2",
   550  						Served:  true,
   551  						Storage: true,
   552  						Schema:  fixtures.AllowAllSchema(),
   553  					},
   554  				},
   555  			},
   556  		},
   557  		// cluster scoped with legacy version field and pruning.
   558  		{
   559  			ObjectMeta: metav1.ObjectMeta{
   560  				Name: "integers.random.numbers.com",
   561  			},
   562  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   563  				Group: "random.numbers.com",
   564  				Scope: apiextensionsv1.ClusterScoped,
   565  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   566  					Plural: "integers",
   567  					Kind:   "Integer",
   568  				},
   569  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   570  					{
   571  						Name:    "v1",
   572  						Served:  true,
   573  						Storage: true,
   574  						Schema: &apiextensionsv1.CustomResourceValidation{
   575  							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   576  								Type: "object",
   577  								Properties: map[string]apiextensionsv1.JSONSchemaProps{
   578  									"value": {
   579  										Type: "number",
   580  									},
   581  								},
   582  							}},
   583  					},
   584  				},
   585  			},
   586  		},
   587  		// cluster scoped with versions field
   588  		{
   589  			ObjectMeta: metav1.ObjectMeta{
   590  				Name: "pandas.awesome.bears.com",
   591  			},
   592  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   593  				Group: "awesome.bears.com",
   594  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   595  					{
   596  						Name:    "v1",
   597  						Served:  true,
   598  						Storage: true,
   599  						Schema:  fixtures.AllowAllSchema(),
   600  						Subresources: &apiextensionsv1.CustomResourceSubresources{
   601  							Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   602  							Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   603  								SpecReplicasPath:   ".spec.replicas",
   604  								StatusReplicasPath: ".status.replicas",
   605  								LabelSelectorPath:  func() *string { path := ".status.selector"; return &path }(),
   606  							},
   607  						},
   608  					},
   609  					{
   610  						Name:    "v2",
   611  						Served:  false,
   612  						Storage: false,
   613  						Schema:  fixtures.AllowAllSchema(),
   614  						Subresources: &apiextensionsv1.CustomResourceSubresources{
   615  							Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   616  							Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   617  								SpecReplicasPath:   ".spec.replicas",
   618  								StatusReplicasPath: ".status.replicas",
   619  								LabelSelectorPath:  func() *string { path := ".status.selector"; return &path }(),
   620  							},
   621  						},
   622  					},
   623  					{
   624  						Name:    "v3",
   625  						Served:  true,
   626  						Storage: false,
   627  						Schema:  fixtures.AllowAllSchema(),
   628  						Subresources: &apiextensionsv1.CustomResourceSubresources{
   629  							Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   630  							Scale: &apiextensionsv1.CustomResourceSubresourceScale{
   631  								SpecReplicasPath:   ".spec.replicas",
   632  								StatusReplicasPath: ".status.replicas",
   633  								LabelSelectorPath:  func() *string { path := ".status.selector"; return &path }(),
   634  							},
   635  						},
   636  					},
   637  				},
   638  				Scope: apiextensionsv1.ClusterScoped,
   639  				Names: apiextensionsv1.CustomResourceDefinitionNames{
   640  					Plural: "pandas",
   641  					Kind:   "Panda",
   642  				},
   643  			},
   644  		},
   645  	}
   646  }
   647  
   648  func gvr(g, v, r string) schema.GroupVersionResource {
   649  	return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
   650  }
   651  
   652  func gvkP(g, v, k string) *schema.GroupVersionKind {
   653  	return &schema.GroupVersionKind{Group: g, Version: v, Kind: k}
   654  }
   655  
   656  func gvk(g, v, k string) schema.GroupVersionKind {
   657  	return schema.GroupVersionKind{Group: g, Version: v, Kind: k}
   658  }