...

Source file src/k8s.io/client-go/tools/clientcmd/client_config_test.go

Documentation: k8s.io/client-go/tools/clientcmd

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

View as plain text