1
16
17 package restmapper
18
19 import (
20 "testing"
21
22 openapi_v2 "github.com/google/gnostic-models/openapiv2"
23 "github.com/google/go-cmp/cmp"
24
25 "k8s.io/apimachinery/pkg/api/errors"
26 "k8s.io/apimachinery/pkg/api/meta"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/version"
30 "k8s.io/client-go/discovery"
31 "k8s.io/client-go/openapi"
32 restclient "k8s.io/client-go/rest"
33 "k8s.io/client-go/rest/fake"
34 )
35
36 func TestReplaceAliases(t *testing.T) {
37 tests := []struct {
38 name string
39 arg string
40 expected schema.GroupVersionResource
41 srvRes []*metav1.APIResourceList
42 }{
43 {
44 name: "storageclasses-no-replacement",
45 arg: "storageclasses",
46 expected: schema.GroupVersionResource{Resource: "storageclasses"},
47 srvRes: []*metav1.APIResourceList{},
48 },
49 {
50 name: "hpa-priority",
51 arg: "hpa",
52 expected: schema.GroupVersionResource{Resource: "superhorizontalpodautoscalers", Group: "autoscaling"},
53 srvRes: []*metav1.APIResourceList{
54 {
55 GroupVersion: "autoscaling/v1",
56 APIResources: []metav1.APIResource{
57 {
58 Name: "superhorizontalpodautoscalers",
59 ShortNames: []string{"hpa"},
60 },
61 },
62 },
63 {
64 GroupVersion: "autoscaling/v1",
65 APIResources: []metav1.APIResource{
66 {
67 Name: "horizontalpodautoscalers",
68 ShortNames: []string{"hpa"},
69 },
70 },
71 },
72 },
73 },
74 {
75 name: "resource-override",
76 arg: "dpl",
77 expected: schema.GroupVersionResource{Resource: "deployments", Group: "foo"},
78 srvRes: []*metav1.APIResourceList{
79 {
80 GroupVersion: "foo/v1",
81 APIResources: []metav1.APIResource{
82 {
83 Name: "deployments",
84 ShortNames: []string{"dpl"},
85 },
86 },
87 },
88 {
89 GroupVersion: "extension/v1beta1",
90 APIResources: []metav1.APIResource{
91 {
92 Name: "deployments",
93 ShortNames: []string{"deploy"},
94 },
95 },
96 },
97 },
98 },
99 {
100 name: "resource-match-preferred",
101 arg: "pods",
102 expected: schema.GroupVersionResource{Resource: "pods", Group: ""},
103 srvRes: []*metav1.APIResourceList{
104 {
105 GroupVersion: "v1",
106 APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod"}},
107 },
108 {
109 GroupVersion: "acme.com/v1",
110 APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"pods", "pod"}}},
111 },
112 },
113 },
114 {
115 name: "resource-match-singular-preferred",
116 arg: "pod",
117 expected: schema.GroupVersionResource{Resource: "pod", Group: ""},
118 srvRes: []*metav1.APIResourceList{
119 {
120 GroupVersion: "v1",
121 APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod"}},
122 },
123 {
124 GroupVersion: "acme.com/v1",
125 APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"pods", "pod"}}},
126 },
127 },
128 },
129 }
130
131 for _, test := range tests {
132 ds := &fakeDiscoveryClient{}
133 ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
134 return test.srvRes, nil
135 }
136 mapper := NewShortcutExpander(&fakeRESTMapper{}, ds, nil).(shortcutExpander)
137
138 actual := mapper.expandResourceShortcut(schema.GroupVersionResource{Resource: test.arg})
139 if actual != test.expected {
140 t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, actual)
141 }
142 }
143 }
144
145 func TestKindFor(t *testing.T) {
146 tests := []struct {
147 in schema.GroupVersionResource
148 expected schema.GroupVersionResource
149 srvRes []*metav1.APIResourceList
150 }{
151 {
152 in: schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "sc"},
153 expected: schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "storageclasses"},
154 srvRes: []*metav1.APIResourceList{
155 {
156 GroupVersion: "storage.k8s.io/v1",
157 APIResources: []metav1.APIResource{
158 {
159 Name: "storageclasses",
160 ShortNames: []string{"sc"},
161 },
162 },
163 },
164 },
165 },
166 {
167 in: schema.GroupVersionResource{Group: "", Version: "", Resource: "sc"},
168 expected: schema.GroupVersionResource{Group: "storage.k8s.io", Version: "", Resource: "storageclasses"},
169 srvRes: []*metav1.APIResourceList{
170 {
171 GroupVersion: "storage.k8s.io/v1",
172 APIResources: []metav1.APIResource{
173 {
174 Name: "storageclasses",
175 ShortNames: []string{"sc"},
176 },
177 },
178 },
179 },
180 },
181 }
182
183 for i, test := range tests {
184 ds := &fakeDiscoveryClient{}
185 ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
186 return test.srvRes, nil
187 }
188
189 delegate := &fakeRESTMapper{}
190 mapper := NewShortcutExpander(delegate, ds, func(a string) {
191 t.Fatalf("unexpected warning message %s", a)
192 })
193
194 mapper.KindFor(test.in)
195 if delegate.kindForInput != test.expected {
196 t.Errorf("%d: unexpected data returned %#v, expected %#v", i, delegate.kindForInput, test.expected)
197 }
198 }
199 }
200
201 func TestKindForWithNewCRDs(t *testing.T) {
202 tests := map[string]struct {
203 in schema.GroupVersionResource
204 expected schema.GroupVersionKind
205 srvRes []*metav1.APIResourceList
206 }{
207 "": {
208 in: schema.GroupVersionResource{Group: "a", Version: "", Resource: "sc"},
209 expected: schema.GroupVersionKind{Group: "a", Version: "v1", Kind: "StorageClass"},
210 srvRes: []*metav1.APIResourceList{
211 {
212 GroupVersion: "a/v1",
213 APIResources: []metav1.APIResource{
214 {
215 Name: "storageclasses",
216 ShortNames: []string{"sc"},
217 Kind: "StorageClass",
218 },
219 },
220 },
221 },
222 },
223 }
224
225 for name, test := range tests {
226 t.Run(name, func(t *testing.T) {
227 invalidateCalled := false
228 fakeDiscovery := &fakeDiscoveryClient{}
229 fakeDiscovery.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
230 if invalidateCalled {
231 return test.srvRes, nil
232 }
233 return []*metav1.APIResourceList{}, nil
234 }
235 fakeCachedDiscovery := &fakeCachedDiscoveryClient{DiscoveryInterface: fakeDiscovery}
236 fakeCachedDiscovery.invalidateHandler = func() {
237 invalidateCalled = true
238 }
239 fakeCachedDiscovery.freshHandler = func() bool {
240 return invalidateCalled
241 }
242
243
244
245
246 delegate := NewDeferredDiscoveryRESTMapper(fakeCachedDiscovery)
247 mapper := NewShortcutExpander(delegate, fakeCachedDiscovery, func(a string) {
248 t.Fatalf("unexpected warning message %s", a)
249 })
250
251 gvk, err := mapper.KindFor(test.in)
252 if err != nil {
253 t.Errorf("unexpected error: %v", err)
254 }
255 if diff := cmp.Equal(gvk, test.expected); !diff {
256 t.Errorf("unexpected data returned %#v, expected %#v", gvk, test.expected)
257 }
258 })
259 }
260 }
261
262 func TestWarnAmbigious(t *testing.T) {
263 tests := []struct {
264 name string
265 arg string
266 expected schema.GroupVersionResource
267 expectedWarningLogs []string
268 srvRes []*metav1.APIResourceList
269 }{
270 {
271 name: "warn ambiguity",
272 arg: "hpa",
273 expected: schema.GroupVersionResource{Resource: "superhorizontalpodautoscalers", Group: "autoscaling"},
274 expectedWarningLogs: []string{`short name "hpa" could also match lower priority resource horizontalpodautoscalers.autoscaling`},
275 srvRes: []*metav1.APIResourceList{
276 {
277 GroupVersion: "autoscaling/v1",
278 APIResources: []metav1.APIResource{
279 {
280 Name: "superhorizontalpodautoscalers",
281 ShortNames: []string{"hpa"},
282 },
283 },
284 },
285 {
286 GroupVersion: "autoscaling/v1",
287 APIResources: []metav1.APIResource{
288 {
289 Name: "horizontalpodautoscalers",
290 ShortNames: []string{"hpa"},
291 },
292 },
293 },
294 },
295 },
296 {
297 name: "warn-builtin-shortname-ambugity",
298 arg: "po",
299 expected: schema.GroupVersionResource{Resource: "pods", Group: ""},
300 expectedWarningLogs: []string{`short name "po" could also match lower priority resource poddlers.acme.com`},
301 srvRes: []*metav1.APIResourceList{
302 {
303 GroupVersion: "v1",
304 APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod", ShortNames: []string{"po"}}},
305 },
306 {
307 GroupVersion: "acme.com/v1",
308 APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"po"}}},
309 },
310 },
311 },
312 {
313 name: "warn-builtin-shortname-ambugity-multi-version",
314 arg: "po",
315 expected: schema.GroupVersionResource{Resource: "pods", Group: ""},
316 expectedWarningLogs: []string{`short name "po" could also match lower priority resource poddlers.acme.com`},
317 srvRes: []*metav1.APIResourceList{
318 {
319 GroupVersion: "v1",
320 APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod", ShortNames: []string{"po"}}},
321 },
322 {
323 GroupVersion: "acme.com/v1",
324 APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"po"}}},
325 },
326 {
327 GroupVersion: "acme.com/v1beta1",
328 APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"po"}}},
329 },
330 },
331 },
332 {
333 name: "resource-match-singular-preferred",
334 arg: "pod",
335 expected: schema.GroupVersionResource{Resource: "pod", Group: ""},
336 srvRes: []*metav1.APIResourceList{
337 {
338 GroupVersion: "v1",
339 APIResources: []metav1.APIResource{{Name: "pods", SingularName: "pod"}},
340 },
341 {
342 GroupVersion: "acme.com/v1",
343 APIResources: []metav1.APIResource{{Name: "poddlers", ShortNames: []string{"pods", "pod"}}},
344 },
345 },
346 },
347 {
348 name: "resource-multiple-versions-shortform",
349 arg: "hpa",
350 expected: schema.GroupVersionResource{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
351 expectedWarningLogs: []string{},
352 srvRes: []*metav1.APIResourceList{
353 {
354 GroupVersion: "autoscaling/v1alphav1",
355 APIResources: []metav1.APIResource{
356 {
357 Name: "horizontalpodautoscalers",
358 ShortNames: []string{"hpa"},
359 },
360 },
361 },
362 {
363 GroupVersion: "autoscaling/v1",
364 APIResources: []metav1.APIResource{
365 {
366 Name: "horizontalpodautoscalers",
367 ShortNames: []string{"hpa"},
368 },
369 },
370 },
371 },
372 },
373 {
374 name: "multi-resource-multiple-versions-shortform",
375 arg: "hpa",
376 expected: schema.GroupVersionResource{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
377 expectedWarningLogs: []string{
378 `short name "hpa" could also match lower priority resource foo.foo`,
379 `short name "hpa" could also match lower priority resource bar.bar`,
380 },
381 srvRes: []*metav1.APIResourceList{
382 {
383 GroupVersion: "autoscaling/v1alphav1",
384 APIResources: []metav1.APIResource{
385 {
386 Name: "horizontalpodautoscalers",
387 ShortNames: []string{"hpa"},
388 },
389 },
390 },
391 {
392 GroupVersion: "autoscaling/v1",
393 APIResources: []metav1.APIResource{
394 {
395 Name: "horizontalpodautoscalers",
396 ShortNames: []string{"hpa"},
397 },
398 },
399 },
400 {
401 GroupVersion: "foo/v1",
402 APIResources: []metav1.APIResource{
403 {
404 Name: "foo",
405 ShortNames: []string{"hpa"},
406 },
407 },
408 },
409 {
410 GroupVersion: "foo/v1beta1",
411 APIResources: []metav1.APIResource{
412 {
413 Name: "foo",
414 ShortNames: []string{"hpa"},
415 },
416 },
417 },
418 {
419 GroupVersion: "bar/v1",
420 APIResources: []metav1.APIResource{
421 {
422 Name: "bar",
423 ShortNames: []string{"hpa"},
424 },
425 },
426 },
427 },
428 },
429 }
430
431 for _, test := range tests {
432 ds := &fakeDiscoveryClient{}
433 ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
434 return test.srvRes, nil
435 }
436
437 var actualWarnings []string
438 mapper := NewShortcutExpander(&fakeRESTMapper{}, ds, func(a string) {
439 actualWarnings = append(actualWarnings, a)
440 }).(shortcutExpander)
441
442 actual := mapper.expandResourceShortcut(schema.GroupVersionResource{Resource: test.arg})
443 if actual != test.expected {
444 t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, actual)
445 }
446
447 if len(actualWarnings) == 0 && len(test.expectedWarningLogs) == 0 {
448 continue
449 }
450
451 if !cmp.Equal(test.expectedWarningLogs, actualWarnings) {
452 t.Fatalf("expected warning message %s but got %s", test.expectedWarningLogs, actualWarnings)
453 }
454 }
455 }
456
457 type fakeRESTMapper struct {
458 kindForInput schema.GroupVersionResource
459 }
460
461 func (f *fakeRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
462 f.kindForInput = resource
463 return schema.GroupVersionKind{}, nil
464 }
465
466 func (f *fakeRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
467 return nil, nil
468 }
469
470 func (f *fakeRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
471 return schema.GroupVersionResource{}, nil
472 }
473
474 func (f *fakeRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
475 return nil, nil
476 }
477
478 func (f *fakeRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
479 return nil, nil
480 }
481
482 func (f *fakeRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
483 return nil, nil
484 }
485
486 func (f *fakeRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
487 return "", nil
488 }
489
490 type fakeDiscoveryClient struct {
491 serverResourcesHandler func() ([]*metav1.APIResourceList, error)
492 }
493
494 var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
495
496 func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
497 return &fake.RESTClient{}
498 }
499
500 func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
501 return &metav1.APIGroupList{
502 Groups: []metav1.APIGroup{
503 {
504 Name: "a",
505 Versions: []metav1.GroupVersionForDiscovery{
506 {
507 GroupVersion: "a/v1",
508 Version: "v1",
509 },
510 },
511 PreferredVersion: metav1.GroupVersionForDiscovery{
512 GroupVersion: "a/v1",
513 Version: "v1",
514 },
515 },
516 },
517 }, nil
518 }
519
520 func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
521 if groupVersion == "a/v1" {
522 return &metav1.APIResourceList{APIResources: []metav1.APIResource{{Name: "widgets", Kind: "Widget"}}}, nil
523 }
524
525 return nil, errors.NewNotFound(schema.GroupResource{}, "")
526 }
527
528 func (c *fakeDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
529 sgs, err := c.ServerGroups()
530 if err != nil {
531 return nil, nil, err
532 }
533 resultGroups := []*metav1.APIGroup{}
534 for i := range sgs.Groups {
535 resultGroups = append(resultGroups, &sgs.Groups[i])
536 }
537 if c.serverResourcesHandler != nil {
538 rs, err := c.serverResourcesHandler()
539 return resultGroups, rs, err
540 }
541 return resultGroups, []*metav1.APIResourceList{}, nil
542 }
543
544 func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
545 return nil, nil
546 }
547
548 func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
549 return nil, nil
550 }
551
552 func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
553 return &version.Info{}, nil
554 }
555
556 func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
557 return &openapi_v2.Document{}, nil
558 }
559
560 func (c *fakeDiscoveryClient) OpenAPIV3() openapi.Client {
561 panic("implement me")
562 }
563
564 func (c *fakeDiscoveryClient) WithLegacy() discovery.DiscoveryInterface {
565 panic("implement me")
566 }
567
568 type fakeCachedDiscoveryClient struct {
569 discovery.DiscoveryInterface
570 freshHandler func() bool
571 invalidateHandler func()
572 }
573
574 var _ discovery.CachedDiscoveryInterface = &fakeCachedDiscoveryClient{}
575
576 func (c *fakeCachedDiscoveryClient) Fresh() bool {
577 if c.freshHandler != nil {
578 return c.freshHandler()
579 }
580 return true
581 }
582
583 func (c *fakeCachedDiscoveryClient) Invalidate() {
584 if c.invalidateHandler != nil {
585 c.invalidateHandler()
586 }
587 }
588
View as plain text