1
16
17 package discovery
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "reflect"
24 "strings"
25 "testing"
26 "time"
27
28 apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
29 "k8s.io/apimachinery/pkg/api/errors"
30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/runtime/schema"
33 "k8s.io/apimachinery/pkg/util/sets"
34 "k8s.io/apimachinery/pkg/util/wait"
35 "k8s.io/client-go/dynamic"
36 "k8s.io/client-go/kubernetes"
37 aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
38
39 apidiscoveryv2 "k8s.io/api/apidiscovery/v2"
40 apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
41 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
42 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
43 apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
44 )
45
46 const acceptV1JSON = "application/json"
47 const acceptV2Beta1JSON = "application/json;g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
48 const acceptV2JSON = "application/json;g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList"
49
50 const maxTimeout = 10 * time.Second
51
52 type testClient interface {
53 kubernetes.Interface
54 aggregator.Interface
55 apiextensions.Interface
56 dynamic.Interface
57 }
58
59
60
61
62 type testCase struct {
63 Name string
64 Actions []testAction
65 }
66
67
68
69 type testAction interface {
70 Do(ctx context.Context, client testClient) error
71 }
72
73 type cleaningAction interface {
74 testAction
75 Cleanup(ctx context.Context, client testClient) error
76 }
77
78
79 type applyAPIService apiregistrationv1.APIServiceSpec
80
81 type applyCRD apiextensionsv1.CustomResourceDefinitionSpec
82
83 type deleteObject struct {
84 metav1.GroupVersionResource
85 Namespace string
86 Name string
87 }
88
89
90 type waitForGroupVersionsV1 []metav1.GroupVersion
91
92
93 type waitForAbsentGroupVersionsV1 []metav1.GroupVersion
94
95
96 type waitForGroupVersionsV2 []metav1.GroupVersion
97
98
99 type waitForGroupVersionsV2Beta1 []metav1.GroupVersion
100
101
102 type waitForAbsentGroupVersionsV2 []metav1.GroupVersion
103
104
105 type waitForAbsentGroupVersionsV2Beta1 []metav1.GroupVersion
106
107 type waitForStaleGroupVersionsV2 []metav1.GroupVersion
108 type waitForFreshGroupVersionsV2 []metav1.GroupVersion
109
110 type waitForResourcesV1 []metav1.GroupVersionResource
111 type waitForResourcesAbsentV1 []metav1.GroupVersionResource
112
113 type waitForResourcesV2 []metav1.GroupVersionResource
114 type waitForResourcesAbsentV2 []metav1.GroupVersionResource
115
116
117 type inlineAction func(ctx context.Context, client testClient) error
118
119 func (a applyAPIService) Do(ctx context.Context, client testClient) error {
120
121
122 obj := &apiregistrationv1.APIService{
123 ObjectMeta: metav1.ObjectMeta{
124 Name: a.Version + "." + a.Group,
125 },
126 Spec: apiregistrationv1.APIServiceSpec(a),
127 }
128
129 unstructuredContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
130 if err != nil {
131 return err
132 }
133
134 unstructedObject := &unstructured.Unstructured{}
135 unstructedObject.SetUnstructuredContent(unstructuredContent)
136 unstructedObject.SetGroupVersionKind(apiregistrationv1.SchemeGroupVersion.WithKind("APIService"))
137
138 _, err = client.
139 Resource(apiregistrationv1.SchemeGroupVersion.WithResource("apiservices")).
140 Apply(ctx, obj.Name, unstructedObject, metav1.ApplyOptions{
141 FieldManager: "test-manager",
142 })
143
144 return err
145 }
146
147 func (a applyAPIService) Cleanup(ctx context.Context, client testClient) error {
148 name := a.Version + "." + a.Group
149 err := client.ApiregistrationV1().APIServices().Delete(ctx, name, metav1.DeleteOptions{})
150
151 if !errors.IsNotFound(err) {
152 return err
153 }
154
155 err = wait.PollUntilContextTimeout(
156 ctx,
157 250*time.Millisecond,
158 maxTimeout,
159 true,
160 func(ctx context.Context) (done bool, err error) {
161 _, err = client.ApiregistrationV1().APIServices().Get(ctx, name, metav1.GetOptions{})
162 if err == nil {
163 return false, nil
164 }
165
166 if !errors.IsNotFound(err) {
167 return false, err
168 }
169 return true, nil
170 },
171 )
172
173 if err != nil {
174 return fmt.Errorf("error waiting for APIService %v to clean up: %w", name, err)
175 }
176
177 return nil
178 }
179
180 func (a applyCRD) Do(ctx context.Context, client testClient) error {
181
182
183 name := a.Names.Plural + "." + a.Group
184 obj := &apiextensionsv1.CustomResourceDefinition{
185 ObjectMeta: metav1.ObjectMeta{
186 Name: name,
187 },
188 Spec: apiextensionsv1.CustomResourceDefinitionSpec(a),
189 }
190
191 if strings.HasSuffix(obj.Name, ".k8s.io") {
192 if obj.Annotations == nil {
193 obj.Annotations = map[string]string{}
194 }
195 obj.Annotations["api-approved.kubernetes.io"] = "https://github.com/kubernetes/kubernetes/fake"
196 }
197
198 unstructuredContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
199 if err != nil {
200 return err
201 }
202
203 unstructedObject := &unstructured.Unstructured{}
204 unstructedObject.SetUnstructuredContent(unstructuredContent)
205 unstructedObject.SetGroupVersionKind(apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition"))
206
207 _, err = client.
208 Resource(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")).
209 Apply(ctx, obj.Name, unstructedObject, metav1.ApplyOptions{
210 FieldManager: "test-manager",
211 })
212
213 return err
214 }
215
216 func (a applyCRD) Cleanup(ctx context.Context, client testClient) error {
217 name := a.Names.Plural + "." + a.Group
218 err := client.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, name, metav1.DeleteOptions{})
219
220 if !errors.IsNotFound(err) {
221 return err
222 }
223
224 err = wait.PollUntilContextTimeout(
225 ctx,
226 250*time.Millisecond,
227 maxTimeout,
228 true,
229 func(ctx context.Context) (done bool, err error) {
230 _, err = client.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, name, metav1.GetOptions{})
231 if err == nil {
232 return false, nil
233 }
234
235 if !errors.IsNotFound(err) {
236 return false, err
237 }
238 return true, nil
239 },
240 )
241
242 if err != nil {
243 return fmt.Errorf("error waiting for CRD %v to clean up: %w", name, err)
244 }
245
246 return nil
247 }
248
249 func (d deleteObject) Do(ctx context.Context, client testClient) error {
250 if d.Namespace == "" {
251 return client.Resource(schema.GroupVersionResource(d.GroupVersionResource)).
252 Delete(ctx, d.Name, metav1.DeleteOptions{})
253 } else {
254 return client.Resource(schema.GroupVersionResource(d.GroupVersionResource)).
255 Namespace(d.Namespace).
256 Delete(ctx, d.Name, metav1.DeleteOptions{})
257 }
258 }
259
260 func (w waitForStaleGroupVersionsV2) Do(ctx context.Context, client testClient) error {
261 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool {
262 for _, gv := range w {
263 if info := FindGroupVersionV2(result, gv); info == nil || info.Freshness != apidiscoveryv2.DiscoveryFreshnessStale {
264 return false
265 }
266 }
267
268 return true
269 })
270
271 if err != nil {
272 return fmt.Errorf("waiting for stale groupversions v2 (%v): %w", w, err)
273 }
274 return nil
275 }
276
277 func (w waitForFreshGroupVersionsV2) Do(ctx context.Context, client testClient) error {
278 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool {
279 for _, gv := range w {
280 if info := FindGroupVersionV2(result, gv); info == nil || info.Freshness != apidiscoveryv2.DiscoveryFreshnessCurrent {
281 return false
282 }
283 }
284
285 return true
286 })
287
288 if err != nil {
289 return fmt.Errorf("waiting for fresh groupversions v2 (%v): %w", w, err)
290 }
291 return nil
292 }
293
294 func (w waitForGroupVersionsV2) Do(ctx context.Context, client testClient) error {
295 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool {
296 for _, gv := range w {
297 if FindGroupVersionV2(result, gv) == nil {
298 return false
299 }
300 }
301
302 return true
303 })
304
305 if err != nil {
306 return fmt.Errorf("waiting for groupversions v2 (%v): %w", w, err)
307 }
308 return nil
309 }
310
311 func (w waitForGroupVersionsV2Beta1) Do(ctx context.Context, client testClient) error {
312 err := WaitForV2Beta1ResultWithCondition(ctx, client, func(result apidiscoveryv2beta1.APIGroupDiscoveryList) bool {
313 for _, gv := range w {
314 if FindGroupVersionV2Beta1(result, gv) == nil {
315 return false
316 }
317 }
318
319 return true
320 })
321
322 if err != nil {
323 return fmt.Errorf("waiting for groupversions v2 (%v): %w", w, err)
324 }
325 return nil
326 }
327
328 func (w waitForAbsentGroupVersionsV2) Do(ctx context.Context, client testClient) error {
329 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool {
330 for _, gv := range w {
331 if FindGroupVersionV2(result, gv) != nil {
332 return false
333 }
334 }
335
336 return true
337 })
338
339 if err != nil {
340 return fmt.Errorf("waiting for absent groupversions v2 (%v): %w", w, err)
341 }
342 return nil
343 }
344
345 func (w waitForAbsentGroupVersionsV2Beta1) Do(ctx context.Context, client testClient) error {
346 err := WaitForV2Beta1ResultWithCondition(ctx, client, func(result apidiscoveryv2beta1.APIGroupDiscoveryList) bool {
347 for _, gv := range w {
348 if FindGroupVersionV2Beta1(result, gv) != nil {
349 return false
350 }
351 }
352
353 return true
354 })
355
356 if err != nil {
357 return fmt.Errorf("waiting for absent groupversions v2 (%v): %w", w, err)
358 }
359 return nil
360 }
361
362 func (w waitForGroupVersionsV1) Do(ctx context.Context, client testClient) error {
363 err := WaitForV1GroupsWithCondition(ctx, client, func(result metav1.APIGroupList) bool {
364 for _, gv := range w {
365 if !FindGroupVersionV1(result, gv) {
366 return false
367 }
368 }
369
370 return true
371 })
372
373 if err != nil {
374 return fmt.Errorf("waiting for groupversions v1 (%v): %w", w, err)
375 }
376 return nil
377 }
378
379 func (w waitForAbsentGroupVersionsV1) Do(ctx context.Context, client testClient) error {
380 err := WaitForV1GroupsWithCondition(ctx, client, func(result metav1.APIGroupList) bool {
381 for _, gv := range w {
382 if FindGroupVersionV1(result, gv) {
383 return false
384 }
385 }
386
387 return true
388 })
389
390 if err != nil {
391 return fmt.Errorf("waiting for absent groupversions v1 (%v): %w", w, err)
392 }
393 return nil
394 }
395
396 func (w waitForResourcesV1) Do(ctx context.Context, client testClient) error {
397 requiredResources := map[metav1.GroupVersion][]string{}
398
399 for _, gvr := range w {
400 gv := metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version}
401 if existing, ok := requiredResources[gv]; ok {
402 requiredResources[gv] = append(existing, gvr.Resource)
403 } else {
404 requiredResources[gv] = []string{gvr.Resource}
405 }
406 }
407
408 for gv, resourceNames := range requiredResources {
409 err := WaitForV1ResourcesWithCondition(ctx, client, gv, func(result metav1.APIResourceList) bool {
410 for _, name := range resourceNames {
411 found := false
412
413 for _, resultResource := range result.APIResources {
414 if resultResource.Name == name {
415 found = true
416 break
417 }
418 }
419
420 if !found {
421 return false
422 }
423 }
424
425 return true
426 })
427
428 if err != nil {
429 if errors.IsNotFound(err) {
430 return nil
431 }
432 return fmt.Errorf("waiting for resources v1 (%v): %w", w, err)
433 }
434 }
435
436 return nil
437 }
438
439 func (w waitForResourcesAbsentV1) Do(ctx context.Context, client testClient) error {
440 requiredResources := map[metav1.GroupVersion][]string{}
441
442 for _, gvr := range w {
443 gv := metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version}
444 if existing, ok := requiredResources[gv]; ok {
445 requiredResources[gv] = append(existing, gvr.Resource)
446 } else {
447 requiredResources[gv] = []string{gvr.Resource}
448 }
449 }
450
451 for gv, resourceNames := range requiredResources {
452 err := WaitForV1ResourcesWithCondition(ctx, client, gv, func(result metav1.APIResourceList) bool {
453 for _, name := range resourceNames {
454 for _, resultResource := range result.APIResources {
455 if resultResource.Name == name {
456 return false
457 }
458 }
459 }
460
461 return true
462 })
463
464 if err != nil {
465 if errors.IsNotFound(err) {
466 return nil
467 }
468 return fmt.Errorf("waiting for absent resources v1 (%v): %w", w, err)
469 }
470 }
471
472 return nil
473 }
474
475 func (w waitForResourcesV2) Do(ctx context.Context, client testClient) error {
476 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool {
477 for _, gvr := range w {
478 if info := FindGroupVersionV2(result, metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version}); info == nil {
479 return false
480 } else {
481 found := false
482 for _, resultResoure := range info.Resources {
483 if resultResoure.Resource == gvr.Resource {
484 found = true
485 break
486 }
487 }
488
489 if !found {
490 return false
491 }
492 }
493 }
494
495 return true
496 })
497
498 if err != nil {
499 return fmt.Errorf("waiting for resources v2 (%v): %w", w, err)
500 }
501 return nil
502 }
503
504 func (w waitForResourcesAbsentV2) Do(ctx context.Context, client testClient) error {
505 err := WaitForResultWithCondition(ctx, client, func(result apidiscoveryv2.APIGroupDiscoveryList) bool {
506 for _, gvr := range w {
507 if info := FindGroupVersionV2(result, metav1.GroupVersion{Group: gvr.Group, Version: gvr.Version}); info == nil {
508 return false
509 } else {
510 for _, resultResoure := range info.Resources {
511 if resultResoure.Resource == gvr.Resource {
512 return false
513 }
514 }
515 }
516 }
517
518 return true
519 })
520
521 if err != nil {
522 return fmt.Errorf("waiting for absent resources v2 (%v): %w", w, err)
523 }
524 return nil
525 }
526
527 func (i inlineAction) Do(ctx context.Context, client testClient) error {
528 return i(ctx, client)
529 }
530
531 func FetchV2Discovery(ctx context.Context, client testClient) (apidiscoveryv2.APIGroupDiscoveryList, error) {
532 result, err := client.
533 Discovery().
534 RESTClient().
535 Get().
536 AbsPath("/apis").
537 SetHeader("Accept", acceptV2JSON).
538 Do(ctx).
539 Raw()
540
541 if err != nil {
542 return apidiscoveryv2.APIGroupDiscoveryList{}, fmt.Errorf("failed to fetch v2 discovery: %w", err)
543 }
544
545 groupList := apidiscoveryv2.APIGroupDiscoveryList{}
546 err = json.Unmarshal(result, &groupList)
547 if err != nil {
548 return apidiscoveryv2.APIGroupDiscoveryList{}, fmt.Errorf("failed to parse v2 discovery: %w", err)
549 }
550
551 return groupList, nil
552 }
553
554 func FetchV2Beta1Discovery(ctx context.Context, client testClient) (apidiscoveryv2beta1.APIGroupDiscoveryList, error) {
555 result, err := client.
556 Discovery().
557 RESTClient().
558 Get().
559 AbsPath("/apis").
560 SetHeader("Accept", acceptV2Beta1JSON).
561 Do(ctx).
562 Raw()
563
564 if err != nil {
565 return apidiscoveryv2beta1.APIGroupDiscoveryList{}, fmt.Errorf("failed to fetch v2 discovery: %w", err)
566 }
567
568 groupList := apidiscoveryv2beta1.APIGroupDiscoveryList{}
569 err = json.Unmarshal(result, &groupList)
570 if err != nil {
571 return apidiscoveryv2beta1.APIGroupDiscoveryList{}, fmt.Errorf("failed to parse v2 discovery: %w", err)
572 }
573
574 return groupList, nil
575 }
576
577 func FetchV1DiscoveryGroups(ctx context.Context, client testClient) (metav1.APIGroupList, error) {
578 return FetchV1DiscoveryGroupsAtPath(ctx, client, "/apis")
579 }
580
581 func FetchV1DiscoveryLegacyGroups(ctx context.Context, client testClient) (metav1.APIGroupList, error) {
582 return FetchV1DiscoveryGroupsAtPath(ctx, client, "/api")
583 }
584
585 func FetchV1DiscoveryGroupsAtPath(ctx context.Context, client testClient, path string) (metav1.APIGroupList, error) {
586 result, err := client.
587 Discovery().
588 RESTClient().
589 Get().
590 AbsPath(path).
591 SetHeader("Accept", acceptV1JSON).
592 Do(ctx).
593 Raw()
594
595 if err != nil {
596 return metav1.APIGroupList{}, fmt.Errorf("failed to fetch v1 discovery at %v: %w", path, err)
597 }
598
599 groupList := metav1.APIGroupList{}
600 err = json.Unmarshal(result, &groupList)
601 if err != nil {
602 return metav1.APIGroupList{}, fmt.Errorf("failed to parse v1 discovery at %v: %w", path, err)
603 }
604
605 return groupList, nil
606 }
607
608 func FetchV1DiscoveryResource(ctx context.Context, client testClient, gv metav1.GroupVersion) (metav1.APIResourceList, error) {
609 result, err := client.
610 Discovery().
611 RESTClient().
612 Get().
613 AbsPath("/apis/"+gv.Group+"/"+gv.Version).
614 SetHeader("Accept", acceptV1JSON).
615 Do(ctx).
616 Raw()
617
618 if err != nil {
619 return metav1.APIResourceList{}, err
620 }
621
622 groupList := metav1.APIResourceList{}
623 err = json.Unmarshal(result, &groupList)
624 if err != nil {
625 return metav1.APIResourceList{}, err
626 }
627
628 return groupList, nil
629 }
630
631 func WaitForGroupsAbsent(ctx context.Context, client testClient, groups ...string) error {
632 return WaitForResultWithCondition(ctx, client, func(groupList apidiscoveryv2.APIGroupDiscoveryList) bool {
633 for _, searchGroup := range groups {
634 for _, docGroup := range groupList.Items {
635 if docGroup.Name == searchGroup {
636 return false
637 }
638 }
639 }
640 return true
641 })
642
643 }
644
645 func WaitForRootPaths(t *testing.T, ctx context.Context, client testClient, requirePaths, forbidPaths sets.Set[string]) error {
646 return wait.PollUntilContextTimeout(ctx, 250*time.Millisecond, maxTimeout, true, func(ctx context.Context) (done bool, err error) {
647 statusContent, err := client.Discovery().RESTClient().Get().AbsPath("/").SetHeader("Accept", "application/json").DoRaw(ctx)
648 if err != nil {
649 return false, err
650 }
651 rootPaths := metav1.RootPaths{}
652 if err := json.Unmarshal(statusContent, &rootPaths); err != nil {
653 return false, err
654 }
655 paths := sets.New(rootPaths.Paths...)
656 if missing := requirePaths.Difference(paths); len(missing) > 0 {
657 t.Logf("missing required root paths %v", sets.List(missing))
658 return false, nil
659 }
660 if present := forbidPaths.Intersection(paths); len(present) > 0 {
661 t.Logf("present forbidden root paths %v", sets.List(present))
662 return false, nil
663 }
664 return true, nil
665 })
666 }
667
668 func WaitForGroups(ctx context.Context, client testClient, groups ...apidiscoveryv2.APIGroupDiscovery) error {
669 return WaitForResultWithCondition(ctx, client, func(groupList apidiscoveryv2.APIGroupDiscoveryList) bool {
670 for _, searchGroup := range groups {
671 found := false
672 for _, docGroup := range groupList.Items {
673 if reflect.DeepEqual(searchGroup, docGroup) {
674 found = true
675 break
676 }
677 }
678 if !found {
679 return false
680 }
681 }
682 return true
683 })
684 }
685
686 func WaitForResultWithCondition(ctx context.Context, client testClient, condition func(result apidiscoveryv2.APIGroupDiscoveryList) bool) error {
687
688
689 return wait.PollUntilContextTimeout(
690 ctx,
691 250*time.Millisecond,
692 maxTimeout,
693 true,
694 func(ctx context.Context) (done bool, err error) {
695 groupList, err := FetchV2Discovery(ctx, client)
696 if err != nil {
697 return false, err
698 }
699
700 if condition(groupList) {
701 return true, nil
702 }
703
704 return false, nil
705 })
706 }
707
708 func WaitForV2Beta1ResultWithCondition(ctx context.Context, client testClient, condition func(result apidiscoveryv2beta1.APIGroupDiscoveryList) bool) error {
709
710
711 return wait.PollUntilContextTimeout(
712 ctx,
713 250*time.Millisecond,
714 maxTimeout,
715 true,
716 func(ctx context.Context) (done bool, err error) {
717 groupList, err := FetchV2Beta1Discovery(ctx, client)
718 if err != nil {
719 return false, err
720 }
721
722 if condition(groupList) {
723 return true, nil
724 }
725
726 return false, nil
727 })
728 }
729
730 func WaitForV1GroupsWithCondition(ctx context.Context, client testClient, condition func(result metav1.APIGroupList) bool) error {
731
732
733 return wait.PollUntilContextTimeout(
734 ctx,
735 250*time.Millisecond,
736 maxTimeout,
737 true,
738 func(ctx context.Context) (done bool, err error) {
739 groupList, err := FetchV1DiscoveryGroups(ctx, client)
740
741 if err != nil {
742 return false, err
743 }
744
745 if condition(groupList) {
746 return true, nil
747 }
748
749 return false, nil
750 })
751 }
752
753 func WaitForV1ResourcesWithCondition(ctx context.Context, client testClient, gv metav1.GroupVersion, condition func(result metav1.APIResourceList) bool) error {
754
755
756 return wait.PollUntilContextTimeout(
757 ctx,
758 250*time.Millisecond,
759 maxTimeout,
760 true,
761 func(ctx context.Context) (done bool, err error) {
762 resourceList, err := FetchV1DiscoveryResource(ctx, client, gv)
763
764 if err != nil {
765 return false, err
766 }
767
768 if condition(resourceList) {
769 return true, nil
770 }
771
772 return false, nil
773 })
774 }
775
776 func FindGroupVersionV1(discovery metav1.APIGroupList, gv metav1.GroupVersion) bool {
777 for _, documentGroup := range discovery.Groups {
778 if documentGroup.Name != gv.Group {
779 continue
780 }
781
782 for _, documentVersion := range documentGroup.Versions {
783 if documentVersion.Version == gv.Version {
784 return true
785 }
786 }
787 }
788
789 return false
790 }
791
792 func FindGroupVersionV2(discovery apidiscoveryv2.APIGroupDiscoveryList, gv metav1.GroupVersion) *apidiscoveryv2.APIVersionDiscovery {
793 for _, documentGroup := range discovery.Items {
794 if documentGroup.Name != gv.Group {
795 continue
796 }
797
798 for _, documentVersion := range documentGroup.Versions {
799 if documentVersion.Version == gv.Version {
800 return &documentVersion
801 }
802 }
803 }
804
805 return nil
806 }
807
808 func FindGroupVersionV2Beta1(discovery apidiscoveryv2beta1.APIGroupDiscoveryList, gv metav1.GroupVersion) *apidiscoveryv2beta1.APIVersionDiscovery {
809 for _, documentGroup := range discovery.Items {
810 if documentGroup.Name != gv.Group {
811 continue
812 }
813
814 for _, documentVersion := range documentGroup.Versions {
815 if documentVersion.Version == gv.Version {
816 return &documentVersion
817 }
818 }
819 }
820
821 return nil
822 }
823
View as plain text