1
16
17 package memory
18
19 import (
20 "encoding/json"
21 "errors"
22 "fmt"
23 "net/http"
24 "net/http/httptest"
25 "reflect"
26 "sync"
27 "testing"
28
29 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
31 apidiscovery "k8s.io/api/apidiscovery/v2"
32 errorsutil "k8s.io/apimachinery/pkg/api/errors"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/runtime"
35 "k8s.io/apimachinery/pkg/runtime/schema"
36 "k8s.io/apimachinery/pkg/util/sets"
37 "k8s.io/client-go/discovery"
38 "k8s.io/client-go/discovery/fake"
39 "k8s.io/client-go/openapi"
40 "k8s.io/client-go/rest"
41 testutil "k8s.io/client-go/util/testing"
42 )
43
44 type resourceMapEntry struct {
45 list *metav1.APIResourceList
46 err error
47 }
48
49 type fakeDiscovery struct {
50 *fake.FakeDiscovery
51
52 lock sync.Mutex
53 groupList *metav1.APIGroupList
54 groupListErr error
55 resourceMap map[string]*resourceMapEntry
56 }
57
58 func (c *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
59 c.lock.Lock()
60 defer c.lock.Unlock()
61 if rl, ok := c.resourceMap[groupVersion]; ok {
62 return rl.list, rl.err
63 }
64 return nil, errors.New("doesn't exist")
65 }
66
67 func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
68 c.lock.Lock()
69 defer c.lock.Unlock()
70 if c.groupList == nil {
71 return nil, errors.New("doesn't exist")
72 }
73 return c.groupList, c.groupListErr
74 }
75
76 func TestClient(t *testing.T) {
77 fake := &fakeDiscovery{
78 groupList: &metav1.APIGroupList{
79 Groups: []metav1.APIGroup{{
80 Name: "astronomy",
81 Versions: []metav1.GroupVersionForDiscovery{{
82 GroupVersion: "astronomy/v8beta1",
83 Version: "v8beta1",
84 }},
85 }},
86 },
87 resourceMap: map[string]*resourceMapEntry{
88 "astronomy/v8beta1": {
89 list: &metav1.APIResourceList{
90 GroupVersion: "astronomy/v8beta1",
91 APIResources: []metav1.APIResource{{
92 Name: "dwarfplanets",
93 SingularName: "dwarfplanet",
94 Namespaced: true,
95 Kind: "DwarfPlanet",
96 ShortNames: []string{"dp"},
97 }},
98 },
99 },
100 },
101 }
102
103 c := NewMemCacheClient(fake)
104 if c.Fresh() {
105 t.Errorf("Expected not fresh.")
106 }
107 g, err := c.ServerGroups()
108 if err != nil {
109 t.Errorf("Unexpected error: %v", err)
110 }
111 if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) {
112 t.Errorf("Expected %#v, got %#v", e, a)
113 }
114 if !c.Fresh() {
115 t.Errorf("Expected fresh.")
116 }
117 c.Invalidate()
118 if c.Fresh() {
119 t.Errorf("Expected not fresh.")
120 }
121
122 g, err = c.ServerGroups()
123 if err != nil {
124 t.Errorf("Unexpected error: %v", err)
125 }
126 if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) {
127 t.Errorf("Expected %#v, got %#v", e, a)
128 }
129 if !c.Fresh() {
130 t.Errorf("Expected fresh.")
131 }
132 r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
133 if err != nil {
134 t.Errorf("Unexpected error: %v", err)
135 }
136 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
137 t.Errorf("Expected %#v, got %#v", e, a)
138 }
139
140 fake.lock.Lock()
141 fake.resourceMap = map[string]*resourceMapEntry{
142 "astronomy/v8beta1": {
143 list: &metav1.APIResourceList{
144 GroupVersion: "astronomy/v8beta1",
145 APIResources: []metav1.APIResource{{
146 Name: "stars",
147 SingularName: "star",
148 Namespaced: true,
149 Kind: "Star",
150 ShortNames: []string{"s"},
151 }},
152 },
153 },
154 }
155 fake.lock.Unlock()
156
157 c.Invalidate()
158 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
159 if err != nil {
160 t.Errorf("Unexpected error: %v", err)
161 }
162 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
163 t.Errorf("Expected %#v, got %#v", e, a)
164 }
165 }
166
167 func TestServerGroupsFails(t *testing.T) {
168 fake := &fakeDiscovery{
169 groupList: &metav1.APIGroupList{
170 Groups: []metav1.APIGroup{{
171 Name: "astronomy",
172 Versions: []metav1.GroupVersionForDiscovery{{
173 GroupVersion: "astronomy/v8beta1",
174 Version: "v8beta1",
175 }},
176 }},
177 },
178 groupListErr: errors.New("some error"),
179 resourceMap: map[string]*resourceMapEntry{
180 "astronomy/v8beta1": {
181 list: &metav1.APIResourceList{
182 GroupVersion: "astronomy/v8beta1",
183 APIResources: []metav1.APIResource{{
184 Name: "dwarfplanets",
185 SingularName: "dwarfplanet",
186 Namespaced: true,
187 Kind: "DwarfPlanet",
188 ShortNames: []string{"dp"},
189 }},
190 },
191 },
192 },
193 }
194
195 c := NewMemCacheClient(fake)
196 if c.Fresh() {
197 t.Errorf("Expected not fresh.")
198 }
199 _, err := c.ServerGroups()
200 if err == nil {
201 t.Errorf("Expected error")
202 }
203 if c.Fresh() {
204 t.Errorf("Expected not fresh.")
205 }
206 fake.lock.Lock()
207 fake.groupListErr = nil
208 fake.lock.Unlock()
209 r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
210 if err != nil {
211 t.Errorf("Unexpected error: %v", err)
212 }
213 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
214 t.Errorf("Expected %#v, got %#v", e, a)
215 }
216 if !c.Fresh() {
217 t.Errorf("Expected not fresh.")
218 }
219 }
220
221 func TestPartialPermanentFailure(t *testing.T) {
222 fake := &fakeDiscovery{
223 groupList: &metav1.APIGroupList{
224 Groups: []metav1.APIGroup{
225 {
226 Name: "astronomy",
227 Versions: []metav1.GroupVersionForDiscovery{{
228 GroupVersion: "astronomy/v8beta1",
229 Version: "v8beta1",
230 }},
231 },
232 {
233 Name: "astronomy2",
234 Versions: []metav1.GroupVersionForDiscovery{{
235 GroupVersion: "astronomy2/v8beta1",
236 Version: "v8beta1",
237 }},
238 },
239 },
240 },
241 resourceMap: map[string]*resourceMapEntry{
242 "astronomy/v8beta1": {
243 err: errors.New("some permanent error"),
244 },
245 "astronomy2/v8beta1": {
246 list: &metav1.APIResourceList{
247 GroupVersion: "astronomy2/v8beta1",
248 APIResources: []metav1.APIResource{{
249 Name: "dwarfplanets",
250 SingularName: "dwarfplanet",
251 Namespaced: true,
252 Kind: "DwarfPlanet",
253 ShortNames: []string{"dp"},
254 }},
255 },
256 },
257 },
258 }
259
260 c := NewMemCacheClient(fake)
261 if c.Fresh() {
262 t.Errorf("Expected not fresh.")
263 }
264 r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1")
265 if err != nil {
266 t.Errorf("Unexpected error: %v", err)
267 }
268 if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
269 t.Errorf("Expected %#v, got %#v", e, a)
270 }
271 _, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
272 if err == nil {
273 t.Errorf("Expected error, got nil")
274 }
275
276 fake.lock.Lock()
277 fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{
278 list: &metav1.APIResourceList{
279 GroupVersion: "astronomy/v8beta1",
280 APIResources: []metav1.APIResource{{
281 Name: "dwarfplanets",
282 SingularName: "dwarfplanet",
283 Namespaced: true,
284 Kind: "DwarfPlanet",
285 ShortNames: []string{"dp"},
286 }},
287 },
288 err: nil,
289 }
290 fake.lock.Unlock()
291
292 _, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
293 if err == nil {
294 t.Errorf("Expected error, got nil")
295 }
296 c.Invalidate()
297
298
299 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
300 if err != nil {
301 t.Errorf("Unexpected error: %v", err)
302 }
303 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
304 t.Errorf("Expected %#v, got %#v", e, a)
305 }
306 }
307
308 func TestPartialRetryableFailure(t *testing.T) {
309 fake := &fakeDiscovery{
310 groupList: &metav1.APIGroupList{
311 Groups: []metav1.APIGroup{
312 {
313 Name: "astronomy",
314 Versions: []metav1.GroupVersionForDiscovery{{
315 GroupVersion: "astronomy/v8beta1",
316 Version: "v8beta1",
317 }},
318 },
319 {
320 Name: "astronomy2",
321 Versions: []metav1.GroupVersionForDiscovery{{
322 GroupVersion: "astronomy2/v8beta1",
323 Version: "v8beta1",
324 }},
325 },
326 },
327 },
328 resourceMap: map[string]*resourceMapEntry{
329 "astronomy/v8beta1": {
330 err: &errorsutil.StatusError{
331 ErrStatus: metav1.Status{
332 Message: "Some retryable error",
333 Code: int32(http.StatusServiceUnavailable),
334 Reason: metav1.StatusReasonServiceUnavailable,
335 },
336 },
337 },
338 "astronomy2/v8beta1": {
339 list: &metav1.APIResourceList{
340 GroupVersion: "astronomy2/v8beta1",
341 APIResources: []metav1.APIResource{{
342 Name: "dwarfplanets",
343 SingularName: "dwarfplanet",
344 Namespaced: true,
345 Kind: "DwarfPlanet",
346 ShortNames: []string{"dp"},
347 }},
348 },
349 },
350 },
351 }
352
353 c := NewMemCacheClient(fake)
354 if c.Fresh() {
355 t.Errorf("Expected not fresh.")
356 }
357 r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1")
358 if err != nil {
359 t.Errorf("Unexpected error: %v", err)
360 }
361 if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
362 t.Errorf("Expected %#v, got %#v", e, a)
363 }
364 _, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
365 if err == nil {
366 t.Errorf("Expected error, got nil")
367 }
368
369 fake.lock.Lock()
370 fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{
371 list: &metav1.APIResourceList{
372 GroupVersion: "astronomy/v8beta1",
373 APIResources: []metav1.APIResource{{
374 Name: "dwarfplanets",
375 SingularName: "dwarfplanet",
376 Namespaced: true,
377 Kind: "DwarfPlanet",
378 ShortNames: []string{"dp"},
379 }},
380 },
381 err: nil,
382 }
383 fake.lock.Unlock()
384
385
386 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
387 if err != nil {
388 t.Errorf("Expected no error, got %v", err)
389 }
390 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
391 t.Errorf("Expected %#v, got %#v", e, a)
392 }
393
394
395 fake.lock.Lock()
396 fake.resourceMap["astronomy/v8beta1"].err = errors.New("some permanent error")
397 fake.lock.Unlock()
398 r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
399 if err != nil {
400 t.Errorf("Expected no error, got %v", err)
401 }
402 if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
403 t.Errorf("Expected %#v, got %#v", e, a)
404 }
405 }
406
407
408
409 func TestOpenAPIMemCache(t *testing.T) {
410 fakeServer, err := testutil.NewFakeOpenAPIV3Server("../../testdata")
411 require.NoError(t, err)
412 defer fakeServer.HttpServer.Close()
413
414 require.Greater(t, len(fakeServer.ServedDocuments), 0)
415
416 client := NewMemCacheClient(
417 discovery.NewDiscoveryClientForConfigOrDie(
418 &rest.Config{Host: fakeServer.HttpServer.URL},
419 ),
420 )
421 openapiClient := client.OpenAPIV3()
422
423 paths, err := openapiClient.Paths()
424 require.NoError(t, err)
425
426 contentTypes := []string{
427 runtime.ContentTypeJSON, openapi.ContentTypeOpenAPIV3PB,
428 }
429
430 for _, contentType := range contentTypes {
431 t.Run(contentType, func(t *testing.T) {
432 for k, v := range paths {
433 original, err := v.Schema(contentType)
434 if !assert.NoError(t, err) {
435 continue
436 }
437
438 pathsAgain, err := openapiClient.Paths()
439 if !assert.NoError(t, err) {
440 continue
441 }
442
443 schemaAgain, err := pathsAgain[k].Schema(contentType)
444 if !assert.NoError(t, err) {
445 continue
446 }
447
448 assert.True(t, reflect.ValueOf(paths).Pointer() == reflect.ValueOf(pathsAgain).Pointer())
449 assert.True(t, reflect.ValueOf(original).Pointer() == reflect.ValueOf(schemaAgain).Pointer())
450
451
452 client.Invalidate()
453
454 pathsAgain, err = client.OpenAPIV3().Paths()
455 if !assert.NoError(t, err) {
456 continue
457 }
458
459 schemaAgain, err = pathsAgain[k].Schema(contentType)
460 if !assert.NoError(t, err) {
461 continue
462 }
463
464 assert.True(t, reflect.ValueOf(paths).Pointer() != reflect.ValueOf(pathsAgain).Pointer())
465 assert.True(t, reflect.ValueOf(original).Pointer() != reflect.ValueOf(schemaAgain).Pointer())
466 assert.Equal(t, original, schemaAgain)
467 }
468 })
469 }
470 }
471
472
473 func TestMemCacheGroupsAndMaybeResources(t *testing.T) {
474 tests := []struct {
475 name string
476 corev1 *metav1.APIVersions
477 apis *metav1.APIGroupList
478 expectedGroupNames []string
479 expectedGroupVersions []string
480 }{
481 {
482 name: "Legacy discovery format: 1 version at /api, 1 group at /apis",
483 corev1: &metav1.APIVersions{
484 Versions: []string{
485 "v1",
486 },
487 },
488 apis: &metav1.APIGroupList{
489 Groups: []metav1.APIGroup{
490 {
491 Name: "extensions",
492 Versions: []metav1.GroupVersionForDiscovery{
493 {GroupVersion: "extensions/v1beta1"},
494 },
495 },
496 },
497 },
498 expectedGroupNames: []string{"", "extensions"},
499 expectedGroupVersions: []string{"v1", "extensions/v1beta1"},
500 },
501 {
502 name: "Legacy discovery format: 1 version at /api, 2 groups/1 version at /apis",
503 corev1: &metav1.APIVersions{
504 Versions: []string{
505 "v1",
506 },
507 },
508 apis: &metav1.APIGroupList{
509 Groups: []metav1.APIGroup{
510 {
511 Name: "apps",
512 Versions: []metav1.GroupVersionForDiscovery{
513 {GroupVersion: "apps/v1"},
514 },
515 },
516 {
517 Name: "extensions",
518 Versions: []metav1.GroupVersionForDiscovery{
519 {GroupVersion: "extensions/v1beta1"},
520 },
521 },
522 },
523 },
524 expectedGroupNames: []string{"", "apps", "extensions"},
525 expectedGroupVersions: []string{"v1", "apps/v1", "extensions/v1beta1"},
526 },
527 {
528 name: "Legacy discovery format: 1 version at /api, 2 groups/2 versions at /apis",
529 corev1: &metav1.APIVersions{
530 Versions: []string{
531 "v1",
532 },
533 },
534 apis: &metav1.APIGroupList{
535 Groups: []metav1.APIGroup{
536 {
537 Name: "batch",
538 Versions: []metav1.GroupVersionForDiscovery{
539 {GroupVersion: "batch/v1"},
540 },
541 },
542 {
543 Name: "batch",
544 Versions: []metav1.GroupVersionForDiscovery{
545 {GroupVersion: "batch/v1beta1"},
546 },
547 },
548 {
549 Name: "extensions",
550 Versions: []metav1.GroupVersionForDiscovery{
551 {GroupVersion: "extensions/v1beta1"},
552 },
553 },
554 {
555 Name: "extensions",
556 Versions: []metav1.GroupVersionForDiscovery{
557 {GroupVersion: "extensions/v1alpha1"},
558 },
559 },
560 },
561 },
562 expectedGroupNames: []string{
563 "",
564 "batch",
565 "extensions",
566 },
567 expectedGroupVersions: []string{
568 "v1",
569 "batch/v1",
570 "batch/v1beta1",
571 "extensions/v1beta1",
572 "extensions/v1alpha1",
573 },
574 },
575 }
576
577 for _, test := range tests {
578 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
579 var body interface{}
580 switch req.URL.Path {
581 case "/api":
582 body = test.corev1
583 case "/apis":
584 body = test.apis
585 default:
586 w.WriteHeader(http.StatusNotFound)
587 return
588 }
589 output, err := json.Marshal(body)
590 require.NoError(t, err)
591
592 w.Header().Set("Content-Type", discovery.AcceptV1)
593 w.WriteHeader(http.StatusOK)
594 w.Write(output)
595 }))
596 defer server.Close()
597 client := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: server.URL})
598 memClient := memCacheClient{
599 delegate: client,
600 groupToServerResources: map[string]*cacheEntry{},
601 }
602 assert.False(t, memClient.Fresh())
603 apiGroupList, resourcesMap, failedGVs, err := memClient.GroupsAndMaybeResources()
604 require.NoError(t, err)
605
606 assert.Nil(t, resourcesMap)
607 assert.True(t, len(failedGVs) == 0, "expected empty failed GroupVersions, got (%d)", len(failedGVs))
608 assert.False(t, memClient.receivedAggregatedDiscovery)
609 assert.True(t, memClient.Fresh())
610
611 expectedGroupNames := sets.NewString(test.expectedGroupNames...)
612 actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...)
613 assert.True(t, expectedGroupNames.Equal(actualGroupNames),
614 "%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
615
616 expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
617 actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...)
618 assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
619 "%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
620
621 memClient.Invalidate()
622 assert.False(t, memClient.Fresh())
623 apiGroupList, resourcesMap, _, err = memClient.GroupsAndMaybeResources()
624 require.NoError(t, err)
625 assert.Nil(t, resourcesMap)
626 assert.False(t, memClient.receivedAggregatedDiscovery)
627
628 actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...)
629 assert.True(t, expectedGroupNames.Equal(actualGroupNames),
630 "%s: Expected after invalidation groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
631 }
632 }
633
634
635 func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
636 tests := []struct {
637 name string
638 corev1 *apidiscovery.APIGroupDiscoveryList
639 apis *apidiscovery.APIGroupDiscoveryList
640 expectedGroupNames []string
641 expectedGroupVersions []string
642 expectedGVKs []string
643 expectedFailedGVs []string
644 }{
645 {
646 name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/1 resources at /apis",
647 corev1: &apidiscovery.APIGroupDiscoveryList{
648 Items: []apidiscovery.APIGroupDiscovery{
649 {
650 Versions: []apidiscovery.APIVersionDiscovery{
651 {
652 Version: "v1",
653 Resources: []apidiscovery.APIResourceDiscovery{
654 {
655 Resource: "pods",
656 ResponseKind: &metav1.GroupVersionKind{
657 Group: "",
658 Version: "v1",
659 Kind: "Pod",
660 },
661 Scope: apidiscovery.ScopeNamespace,
662 },
663 },
664 },
665 },
666 },
667 },
668 },
669 apis: &apidiscovery.APIGroupDiscoveryList{
670 Items: []apidiscovery.APIGroupDiscovery{
671 {
672 ObjectMeta: metav1.ObjectMeta{
673 Name: "apps",
674 },
675 Versions: []apidiscovery.APIVersionDiscovery{
676 {
677 Version: "v1",
678 Resources: []apidiscovery.APIResourceDiscovery{
679 {
680 Resource: "deployments",
681 ResponseKind: &metav1.GroupVersionKind{
682 Group: "apps",
683 Version: "v1",
684 Kind: "Deployment",
685 },
686 Scope: apidiscovery.ScopeNamespace,
687 },
688 },
689 },
690 },
691 },
692 },
693 },
694 expectedGroupNames: []string{"", "apps"},
695 expectedGroupVersions: []string{"v1", "apps/v1"},
696 expectedGVKs: []string{
697 "/v1/Pod",
698 "apps/v1/Deployment",
699 },
700 expectedFailedGVs: []string{},
701 },
702 {
703 name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources/1 stale GV at /apis",
704 corev1: &apidiscovery.APIGroupDiscoveryList{
705 Items: []apidiscovery.APIGroupDiscovery{
706 {
707 Versions: []apidiscovery.APIVersionDiscovery{
708 {
709 Version: "v1",
710 Resources: []apidiscovery.APIResourceDiscovery{
711 {
712 Resource: "pods",
713 ResponseKind: &metav1.GroupVersionKind{
714 Group: "",
715 Version: "v1",
716 Kind: "Pod",
717 },
718 Scope: apidiscovery.ScopeNamespace,
719 },
720 },
721 },
722 },
723 },
724 },
725 },
726 apis: &apidiscovery.APIGroupDiscoveryList{
727 Items: []apidiscovery.APIGroupDiscovery{
728 {
729 ObjectMeta: metav1.ObjectMeta{
730 Name: "apps",
731 },
732 Versions: []apidiscovery.APIVersionDiscovery{
733 {
734 Version: "v1",
735 Resources: []apidiscovery.APIResourceDiscovery{
736 {
737 Resource: "deployments",
738 ResponseKind: &metav1.GroupVersionKind{
739 Group: "apps",
740 Version: "v1",
741 Kind: "Deployment",
742 },
743 Scope: apidiscovery.ScopeNamespace,
744 },
745 },
746 },
747 {
748
749 Version: "v2",
750 Resources: []apidiscovery.APIResourceDiscovery{
751 {
752 Resource: "deployments",
753 ResponseKind: &metav1.GroupVersionKind{
754 Group: "apps",
755 Version: "v2",
756 Kind: "Deployment",
757 },
758 Scope: apidiscovery.ScopeNamespace,
759 },
760 },
761 Freshness: apidiscovery.DiscoveryFreshnessStale,
762 },
763 },
764 },
765 },
766 },
767 expectedGroupNames: []string{"", "apps"},
768 expectedGroupVersions: []string{"v1", "apps/v1"},
769 expectedGVKs: []string{
770 "/v1/Pod",
771 "apps/v1/Deployment",
772 },
773 expectedFailedGVs: []string{"apps/v2"},
774 },
775 {
776 name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis",
777 corev1: &apidiscovery.APIGroupDiscoveryList{
778 Items: []apidiscovery.APIGroupDiscovery{
779 {
780 Versions: []apidiscovery.APIVersionDiscovery{
781 {
782 Version: "v1",
783 Resources: []apidiscovery.APIResourceDiscovery{
784 {
785 Resource: "pods",
786 ResponseKind: &metav1.GroupVersionKind{
787 Group: "",
788 Version: "v1",
789 Kind: "Pod",
790 },
791 Scope: apidiscovery.ScopeNamespace,
792 },
793 {
794 Resource: "services",
795 ResponseKind: &metav1.GroupVersionKind{
796 Group: "",
797 Version: "v1",
798 Kind: "Service",
799 },
800 Scope: apidiscovery.ScopeNamespace,
801 },
802 },
803 },
804 },
805 },
806 },
807 },
808 apis: &apidiscovery.APIGroupDiscoveryList{
809 Items: []apidiscovery.APIGroupDiscovery{
810 {
811 ObjectMeta: metav1.ObjectMeta{
812 Name: "apps",
813 },
814 Versions: []apidiscovery.APIVersionDiscovery{
815 {
816 Version: "v1",
817 Resources: []apidiscovery.APIResourceDiscovery{
818 {
819 Resource: "deployments",
820 ResponseKind: &metav1.GroupVersionKind{
821 Group: "apps",
822 Version: "v1",
823 Kind: "Deployment",
824 },
825 Scope: apidiscovery.ScopeNamespace,
826 },
827 {
828 Resource: "statefulsets",
829 ResponseKind: &metav1.GroupVersionKind{
830 Group: "apps",
831 Version: "v1",
832 Kind: "StatefulSet",
833 },
834 Scope: apidiscovery.ScopeNamespace,
835 },
836 },
837 },
838 },
839 },
840 },
841 },
842 expectedGroupNames: []string{"", "apps"},
843 expectedGroupVersions: []string{"v1", "apps/v1"},
844 expectedGVKs: []string{
845 "/v1/Pod",
846 "/v1/Service",
847 "apps/v1/Deployment",
848 "apps/v1/StatefulSet",
849 },
850 expectedFailedGVs: []string{},
851 },
852 {
853 name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources/1 stale GV at /apis",
854 corev1: &apidiscovery.APIGroupDiscoveryList{
855 Items: []apidiscovery.APIGroupDiscovery{
856 {
857 Versions: []apidiscovery.APIVersionDiscovery{
858 {
859 Version: "v1",
860 Resources: []apidiscovery.APIResourceDiscovery{
861 {
862 Resource: "pods",
863 ResponseKind: &metav1.GroupVersionKind{
864 Group: "",
865 Version: "v1",
866 Kind: "Pod",
867 },
868 Scope: apidiscovery.ScopeNamespace,
869 },
870 {
871 Resource: "services",
872 ResponseKind: &metav1.GroupVersionKind{
873 Group: "",
874 Version: "v1",
875 Kind: "Service",
876 },
877 Scope: apidiscovery.ScopeNamespace,
878 },
879 },
880 },
881 },
882 },
883 },
884 },
885 apis: &apidiscovery.APIGroupDiscoveryList{
886 Items: []apidiscovery.APIGroupDiscovery{
887 {
888 ObjectMeta: metav1.ObjectMeta{
889 Name: "apps",
890 },
891 Versions: []apidiscovery.APIVersionDiscovery{
892 {
893 Version: "v1",
894 Resources: []apidiscovery.APIResourceDiscovery{
895 {
896 Resource: "deployments",
897 ResponseKind: &metav1.GroupVersionKind{
898 Group: "apps",
899 Version: "v1",
900 Kind: "Deployment",
901 },
902 Scope: apidiscovery.ScopeNamespace,
903 },
904 {
905 Resource: "statefulsets",
906 ResponseKind: &metav1.GroupVersionKind{
907 Group: "apps",
908 Version: "v1",
909 Kind: "StatefulSet",
910 },
911 Scope: apidiscovery.ScopeNamespace,
912 },
913 },
914 },
915 {
916
917 Version: "v1beta1",
918 Resources: []apidiscovery.APIResourceDiscovery{
919 {
920 Resource: "deployments",
921 ResponseKind: &metav1.GroupVersionKind{
922 Group: "apps",
923 Version: "v1beta1",
924 Kind: "Deployment",
925 },
926 Scope: apidiscovery.ScopeNamespace,
927 },
928 {
929 Resource: "statefulsets",
930 ResponseKind: &metav1.GroupVersionKind{
931 Group: "apps",
932 Version: "v1beta1",
933 Kind: "StatefulSet",
934 },
935 Scope: apidiscovery.ScopeNamespace,
936 },
937 },
938 Freshness: apidiscovery.DiscoveryFreshnessStale,
939 },
940 },
941 },
942 {
943 ObjectMeta: metav1.ObjectMeta{
944 Name: "batch",
945 },
946 Versions: []apidiscovery.APIVersionDiscovery{
947 {
948 Version: "v1",
949 Resources: []apidiscovery.APIResourceDiscovery{
950 {
951 Resource: "jobs",
952 ResponseKind: &metav1.GroupVersionKind{
953 Group: "batch",
954 Version: "v1",
955 Kind: "Job",
956 },
957 Scope: apidiscovery.ScopeNamespace,
958 },
959 {
960 Resource: "cronjobs",
961 ResponseKind: &metav1.GroupVersionKind{
962 Group: "batch",
963 Version: "v1",
964 Kind: "CronJob",
965 },
966 Scope: apidiscovery.ScopeNamespace,
967 },
968 },
969 },
970 },
971 },
972 },
973 },
974 expectedGroupNames: []string{"", "apps", "batch"},
975 expectedGroupVersions: []string{"v1", "apps/v1", "batch/v1"},
976 expectedGVKs: []string{
977 "/v1/Pod",
978 "/v1/Service",
979 "apps/v1/Deployment",
980 "apps/v1/StatefulSet",
981 "batch/v1/Job",
982 "batch/v1/CronJob",
983 },
984 expectedFailedGVs: []string{"apps/v1beta1"},
985 },
986 {
987 name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources/2 stale GV at /apis",
988 corev1: &apidiscovery.APIGroupDiscoveryList{},
989 apis: &apidiscovery.APIGroupDiscoveryList{
990 Items: []apidiscovery.APIGroupDiscovery{
991 {
992 ObjectMeta: metav1.ObjectMeta{
993 Name: "apps",
994 },
995 Versions: []apidiscovery.APIVersionDiscovery{
996
997 {
998 Version: "v1",
999 Resources: []apidiscovery.APIResourceDiscovery{
1000 {
1001 Resource: "deployments",
1002 ResponseKind: &metav1.GroupVersionKind{
1003 Group: "apps",
1004 Version: "v1",
1005 Kind: "Deployment",
1006 },
1007 Scope: apidiscovery.ScopeNamespace,
1008 },
1009 {
1010 Resource: "statefulsets",
1011 ResponseKind: &metav1.GroupVersionKind{
1012 Group: "apps",
1013 Version: "v1",
1014 Kind: "StatefulSet",
1015 },
1016 Scope: apidiscovery.ScopeNamespace,
1017 },
1018 },
1019 Freshness: apidiscovery.DiscoveryFreshnessStale,
1020 },
1021 {
1022 Version: "v1beta1",
1023 Resources: []apidiscovery.APIResourceDiscovery{
1024 {
1025 Resource: "deployments",
1026 ResponseKind: &metav1.GroupVersionKind{
1027 Group: "apps",
1028 Version: "v1beta1",
1029 Kind: "Deployment",
1030 },
1031 Scope: apidiscovery.ScopeNamespace,
1032 },
1033 {
1034 Resource: "statefulsets",
1035 ResponseKind: &metav1.GroupVersionKind{
1036 Group: "apps",
1037 Version: "v1beta1",
1038 Kind: "StatefulSet",
1039 },
1040 Scope: apidiscovery.ScopeNamespace,
1041 },
1042 },
1043 },
1044 },
1045 },
1046 {
1047 ObjectMeta: metav1.ObjectMeta{
1048 Name: "batch",
1049 },
1050 Versions: []apidiscovery.APIVersionDiscovery{
1051 {
1052 Version: "v1",
1053 Resources: []apidiscovery.APIResourceDiscovery{
1054 {
1055 Resource: "jobs",
1056 ResponseKind: &metav1.GroupVersionKind{
1057 Group: "batch",
1058 Version: "v1",
1059 Kind: "Job",
1060 },
1061 Scope: apidiscovery.ScopeNamespace,
1062 },
1063 {
1064 Resource: "cronjobs",
1065 ResponseKind: &metav1.GroupVersionKind{
1066 Group: "batch",
1067 Version: "v1",
1068 Kind: "CronJob",
1069 },
1070 Scope: apidiscovery.ScopeNamespace,
1071 },
1072 },
1073 },
1074 {
1075
1076 Version: "v1beta1",
1077 Resources: []apidiscovery.APIResourceDiscovery{
1078 {
1079 Resource: "jobs",
1080 ResponseKind: &metav1.GroupVersionKind{
1081 Group: "batch",
1082 Version: "v1beta1",
1083 Kind: "Job",
1084 },
1085 Scope: apidiscovery.ScopeNamespace,
1086 },
1087 },
1088 Freshness: apidiscovery.DiscoveryFreshnessStale,
1089 },
1090 },
1091 },
1092 },
1093 },
1094 expectedGroupNames: []string{"apps", "batch"},
1095 expectedGroupVersions: []string{"apps/v1beta1", "batch/v1"},
1096 expectedGVKs: []string{
1097 "apps/v1beta1/Deployment",
1098 "apps/v1beta1/StatefulSet",
1099 "batch/v1/Job",
1100 "batch/v1/CronJob",
1101 },
1102 expectedFailedGVs: []string{"apps/v1", "batch/v1beta1"},
1103 },
1104 }
1105
1106 for _, test := range tests {
1107 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
1108 var agg *apidiscovery.APIGroupDiscoveryList
1109 switch req.URL.Path {
1110 case "/api":
1111 agg = test.corev1
1112 case "/apis":
1113 agg = test.apis
1114 default:
1115 w.WriteHeader(http.StatusNotFound)
1116 return
1117 }
1118 output, err := json.Marshal(agg)
1119 require.NoError(t, err)
1120
1121 w.Header().Set("Content-Type", discovery.AcceptV2)
1122 w.WriteHeader(http.StatusOK)
1123 w.Write(output)
1124 }))
1125 defer server.Close()
1126 client := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: server.URL})
1127 memClient := memCacheClient{
1128 delegate: client,
1129 groupToServerResources: map[string]*cacheEntry{},
1130 }
1131 assert.False(t, memClient.Fresh())
1132 apiGroupList, resourcesMap, failedGVs, err := memClient.GroupsAndMaybeResources()
1133 require.NoError(t, err)
1134 assert.True(t, memClient.receivedAggregatedDiscovery)
1135 assert.True(t, memClient.Fresh())
1136
1137 expectedGroupNames := sets.NewString(test.expectedGroupNames...)
1138 actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...)
1139 assert.True(t, expectedGroupNames.Equal(actualGroupNames),
1140 "%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
1141
1142 expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
1143 actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...)
1144 assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
1145 "%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
1146
1147 expectedGVKs := sets.NewString(test.expectedGVKs...)
1148 resources := []*metav1.APIResourceList{}
1149 for _, resourceList := range resourcesMap {
1150 resources = append(resources, resourceList)
1151 }
1152 actualGVKs := sets.NewString(groupVersionKinds(resources)...)
1153 assert.True(t, expectedGVKs.Equal(actualGVKs),
1154 "%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List())
1155
1156 expectedFailedGVs := sets.NewString(test.expectedFailedGVs...)
1157 actualFailedGVs := sets.NewString(failedGroupVersions(failedGVs)...)
1158 assert.True(t, expectedFailedGVs.Equal(actualFailedGVs),
1159 "%s: Expected Failed GroupVersions (%s), got (%s)", test.name, expectedFailedGVs.List(), actualFailedGVs.List())
1160
1161 memClient.Invalidate()
1162 assert.False(t, memClient.Fresh())
1163 apiGroupList, _, _, err = memClient.GroupsAndMaybeResources()
1164
1165 require.NoError(t, err)
1166
1167 actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...)
1168 assert.True(t, expectedGroupNames.Equal(actualGroupNames),
1169 "%s: Expected after invalidation groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
1170 }
1171 }
1172
1173
1174 func TestMemCacheAggregatedServerGroups(t *testing.T) {
1175 tests := []struct {
1176 name string
1177 corev1 *apidiscovery.APIGroupDiscoveryList
1178 apis *apidiscovery.APIGroupDiscoveryList
1179 expectedGroupNames []string
1180 expectedGroupVersions []string
1181 expectedPreferredVersions []string
1182 }{
1183 {
1184 name: "Aggregated discovery: 1 group/1 version at /api, 1 group/1 version at /apis",
1185 corev1: &apidiscovery.APIGroupDiscoveryList{
1186 Items: []apidiscovery.APIGroupDiscovery{
1187 {
1188 Versions: []apidiscovery.APIVersionDiscovery{
1189 {
1190 Version: "v1",
1191 Resources: []apidiscovery.APIResourceDiscovery{
1192 {
1193 Resource: "pods",
1194 ResponseKind: &metav1.GroupVersionKind{
1195 Group: "",
1196 Version: "v1",
1197 Kind: "Pod",
1198 },
1199 Scope: apidiscovery.ScopeNamespace,
1200 },
1201 },
1202 },
1203 },
1204 },
1205 },
1206 },
1207 apis: &apidiscovery.APIGroupDiscoveryList{
1208 Items: []apidiscovery.APIGroupDiscovery{
1209 {
1210 ObjectMeta: metav1.ObjectMeta{
1211 Name: "apps",
1212 },
1213 Versions: []apidiscovery.APIVersionDiscovery{
1214 {
1215 Version: "v1",
1216 Resources: []apidiscovery.APIResourceDiscovery{
1217 {
1218 Resource: "deployments",
1219 ResponseKind: &metav1.GroupVersionKind{
1220 Group: "apps",
1221 Version: "v1",
1222 Kind: "Deployment",
1223 },
1224 Scope: apidiscovery.ScopeNamespace,
1225 },
1226 },
1227 },
1228 },
1229 },
1230 },
1231 },
1232 expectedGroupNames: []string{"", "apps"},
1233 expectedGroupVersions: []string{"v1", "apps/v1"},
1234 expectedPreferredVersions: []string{"v1", "apps/v1"},
1235 },
1236 {
1237 name: "Aggregated discovery: 1 group/1 version at /api, 1 group/2 versions at /apis",
1238 corev1: &apidiscovery.APIGroupDiscoveryList{
1239 Items: []apidiscovery.APIGroupDiscovery{
1240 {
1241 ObjectMeta: metav1.ObjectMeta{
1242 Name: "",
1243 },
1244 Versions: []apidiscovery.APIVersionDiscovery{
1245 {
1246 Version: "v1",
1247 Resources: []apidiscovery.APIResourceDiscovery{
1248 {
1249 Resource: "pods",
1250 ResponseKind: &metav1.GroupVersionKind{
1251 Group: "",
1252 Version: "v1",
1253 Kind: "Pod",
1254 },
1255 Scope: apidiscovery.ScopeNamespace,
1256 },
1257 },
1258 },
1259 },
1260 },
1261 },
1262 },
1263 apis: &apidiscovery.APIGroupDiscoveryList{
1264 Items: []apidiscovery.APIGroupDiscovery{
1265 {
1266 ObjectMeta: metav1.ObjectMeta{
1267 Name: "apps",
1268 },
1269 Versions: []apidiscovery.APIVersionDiscovery{
1270
1271 {
1272 Version: "v2",
1273 Resources: []apidiscovery.APIResourceDiscovery{
1274 {
1275 Resource: "deployments",
1276 ResponseKind: &metav1.GroupVersionKind{
1277 Group: "apps",
1278 Version: "v2",
1279 Kind: "Deployment",
1280 },
1281 Scope: apidiscovery.ScopeNamespace,
1282 },
1283 },
1284 },
1285 {
1286 Version: "v1",
1287 Resources: []apidiscovery.APIResourceDiscovery{
1288 {
1289 Resource: "deployments",
1290 ResponseKind: &metav1.GroupVersionKind{
1291 Group: "apps",
1292 Version: "v1",
1293 Kind: "Deployment",
1294 },
1295 Scope: apidiscovery.ScopeNamespace,
1296 },
1297 },
1298 },
1299 },
1300 },
1301 },
1302 },
1303 expectedGroupNames: []string{"", "apps"},
1304 expectedGroupVersions: []string{"v1", "apps/v1", "apps/v2"},
1305 expectedPreferredVersions: []string{"v1", "apps/v2"},
1306 },
1307 {
1308 name: "Aggregated discovery: /api returns nothing, 2 groups at /apis",
1309 corev1: &apidiscovery.APIGroupDiscoveryList{},
1310 apis: &apidiscovery.APIGroupDiscoveryList{
1311 Items: []apidiscovery.APIGroupDiscovery{
1312 {
1313 ObjectMeta: metav1.ObjectMeta{
1314 Name: "apps",
1315 },
1316 Versions: []apidiscovery.APIVersionDiscovery{
1317 {
1318 Version: "v1",
1319 Resources: []apidiscovery.APIResourceDiscovery{
1320 {
1321 Resource: "deployments",
1322 ResponseKind: &metav1.GroupVersionKind{
1323 Group: "apps",
1324 Version: "v1",
1325 Kind: "Deployment",
1326 },
1327 Scope: apidiscovery.ScopeNamespace,
1328 },
1329 {
1330 Resource: "statefulsets",
1331 ResponseKind: &metav1.GroupVersionKind{
1332 Group: "apps",
1333 Version: "v1",
1334 Kind: "StatefulSet",
1335 },
1336 Scope: apidiscovery.ScopeNamespace,
1337 },
1338 },
1339 },
1340 },
1341 },
1342 {
1343 ObjectMeta: metav1.ObjectMeta{
1344 Name: "batch",
1345 },
1346 Versions: []apidiscovery.APIVersionDiscovery{
1347
1348 {
1349 Version: "v1",
1350 Resources: []apidiscovery.APIResourceDiscovery{
1351 {
1352 Resource: "jobs",
1353 ResponseKind: &metav1.GroupVersionKind{
1354 Group: "batch",
1355 Version: "v1",
1356 Kind: "Job",
1357 },
1358 Scope: apidiscovery.ScopeNamespace,
1359 },
1360 {
1361 Resource: "cronjobs",
1362 ResponseKind: &metav1.GroupVersionKind{
1363 Group: "batch",
1364 Version: "v1",
1365 Kind: "CronJob",
1366 },
1367 Scope: apidiscovery.ScopeNamespace,
1368 },
1369 },
1370 },
1371 {
1372 Version: "v1beta1",
1373 Resources: []apidiscovery.APIResourceDiscovery{
1374 {
1375 Resource: "jobs",
1376 ResponseKind: &metav1.GroupVersionKind{
1377 Group: "batch",
1378 Version: "v1beta1",
1379 Kind: "Job",
1380 },
1381 Scope: apidiscovery.ScopeNamespace,
1382 },
1383 {
1384 Resource: "cronjobs",
1385 ResponseKind: &metav1.GroupVersionKind{
1386 Group: "batch",
1387 Version: "v1beta1",
1388 Kind: "CronJob",
1389 },
1390 Scope: apidiscovery.ScopeNamespace,
1391 },
1392 },
1393 },
1394 },
1395 },
1396 },
1397 },
1398 expectedGroupNames: []string{"apps", "batch"},
1399 expectedGroupVersions: []string{"apps/v1", "batch/v1", "batch/v1beta1"},
1400 expectedPreferredVersions: []string{"apps/v1", "batch/v1"},
1401 },
1402 }
1403
1404 for _, test := range tests {
1405 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
1406 var agg *apidiscovery.APIGroupDiscoveryList
1407 switch req.URL.Path {
1408 case "/api":
1409 agg = test.corev1
1410 case "/apis":
1411 agg = test.apis
1412 default:
1413 w.WriteHeader(http.StatusNotFound)
1414 return
1415 }
1416 output, err := json.Marshal(agg)
1417 require.NoError(t, err)
1418
1419 w.Header().Set("Content-Type", discovery.AcceptV2Beta1)
1420 w.WriteHeader(http.StatusOK)
1421 w.Write(output)
1422 }))
1423 defer server.Close()
1424 client := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: server.URL})
1425 memCacheClient := NewMemCacheClient(client)
1426 assert.False(t, memCacheClient.Fresh())
1427 apiGroupList, err := memCacheClient.ServerGroups()
1428 require.NoError(t, err)
1429 assert.True(t, memCacheClient.Fresh())
1430
1431 expectedGroupNames := sets.NewString(test.expectedGroupNames...)
1432 actualGroupNames := sets.NewString(groupNamesFromList(apiGroupList)...)
1433 assert.True(t, expectedGroupNames.Equal(actualGroupNames),
1434 "%s: Expected groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
1435
1436 expectedGroupVersions := sets.NewString(test.expectedGroupVersions...)
1437 actualGroupVersions := sets.NewString(groupVersionsFromGroups(apiGroupList)...)
1438 assert.True(t, expectedGroupVersions.Equal(actualGroupVersions),
1439 "%s: Expected group/versions (%s), got (%s)", test.name, expectedGroupVersions.List(), actualGroupVersions.List())
1440
1441 expectedPreferredVersions := sets.NewString(test.expectedPreferredVersions...)
1442 actualPreferredVersions := sets.NewString(preferredVersionsFromList(apiGroupList)...)
1443 assert.True(t, expectedPreferredVersions.Equal(actualPreferredVersions),
1444 "%s: Expected preferred group/version (%s), got (%s)", test.name, expectedPreferredVersions.List(), actualPreferredVersions.List())
1445
1446 memCacheClient.Invalidate()
1447 assert.False(t, memCacheClient.Fresh())
1448 apiGroupList, err = memCacheClient.ServerGroups()
1449 require.NoError(t, err)
1450
1451 actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...)
1452 assert.True(t, expectedGroupNames.Equal(actualGroupNames),
1453 "%s: Expected after invalidation groups (%s), got (%s)", test.name, expectedGroupNames.List(), actualGroupNames.List())
1454 }
1455 }
1456
1457 func groupNamesFromList(groups *metav1.APIGroupList) []string {
1458 result := []string{}
1459 for _, group := range groups.Groups {
1460 result = append(result, group.Name)
1461 }
1462 return result
1463 }
1464
1465 func preferredVersionsFromList(groups *metav1.APIGroupList) []string {
1466 result := []string{}
1467 for _, group := range groups.Groups {
1468 preferredGV := group.PreferredVersion.GroupVersion
1469 result = append(result, preferredGV)
1470 }
1471 return result
1472 }
1473
1474 func groupVersionsFromGroups(groups *metav1.APIGroupList) []string {
1475 result := []string{}
1476 for _, group := range groups.Groups {
1477 for _, version := range group.Versions {
1478 result = append(result, version.GroupVersion)
1479 }
1480 }
1481 return result
1482 }
1483
1484 func groupVersionKinds(resources []*metav1.APIResourceList) []string {
1485 result := []string{}
1486 for _, resourceList := range resources {
1487 for _, resource := range resourceList.APIResources {
1488 gvk := fmt.Sprintf("%s/%s/%s", resource.Group, resource.Version, resource.Kind)
1489 result = append(result, gvk)
1490 }
1491 }
1492 return result
1493 }
1494
1495 func failedGroupVersions(gvs map[schema.GroupVersion]error) []string {
1496 result := []string{}
1497 for gv := range gvs {
1498 result = append(result, gv.String())
1499 }
1500 return result
1501 }
1502
View as plain text