k8s.io/client-go@v0.22.2/tools/clientcmd/client_config_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package clientcmd
    18  
    19  import (
    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  
    33  func 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  
   113  func 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  
   135  func createCAValidTestConfig() *clientcmdapi.Config {
   136  
   137  	config := createValidTestConfig()
   138  	config.Clusters["clean"].CertificateAuthorityData = []byte{0, 0}
   139  	return config
   140  }
   141  
   142  func 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  
   160  func 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  
   184  func 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  
   203  func 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  
   220  func 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  
   253  func 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  
   301  func 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  
   334  func 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  
   412  func 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  
   442  func 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  
   478  func 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  
   516  func 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  
   532  func 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  
   570  func 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  
   585  func 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  
   602  func 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  
   612  func 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  
   628  func 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  
   649  func 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  
   793  func matchBoolArg(expected, got bool, t *testing.T) {
   794  	if expected != got {
   795  		t.Errorf("Expected %v, got %v", expected, got)
   796  	}
   797  }
   798  
   799  func matchStringArg(expected, got string, t *testing.T) {
   800  	if expected != got {
   801  		t.Errorf("Expected %q, got %q", expected, got)
   802  	}
   803  }
   804  
   805  func 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  
   811  func 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  
   833  func TestAuthConfigMerge(t *testing.T) {
   834  	content := `
   835  apiVersion: v1
   836  clusters:
   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
   845  contexts:
   846  - context:
   847      cluster: foo-cluster
   848      user: foo-user
   849      namespace: bar
   850    name: foo-context
   851  current-context: foo-context
   852  kind: Config
   853  users:
   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  
   891  func 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  }