1
16
17 package plugin
18
19 import (
20 "context"
21 "fmt"
22 "reflect"
23 "sync"
24 "testing"
25 "time"
26
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/util/rand"
31 "k8s.io/client-go/tools/cache"
32 credentialproviderapi "k8s.io/kubelet/pkg/apis/credentialprovider"
33 credentialproviderv1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1"
34 credentialproviderv1alpha1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1"
35 credentialproviderv1beta1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1beta1"
36 "k8s.io/kubernetes/pkg/credentialprovider"
37 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
38 "k8s.io/utils/clock"
39 testingclock "k8s.io/utils/clock/testing"
40 )
41
42 type fakeExecPlugin struct {
43 cacheKeyType credentialproviderapi.PluginCacheKeyType
44 cacheDuration time.Duration
45
46 auth map[string]credentialproviderapi.AuthConfig
47 }
48
49 func (f *fakeExecPlugin) ExecPlugin(ctx context.Context, image string) (*credentialproviderapi.CredentialProviderResponse, error) {
50 return &credentialproviderapi.CredentialProviderResponse{
51 CacheKeyType: f.cacheKeyType,
52 CacheDuration: &metav1.Duration{
53 Duration: f.cacheDuration,
54 },
55 Auth: f.auth,
56 }, nil
57 }
58
59 func Test_Provide(t *testing.T) {
60 tclock := clock.RealClock{}
61 testcases := []struct {
62 name string
63 pluginProvider *pluginProvider
64 image string
65 dockerconfig credentialprovider.DockerConfig
66 }{
67 {
68 name: "exact image match, with Registry cache key",
69 pluginProvider: &pluginProvider{
70 clock: tclock,
71 lastCachePurge: tclock.Now(),
72 matchImages: []string{"test.registry.io"},
73 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
74 plugin: &fakeExecPlugin{
75 cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
76 auth: map[string]credentialproviderapi.AuthConfig{
77 "test.registry.io": {
78 Username: "user",
79 Password: "password",
80 },
81 },
82 },
83 },
84 image: "test.registry.io/foo/bar",
85 dockerconfig: credentialprovider.DockerConfig{
86 "test.registry.io": credentialprovider.DockerConfigEntry{
87 Username: "user",
88 Password: "password",
89 },
90 },
91 },
92 {
93 name: "exact image match, with Image cache key",
94 pluginProvider: &pluginProvider{
95 clock: tclock,
96 lastCachePurge: tclock.Now(),
97 matchImages: []string{"test.registry.io/foo/bar"},
98 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
99 plugin: &fakeExecPlugin{
100 cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
101 auth: map[string]credentialproviderapi.AuthConfig{
102 "test.registry.io/foo/bar": {
103 Username: "user",
104 Password: "password",
105 },
106 },
107 },
108 },
109 image: "test.registry.io/foo/bar",
110 dockerconfig: credentialprovider.DockerConfig{
111 "test.registry.io/foo/bar": credentialprovider.DockerConfigEntry{
112 Username: "user",
113 Password: "password",
114 },
115 },
116 },
117 {
118 name: "exact image match, with Global cache key",
119 pluginProvider: &pluginProvider{
120 clock: tclock,
121 lastCachePurge: tclock.Now(),
122 matchImages: []string{"test.registry.io"},
123 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
124 plugin: &fakeExecPlugin{
125 cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
126 auth: map[string]credentialproviderapi.AuthConfig{
127 "test.registry.io": {
128 Username: "user",
129 Password: "password",
130 },
131 },
132 },
133 },
134 image: "test.registry.io",
135 dockerconfig: credentialprovider.DockerConfig{
136 "test.registry.io": credentialprovider.DockerConfigEntry{
137 Username: "user",
138 Password: "password",
139 },
140 },
141 },
142 {
143 name: "wild card image match, with Registry cache key",
144 pluginProvider: &pluginProvider{
145 clock: tclock,
146 lastCachePurge: tclock.Now(),
147 matchImages: []string{"*.registry.io:8080"},
148 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
149 plugin: &fakeExecPlugin{
150 cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
151 auth: map[string]credentialproviderapi.AuthConfig{
152 "*.registry.io:8080": {
153 Username: "user",
154 Password: "password",
155 },
156 },
157 },
158 },
159 image: "test.registry.io:8080/foo",
160 dockerconfig: credentialprovider.DockerConfig{
161 "*.registry.io:8080": credentialprovider.DockerConfigEntry{
162 Username: "user",
163 Password: "password",
164 },
165 },
166 },
167 {
168 name: "wild card image match, with Image cache key",
169 pluginProvider: &pluginProvider{
170 clock: tclock,
171 lastCachePurge: tclock.Now(),
172 matchImages: []string{"*.*.registry.io"},
173 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
174 plugin: &fakeExecPlugin{
175 cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
176 auth: map[string]credentialproviderapi.AuthConfig{
177 "*.*.registry.io": {
178 Username: "user",
179 Password: "password",
180 },
181 },
182 },
183 },
184 image: "foo.bar.registry.io/foo/bar",
185 dockerconfig: credentialprovider.DockerConfig{
186 "*.*.registry.io": credentialprovider.DockerConfigEntry{
187 Username: "user",
188 Password: "password",
189 },
190 },
191 },
192 {
193 name: "wild card image match, with Global cache key",
194 pluginProvider: &pluginProvider{
195 clock: tclock,
196 lastCachePurge: tclock.Now(),
197 matchImages: []string{"*.registry.io"},
198 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
199 plugin: &fakeExecPlugin{
200 cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
201 auth: map[string]credentialproviderapi.AuthConfig{
202 "*.registry.io": {
203 Username: "user",
204 Password: "password",
205 },
206 },
207 },
208 },
209 image: "test.registry.io",
210 dockerconfig: credentialprovider.DockerConfig{
211 "*.registry.io": credentialprovider.DockerConfigEntry{
212 Username: "user",
213 Password: "password",
214 },
215 },
216 },
217 }
218
219 for _, testcase := range testcases {
220 testcase := testcase
221 t.Run(testcase.name, func(t *testing.T) {
222 t.Parallel()
223 dockerconfig := testcase.pluginProvider.Provide(testcase.image)
224 if !reflect.DeepEqual(dockerconfig, testcase.dockerconfig) {
225 t.Logf("actual docker config: %v", dockerconfig)
226 t.Logf("expected docker config: %v", testcase.dockerconfig)
227 t.Error("unexpected docker config")
228 }
229 })
230 }
231 }
232
233
234
235 func Test_ProvideParallel(t *testing.T) {
236 tclock := clock.RealClock{}
237
238 testcases := []struct {
239 name string
240 registry string
241 }{
242 {
243 name: "provide for registry 1",
244 registry: "test1.registry.io",
245 },
246 {
247 name: "provide for registry 2",
248 registry: "test2.registry.io",
249 },
250 {
251 name: "provide for registry 3",
252 registry: "test3.registry.io",
253 },
254 {
255 name: "provide for registry 4",
256 registry: "test4.registry.io",
257 },
258 }
259
260 pluginProvider := &pluginProvider{
261 clock: tclock,
262 lastCachePurge: tclock.Now(),
263 matchImages: []string{"test1.registry.io", "test2.registry.io", "test3.registry.io", "test4.registry.io"},
264 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
265 plugin: &fakeExecPlugin{
266 cacheDuration: time.Minute * 1,
267 cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
268 auth: map[string]credentialproviderapi.AuthConfig{
269 "test.registry.io": {
270 Username: "user",
271 Password: "password",
272 },
273 },
274 },
275 }
276
277 dockerconfig := credentialprovider.DockerConfig{
278 "test.registry.io": credentialprovider.DockerConfigEntry{
279 Username: "user",
280 Password: "password",
281 },
282 }
283
284 for _, testcase := range testcases {
285 testcase := testcase
286 t.Run(testcase.name, func(t *testing.T) {
287 t.Parallel()
288 var wg sync.WaitGroup
289 wg.Add(5)
290
291 for i := 0; i < 5; i++ {
292 go func(w *sync.WaitGroup) {
293 image := fmt.Sprintf(testcase.registry+"/%s", rand.String(5))
294 dockerconfigResponse := pluginProvider.Provide(image)
295 if !reflect.DeepEqual(dockerconfigResponse, dockerconfig) {
296 t.Logf("actual docker config: %v", dockerconfigResponse)
297 t.Logf("expected docker config: %v", dockerconfig)
298 t.Error("unexpected docker config")
299 }
300 w.Done()
301 }(&wg)
302 }
303 wg.Wait()
304
305 })
306 }
307 }
308
309 func Test_getCachedCredentials(t *testing.T) {
310 fakeClock := testingclock.NewFakeClock(time.Now())
311 p := &pluginProvider{
312 clock: fakeClock,
313 lastCachePurge: fakeClock.Now(),
314 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: fakeClock}),
315 plugin: &fakeExecPlugin{},
316 }
317
318 testcases := []struct {
319 name string
320 step time.Duration
321 cacheEntry cacheEntry
322 expectedResponse credentialprovider.DockerConfig
323 keyLength int
324 getKey string
325 }{
326 {
327 name: "It should return not expired credential",
328 step: 1 * time.Second,
329 keyLength: 1,
330 getKey: "image1",
331 expectedResponse: map[string]credentialprovider.DockerConfigEntry{
332 "image1": {
333 Username: "user1",
334 Password: "pass1",
335 },
336 },
337 cacheEntry: cacheEntry{
338 key: "image1",
339 expiresAt: fakeClock.Now().Add(1 * time.Minute),
340 credentials: map[string]credentialprovider.DockerConfigEntry{
341 "image1": {
342 Username: "user1",
343 Password: "pass1",
344 },
345 },
346 },
347 },
348
349 {
350 name: "It should not return expired credential",
351 step: 2 * time.Minute,
352 getKey: "image2",
353 keyLength: 1,
354 cacheEntry: cacheEntry{
355 key: "image2",
356 expiresAt: fakeClock.Now(),
357 credentials: map[string]credentialprovider.DockerConfigEntry{
358 "image2": {
359 Username: "user2",
360 Password: "pass2",
361 },
362 },
363 },
364 },
365
366 {
367 name: "It should delete expired credential during purge",
368 step: 18 * time.Minute,
369 keyLength: 0,
370
371
372
373 getKey: "random",
374 cacheEntry: cacheEntry{
375 key: "image3",
376 expiresAt: fakeClock.Now().Add(2 * time.Minute),
377 credentials: map[string]credentialprovider.DockerConfigEntry{
378 "image3": {
379 Username: "user3",
380 Password: "pass3",
381 },
382 },
383 },
384 },
385 }
386
387 for _, tc := range testcases {
388 t.Run(tc.name, func(t *testing.T) {
389 p.cache.Add(&tc.cacheEntry)
390 fakeClock.Step(tc.step)
391
392
393 res, _, err := p.getCachedCredentials(tc.getKey)
394 if err != nil {
395 t.Errorf("Unexpected error %v", err)
396 }
397 if !reflect.DeepEqual(res, tc.expectedResponse) {
398 t.Logf("response %v", res)
399 t.Logf("expected response %v", tc.expectedResponse)
400 t.Errorf("Unexpected response")
401 }
402
403
404 if len(p.cache.ListKeys()) != tc.keyLength {
405 t.Errorf("Unexpected cache key length")
406 }
407 })
408 }
409 }
410
411 func Test_encodeRequest(t *testing.T) {
412 testcases := []struct {
413 name string
414 apiVersion schema.GroupVersion
415 request *credentialproviderapi.CredentialProviderRequest
416 expectedData []byte
417 expectedErr bool
418 }{
419 {
420 name: "successful with v1alpha1",
421 apiVersion: credentialproviderv1alpha1.SchemeGroupVersion,
422 request: &credentialproviderapi.CredentialProviderRequest{
423 Image: "test.registry.io/foobar",
424 },
425 expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","image":"test.registry.io/foobar"}
426 `),
427 expectedErr: false,
428 },
429 {
430 name: "successful with v1beta1",
431 apiVersion: credentialproviderv1beta1.SchemeGroupVersion,
432 request: &credentialproviderapi.CredentialProviderRequest{
433 Image: "test.registry.io/foobar",
434 },
435 expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1beta1","image":"test.registry.io/foobar"}
436 `),
437 expectedErr: false,
438 },
439 {
440 name: "successful with v1",
441 apiVersion: credentialproviderv1.SchemeGroupVersion,
442 request: &credentialproviderapi.CredentialProviderRequest{
443 Image: "test.registry.io/foobar",
444 },
445 expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1","image":"test.registry.io/foobar"}
446 `),
447 expectedErr: false,
448 },
449 }
450
451 for _, testcase := range testcases {
452 t.Run(testcase.name, func(t *testing.T) {
453 mediaType := "application/json"
454 info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
455 if !ok {
456 t.Fatalf("unsupported media type: %s", mediaType)
457 }
458
459 e := &execPlugin{
460 encoder: codecs.EncoderForVersion(info.Serializer, testcase.apiVersion),
461 }
462
463 data, err := e.encodeRequest(testcase.request)
464 if err != nil && !testcase.expectedErr {
465 t.Fatalf("unexpected error: %v", err)
466 }
467
468 if err == nil && testcase.expectedErr {
469 t.Fatalf("expected error %v but got nil", testcase.expectedErr)
470 }
471
472 if !reflect.DeepEqual(data, testcase.expectedData) {
473 t.Errorf("actual encoded data: %v", string(data))
474 t.Errorf("expected encoded data: %v", string(testcase.expectedData))
475 t.Errorf("unexpected encoded response")
476 }
477 })
478 }
479 }
480
481 func Test_decodeResponse(t *testing.T) {
482 testcases := []struct {
483 name string
484 data []byte
485 expectedResponse *credentialproviderapi.CredentialProviderResponse
486 expectedErr bool
487 }{
488 {
489 name: "success with v1",
490 data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
491 expectedResponse: &credentialproviderapi.CredentialProviderResponse{
492 CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
493 CacheDuration: &metav1.Duration{
494 Duration: time.Minute,
495 },
496 Auth: map[string]credentialproviderapi.AuthConfig{
497 "*.registry.io": {
498 Username: "user",
499 Password: "password",
500 },
501 },
502 },
503 expectedErr: false,
504 },
505 {
506 name: "success with v1beta1",
507 data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1beta1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
508 expectedResponse: &credentialproviderapi.CredentialProviderResponse{
509 CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
510 CacheDuration: &metav1.Duration{
511 Duration: time.Minute,
512 },
513 Auth: map[string]credentialproviderapi.AuthConfig{
514 "*.registry.io": {
515 Username: "user",
516 Password: "password",
517 },
518 },
519 },
520 expectedErr: false,
521 },
522 {
523 name: "success with v1alpha1",
524 data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
525 expectedResponse: &credentialproviderapi.CredentialProviderResponse{
526 CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
527 CacheDuration: &metav1.Duration{
528 Duration: time.Minute,
529 },
530 Auth: map[string]credentialproviderapi.AuthConfig{
531 "*.registry.io": {
532 Username: "user",
533 Password: "password",
534 },
535 },
536 },
537 expectedErr: false,
538 },
539 {
540 name: "wrong Kind",
541 data: []byte(`{"kind":"WrongKind","apiVersion":"credentialprovider.kubelet.k8s.io/v1beta1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
542 expectedResponse: nil,
543 expectedErr: true,
544 },
545 {
546 name: "wrong Group",
547 data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"foobar.kubelet.k8s.io/v1beta1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
548 expectedResponse: nil,
549 expectedErr: true,
550 },
551 }
552
553 for _, testcase := range testcases {
554 t.Run(testcase.name, func(t *testing.T) {
555 e := &execPlugin{}
556
557 decodedResponse, err := e.decodeResponse(testcase.data)
558 if err != nil && !testcase.expectedErr {
559 t.Fatalf("unexpected error: %v", err)
560 }
561
562 if err == nil && testcase.expectedErr {
563 t.Fatalf("expected error %v but not nil", testcase.expectedErr)
564 }
565
566 if !reflect.DeepEqual(decodedResponse, testcase.expectedResponse) {
567 t.Logf("actual decoded response: %#v", decodedResponse)
568 t.Logf("expected decoded response: %#v", testcase.expectedResponse)
569 t.Errorf("unexpected decoded response")
570 }
571 })
572 }
573 }
574
575 func Test_RegistryCacheKeyType(t *testing.T) {
576 tclock := clock.RealClock{}
577 pluginProvider := &pluginProvider{
578 clock: tclock,
579 lastCachePurge: tclock.Now(),
580 matchImages: []string{"*.registry.io"},
581 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
582 plugin: &fakeExecPlugin{
583 cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
584 cacheDuration: time.Hour,
585 auth: map[string]credentialproviderapi.AuthConfig{
586 "*.registry.io": {
587 Username: "user",
588 Password: "password",
589 },
590 },
591 },
592 }
593
594 expectedDockerConfig := credentialprovider.DockerConfig{
595 "*.registry.io": credentialprovider.DockerConfigEntry{
596 Username: "user",
597 Password: "password",
598 },
599 }
600
601 dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
602 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
603 t.Logf("actual docker config: %v", dockerConfig)
604 t.Logf("expected docker config: %v", expectedDockerConfig)
605 t.Fatal("unexpected docker config")
606 }
607
608 expectedCacheKeys := []string{"test.registry.io"}
609 cacheKeys := pluginProvider.cache.ListKeys()
610
611 if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
612 t.Logf("actual cache keys: %v", cacheKeys)
613 t.Logf("expected cache keys: %v", expectedCacheKeys)
614 t.Error("unexpected cache keys")
615 }
616
617
618
619 pluginProvider.plugin = nil
620 dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
621 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
622 t.Logf("actual docker config: %v", dockerConfig)
623 t.Logf("expected docker config: %v", expectedDockerConfig)
624 t.Fatal("unexpected docker config")
625 }
626 }
627
628 func Test_ImageCacheKeyType(t *testing.T) {
629 tclock := clock.RealClock{}
630 pluginProvider := &pluginProvider{
631 clock: tclock,
632 lastCachePurge: tclock.Now(),
633 matchImages: []string{"*.registry.io"},
634 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
635 plugin: &fakeExecPlugin{
636 cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
637 cacheDuration: time.Hour,
638 auth: map[string]credentialproviderapi.AuthConfig{
639 "*.registry.io": {
640 Username: "user",
641 Password: "password",
642 },
643 },
644 },
645 }
646
647 expectedDockerConfig := credentialprovider.DockerConfig{
648 "*.registry.io": credentialprovider.DockerConfigEntry{
649 Username: "user",
650 Password: "password",
651 },
652 }
653
654 dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
655 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
656 t.Logf("actual docker config: %v", dockerConfig)
657 t.Logf("expected docker config: %v", expectedDockerConfig)
658 t.Fatal("unexpected docker config")
659 }
660
661 expectedCacheKeys := []string{"test.registry.io/foo/bar"}
662 cacheKeys := pluginProvider.cache.ListKeys()
663
664 if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
665 t.Logf("actual cache keys: %v", cacheKeys)
666 t.Logf("expected cache keys: %v", expectedCacheKeys)
667 t.Error("unexpected cache keys")
668 }
669
670
671
672 pluginProvider.plugin = nil
673 dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
674 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
675 t.Logf("actual docker config: %v", dockerConfig)
676 t.Logf("expected docker config: %v", expectedDockerConfig)
677 t.Fatal("unexpected docker config")
678 }
679 }
680
681 func Test_GlobalCacheKeyType(t *testing.T) {
682 tclock := clock.RealClock{}
683 pluginProvider := &pluginProvider{
684 clock: tclock,
685 lastCachePurge: tclock.Now(),
686 matchImages: []string{"*.registry.io"},
687 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
688 plugin: &fakeExecPlugin{
689 cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
690 cacheDuration: time.Hour,
691 auth: map[string]credentialproviderapi.AuthConfig{
692 "*.registry.io": {
693 Username: "user",
694 Password: "password",
695 },
696 },
697 },
698 }
699
700 expectedDockerConfig := credentialprovider.DockerConfig{
701 "*.registry.io": credentialprovider.DockerConfigEntry{
702 Username: "user",
703 Password: "password",
704 },
705 }
706
707 dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
708 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
709 t.Logf("actual docker config: %v", dockerConfig)
710 t.Logf("expected docker config: %v", expectedDockerConfig)
711 t.Fatal("unexpected docker config")
712 }
713
714 expectedCacheKeys := []string{"global"}
715 cacheKeys := pluginProvider.cache.ListKeys()
716
717 if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
718 t.Logf("actual cache keys: %v", cacheKeys)
719 t.Logf("expected cache keys: %v", expectedCacheKeys)
720 t.Error("unexpected cache keys")
721 }
722
723
724
725 pluginProvider.plugin = nil
726 dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
727 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
728 t.Logf("actual docker config: %v", dockerConfig)
729 t.Logf("expected docker config: %v", expectedDockerConfig)
730 t.Fatal("unexpected docker config")
731 }
732 }
733
734 func Test_NoCacheResponse(t *testing.T) {
735 tclock := clock.RealClock{}
736 pluginProvider := &pluginProvider{
737 clock: tclock,
738 lastCachePurge: tclock.Now(),
739 matchImages: []string{"*.registry.io"},
740 cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
741 plugin: &fakeExecPlugin{
742 cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
743 cacheDuration: 0,
744 auth: map[string]credentialproviderapi.AuthConfig{
745 "*.registry.io": {
746 Username: "user",
747 Password: "password",
748 },
749 },
750 },
751 }
752
753 expectedDockerConfig := credentialprovider.DockerConfig{
754 "*.registry.io": credentialprovider.DockerConfigEntry{
755 Username: "user",
756 Password: "password",
757 },
758 }
759
760 dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
761 if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
762 t.Logf("actual docker config: %v", dockerConfig)
763 t.Logf("expected docker config: %v", expectedDockerConfig)
764 t.Fatal("unexpected docker config")
765 }
766
767 expectedCacheKeys := []string{}
768 cacheKeys := pluginProvider.cache.ListKeys()
769 if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
770 t.Logf("actual cache keys: %v", cacheKeys)
771 t.Logf("expected cache keys: %v", expectedCacheKeys)
772 t.Error("unexpected cache keys")
773 }
774 }
775
776 func Test_ExecPluginEnvVars(t *testing.T) {
777 testcases := []struct {
778 name string
779 systemEnvVars []string
780 execPlugin *execPlugin
781 expectedEnvVars []string
782 }{
783 {
784 name: "positive append system env vars",
785 systemEnvVars: []string{"HOME=/home/foo", "PATH=/usr/bin"},
786 execPlugin: &execPlugin{
787 envVars: []kubeletconfig.ExecEnvVar{
788 {
789 Name: "SUPER_SECRET_STRONG_ACCESS_KEY",
790 Value: "123456789",
791 },
792 },
793 },
794 expectedEnvVars: []string{
795 "HOME=/home/foo",
796 "PATH=/usr/bin",
797 "SUPER_SECRET_STRONG_ACCESS_KEY=123456789",
798 },
799 },
800 {
801 name: "positive no env vars provided in plugin",
802 systemEnvVars: []string{"HOME=/home/foo", "PATH=/usr/bin"},
803 execPlugin: &execPlugin{},
804 expectedEnvVars: []string{
805 "HOME=/home/foo",
806 "PATH=/usr/bin",
807 },
808 },
809 {
810 name: "positive no system env vars but env vars are provided in plugin",
811 execPlugin: &execPlugin{
812 envVars: []kubeletconfig.ExecEnvVar{
813 {
814 Name: "SUPER_SECRET_STRONG_ACCESS_KEY",
815 Value: "123456789",
816 },
817 },
818 },
819 expectedEnvVars: []string{
820 "SUPER_SECRET_STRONG_ACCESS_KEY=123456789",
821 },
822 },
823 {
824 name: "positive no system or plugin provided env vars",
825 execPlugin: &execPlugin{},
826 expectedEnvVars: nil,
827 },
828 {
829 name: "positive plugin provided vars takes priority",
830 systemEnvVars: []string{"HOME=/home/foo", "PATH=/usr/bin", "SUPER_SECRET_STRONG_ACCESS_KEY=1111"},
831 execPlugin: &execPlugin{
832 envVars: []kubeletconfig.ExecEnvVar{
833 {
834 Name: "SUPER_SECRET_STRONG_ACCESS_KEY",
835 Value: "123456789",
836 },
837 },
838 },
839 expectedEnvVars: []string{
840 "HOME=/home/foo",
841 "PATH=/usr/bin",
842 "SUPER_SECRET_STRONG_ACCESS_KEY=1111",
843 "SUPER_SECRET_STRONG_ACCESS_KEY=123456789",
844 },
845 },
846 }
847
848 for _, testcase := range testcases {
849 t.Run(testcase.name, func(t *testing.T) {
850 testcase.execPlugin.environ = func() []string {
851 return testcase.systemEnvVars
852 }
853
854 var configVars []string
855 for _, envVar := range testcase.execPlugin.envVars {
856 configVars = append(configVars, fmt.Sprintf("%s=%s", envVar.Name, envVar.Value))
857 }
858 merged := mergeEnvVars(testcase.systemEnvVars, configVars)
859
860 err := validate(testcase.expectedEnvVars, merged)
861 if err != nil {
862 t.Logf("unexpecged error %v", err)
863 }
864 })
865 }
866 }
867
868 func validate(expected, actual []string) error {
869 if len(actual) != len(expected) {
870 return fmt.Errorf("actual env var length [%d] and expected env var length [%d] don't match",
871 len(actual), len(expected))
872 }
873
874 for i := range actual {
875 if actual[i] != expected[i] {
876 return fmt.Errorf("mismatch in expected env var %s and actual env var %s", actual[i], expected[i])
877 }
878 }
879
880 return nil
881 }
882
View as plain text