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.MergeWithOverwrite(&data.dst, &data.src)
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.MergeWithOverwrite(&data.dst, &data.src)
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			},
637		},
638	}, nil)
639	cleanedInstallHint := "some install hint with U+001B[1mcontrol charsU+001B[0m\nand a newline"
640
641	clientConfig, err := clientBuilder.ClientConfig()
642	if err != nil {
643		t.Fatalf("Unexpected error: %v", err)
644	}
645	matchStringArg(cleanedInstallHint, clientConfig.ExecProvider.InstallHint, t)
646}
647
648func TestInClusterClientConfigPrecedence(t *testing.T) {
649	tt := []struct {
650		overrides *ConfigOverrides
651	}{
652		{
653			overrides: &ConfigOverrides{
654				ClusterInfo: clientcmdapi.Cluster{
655					Server: "https://host-from-overrides.com",
656				},
657			},
658		},
659		{
660			overrides: &ConfigOverrides{
661				AuthInfo: clientcmdapi.AuthInfo{
662					Token: "https://host-from-overrides.com",
663				},
664			},
665		},
666		{
667			overrides: &ConfigOverrides{
668				ClusterInfo: clientcmdapi.Cluster{
669					CertificateAuthority: "/path/to/ca-from-overrides.crt",
670				},
671			},
672		},
673		{
674			overrides: &ConfigOverrides{
675				ClusterInfo: clientcmdapi.Cluster{
676					Server: "https://host-from-overrides.com",
677				},
678				AuthInfo: clientcmdapi.AuthInfo{
679					Token: "https://host-from-overrides.com",
680				},
681			},
682		},
683		{
684			overrides: &ConfigOverrides{
685				ClusterInfo: clientcmdapi.Cluster{
686					Server:               "https://host-from-overrides.com",
687					CertificateAuthority: "/path/to/ca-from-overrides.crt",
688				},
689			},
690		},
691		{
692			overrides: &ConfigOverrides{
693				ClusterInfo: clientcmdapi.Cluster{
694					CertificateAuthority: "/path/to/ca-from-overrides.crt",
695				},
696				AuthInfo: clientcmdapi.AuthInfo{
697					Token: "https://host-from-overrides.com",
698				},
699			},
700		},
701		{
702			overrides: &ConfigOverrides{
703				ClusterInfo: clientcmdapi.Cluster{
704					Server:               "https://host-from-overrides.com",
705					CertificateAuthority: "/path/to/ca-from-overrides.crt",
706				},
707				AuthInfo: clientcmdapi.AuthInfo{
708					Token: "https://host-from-overrides.com",
709				},
710			},
711		},
712		{
713			overrides: &ConfigOverrides{
714				ClusterInfo: clientcmdapi.Cluster{
715					Server:               "https://host-from-overrides.com",
716					CertificateAuthority: "/path/to/ca-from-overrides.crt",
717				},
718				AuthInfo: clientcmdapi.AuthInfo{
719					Token:     "token-from-override",
720					TokenFile: "tokenfile-from-override",
721				},
722			},
723		},
724		{
725			overrides: &ConfigOverrides{
726				ClusterInfo: clientcmdapi.Cluster{
727					Server:               "https://host-from-overrides.com",
728					CertificateAuthority: "/path/to/ca-from-overrides.crt",
729				},
730				AuthInfo: clientcmdapi.AuthInfo{
731					Token:     "",
732					TokenFile: "tokenfile-from-override",
733				},
734			},
735		},
736		{
737			overrides: &ConfigOverrides{},
738		},
739	}
740
741	for _, tc := range tt {
742		expectedServer := "https://host-from-cluster.com"
743		expectedToken := "token-from-cluster"
744		expectedTokenFile := "tokenfile-from-cluster"
745		expectedCAFile := "/path/to/ca-from-cluster.crt"
746
747		icc := &inClusterClientConfig{
748			inClusterConfigProvider: func() (*restclient.Config, error) {
749				return &restclient.Config{
750					Host:            expectedServer,
751					BearerToken:     expectedToken,
752					BearerTokenFile: expectedTokenFile,
753					TLSClientConfig: restclient.TLSClientConfig{
754						CAFile: expectedCAFile,
755					},
756				}, nil
757			},
758			overrides: tc.overrides,
759		}
760
761		clientConfig, err := icc.ClientConfig()
762		if err != nil {
763			t.Fatalf("Unxpected error: %v", err)
764		}
765
766		if overridenServer := tc.overrides.ClusterInfo.Server; len(overridenServer) > 0 {
767			expectedServer = overridenServer
768		}
769		if len(tc.overrides.AuthInfo.Token) > 0 || len(tc.overrides.AuthInfo.TokenFile) > 0 {
770			expectedToken = tc.overrides.AuthInfo.Token
771			expectedTokenFile = tc.overrides.AuthInfo.TokenFile
772		}
773		if overridenCAFile := tc.overrides.ClusterInfo.CertificateAuthority; len(overridenCAFile) > 0 {
774			expectedCAFile = overridenCAFile
775		}
776
777		if clientConfig.Host != expectedServer {
778			t.Errorf("Expected server %v, got %v", expectedServer, clientConfig.Host)
779		}
780		if clientConfig.BearerToken != expectedToken {
781			t.Errorf("Expected token %v, got %v", expectedToken, clientConfig.BearerToken)
782		}
783		if clientConfig.BearerTokenFile != expectedTokenFile {
784			t.Errorf("Expected tokenfile %v, got %v", expectedTokenFile, clientConfig.BearerTokenFile)
785		}
786		if clientConfig.TLSClientConfig.CAFile != expectedCAFile {
787			t.Errorf("Expected Certificate Authority %v, got %v", expectedCAFile, clientConfig.TLSClientConfig.CAFile)
788		}
789	}
790}
791
792func matchBoolArg(expected, got bool, t *testing.T) {
793	if expected != got {
794		t.Errorf("Expected %v, got %v", expected, got)
795	}
796}
797
798func matchStringArg(expected, got string, t *testing.T) {
799	if expected != got {
800		t.Errorf("Expected %q, got %q", expected, got)
801	}
802}
803
804func matchByteArg(expected, got []byte, t *testing.T) {
805	if !reflect.DeepEqual(expected, got) {
806		t.Errorf("Expected %v, got %v", expected, got)
807	}
808}
809
810func TestNamespaceOverride(t *testing.T) {
811	config := &DirectClientConfig{
812		overrides: &ConfigOverrides{
813			Context: clientcmdapi.Context{
814				Namespace: "foo",
815			},
816		},
817	}
818
819	ns, overridden, err := config.Namespace()
820
821	if err != nil {
822		t.Errorf("Unexpected error: %v", err)
823	}
824
825	if !overridden {
826		t.Errorf("Expected overridden = true")
827	}
828
829	matchStringArg("foo", ns, t)
830}
831
832func TestAuthConfigMerge(t *testing.T) {
833	content := `
834apiVersion: v1
835clusters:
836- cluster:
837    server: https://localhost:8080
838    extensions:
839    - name: client.authentication.k8s.io/exec
840      extension:
841        audience: foo
842        other: bar
843  name: foo-cluster
844contexts:
845- context:
846    cluster: foo-cluster
847    user: foo-user
848    namespace: bar
849  name: foo-context
850current-context: foo-context
851kind: Config
852users:
853- name: foo-user
854  user:
855    exec:
856      apiVersion: client.authentication.k8s.io/v1alpha1
857      args:
858      - arg-1
859      - arg-2
860      command: foo-command
861      provideClusterInfo: true
862`
863	tmpfile, err := ioutil.TempFile("", "kubeconfig")
864	if err != nil {
865		t.Error(err)
866	}
867	defer os.Remove(tmpfile.Name())
868	if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
869		t.Error(err)
870	}
871	config, err := BuildConfigFromFlags("", tmpfile.Name())
872	if err != nil {
873		t.Error(err)
874	}
875	if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
876		t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
877	}
878	if !config.ExecProvider.ProvideClusterInfo {
879		t.Error("Wanted provider cluster info to be true")
880	}
881	want := &runtime.Unknown{
882		Raw:         []byte(`{"audience":"foo","other":"bar"}`),
883		ContentType: "application/json",
884	}
885	if !reflect.DeepEqual(config.ExecProvider.Config, want) {
886		t.Errorf("Got config %v when it should be %v\n", config.ExecProvider.Config, want)
887	}
888}
889
890func TestCleanANSIEscapeCodes(t *testing.T) {
891	tests := []struct {
892		name    string
893		in, out string
894	}{
895		{
896			name: "DenyBoldCharacters",
897			in:   "\x1b[1mbold tuna\x1b[0m, fish, \x1b[1mbold marlin\x1b[0m",
898			out:  "U+001B[1mbold tunaU+001B[0m, fish, U+001B[1mbold marlinU+001B[0m",
899		},
900		{
901			name: "DenyCursorNavigation",
902			in:   "\x1b[2Aup up, \x1b[2Cright right",
903			out:  "U+001B[2Aup up, U+001B[2Cright right",
904		},
905		{
906			name: "DenyClearScreen",
907			in:   "clear: \x1b[2J",
908			out:  "clear: U+001B[2J",
909		},
910		{
911			name: "AllowSpaceCharactersUnchanged",
912			in:   "tuna\nfish\r\nmarlin\t\r\ntuna\vfish\fmarlin",
913		},
914		{
915			name: "AllowLetters",
916			in:   "alpha: \u03b1, beta: \u03b2, gamma: \u03b3",
917		},
918		{
919			name: "AllowMarks",
920			in: "tu\u0301na with a mark over the u, fi\u0302sh with a mark over the i," +
921				" ma\u030Arlin with a mark over the a",
922		},
923		{
924			name: "AllowNumbers",
925			in:   "t1na, f2sh, m3rlin, t12a, f34h, m56lin, t123, f456, m567n",
926		},
927		{
928			name: "AllowPunctuation",
929			in:   "\"here's a sentence; with! some...punctuation ;)\"",
930		},
931		{
932			name: "AllowSymbols",
933			in: "the integral of f(x) from 0 to n approximately equals the sum of f(x)" +
934				" from a = 0 to n, where a and n are natural numbers:" +
935				"\u222b\u2081\u207F f(x) dx \u2248 \u2211\u2090\u208C\u2081\u207F f(x)," +
936				" a \u2208 \u2115, n \u2208 \u2115",
937		},
938		{
939			name: "AllowSepatators",
940			in: "here is a paragraph separator\u2029and here\u2003are\u2003some" +
941				"\u2003em\u2003spaces",
942		},
943	}
944	for _, test := range tests {
945		t.Run(test.name, func(t *testing.T) {
946			if len(test.out) == 0 {
947				test.out = test.in
948			}
949
950			if actualOut := cleanANSIEscapeCodes(test.in); test.out != actualOut {
951				t.Errorf("expected %q, actual %q", test.out, actualOut)
952			}
953		})
954	}
955}
956