1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package clientcmd
18
19import (
20	"io/ioutil"
21	"os"
22	"reflect"
23	"strings"
24	"testing"
25
26	"github.com/imdario/mergo"
27
28	"k8s.io/apimachinery/pkg/runtime"
29	restclient "k8s.io/client-go/rest"
30	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
31)
32
33func TestMergoSemantics(t *testing.T) {
34	type U struct {
35		A string
36		B int64
37	}
38	type T struct {
39		S []string
40		X string
41		Y int64
42		U U
43	}
44	var testDataStruct = []struct {
45		dst      T
46		src      T
47		expected T
48	}{
49		{
50			dst:      T{X: "one"},
51			src:      T{X: "two"},
52			expected: T{X: "two"},
53		},
54		{
55			dst:      T{X: "one", Y: 5, U: U{A: "four", B: 6}},
56			src:      T{X: "two", U: U{A: "three", B: 4}},
57			expected: T{X: "two", Y: 5, U: U{A: "three", B: 4}},
58		},
59		{
60			dst:      T{S: []string{"test3", "test4", "test5"}},
61			src:      T{S: []string{"test1", "test2", "test3"}},
62			expected: T{S: []string{"test1", "test2", "test3"}},
63		},
64	}
65	for _, data := range testDataStruct {
66		err := mergo.Merge(&data.dst, &data.src, mergo.WithOverride)
67		if err != nil {
68			t.Errorf("error while merging: %s", err)
69		}
70		if !reflect.DeepEqual(data.dst, data.expected) {
71			// The mergo library has previously changed in a an incompatible way.
72			// example:
73			//
74			//   https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
75			//
76			// This test verifies that the semantics of the merge are what we expect.
77			// If they are not, the mergo library may have been updated and broken
78			// unexpectedly.
79			t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
80		}
81	}
82
83	var testDataMap = []struct {
84		dst      map[string]int
85		src      map[string]int
86		expected map[string]int
87	}{
88		{
89			dst:      map[string]int{"rsc": 6543, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
90			src:      map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912},
91			expected: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
92		},
93	}
94	for _, data := range testDataMap {
95		err := mergo.Merge(&data.dst, &data.src, mergo.WithOverride)
96		if err != nil {
97			t.Errorf("error while merging: %s", err)
98		}
99		if !reflect.DeepEqual(data.dst, data.expected) {
100			// The mergo library has previously changed in a an incompatible way.
101			// example:
102			//
103			//   https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
104			//
105			// This test verifies that the semantics of the merge are what we expect.
106			// If they are not, the mergo library may have been updated and broken
107			// unexpectedly.
108			t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
109		}
110	}
111}
112
113func createValidTestConfig() *clientcmdapi.Config {
114	const (
115		server = "https://anything.com:8080"
116		token  = "the-token"
117	)
118
119	config := clientcmdapi.NewConfig()
120	config.Clusters["clean"] = &clientcmdapi.Cluster{
121		Server: server,
122	}
123	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
124		Token: token,
125	}
126	config.Contexts["clean"] = &clientcmdapi.Context{
127		Cluster:  "clean",
128		AuthInfo: "clean",
129	}
130	config.CurrentContext = "clean"
131
132	return config
133}
134
135func createCAValidTestConfig() *clientcmdapi.Config {
136
137	config := createValidTestConfig()
138	config.Clusters["clean"].CertificateAuthorityData = []byte{0, 0}
139	return config
140}
141
142func TestInsecureOverridesCA(t *testing.T) {
143	config := createCAValidTestConfig()
144	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
145		ClusterInfo: clientcmdapi.Cluster{
146			InsecureSkipTLSVerify: true,
147		},
148	}, nil)
149
150	actualCfg, err := clientBuilder.ClientConfig()
151	if err != nil {
152		t.Fatalf("Unexpected error: %v", err)
153	}
154
155	matchBoolArg(true, actualCfg.Insecure, t)
156	matchStringArg("", actualCfg.TLSClientConfig.CAFile, t)
157	matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t)
158}
159
160func TestCAOverridesCAData(t *testing.T) {
161	file, err := ioutil.TempFile("", "my.ca")
162	if err != nil {
163		t.Fatalf("could not create tempfile: %v", err)
164	}
165	defer os.Remove(file.Name())
166
167	config := createCAValidTestConfig()
168	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
169		ClusterInfo: clientcmdapi.Cluster{
170			CertificateAuthority: file.Name(),
171		},
172	}, nil)
173
174	actualCfg, err := clientBuilder.ClientConfig()
175	if err != nil {
176		t.Fatalf("Unexpected error: %v", err)
177	}
178
179	matchBoolArg(false, actualCfg.Insecure, t)
180	matchStringArg(file.Name(), actualCfg.TLSClientConfig.CAFile, t)
181	matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t)
182}
183
184func TestTLSServerName(t *testing.T) {
185	config := createValidTestConfig()
186
187	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
188		ClusterInfo: clientcmdapi.Cluster{
189			TLSServerName: "overridden-server-name",
190		},
191	}, nil)
192
193	actualCfg, err := clientBuilder.ClientConfig()
194	if err != nil {
195		t.Errorf("Unexpected error: %v", err)
196	}
197
198	matchStringArg("overridden-server-name", actualCfg.ServerName, t)
199	matchStringArg("", actualCfg.TLSClientConfig.CAFile, t)
200	matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t)
201}
202
203func TestTLSServerNameClearsWhenServerNameSet(t *testing.T) {
204	config := createValidTestConfig()
205
206	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
207		ClusterInfo: clientcmdapi.Cluster{
208			Server: "http://something",
209		},
210	}, nil)
211
212	actualCfg, err := clientBuilder.ClientConfig()
213	if err != nil {
214		t.Errorf("Unexpected error: %v", err)
215	}
216
217	matchStringArg("", actualCfg.ServerName, t)
218}
219
220func TestMergeContext(t *testing.T) {
221	const namespace = "overridden-namespace"
222
223	config := createValidTestConfig()
224	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
225
226	_, overridden, err := clientBuilder.Namespace()
227	if err != nil {
228		t.Errorf("Unexpected error: %v", err)
229	}
230
231	if overridden {
232		t.Error("Expected namespace to not be overridden")
233	}
234
235	clientBuilder = NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
236		Context: clientcmdapi.Context{
237			Namespace: namespace,
238		},
239	}, nil)
240
241	actual, overridden, err := clientBuilder.Namespace()
242	if err != nil {
243		t.Errorf("Unexpected error: %v", err)
244	}
245
246	if !overridden {
247		t.Error("Expected namespace to be overridden")
248	}
249
250	matchStringArg(namespace, actual, t)
251}
252
253func TestModifyContext(t *testing.T) {
254	expectedCtx := map[string]bool{
255		"updated": true,
256		"clean":   true,
257	}
258
259	tempPath, err := ioutil.TempFile("", "testclientcmd-")
260	if err != nil {
261		t.Fatalf("unexpected error: %v", err)
262	}
263	defer os.Remove(tempPath.Name())
264
265	pathOptions := NewDefaultPathOptions()
266	config := createValidTestConfig()
267
268	pathOptions.GlobalFile = tempPath.Name()
269
270	// define new context and assign it - our path options config
271	config.Contexts["updated"] = &clientcmdapi.Context{
272		Cluster:  "updated",
273		AuthInfo: "updated",
274	}
275	config.CurrentContext = "updated"
276
277	if err := ModifyConfig(pathOptions, *config, true); err != nil {
278		t.Errorf("Unexpected error: %v", err)
279	}
280
281	startingConfig, err := pathOptions.GetStartingConfig()
282	if err != nil {
283		t.Fatalf("Unexpected error: %v", err)
284	}
285
286	// make sure the current context was updated
287	matchStringArg("updated", startingConfig.CurrentContext, t)
288
289	// there should now be two contexts
290	if len(startingConfig.Contexts) != len(expectedCtx) {
291		t.Fatalf("unexpected number of contexts, expecting %v, but found %v", len(expectedCtx), len(startingConfig.Contexts))
292	}
293
294	for key := range startingConfig.Contexts {
295		if !expectedCtx[key] {
296			t.Fatalf("expected context %q to exist", key)
297		}
298	}
299}
300
301func TestCertificateData(t *testing.T) {
302	caData := []byte("ca-data")
303	certData := []byte("cert-data")
304	keyData := []byte("key-data")
305
306	config := clientcmdapi.NewConfig()
307	config.Clusters["clean"] = &clientcmdapi.Cluster{
308		Server:                   "https://localhost:8443",
309		CertificateAuthorityData: caData,
310	}
311	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
312		ClientCertificateData: certData,
313		ClientKeyData:         keyData,
314	}
315	config.Contexts["clean"] = &clientcmdapi.Context{
316		Cluster:  "clean",
317		AuthInfo: "clean",
318	}
319	config.CurrentContext = "clean"
320
321	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
322
323	clientConfig, err := clientBuilder.ClientConfig()
324	if err != nil {
325		t.Fatalf("Unexpected error: %v", err)
326	}
327
328	// Make sure cert data gets into config (will override file paths)
329	matchByteArg(caData, clientConfig.TLSClientConfig.CAData, t)
330	matchByteArg(certData, clientConfig.TLSClientConfig.CertData, t)
331	matchByteArg(keyData, clientConfig.TLSClientConfig.KeyData, t)
332}
333
334func TestProxyURL(t *testing.T) {
335	tests := []struct {
336		desc      string
337		proxyURL  string
338		expectErr bool
339	}{
340		{
341			desc: "no proxy-url",
342		},
343		{
344			desc:     "socks5 proxy-url",
345			proxyURL: "socks5://example.com",
346		},
347		{
348			desc:     "https proxy-url",
349			proxyURL: "https://example.com",
350		},
351		{
352			desc:     "http proxy-url",
353			proxyURL: "http://example.com",
354		},
355		{
356			desc:      "bad scheme proxy-url",
357			proxyURL:  "socks6://example.com",
358			expectErr: true,
359		},
360		{
361			desc:      "no scheme proxy-url",
362			proxyURL:  "example.com",
363			expectErr: true,
364		},
365		{
366			desc:      "not a url proxy-url",
367			proxyURL:  "chewbacca@example.com",
368			expectErr: true,
369		},
370	}
371
372	for _, test := range tests {
373		t.Run(test.proxyURL, func(t *testing.T) {
374
375			config := clientcmdapi.NewConfig()
376			config.Clusters["clean"] = &clientcmdapi.Cluster{
377				Server:   "https://localhost:8443",
378				ProxyURL: test.proxyURL,
379			}
380			config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{}
381			config.Contexts["clean"] = &clientcmdapi.Context{
382				Cluster:  "clean",
383				AuthInfo: "clean",
384			}
385			config.CurrentContext = "clean"
386
387			clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
388
389			clientConfig, err := clientBuilder.ClientConfig()
390			if test.expectErr {
391				if err == nil {
392					t.Fatal("Expected error constructing config")
393				}
394				return
395			}
396			if err != nil {
397				t.Fatalf("Unexpected error constructing config: %v", err)
398			}
399
400			if test.proxyURL == "" {
401				return
402			}
403			gotURL, err := clientConfig.Proxy(nil)
404			if err != nil {
405				t.Fatalf("Unexpected error from proxier: %v", err)
406			}
407			matchStringArg(test.proxyURL, gotURL.String(), t)
408		})
409	}
410}
411
412func TestBasicAuthData(t *testing.T) {
413	username := "myuser"
414	password := "mypass" // Fake value for testing.
415
416	config := clientcmdapi.NewConfig()
417	config.Clusters["clean"] = &clientcmdapi.Cluster{
418		Server: "https://localhost:8443",
419	}
420	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
421		Username: username,
422		Password: password,
423	}
424	config.Contexts["clean"] = &clientcmdapi.Context{
425		Cluster:  "clean",
426		AuthInfo: "clean",
427	}
428	config.CurrentContext = "clean"
429
430	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
431
432	clientConfig, err := clientBuilder.ClientConfig()
433	if err != nil {
434		t.Fatalf("Unexpected error: %v", err)
435	}
436
437	// Make sure basic auth data gets into config
438	matchStringArg(username, clientConfig.Username, t)
439	matchStringArg(password, clientConfig.Password, t)
440}
441
442func TestBasicTokenFile(t *testing.T) {
443	token := "exampletoken"
444	f, err := ioutil.TempFile("", "tokenfile")
445	if err != nil {
446		t.Errorf("Unexpected error: %v", err)
447		return
448	}
449	defer os.Remove(f.Name())
450	if err := ioutil.WriteFile(f.Name(), []byte(token), 0644); err != nil {
451		t.Errorf("Unexpected error: %v", err)
452		return
453	}
454
455	config := clientcmdapi.NewConfig()
456	config.Clusters["clean"] = &clientcmdapi.Cluster{
457		Server: "https://localhost:8443",
458	}
459	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
460		TokenFile: f.Name(),
461	}
462	config.Contexts["clean"] = &clientcmdapi.Context{
463		Cluster:  "clean",
464		AuthInfo: "clean",
465	}
466	config.CurrentContext = "clean"
467
468	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
469
470	clientConfig, err := clientBuilder.ClientConfig()
471	if err != nil {
472		t.Fatalf("Unexpected error: %v", err)
473	}
474
475	matchStringArg(token, clientConfig.BearerToken, t)
476}
477
478func TestPrecedenceTokenFile(t *testing.T) {
479	token := "exampletoken"
480	f, err := ioutil.TempFile("", "tokenfile")
481	if err != nil {
482		t.Errorf("Unexpected error: %v", err)
483		return
484	}
485	defer os.Remove(f.Name())
486	if err := ioutil.WriteFile(f.Name(), []byte(token), 0644); err != nil {
487		t.Errorf("Unexpected error: %v", err)
488		return
489	}
490
491	config := clientcmdapi.NewConfig()
492	config.Clusters["clean"] = &clientcmdapi.Cluster{
493		Server: "https://localhost:8443",
494	}
495	expectedToken := "expected"
496	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
497		Token:     expectedToken,
498		TokenFile: f.Name(),
499	}
500	config.Contexts["clean"] = &clientcmdapi.Context{
501		Cluster:  "clean",
502		AuthInfo: "clean",
503	}
504	config.CurrentContext = "clean"
505
506	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
507
508	clientConfig, err := clientBuilder.ClientConfig()
509	if err != nil {
510		t.Fatalf("Unexpected error: %v", err)
511	}
512
513	matchStringArg(expectedToken, clientConfig.BearerToken, t)
514}
515
516func TestCreateClean(t *testing.T) {
517	config := createValidTestConfig()
518	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{}, nil)
519
520	clientConfig, err := clientBuilder.ClientConfig()
521	if err != nil {
522		t.Errorf("Unexpected error: %v", err)
523	}
524
525	matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t)
526	matchStringArg("", clientConfig.APIPath, t)
527	matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t)
528	matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t)
529	matchStringArg(config.Clusters["clean"].TLSServerName, clientConfig.ServerName, t)
530}
531
532func TestCreateCleanWithPrefix(t *testing.T) {
533	tt := []struct {
534		server string
535		host   string
536	}{
537		{"https://anything.com:8080/foo/bar", "https://anything.com:8080/foo/bar"},
538		{"http://anything.com:8080/foo/bar", "http://anything.com:8080/foo/bar"},
539		{"http://anything.com:8080/foo/bar/", "http://anything.com:8080/foo/bar/"},
540		{"http://anything.com:8080/", "http://anything.com:8080/"},
541		{"http://anything.com:8080//", "http://anything.com:8080//"},
542		{"anything.com:8080/foo/bar", "anything.com:8080/foo/bar"},
543		{"anything.com:8080", "anything.com:8080"},
544		{"anything.com", "anything.com"},
545		{"anything", "anything"},
546	}
547
548	tt = append(tt, struct{ server, host string }{"", "http://localhost:8080"})
549
550	for _, tc := range tt {
551		config := createValidTestConfig()
552
553		cleanConfig := config.Clusters["clean"]
554		cleanConfig.Server = tc.server
555		config.Clusters["clean"] = cleanConfig
556
557		clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
558			ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"},
559		}, nil)
560
561		clientConfig, err := clientBuilder.ClientConfig()
562		if err != nil {
563			t.Fatalf("Unexpected error: %v", err)
564		}
565
566		matchStringArg(tc.host, clientConfig.Host, t)
567	}
568}
569
570func TestCreateCleanDefault(t *testing.T) {
571	config := createValidTestConfig()
572	clientBuilder := NewDefaultClientConfig(*config, &ConfigOverrides{})
573
574	clientConfig, err := clientBuilder.ClientConfig()
575	if err != nil {
576		t.Fatalf("Unexpected error: %v", err)
577	}
578
579	matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t)
580	matchStringArg(config.Clusters["clean"].TLSServerName, clientConfig.ServerName, t)
581	matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t)
582	matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t)
583}
584
585func TestCreateCleanDefaultCluster(t *testing.T) {
586	config := createValidTestConfig()
587	clientBuilder := NewDefaultClientConfig(*config, &ConfigOverrides{
588		ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"},
589	})
590
591	clientConfig, err := clientBuilder.ClientConfig()
592	if err != nil {
593		t.Fatalf("Unexpected error: %v", err)
594	}
595
596	matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t)
597	matchStringArg(config.Clusters["clean"].TLSServerName, clientConfig.ServerName, t)
598	matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t)
599	matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t)
600}
601
602func TestCreateMissingContextNoDefault(t *testing.T) {
603	config := createValidTestConfig()
604	clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{}, nil)
605
606	_, err := clientBuilder.ClientConfig()
607	if err == nil {
608		t.Fatalf("Unexpected error: %v", err)
609	}
610}
611
612func TestCreateMissingContext(t *testing.T) {
613	const expectedErrorContains = "context was not found for specified context: not-present"
614	config := createValidTestConfig()
615	clientBuilder := NewNonInteractiveClientConfig(*config, "not-present", &ConfigOverrides{
616		ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"},
617	}, nil)
618
619	_, err := clientBuilder.ClientConfig()
620	if err == nil {
621		t.Fatalf("Expected error: %v", expectedErrorContains)
622	}
623	if !strings.Contains(err.Error(), expectedErrorContains) {
624		t.Fatalf("Expected error: %v, but got %v", expectedErrorContains, err)
625	}
626}
627
628func TestCreateAuthConfigExecInstallHintCleanup(t *testing.T) {
629	config := createValidTestConfig()
630	clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
631		AuthInfo: clientcmdapi.AuthInfo{
632			Exec: &clientcmdapi.ExecConfig{
633				APIVersion:      "client.authentication.k8s.io/v1alpha1",
634				Command:         "some-command",
635				InstallHint:     "some install hint with \x1b[1mcontrol chars\x1b[0m\nand a newline",
636				InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
637			},
638		},
639	}, nil)
640	cleanedInstallHint := "some install hint with U+001B[1mcontrol charsU+001B[0m\nand a newline"
641
642	clientConfig, err := clientBuilder.ClientConfig()
643	if err != nil {
644		t.Fatalf("Unexpected error: %v", err)
645	}
646	matchStringArg(cleanedInstallHint, clientConfig.ExecProvider.InstallHint, t)
647}
648
649func TestInClusterClientConfigPrecedence(t *testing.T) {
650	tt := []struct {
651		overrides *ConfigOverrides
652	}{
653		{
654			overrides: &ConfigOverrides{
655				ClusterInfo: clientcmdapi.Cluster{
656					Server: "https://host-from-overrides.com",
657				},
658			},
659		},
660		{
661			overrides: &ConfigOverrides{
662				AuthInfo: clientcmdapi.AuthInfo{
663					Token: "https://host-from-overrides.com",
664				},
665			},
666		},
667		{
668			overrides: &ConfigOverrides{
669				ClusterInfo: clientcmdapi.Cluster{
670					CertificateAuthority: "/path/to/ca-from-overrides.crt",
671				},
672			},
673		},
674		{
675			overrides: &ConfigOverrides{
676				ClusterInfo: clientcmdapi.Cluster{
677					Server: "https://host-from-overrides.com",
678				},
679				AuthInfo: clientcmdapi.AuthInfo{
680					Token: "https://host-from-overrides.com",
681				},
682			},
683		},
684		{
685			overrides: &ConfigOverrides{
686				ClusterInfo: clientcmdapi.Cluster{
687					Server:               "https://host-from-overrides.com",
688					CertificateAuthority: "/path/to/ca-from-overrides.crt",
689				},
690			},
691		},
692		{
693			overrides: &ConfigOverrides{
694				ClusterInfo: clientcmdapi.Cluster{
695					CertificateAuthority: "/path/to/ca-from-overrides.crt",
696				},
697				AuthInfo: clientcmdapi.AuthInfo{
698					Token: "https://host-from-overrides.com",
699				},
700			},
701		},
702		{
703			overrides: &ConfigOverrides{
704				ClusterInfo: clientcmdapi.Cluster{
705					Server:               "https://host-from-overrides.com",
706					CertificateAuthority: "/path/to/ca-from-overrides.crt",
707				},
708				AuthInfo: clientcmdapi.AuthInfo{
709					Token: "https://host-from-overrides.com",
710				},
711			},
712		},
713		{
714			overrides: &ConfigOverrides{
715				ClusterInfo: clientcmdapi.Cluster{
716					Server:               "https://host-from-overrides.com",
717					CertificateAuthority: "/path/to/ca-from-overrides.crt",
718				},
719				AuthInfo: clientcmdapi.AuthInfo{
720					Token:     "token-from-override",
721					TokenFile: "tokenfile-from-override",
722				},
723			},
724		},
725		{
726			overrides: &ConfigOverrides{
727				ClusterInfo: clientcmdapi.Cluster{
728					Server:               "https://host-from-overrides.com",
729					CertificateAuthority: "/path/to/ca-from-overrides.crt",
730				},
731				AuthInfo: clientcmdapi.AuthInfo{
732					Token:     "",
733					TokenFile: "tokenfile-from-override",
734				},
735			},
736		},
737		{
738			overrides: &ConfigOverrides{},
739		},
740	}
741
742	for _, tc := range tt {
743		expectedServer := "https://host-from-cluster.com"
744		expectedToken := "token-from-cluster"
745		expectedTokenFile := "tokenfile-from-cluster"
746		expectedCAFile := "/path/to/ca-from-cluster.crt"
747
748		icc := &inClusterClientConfig{
749			inClusterConfigProvider: func() (*restclient.Config, error) {
750				return &restclient.Config{
751					Host:            expectedServer,
752					BearerToken:     expectedToken,
753					BearerTokenFile: expectedTokenFile,
754					TLSClientConfig: restclient.TLSClientConfig{
755						CAFile: expectedCAFile,
756					},
757				}, nil
758			},
759			overrides: tc.overrides,
760		}
761
762		clientConfig, err := icc.ClientConfig()
763		if err != nil {
764			t.Fatalf("Unxpected error: %v", err)
765		}
766
767		if overridenServer := tc.overrides.ClusterInfo.Server; len(overridenServer) > 0 {
768			expectedServer = overridenServer
769		}
770		if len(tc.overrides.AuthInfo.Token) > 0 || len(tc.overrides.AuthInfo.TokenFile) > 0 {
771			expectedToken = tc.overrides.AuthInfo.Token
772			expectedTokenFile = tc.overrides.AuthInfo.TokenFile
773		}
774		if overridenCAFile := tc.overrides.ClusterInfo.CertificateAuthority; len(overridenCAFile) > 0 {
775			expectedCAFile = overridenCAFile
776		}
777
778		if clientConfig.Host != expectedServer {
779			t.Errorf("Expected server %v, got %v", expectedServer, clientConfig.Host)
780		}
781		if clientConfig.BearerToken != expectedToken {
782			t.Errorf("Expected token %v, got %v", expectedToken, clientConfig.BearerToken)
783		}
784		if clientConfig.BearerTokenFile != expectedTokenFile {
785			t.Errorf("Expected tokenfile %v, got %v", expectedTokenFile, clientConfig.BearerTokenFile)
786		}
787		if clientConfig.TLSClientConfig.CAFile != expectedCAFile {
788			t.Errorf("Expected Certificate Authority %v, got %v", expectedCAFile, clientConfig.TLSClientConfig.CAFile)
789		}
790	}
791}
792
793func matchBoolArg(expected, got bool, t *testing.T) {
794	if expected != got {
795		t.Errorf("Expected %v, got %v", expected, got)
796	}
797}
798
799func matchStringArg(expected, got string, t *testing.T) {
800	if expected != got {
801		t.Errorf("Expected %q, got %q", expected, got)
802	}
803}
804
805func matchByteArg(expected, got []byte, t *testing.T) {
806	if !reflect.DeepEqual(expected, got) {
807		t.Errorf("Expected %v, got %v", expected, got)
808	}
809}
810
811func TestNamespaceOverride(t *testing.T) {
812	config := &DirectClientConfig{
813		overrides: &ConfigOverrides{
814			Context: clientcmdapi.Context{
815				Namespace: "foo",
816			},
817		},
818	}
819
820	ns, overridden, err := config.Namespace()
821
822	if err != nil {
823		t.Errorf("Unexpected error: %v", err)
824	}
825
826	if !overridden {
827		t.Errorf("Expected overridden = true")
828	}
829
830	matchStringArg("foo", ns, t)
831}
832
833func TestAuthConfigMerge(t *testing.T) {
834	content := `
835apiVersion: v1
836clusters:
837- cluster:
838    server: https://localhost:8080
839    extensions:
840    - name: client.authentication.k8s.io/exec
841      extension:
842        audience: foo
843        other: bar
844  name: foo-cluster
845contexts:
846- context:
847    cluster: foo-cluster
848    user: foo-user
849    namespace: bar
850  name: foo-context
851current-context: foo-context
852kind: Config
853users:
854- name: foo-user
855  user:
856    exec:
857      apiVersion: client.authentication.k8s.io/v1alpha1
858      args:
859      - arg-1
860      - arg-2
861      command: foo-command
862      provideClusterInfo: true
863`
864	tmpfile, err := ioutil.TempFile("", "kubeconfig")
865	if err != nil {
866		t.Error(err)
867	}
868	defer os.Remove(tmpfile.Name())
869	if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
870		t.Error(err)
871	}
872	config, err := BuildConfigFromFlags("", tmpfile.Name())
873	if err != nil {
874		t.Error(err)
875	}
876	if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
877		t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
878	}
879	if !config.ExecProvider.ProvideClusterInfo {
880		t.Error("Wanted provider cluster info to be true")
881	}
882	want := &runtime.Unknown{
883		Raw:         []byte(`{"audience":"foo","other":"bar"}`),
884		ContentType: "application/json",
885	}
886	if !reflect.DeepEqual(config.ExecProvider.Config, want) {
887		t.Errorf("Got config %v when it should be %v\n", config.ExecProvider.Config, want)
888	}
889}
890
891func TestCleanANSIEscapeCodes(t *testing.T) {
892	tests := []struct {
893		name    string
894		in, out string
895	}{
896		{
897			name: "DenyBoldCharacters",
898			in:   "\x1b[1mbold tuna\x1b[0m, fish, \x1b[1mbold marlin\x1b[0m",
899			out:  "U+001B[1mbold tunaU+001B[0m, fish, U+001B[1mbold marlinU+001B[0m",
900		},
901		{
902			name: "DenyCursorNavigation",
903			in:   "\x1b[2Aup up, \x1b[2Cright right",
904			out:  "U+001B[2Aup up, U+001B[2Cright right",
905		},
906		{
907			name: "DenyClearScreen",
908			in:   "clear: \x1b[2J",
909			out:  "clear: U+001B[2J",
910		},
911		{
912			name: "AllowSpaceCharactersUnchanged",
913			in:   "tuna\nfish\r\nmarlin\t\r\ntuna\vfish\fmarlin",
914		},
915		{
916			name: "AllowLetters",
917			in:   "alpha: \u03b1, beta: \u03b2, gamma: \u03b3",
918		},
919		{
920			name: "AllowMarks",
921			in: "tu\u0301na with a mark over the u, fi\u0302sh with a mark over the i," +
922				" ma\u030Arlin with a mark over the a",
923		},
924		{
925			name: "AllowNumbers",
926			in:   "t1na, f2sh, m3rlin, t12a, f34h, m56lin, t123, f456, m567n",
927		},
928		{
929			name: "AllowPunctuation",
930			in:   "\"here's a sentence; with! some...punctuation ;)\"",
931		},
932		{
933			name: "AllowSymbols",
934			in: "the integral of f(x) from 0 to n approximately equals the sum of f(x)" +
935				" from a = 0 to n, where a and n are natural numbers:" +
936				"\u222b\u2081\u207F f(x) dx \u2248 \u2211\u2090\u208C\u2081\u207F f(x)," +
937				" a \u2208 \u2115, n \u2208 \u2115",
938		},
939		{
940			name: "AllowSepatators",
941			in: "here is a paragraph separator\u2029and here\u2003are\u2003some" +
942				"\u2003em\u2003spaces",
943		},
944	}
945	for _, test := range tests {
946		t.Run(test.name, func(t *testing.T) {
947			if len(test.out) == 0 {
948				test.out = test.in
949			}
950
951			if actualOut := cleanANSIEscapeCodes(test.in); test.out != actualOut {
952				t.Errorf("expected %q, actual %q", test.out, actualOut)
953			}
954		})
955	}
956}
957