data.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package etcd
  14. import (
  15. apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. "k8s.io/apimachinery/pkg/runtime/schema"
  18. utilfeature "k8s.io/apiserver/pkg/util/feature"
  19. "k8s.io/kubernetes/pkg/features"
  20. "k8s.io/utils/pointer"
  21. )
  22. // GetEtcdStorageData returns etcd data for all persisted objects.
  23. // It is exported so that it can be reused across multiple tests.
  24. // It returns a new map on every invocation to prevent different tests from mutating shared state.
  25. func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
  26. return GetEtcdStorageDataForNamespace("etcdstoragepathtestnamespace")
  27. }
  28. // GetEtcdStorageDataForNamespace 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. // Namespaced objects keys are computed for the specified namespace.
  32. func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionResource]StorageData {
  33. etcdStorageData := map[schema.GroupVersionResource]StorageData{
  34. // k8s.io/kubernetes/pkg/api/v1
  35. gvr("", "v1", "configmaps"): {
  36. Stub: `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
  37. ExpectedEtcdPath: "/registry/configmaps/" + namespace + "/cm1",
  38. },
  39. gvr("", "v1", "services"): {
  40. Stub: `{"metadata": {"name": "service1"}, "spec": {"externalName": "service1name", "ports": [{"port": 10000, "targetPort": 11000}], "selector": {"test": "data"}}}`,
  41. ExpectedEtcdPath: "/registry/services/specs/" + namespace + "/service1",
  42. },
  43. gvr("", "v1", "podtemplates"): {
  44. Stub: `{"metadata": {"name": "pt1name"}, "template": {"metadata": {"labels": {"pt": "01"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container9"}]}}}`,
  45. ExpectedEtcdPath: "/registry/podtemplates/" + namespace + "/pt1name",
  46. },
  47. gvr("", "v1", "pods"): {
  48. Stub: `{"metadata": {"name": "pod1"}, "spec": {"containers": [{"image": "fedora:latest", "name": "container7", "resources": {"limits": {"cpu": "1M"}, "requests": {"cpu": "1M"}}}]}}`,
  49. ExpectedEtcdPath: "/registry/pods/" + namespace + "/pod1",
  50. },
  51. gvr("", "v1", "endpoints"): {
  52. Stub: `{"metadata": {"name": "ep1name"}, "subsets": [{"addresses": [{"hostname": "bar-001", "ip": "192.168.3.1"}], "ports": [{"port": 8000}]}]}`,
  53. ExpectedEtcdPath: "/registry/services/endpoints/" + namespace + "/ep1name",
  54. },
  55. gvr("", "v1", "resourcequotas"): {
  56. Stub: `{"metadata": {"name": "rq1name"}, "spec": {"hard": {"cpu": "5M"}}}`,
  57. ExpectedEtcdPath: "/registry/resourcequotas/" + namespace + "/rq1name",
  58. },
  59. gvr("", "v1", "limitranges"): {
  60. Stub: `{"metadata": {"name": "lr1name"}, "spec": {"limits": [{"type": "Pod"}]}}`,
  61. ExpectedEtcdPath: "/registry/limitranges/" + namespace + "/lr1name",
  62. },
  63. gvr("", "v1", "namespaces"): {
  64. Stub: `{"metadata": {"name": "namespace1"}, "spec": {"finalizers": ["kubernetes"]}}`,
  65. ExpectedEtcdPath: "/registry/namespaces/namespace1",
  66. },
  67. gvr("", "v1", "nodes"): {
  68. Stub: `{"metadata": {"name": "node1"}, "spec": {"unschedulable": true}}`,
  69. ExpectedEtcdPath: "/registry/minions/node1",
  70. },
  71. gvr("", "v1", "persistentvolumes"): {
  72. Stub: `{"metadata": {"name": "pv1name"}, "spec": {"accessModes": ["ReadWriteOnce"], "capacity": {"storage": "3M"}, "hostPath": {"path": "/tmp/test/"}}}`,
  73. ExpectedEtcdPath: "/registry/persistentvolumes/pv1name",
  74. },
  75. gvr("", "v1", "events"): {
  76. Stub: `{"involvedObject": {"namespace": "` + namespace + `"}, "message": "some data here", "metadata": {"name": "event1"}}`,
  77. ExpectedEtcdPath: "/registry/events/" + namespace + "/event1",
  78. },
  79. gvr("", "v1", "persistentvolumeclaims"): {
  80. Stub: `{"metadata": {"name": "pvc1"}, "spec": {"accessModes": ["ReadWriteOnce"], "resources": {"limits": {"storage": "1M"}, "requests": {"storage": "2M"}}, "selector": {"matchLabels": {"pvc": "stuff"}}}}`,
  81. ExpectedEtcdPath: "/registry/persistentvolumeclaims/" + namespace + "/pvc1",
  82. },
  83. gvr("", "v1", "serviceaccounts"): {
  84. Stub: `{"metadata": {"name": "sa1name"}, "secrets": [{"name": "secret00"}]}`,
  85. ExpectedEtcdPath: "/registry/serviceaccounts/" + namespace + "/sa1name",
  86. },
  87. gvr("", "v1", "secrets"): {
  88. Stub: `{"data": {"key": "ZGF0YSBmaWxl"}, "metadata": {"name": "secret1"}}`,
  89. ExpectedEtcdPath: "/registry/secrets/" + namespace + "/secret1",
  90. },
  91. gvr("", "v1", "replicationcontrollers"): {
  92. Stub: `{"metadata": {"name": "rc1"}, "spec": {"selector": {"new": "stuff"}, "template": {"metadata": {"labels": {"new": "stuff"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container8"}]}}}}`,
  93. ExpectedEtcdPath: "/registry/controllers/" + namespace + "/rc1",
  94. },
  95. // --
  96. // k8s.io/kubernetes/pkg/apis/apps/v1
  97. gvr("apps", "v1", "daemonsets"): {
  98. Stub: `{"metadata": {"name": "ds6"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
  99. ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds6",
  100. },
  101. gvr("apps", "v1", "deployments"): {
  102. Stub: `{"metadata": {"name": "deployment4"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
  103. ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment4",
  104. },
  105. gvr("apps", "v1", "statefulsets"): {
  106. Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
  107. ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3",
  108. },
  109. gvr("apps", "v1", "replicasets"): {
  110. Stub: `{"metadata": {"name": "rs3"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container4"}]}}}}`,
  111. ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs3",
  112. },
  113. gvr("apps", "v1", "controllerrevisions"): {
  114. 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}`,
  115. ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs3",
  116. },
  117. // --
  118. // k8s.io/kubernetes/pkg/apis/autoscaling/v1
  119. gvr("autoscaling", "v1", "horizontalpodautoscalers"): {
  120. Stub: `{"metadata": {"name": "hpa2"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
  121. ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa2",
  122. },
  123. // --
  124. // k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1
  125. gvr("autoscaling", "v2beta1", "horizontalpodautoscalers"): {
  126. Stub: `{"metadata": {"name": "hpa1"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
  127. ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa1",
  128. ExpectedGVK: gvkP("autoscaling", "v1", "HorizontalPodAutoscaler"),
  129. },
  130. // --
  131. // k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2
  132. gvr("autoscaling", "v2beta2", "horizontalpodautoscalers"): {
  133. Stub: `{"metadata": {"name": "hpa3"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
  134. ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa3",
  135. ExpectedGVK: gvkP("autoscaling", "v1", "HorizontalPodAutoscaler"),
  136. },
  137. // --
  138. // k8s.io/kubernetes/pkg/apis/batch/v1
  139. gvr("batch", "v1", "jobs"): {
  140. Stub: `{"metadata": {"name": "job1"}, "spec": {"manualSelector": true, "selector": {"matchLabels": {"controller-uid": "uid1"}}, "template": {"metadata": {"labels": {"controller-uid": "uid1"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container1"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}`,
  141. ExpectedEtcdPath: "/registry/jobs/" + namespace + "/job1",
  142. },
  143. // --
  144. // k8s.io/kubernetes/pkg/apis/batch/v1beta1
  145. gvr("batch", "v1beta1", "cronjobs"): {
  146. Stub: `{"metadata": {"name": "cjv1beta1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
  147. ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv1beta1",
  148. },
  149. // --
  150. // k8s.io/kubernetes/pkg/apis/batch/v2alpha1
  151. gvr("batch", "v2alpha1", "cronjobs"): {
  152. Stub: `{"metadata": {"name": "cjv2alpha1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
  153. ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv2alpha1",
  154. ExpectedGVK: gvkP("batch", "v1beta1", "CronJob"),
  155. },
  156. // --
  157. // k8s.io/kubernetes/pkg/apis/certificates/v1beta1
  158. gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): {
  159. Stub: `{"metadata": {"name": "csr1"}, "spec": {"request": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQnlqQ0NBVE1DQVFBd2dZa3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saApNUll3RkFZRFZRUUhFdzFOYjNWdWRHRnBiaUJXYVdWM01STXdFUVlEVlFRS0V3cEhiMjluYkdVZ1NXNWpNUjh3CkhRWURWUVFMRXhaSmJtWnZjbTFoZEdsdmJpQlVaV05vYm05c2IyZDVNUmN3RlFZRFZRUURFdzUzZDNjdVoyOXYKWjJ4bExtTnZiVENCbnpBTkJna3Foa2lHOXcwQkFRRUZBQU9CalFBd2dZa0NnWUVBcFp0WUpDSEo0VnBWWEhmVgpJbHN0UVRsTzRxQzAzaGpYK1prUHl2ZFlkMVE0K3FiQWVUd1htQ1VLWUhUaFZSZDVhWFNxbFB6eUlCd2llTVpyCldGbFJRZGRaMUl6WEFsVlJEV3dBbzYwS2VjcWVBWG5uVUsrNWZYb1RJL1VnV3NocmU4dEoreC9UTUhhUUtSL0oKY0lXUGhxYVFoc0p1elpidkFkR0E4MEJMeGRNQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUlobAo0UHZGcStlN2lwQVJnSTVaTStHWng2bXBDejQ0RFRvMEprd2ZSRGYrQnRyc2FDMHE2OGVUZjJYaFlPc3E0ZmtIClEwdUEwYVZvZzNmNWlKeENhM0hwNWd4YkpRNnpWNmtKMFRFc3VhYU9oRWtvOXNkcENvUE9uUkJtMmkvWFJEMkQKNmlOaDhmOHowU2hHc0ZxakRnRkh5RjNvK2xVeWorVUM2SDFRVzdibgotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0="}}`,
  160. ExpectedEtcdPath: "/registry/certificatesigningrequests/csr1",
  161. },
  162. // --
  163. // k8s.io/kubernetes/pkg/apis/coordination/v1
  164. gvr("coordination.k8s.io", "v1", "leases"): {
  165. Stub: `{"metadata": {"name": "leasev1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
  166. ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1",
  167. ExpectedGVK: gvkP("coordination.k8s.io", "v1beta1", "Lease"),
  168. },
  169. // --
  170. // k8s.io/kubernetes/pkg/apis/coordination/v1beta1
  171. gvr("coordination.k8s.io", "v1beta1", "leases"): {
  172. Stub: `{"metadata": {"name": "leasev1beta1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
  173. ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1beta1",
  174. },
  175. // --
  176. // k8s.io/kubernetes/pkg/apis/discovery/v1beta1
  177. gvr("discovery.k8s.io", "v1beta1", "endpointslices"): {
  178. Stub: `{"metadata": {"name": "slicev1beta1"}, "addressType": "IPv4", "protocol": "TCP", "ports": [], "endpoints": []}`,
  179. ExpectedEtcdPath: "/registry/endpointslices/" + namespace + "/slicev1beta1",
  180. },
  181. // --
  182. // k8s.io/kubernetes/pkg/apis/events/v1beta1
  183. gvr("events.k8s.io", "v1beta1", "events"): {
  184. Stub: `{"metadata": {"name": "event2"}, "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"}`,
  185. ExpectedEtcdPath: "/registry/events/" + namespace + "/event2",
  186. ExpectedGVK: gvkP("", "v1", "Event"),
  187. },
  188. // --
  189. // k8s.io/kubernetes/pkg/apis/extensions/v1beta1
  190. gvr("extensions", "v1beta1", "ingresses"): {
  191. Stub: `{"metadata": {"name": "ingress1"}, "spec": {"backend": {"serviceName": "service", "servicePort": 5000}}}`,
  192. ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress1",
  193. ExpectedGVK: gvkP("networking.k8s.io", "v1beta1", "Ingress"),
  194. },
  195. // --
  196. // k8s.io/kubernetes/pkg/apis/networking/v1beta1
  197. gvr("networking.k8s.io", "v1beta1", "ingresses"): {
  198. Stub: `{"metadata": {"name": "ingress2"}, "spec": {"backend": {"serviceName": "service", "servicePort": 5000}}}`,
  199. ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress2",
  200. },
  201. // --
  202. // k8s.io/kubernetes/pkg/apis/networking/v1
  203. gvr("networking.k8s.io", "v1", "networkpolicies"): {
  204. Stub: `{"metadata": {"name": "np2"}, "spec": {"podSelector": {"matchLabels": {"e": "f"}}}}`,
  205. ExpectedEtcdPath: "/registry/networkpolicies/" + namespace + "/np2",
  206. },
  207. // --
  208. // k8s.io/kubernetes/pkg/apis/policy/v1beta1
  209. gvr("policy", "v1beta1", "poddisruptionbudgets"): {
  210. Stub: `{"metadata": {"name": "pdb1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
  211. ExpectedEtcdPath: "/registry/poddisruptionbudgets/" + namespace + "/pdb1",
  212. },
  213. gvr("policy", "v1beta1", "podsecuritypolicies"): {
  214. Stub: `{"metadata": {"name": "psp2"}, "spec": {"fsGroup": {"rule": "RunAsAny"}, "privileged": true, "runAsUser": {"rule": "RunAsAny"}, "seLinux": {"rule": "MustRunAs"}, "supplementalGroups": {"rule": "RunAsAny"}}}`,
  215. ExpectedEtcdPath: "/registry/podsecuritypolicy/psp2",
  216. },
  217. // --
  218. // k8s.io/kubernetes/pkg/apis/storage/v1alpha1
  219. gvr("storage.k8s.io", "v1alpha1", "volumeattachments"): {
  220. Stub: `{"metadata": {"name": "va1"}, "spec": {"attacher": "gce", "nodeName": "localhost", "source": {"persistentVolumeName": "pv1"}}}`,
  221. ExpectedEtcdPath: "/registry/volumeattachments/va1",
  222. ExpectedGVK: gvkP("storage.k8s.io", "v1", "VolumeAttachment"),
  223. },
  224. // --
  225. // k8s.io/kubernetes/pkg/apis/flowcontrol/v1alpha1
  226. gvr("flowcontrol.apiserver.k8s.io", "v1alpha1", "flowschemas"): {
  227. Stub: `{"metadata": {"name": "va1"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`,
  228. ExpectedEtcdPath: "/registry/flowschemas/va1",
  229. },
  230. // --
  231. // k8s.io/kubernetes/pkg/apis/flowcontrol/v1alpha1
  232. gvr("flowcontrol.apiserver.k8s.io", "v1alpha1", "prioritylevelconfigurations"): {
  233. Stub: `{"metadata": {"name": "conf1"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`,
  234. ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf1",
  235. },
  236. // --
  237. // k8s.io/kubernetes/pkg/apis/storage/v1beta1
  238. gvr("storage.k8s.io", "v1beta1", "volumeattachments"): {
  239. Stub: `{"metadata": {"name": "va2"}, "spec": {"attacher": "gce", "nodeName": "localhost", "source": {"persistentVolumeName": "pv2"}}}`,
  240. ExpectedEtcdPath: "/registry/volumeattachments/va2",
  241. ExpectedGVK: gvkP("storage.k8s.io", "v1", "VolumeAttachment"),
  242. },
  243. // --
  244. // k8s.io/kubernetes/pkg/apis/storage/v1
  245. gvr("storage.k8s.io", "v1", "volumeattachments"): {
  246. Stub: `{"metadata": {"name": "va3"}, "spec": {"attacher": "gce", "nodeName": "localhost", "source": {"persistentVolumeName": "pv3"}}}`,
  247. ExpectedEtcdPath: "/registry/volumeattachments/va3",
  248. },
  249. // --
  250. // k8s.io/kubernetes/pkg/apis/storage/v1beta1
  251. gvr("storage.k8s.io", "v1beta1", "storageclasses"): {
  252. Stub: `{"metadata": {"name": "sc1"}, "provisioner": "aws"}`,
  253. ExpectedEtcdPath: "/registry/storageclasses/sc1",
  254. ExpectedGVK: gvkP("storage.k8s.io", "v1", "StorageClass"),
  255. },
  256. // --
  257. // k8s.io/kubernetes/pkg/apis/storage/v1
  258. gvr("storage.k8s.io", "v1", "storageclasses"): {
  259. Stub: `{"metadata": {"name": "sc2"}, "provisioner": "aws"}`,
  260. ExpectedEtcdPath: "/registry/storageclasses/sc2",
  261. },
  262. // --
  263. // k8s.io/kubernetes/pkg/apis/settings/v1alpha1
  264. gvr("settings.k8s.io", "v1alpha1", "podpresets"): {
  265. Stub: `{"metadata": {"name": "podpre1"}, "spec": {"env": [{"name": "FOO"}]}}`,
  266. ExpectedEtcdPath: "/registry/podpresets/" + namespace + "/podpre1",
  267. },
  268. // --
  269. // k8s.io/kubernetes/pkg/apis/rbac/v1alpha1
  270. gvr("rbac.authorization.k8s.io", "v1alpha1", "roles"): {
  271. Stub: `{"metadata": {"name": "role1"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
  272. ExpectedEtcdPath: "/registry/roles/" + namespace + "/role1",
  273. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "Role"),
  274. },
  275. gvr("rbac.authorization.k8s.io", "v1alpha1", "clusterroles"): {
  276. Stub: `{"metadata": {"name": "crole1"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
  277. ExpectedEtcdPath: "/registry/clusterroles/crole1",
  278. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "ClusterRole"),
  279. },
  280. gvr("rbac.authorization.k8s.io", "v1alpha1", "rolebindings"): {
  281. Stub: `{"metadata": {"name": "roleb1"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
  282. ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb1",
  283. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "RoleBinding"),
  284. },
  285. gvr("rbac.authorization.k8s.io", "v1alpha1", "clusterrolebindings"): {
  286. Stub: `{"metadata": {"name": "croleb1"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
  287. ExpectedEtcdPath: "/registry/clusterrolebindings/croleb1",
  288. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
  289. },
  290. // --
  291. // k8s.io/kubernetes/pkg/apis/rbac/v1beta1
  292. gvr("rbac.authorization.k8s.io", "v1beta1", "roles"): {
  293. Stub: `{"metadata": {"name": "role2"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
  294. ExpectedEtcdPath: "/registry/roles/" + namespace + "/role2",
  295. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "Role"),
  296. },
  297. gvr("rbac.authorization.k8s.io", "v1beta1", "clusterroles"): {
  298. Stub: `{"metadata": {"name": "crole2"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
  299. ExpectedEtcdPath: "/registry/clusterroles/crole2",
  300. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "ClusterRole"),
  301. },
  302. gvr("rbac.authorization.k8s.io", "v1beta1", "rolebindings"): {
  303. Stub: `{"metadata": {"name": "roleb2"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
  304. ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb2",
  305. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "RoleBinding"),
  306. },
  307. gvr("rbac.authorization.k8s.io", "v1beta1", "clusterrolebindings"): {
  308. Stub: `{"metadata": {"name": "croleb2"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
  309. ExpectedEtcdPath: "/registry/clusterrolebindings/croleb2",
  310. ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "ClusterRoleBinding"),
  311. },
  312. // --
  313. // k8s.io/kubernetes/pkg/apis/rbac/v1
  314. gvr("rbac.authorization.k8s.io", "v1", "roles"): {
  315. Stub: `{"metadata": {"name": "role3"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
  316. ExpectedEtcdPath: "/registry/roles/" + namespace + "/role3",
  317. },
  318. gvr("rbac.authorization.k8s.io", "v1", "clusterroles"): {
  319. Stub: `{"metadata": {"name": "crole3"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
  320. ExpectedEtcdPath: "/registry/clusterroles/crole3",
  321. },
  322. gvr("rbac.authorization.k8s.io", "v1", "rolebindings"): {
  323. 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"}]}`,
  324. ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb3",
  325. },
  326. gvr("rbac.authorization.k8s.io", "v1", "clusterrolebindings"): {
  327. 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"}]}`,
  328. ExpectedEtcdPath: "/registry/clusterrolebindings/croleb3",
  329. },
  330. // --
  331. // k8s.io/kubernetes/pkg/apis/admissionregistration/v1
  332. gvr("admissionregistration.k8s.io", "v1", "validatingwebhookconfigurations"): {
  333. 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"]}]}`,
  334. ExpectedEtcdPath: "/registry/validatingwebhookconfigurations/hook2",
  335. ExpectedGVK: gvkP("admissionregistration.k8s.io", "v1beta1", "ValidatingWebhookConfiguration"),
  336. },
  337. gvr("admissionregistration.k8s.io", "v1", "mutatingwebhookconfigurations"): {
  338. 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"]}]}`,
  339. ExpectedEtcdPath: "/registry/mutatingwebhookconfigurations/hook2",
  340. ExpectedGVK: gvkP("admissionregistration.k8s.io", "v1beta1", "MutatingWebhookConfiguration"),
  341. },
  342. // --
  343. // k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1
  344. gvr("admissionregistration.k8s.io", "v1beta1", "validatingwebhookconfigurations"): {
  345. Stub: `{"metadata":{"name":"hook1","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"}]}`,
  346. ExpectedEtcdPath: "/registry/validatingwebhookconfigurations/hook1",
  347. },
  348. gvr("admissionregistration.k8s.io", "v1beta1", "mutatingwebhookconfigurations"): {
  349. Stub: `{"metadata":{"name":"hook1","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"}]}`,
  350. ExpectedEtcdPath: "/registry/mutatingwebhookconfigurations/hook1",
  351. },
  352. // --
  353. // k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1
  354. gvr("scheduling.k8s.io", "v1alpha1", "priorityclasses"): {
  355. Stub: `{"metadata":{"name":"pc1"},"Value":1000}`,
  356. ExpectedEtcdPath: "/registry/priorityclasses/pc1",
  357. ExpectedGVK: gvkP("scheduling.k8s.io", "v1", "PriorityClass"),
  358. },
  359. // --
  360. // k8s.io/kubernetes/pkg/apis/scheduling/v1beta1
  361. gvr("scheduling.k8s.io", "v1beta1", "priorityclasses"): {
  362. Stub: `{"metadata":{"name":"pc2"},"Value":1000}`,
  363. ExpectedEtcdPath: "/registry/priorityclasses/pc2",
  364. ExpectedGVK: gvkP("scheduling.k8s.io", "v1", "PriorityClass"),
  365. },
  366. // --
  367. // k8s.io/kubernetes/pkg/apis/scheduling/v1
  368. gvr("scheduling.k8s.io", "v1", "priorityclasses"): {
  369. Stub: `{"metadata":{"name":"pc3"},"Value":1000}`,
  370. ExpectedEtcdPath: "/registry/priorityclasses/pc3",
  371. },
  372. // --
  373. // k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1
  374. // depends on aggregator using the same ungrouped RESTOptionsGetter as the kube apiserver, not SimpleRestOptionsFactory in aggregator.go
  375. gvr("apiregistration.k8s.io", "v1beta1", "apiservices"): {
  376. Stub: `{"metadata": {"name": "as1.foo.com"}, "spec": {"group": "foo.com", "version": "as1", "groupPriorityMinimum":100, "versionPriority":10}}`,
  377. ExpectedEtcdPath: "/registry/apiregistration.k8s.io/apiservices/as1.foo.com",
  378. },
  379. // --
  380. // k8s.io/kube-aggregator/pkg/apis/apiregistration/v1
  381. // depends on aggregator using the same ungrouped RESTOptionsGetter as the kube apiserver, not SimpleRestOptionsFactory in aggregator.go
  382. gvr("apiregistration.k8s.io", "v1", "apiservices"): {
  383. Stub: `{"metadata": {"name": "as2.foo.com"}, "spec": {"group": "foo.com", "version": "as2", "groupPriorityMinimum":100, "versionPriority":10}}`,
  384. ExpectedEtcdPath: "/registry/apiregistration.k8s.io/apiservices/as2.foo.com",
  385. ExpectedGVK: gvkP("apiregistration.k8s.io", "v1beta1", "APIService"),
  386. },
  387. // --
  388. // k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
  389. gvr("apiextensions.k8s.io", "v1", "customresourcedefinitions"): {
  390. Stub: `{"metadata": {"name": "openshiftwebconsoleconfigs.webconsole2.operator.openshift.io"},"spec": {` +
  391. `"scope": "Cluster","group": "webconsole2.operator.openshift.io",` +
  392. `"versions": [{"name":"v1alpha1","storage":true,"served":true,"schema":{"openAPIV3Schema":{"type":"object"}}}],` +
  393. `"names": {"kind": "OpenShiftWebConsoleConfig","plural": "openshiftwebconsoleconfigs","singular": "openshiftwebconsoleconfig"}}}`,
  394. ExpectedEtcdPath: "/registry/apiextensions.k8s.io/customresourcedefinitions/openshiftwebconsoleconfigs.webconsole2.operator.openshift.io",
  395. ExpectedGVK: gvkP("apiextensions.k8s.io", "v1beta1", "CustomResourceDefinition"),
  396. },
  397. // k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
  398. gvr("apiextensions.k8s.io", "v1beta1", "customresourcedefinitions"): {
  399. Stub: `{"metadata": {"name": "openshiftwebconsoleconfigs.webconsole.operator.openshift.io"},"spec": {"scope": "Cluster","group": "webconsole.operator.openshift.io","version": "v1alpha1","names": {"kind": "OpenShiftWebConsoleConfig","plural": "openshiftwebconsoleconfigs","singular": "openshiftwebconsoleconfig"}}}`,
  400. ExpectedEtcdPath: "/registry/apiextensions.k8s.io/customresourcedefinitions/openshiftwebconsoleconfigs.webconsole.operator.openshift.io",
  401. },
  402. gvr("cr.bar.com", "v1", "foos"): {
  403. Stub: `{"kind": "Foo", "apiVersion": "cr.bar.com/v1", "metadata": {"name": "cr1foo"}, "color": "blue"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
  404. ExpectedEtcdPath: "/registry/cr.bar.com/foos/" + namespace + "/cr1foo",
  405. },
  406. gvr("custom.fancy.com", "v2", "pants"): {
  407. Stub: `{"kind": "Pant", "apiVersion": "custom.fancy.com/v2", "metadata": {"name": "cr2pant"}, "isFancy": true}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
  408. ExpectedEtcdPath: "/registry/custom.fancy.com/pants/cr2pant",
  409. },
  410. gvr("awesome.bears.com", "v1", "pandas"): {
  411. Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v1", "metadata": {"name": "cr3panda"}, "spec":{"replicas": 100}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
  412. ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr3panda",
  413. },
  414. gvr("awesome.bears.com", "v3", "pandas"): {
  415. Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v3", "metadata": {"name": "cr4panda"}, "spec":{"replicas": 300}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
  416. ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
  417. ExpectedGVK: gvkP("awesome.bears.com", "v1", "Panda"),
  418. },
  419. gvr("random.numbers.com", "v1", "integers"): {
  420. Stub: `{"kind": "Integer", "apiVersion": "random.numbers.com/v1", "metadata": {"name": "fortytwo"}, "value": 42, "garbage": "oiujnasdf"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
  421. ExpectedEtcdPath: "/registry/random.numbers.com/integers/fortytwo",
  422. },
  423. // --
  424. // k8s.io/kubernetes/pkg/apis/auditregistration/v1alpha1
  425. gvr("auditregistration.k8s.io", "v1alpha1", "auditsinks"): {
  426. Stub: `{"metadata":{"name":"sink1"},"spec":{"policy":{"level":"Metadata","stages":["ResponseStarted"]},"webhook":{"clientConfig":{"url":"http://localhost:4444","service":null,"caBundle":null}}}}`,
  427. ExpectedEtcdPath: "/registry/auditsinks/sink1",
  428. },
  429. // --
  430. // k8s.io/kubernetes/pkg/apis/node/v1alpha1
  431. gvr("node.k8s.io", "v1alpha1", "runtimeclasses"): {
  432. Stub: `{"metadata": {"name": "rc1"}, "spec": {"runtimeHandler": "h1"}}`,
  433. ExpectedEtcdPath: "/registry/runtimeclasses/rc1",
  434. ExpectedGVK: gvkP("node.k8s.io", "v1beta1", "RuntimeClass"),
  435. },
  436. // --
  437. // k8s.io/kubernetes/pkg/apis/node/v1beta1
  438. gvr("node.k8s.io", "v1beta1", "runtimeclasses"): {
  439. Stub: `{"metadata": {"name": "rc2"}, "handler": "h2"}`,
  440. ExpectedEtcdPath: "/registry/runtimeclasses/rc2",
  441. },
  442. // --
  443. }
  444. // add csinodes if CSINodeInfo feature gate is enabled
  445. if utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
  446. // k8s.io/kubernetes/pkg/apis/storage/v1beta1
  447. etcdStorageData[gvr("storage.k8s.io", "v1beta1", "csinodes")] = StorageData{
  448. Stub: `{"metadata": {"name": "csini1"}, "spec": {"drivers": [{"name": "test-driver", "nodeID": "localhost", "topologyKeys": ["company.com/zone1", "company.com/zone2"]}]}}`,
  449. ExpectedEtcdPath: "/registry/csinodes/csini1",
  450. ExpectedGVK: gvkP("storage.k8s.io", "v1", "CSINode"),
  451. }
  452. // k8s.io/kubernetes/pkg/apis/storage/v1
  453. etcdStorageData[gvr("storage.k8s.io", "v1", "csinodes")] = StorageData{
  454. Stub: `{"metadata": {"name": "csini2"}, "spec": {"drivers": [{"name": "test-driver", "nodeID": "localhost", "topologyKeys": ["company.com/zone1", "company.com/zone2"]}]}}`,
  455. ExpectedEtcdPath: "/registry/csinodes/csini2",
  456. }
  457. }
  458. // k8s.io/kubernetes/pkg/apis/storage/v1beta1
  459. // add csidrivers if CSIDriverRegistry feature gate is enabled
  460. if utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
  461. etcdStorageData[gvr("storage.k8s.io", "v1beta1", "csidrivers")] = StorageData{
  462. Stub: `{"metadata": {"name": "csid1"}, "spec": {"attachRequired": true, "podInfoOnMount": true}}`,
  463. ExpectedEtcdPath: "/registry/csidrivers/csid1",
  464. }
  465. }
  466. return etcdStorageData
  467. }
  468. // StorageData contains information required to create an object and verify its storage in etcd
  469. // It must be paired with a specific resource
  470. type StorageData struct {
  471. Stub string // Valid JSON stub to use during create
  472. Prerequisites []Prerequisite // Optional, ordered list of JSON objects to create before stub
  473. 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
  474. ExpectedGVK *schema.GroupVersionKind // The GVK that we expect this object to be stored as - leave this nil to use the default
  475. }
  476. // Prerequisite contains information required to create a resource (but not verify it)
  477. type Prerequisite struct {
  478. GvrData schema.GroupVersionResource
  479. Stub string
  480. }
  481. // GetCustomResourceDefinitionData returns the resource definitions that back the custom resources
  482. // included in GetEtcdStorageData. They should be created using CreateTestCRDs before running any tests.
  483. func GetCustomResourceDefinitionData() []*apiextensionsv1beta1.CustomResourceDefinition {
  484. return []*apiextensionsv1beta1.CustomResourceDefinition{
  485. // namespaced with legacy version field
  486. {
  487. ObjectMeta: metav1.ObjectMeta{
  488. Name: "foos.cr.bar.com",
  489. },
  490. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  491. Group: "cr.bar.com",
  492. Version: "v1",
  493. Scope: apiextensionsv1beta1.NamespaceScoped,
  494. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  495. Plural: "foos",
  496. Kind: "Foo",
  497. },
  498. },
  499. },
  500. // cluster scoped with legacy version field
  501. {
  502. ObjectMeta: metav1.ObjectMeta{
  503. Name: "pants.custom.fancy.com",
  504. },
  505. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  506. Group: "custom.fancy.com",
  507. Version: "v2",
  508. Scope: apiextensionsv1beta1.ClusterScoped,
  509. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  510. Plural: "pants",
  511. Kind: "Pant",
  512. },
  513. },
  514. },
  515. // cluster scoped with legacy version field and pruning.
  516. {
  517. ObjectMeta: metav1.ObjectMeta{
  518. Name: "integers.random.numbers.com",
  519. },
  520. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  521. Group: "random.numbers.com",
  522. Version: "v1",
  523. Scope: apiextensionsv1beta1.ClusterScoped,
  524. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  525. Plural: "integers",
  526. Kind: "Integer",
  527. },
  528. Validation: &apiextensionsv1beta1.CustomResourceValidation{
  529. OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
  530. Type: "object",
  531. Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
  532. "value": {
  533. Type: "number",
  534. },
  535. },
  536. },
  537. },
  538. PreserveUnknownFields: pointer.BoolPtr(false),
  539. },
  540. },
  541. // cluster scoped with versions field
  542. {
  543. ObjectMeta: metav1.ObjectMeta{
  544. Name: "pandas.awesome.bears.com",
  545. },
  546. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  547. Group: "awesome.bears.com",
  548. Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
  549. {
  550. Name: "v1",
  551. Served: true,
  552. Storage: true,
  553. },
  554. {
  555. Name: "v2",
  556. Served: false,
  557. Storage: false,
  558. },
  559. {
  560. Name: "v3",
  561. Served: true,
  562. Storage: false,
  563. },
  564. },
  565. Scope: apiextensionsv1beta1.ClusterScoped,
  566. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  567. Plural: "pandas",
  568. Kind: "Panda",
  569. },
  570. Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
  571. Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
  572. Scale: &apiextensionsv1beta1.CustomResourceSubresourceScale{
  573. SpecReplicasPath: ".spec.replicas",
  574. StatusReplicasPath: ".status.replicas",
  575. LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
  576. },
  577. },
  578. },
  579. },
  580. }
  581. }
  582. func gvr(g, v, r string) schema.GroupVersionResource {
  583. return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
  584. }
  585. func gvkP(g, v, k string) *schema.GroupVersionKind {
  586. return &schema.GroupVersionKind{Group: g, Version: v, Kind: k}
  587. }