...

Source file src/k8s.io/kubectl/pkg/cmd/config/config_test.go

Documentation: k8s.io/kubectl/pkg/cmd/config

     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 config
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    29  	"k8s.io/cli-runtime/pkg/genericiooptions"
    30  	"k8s.io/client-go/tools/clientcmd"
    31  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    32  	utiltesting "k8s.io/client-go/util/testing"
    33  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    34  )
    35  
    36  func newRedFederalCowHammerConfig() clientcmdapi.Config {
    37  	return clientcmdapi.Config{
    38  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
    39  			"red-user": {Token: "red-token"}},
    40  		Clusters: map[string]*clientcmdapi.Cluster{
    41  			"cow-cluster": {Server: "http://cow.org:8080"}},
    42  		Contexts: map[string]*clientcmdapi.Context{
    43  			"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"}},
    44  		CurrentContext: "federal-context",
    45  	}
    46  }
    47  
    48  func Example_view() {
    49  	expectedConfig := newRedFederalCowHammerConfig()
    50  	test := configCommandTest{
    51  		args:           []string{"view"},
    52  		startingConfig: newRedFederalCowHammerConfig(),
    53  		expectedConfig: expectedConfig,
    54  	}
    55  
    56  	output := test.run(&testing.T{})
    57  	fmt.Printf("%v", output)
    58  	// Output:
    59  	// apiVersion: v1
    60  	// clusters:
    61  	// - cluster:
    62  	//     server: http://cow.org:8080
    63  	//   name: cow-cluster
    64  	// contexts:
    65  	// - context:
    66  	//     cluster: cow-cluster
    67  	//     user: red-user
    68  	//   name: federal-context
    69  	// current-context: federal-context
    70  	// kind: Config
    71  	// preferences: {}
    72  	// users:
    73  	// - name: red-user
    74  	//   user:
    75  	//     token: REDACTED
    76  }
    77  
    78  func TestCurrentContext(t *testing.T) {
    79  	startingConfig := newRedFederalCowHammerConfig()
    80  	test := configCommandTest{
    81  		args:            []string{"current-context"},
    82  		startingConfig:  startingConfig,
    83  		expectedConfig:  startingConfig,
    84  		expectedOutputs: []string{startingConfig.CurrentContext},
    85  	}
    86  	test.run(t)
    87  }
    88  
    89  func TestSetCurrentContext(t *testing.T) {
    90  	expectedConfig := newRedFederalCowHammerConfig()
    91  	startingConfig := newRedFederalCowHammerConfig()
    92  
    93  	newContextName := "the-new-context"
    94  
    95  	startingConfig.Contexts[newContextName] = clientcmdapi.NewContext()
    96  	expectedConfig.Contexts[newContextName] = clientcmdapi.NewContext()
    97  
    98  	expectedConfig.CurrentContext = newContextName
    99  
   100  	test := configCommandTest{
   101  		args:           []string{"use-context", "the-new-context"},
   102  		startingConfig: startingConfig,
   103  		expectedConfig: expectedConfig,
   104  	}
   105  
   106  	test.run(t)
   107  }
   108  
   109  func TestSetNonExistentContext(t *testing.T) {
   110  	expectedConfig := newRedFederalCowHammerConfig()
   111  
   112  	test := configCommandTest{
   113  		args:           []string{"use-context", "non-existent-config"},
   114  		startingConfig: expectedConfig,
   115  		expectedConfig: expectedConfig,
   116  	}
   117  
   118  	func() {
   119  		defer func() {
   120  			// Restore cmdutil behavior.
   121  			cmdutil.DefaultBehaviorOnFatal()
   122  		}()
   123  
   124  		// Check exit code.
   125  		cmdutil.BehaviorOnFatal(func(e string, code int) {
   126  			if code != 1 {
   127  				t.Errorf("The exit code is %d, expected 1", code)
   128  			}
   129  			expectedOutputs := []string{`no context exists with the name: "non-existent-config"`}
   130  			test.checkOutput(e, expectedOutputs, t)
   131  		})
   132  
   133  		test.run(t)
   134  	}()
   135  }
   136  
   137  func TestSetIntoExistingStruct(t *testing.T) {
   138  	expectedConfig := newRedFederalCowHammerConfig()
   139  	expectedConfig.AuthInfos["red-user"].Password = "new-path-value" // Fake value for testing.
   140  	test := configCommandTest{
   141  		args:           []string{"set", "users.red-user.password", "new-path-value"},
   142  		startingConfig: newRedFederalCowHammerConfig(),
   143  		expectedConfig: expectedConfig,
   144  	}
   145  
   146  	test.run(t)
   147  }
   148  
   149  func TestSetWithPathPrefixIntoExistingStruct(t *testing.T) {
   150  	expectedConfig := newRedFederalCowHammerConfig()
   151  	expectedConfig.Clusters["cow-cluster"].Server = "http://cow.org:8080/foo/baz"
   152  	test := configCommandTest{
   153  		args:           []string{"set", "clusters.cow-cluster.server", "http://cow.org:8080/foo/baz"},
   154  		startingConfig: newRedFederalCowHammerConfig(),
   155  		expectedConfig: expectedConfig,
   156  	}
   157  
   158  	test.run(t)
   159  
   160  	dc := clientcmd.NewDefaultClientConfig(expectedConfig, &clientcmd.ConfigOverrides{})
   161  	dcc, err := dc.ClientConfig()
   162  	if err != nil {
   163  		t.Fatalf("unexpected error: %v", err)
   164  	}
   165  	expectedHost := "http://cow.org:8080/foo/baz"
   166  	if expectedHost != dcc.Host {
   167  		t.Fatalf("expected client.Config.Host = %q instead of %q", expectedHost, dcc.Host)
   168  	}
   169  }
   170  
   171  func TestUnsetStruct(t *testing.T) {
   172  	expectedConfig := newRedFederalCowHammerConfig()
   173  	delete(expectedConfig.AuthInfos, "red-user")
   174  	test := configCommandTest{
   175  		args:           []string{"unset", "users.red-user"},
   176  		startingConfig: newRedFederalCowHammerConfig(),
   177  		expectedConfig: expectedConfig,
   178  	}
   179  
   180  	test.run(t)
   181  }
   182  
   183  func TestUnsetField(t *testing.T) {
   184  	expectedConfig := newRedFederalCowHammerConfig()
   185  	expectedConfig.AuthInfos["red-user"] = clientcmdapi.NewAuthInfo()
   186  	test := configCommandTest{
   187  		args:           []string{"unset", "users.red-user.token"},
   188  		startingConfig: newRedFederalCowHammerConfig(),
   189  		expectedConfig: expectedConfig,
   190  	}
   191  
   192  	test.run(t)
   193  }
   194  
   195  func TestSetIntoNewStruct(t *testing.T) {
   196  	expectedConfig := newRedFederalCowHammerConfig()
   197  	cluster := clientcmdapi.NewCluster()
   198  	cluster.Server = "new-server-value"
   199  	expectedConfig.Clusters["big-cluster"] = cluster
   200  	test := configCommandTest{
   201  		args:           []string{"set", "clusters.big-cluster.server", "new-server-value"},
   202  		startingConfig: newRedFederalCowHammerConfig(),
   203  		expectedConfig: expectedConfig,
   204  	}
   205  
   206  	test.run(t)
   207  }
   208  
   209  func TestSetBoolean(t *testing.T) {
   210  	expectedConfig := newRedFederalCowHammerConfig()
   211  	cluster := clientcmdapi.NewCluster()
   212  	cluster.InsecureSkipTLSVerify = true
   213  	expectedConfig.Clusters["big-cluster"] = cluster
   214  	test := configCommandTest{
   215  		args:           []string{"set", "clusters.big-cluster.insecure-skip-tls-verify", "true"},
   216  		startingConfig: newRedFederalCowHammerConfig(),
   217  		expectedConfig: expectedConfig,
   218  	}
   219  
   220  	test.run(t)
   221  }
   222  
   223  func TestSetIntoNewConfig(t *testing.T) {
   224  	expectedConfig := *clientcmdapi.NewConfig()
   225  	context := clientcmdapi.NewContext()
   226  	context.AuthInfo = "fake-user"
   227  	expectedConfig.Contexts["new-context"] = context
   228  	test := configCommandTest{
   229  		args:           []string{"set", "contexts.new-context.user", "fake-user"},
   230  		startingConfig: *clientcmdapi.NewConfig(),
   231  		expectedConfig: expectedConfig,
   232  	}
   233  
   234  	test.run(t)
   235  }
   236  
   237  func TestNewEmptyAuth(t *testing.T) {
   238  	expectedConfig := *clientcmdapi.NewConfig()
   239  	expectedConfig.AuthInfos["the-user-name"] = clientcmdapi.NewAuthInfo()
   240  	test := configCommandTest{
   241  		args:           []string{"set-credentials", "the-user-name"},
   242  		startingConfig: *clientcmdapi.NewConfig(),
   243  		expectedConfig: expectedConfig,
   244  	}
   245  
   246  	test.run(t)
   247  }
   248  
   249  func TestAdditionalAuth(t *testing.T) {
   250  	expectedConfig := newRedFederalCowHammerConfig()
   251  	authInfo := clientcmdapi.NewAuthInfo()
   252  	authInfo.Token = "token"
   253  	expectedConfig.AuthInfos["another-user"] = authInfo
   254  	test := configCommandTest{
   255  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
   256  		startingConfig: newRedFederalCowHammerConfig(),
   257  		expectedConfig: expectedConfig,
   258  	}
   259  
   260  	test.run(t)
   261  }
   262  
   263  func TestEmbedClientCert(t *testing.T) {
   264  	fakeCertFile, _ := os.CreateTemp(os.TempDir(), "")
   265  	defer utiltesting.CloseAndRemove(t, fakeCertFile)
   266  	fakeData := []byte("fake-data")
   267  	os.WriteFile(fakeCertFile.Name(), fakeData, 0600)
   268  	expectedConfig := newRedFederalCowHammerConfig()
   269  	authInfo := clientcmdapi.NewAuthInfo()
   270  	authInfo.ClientCertificateData = fakeData
   271  	expectedConfig.AuthInfos["another-user"] = authInfo
   272  
   273  	test := configCommandTest{
   274  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
   275  		startingConfig: newRedFederalCowHammerConfig(),
   276  		expectedConfig: expectedConfig,
   277  	}
   278  
   279  	test.run(t)
   280  }
   281  
   282  func TestExecPlugin(t *testing.T) {
   283  	fakeCertFile, _ := os.CreateTemp(os.TempDir(), "")
   284  	defer utiltesting.CloseAndRemove(t, fakeCertFile)
   285  	fakeData := []byte("fake-data")
   286  	err := os.WriteFile(fakeCertFile.Name(), fakeData, 0600)
   287  	if err != nil {
   288  		t.Errorf("unexpected error %v", err)
   289  	}
   290  	expectedConfig := newRedFederalCowHammerConfig()
   291  	authInfo := clientcmdapi.NewAuthInfo()
   292  	authInfo.Exec = &clientcmdapi.ExecConfig{
   293  		Command: "example-client-go-exec-plugin",
   294  		Args:    []string{"arg1", "arg2"},
   295  		Env: []clientcmdapi.ExecEnvVar{
   296  			{
   297  				Name:  "FOO",
   298  				Value: "bar",
   299  			},
   300  		},
   301  		APIVersion:         "client.authentication.k8s.io/v1",
   302  		ProvideClusterInfo: false,
   303  		InteractiveMode:    "Never",
   304  	}
   305  	expectedConfig.AuthInfos["cred-exec-user"] = authInfo
   306  
   307  	test := configCommandTest{
   308  		args: []string{
   309  			"set-credentials",
   310  			"cred-exec-user",
   311  			"--exec-api-version=client.authentication.k8s.io/v1",
   312  			"--exec-command=example-client-go-exec-plugin",
   313  			"--exec-arg=arg1,arg2",
   314  			"--exec-env=FOO=bar",
   315  			"--exec-interactive-mode=Never",
   316  		},
   317  		startingConfig: newRedFederalCowHammerConfig(),
   318  		expectedConfig: expectedConfig,
   319  	}
   320  
   321  	test.run(t)
   322  }
   323  
   324  func TestExecPluginWithProveClusterInfo(t *testing.T) {
   325  	fakeCertFile, _ := os.CreateTemp(os.TempDir(), "")
   326  	defer utiltesting.CloseAndRemove(t, fakeCertFile)
   327  	fakeData := []byte("fake-data")
   328  	err := os.WriteFile(fakeCertFile.Name(), fakeData, 0600)
   329  	if err != nil {
   330  		t.Errorf("unexpected error %v", err)
   331  	}
   332  	expectedConfig := newRedFederalCowHammerConfig()
   333  	authInfo := clientcmdapi.NewAuthInfo()
   334  	authInfo.Exec = &clientcmdapi.ExecConfig{
   335  		Command: "example-client-go-exec-plugin",
   336  		Args:    []string{"arg1", "arg2"},
   337  		Env: []clientcmdapi.ExecEnvVar{
   338  			{
   339  				Name:  "FOO",
   340  				Value: "bar",
   341  			},
   342  		},
   343  		APIVersion:         "client.authentication.k8s.io/v1",
   344  		ProvideClusterInfo: true,
   345  		InteractiveMode:    "Always",
   346  	}
   347  	expectedConfig.AuthInfos["cred-exec-user"] = authInfo
   348  
   349  	test := configCommandTest{
   350  		args: []string{
   351  			"set-credentials",
   352  			"cred-exec-user",
   353  			"--exec-api-version=client.authentication.k8s.io/v1",
   354  			"--exec-command=example-client-go-exec-plugin",
   355  			"--exec-arg=arg1,arg2",
   356  			"--exec-env=FOO=bar",
   357  			"--exec-interactive-mode=Always",
   358  			"--exec-provide-cluster-info=true",
   359  		},
   360  		startingConfig: newRedFederalCowHammerConfig(),
   361  		expectedConfig: expectedConfig,
   362  	}
   363  
   364  	test.run(t)
   365  }
   366  
   367  func TestEmbedClientKey(t *testing.T) {
   368  	fakeKeyFile, _ := os.CreateTemp(os.TempDir(), "")
   369  	defer utiltesting.CloseAndRemove(t, fakeKeyFile)
   370  	fakeData := []byte("fake-data")
   371  	os.WriteFile(fakeKeyFile.Name(), fakeData, 0600)
   372  	expectedConfig := newRedFederalCowHammerConfig()
   373  	authInfo := clientcmdapi.NewAuthInfo()
   374  	authInfo.ClientKeyData = fakeData
   375  	expectedConfig.AuthInfos["another-user"] = authInfo
   376  
   377  	test := configCommandTest{
   378  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagKeyFile + "=" + fakeKeyFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
   379  		startingConfig: newRedFederalCowHammerConfig(),
   380  		expectedConfig: expectedConfig,
   381  	}
   382  
   383  	test.run(t)
   384  }
   385  
   386  func TestEmbedNoKeyOrCertDisallowed(t *testing.T) {
   387  	expectedConfig := newRedFederalCowHammerConfig()
   388  	test := configCommandTest{
   389  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagEmbedCerts + "=true"},
   390  		startingConfig: newRedFederalCowHammerConfig(),
   391  		expectedConfig: expectedConfig,
   392  	}
   393  
   394  	func() {
   395  		defer func() {
   396  			// Restore cmdutil behavior.
   397  			cmdutil.DefaultBehaviorOnFatal()
   398  		}()
   399  
   400  		// Check exit code.
   401  		cmdutil.BehaviorOnFatal(func(e string, code int) {
   402  			if code != 1 {
   403  				t.Errorf("The exit code is %d, expected 1", code)
   404  			}
   405  			expectedOutputs := []string{"--client-certificate", "--client-key", "embed"}
   406  			test.checkOutput(e, expectedOutputs, t)
   407  		})
   408  
   409  		test.run(t)
   410  	}()
   411  }
   412  
   413  func TestEmptyTokenAndCertAllowed(t *testing.T) {
   414  	fakeCertFile, _ := os.CreateTemp(os.TempDir(), "cert-file")
   415  	defer utiltesting.CloseAndRemove(t, fakeCertFile)
   416  	expectedConfig := newRedFederalCowHammerConfig()
   417  	authInfo := clientcmdapi.NewAuthInfo()
   418  	authInfo.ClientCertificate = path.Base(fakeCertFile.Name())
   419  	expectedConfig.AuthInfos["another-user"] = authInfo
   420  
   421  	test := configCommandTest{
   422  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagBearerToken + "="},
   423  		startingConfig: newRedFederalCowHammerConfig(),
   424  		expectedConfig: expectedConfig,
   425  	}
   426  
   427  	test.run(t)
   428  }
   429  
   430  func TestTokenAndCertAllowed(t *testing.T) {
   431  	expectedConfig := newRedFederalCowHammerConfig()
   432  	authInfo := clientcmdapi.NewAuthInfo()
   433  	authInfo.Token = "token"
   434  	authInfo.ClientCertificate = "/cert-file"
   435  	expectedConfig.AuthInfos["another-user"] = authInfo
   436  	test := configCommandTest{
   437  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert-file", "--" + clientcmd.FlagBearerToken + "=token"},
   438  		startingConfig: newRedFederalCowHammerConfig(),
   439  		expectedConfig: expectedConfig,
   440  	}
   441  
   442  	test.run(t)
   443  }
   444  
   445  func TestTokenAndBasicDisallowed(t *testing.T) {
   446  	expectedConfig := newRedFederalCowHammerConfig()
   447  	test := configCommandTest{
   448  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagBearerToken + "=token"},
   449  		startingConfig: newRedFederalCowHammerConfig(),
   450  		expectedConfig: expectedConfig,
   451  	}
   452  
   453  	func() {
   454  		defer func() {
   455  			// Restore cmdutil behavior.
   456  			cmdutil.DefaultBehaviorOnFatal()
   457  		}()
   458  
   459  		// Check exit code.
   460  		cmdutil.BehaviorOnFatal(func(e string, code int) {
   461  			if code != 1 {
   462  				t.Errorf("The exit code is %d, expected 1", code)
   463  			}
   464  
   465  			expectedOutputs := []string{"--token", "--username"}
   466  			test.checkOutput(e, expectedOutputs, t)
   467  		})
   468  
   469  		test.run(t)
   470  	}()
   471  }
   472  
   473  func TestBasicClearsToken(t *testing.T) {
   474  	authInfoWithToken := clientcmdapi.NewAuthInfo()
   475  	authInfoWithToken.Token = "token"
   476  
   477  	authInfoWithBasic := clientcmdapi.NewAuthInfo()
   478  	authInfoWithBasic.Username = "myuser"
   479  	authInfoWithBasic.Password = "mypass" // Fake value for testing.
   480  
   481  	startingConfig := newRedFederalCowHammerConfig()
   482  	startingConfig.AuthInfos["another-user"] = authInfoWithToken
   483  
   484  	expectedConfig := newRedFederalCowHammerConfig()
   485  	expectedConfig.AuthInfos["another-user"] = authInfoWithBasic
   486  
   487  	test := configCommandTest{
   488  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagPassword + "=mypass"},
   489  		startingConfig: startingConfig,
   490  		expectedConfig: expectedConfig,
   491  	}
   492  
   493  	test.run(t)
   494  }
   495  
   496  func TestTokenClearsBasic(t *testing.T) {
   497  	authInfoWithBasic := clientcmdapi.NewAuthInfo()
   498  	authInfoWithBasic.Username = "myuser"
   499  	authInfoWithBasic.Password = "mypass" // Fake value for testing.
   500  
   501  	authInfoWithToken := clientcmdapi.NewAuthInfo()
   502  	authInfoWithToken.Token = "token"
   503  
   504  	startingConfig := newRedFederalCowHammerConfig()
   505  	startingConfig.AuthInfos["another-user"] = authInfoWithBasic
   506  
   507  	expectedConfig := newRedFederalCowHammerConfig()
   508  	expectedConfig.AuthInfos["another-user"] = authInfoWithToken
   509  
   510  	test := configCommandTest{
   511  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
   512  		startingConfig: startingConfig,
   513  		expectedConfig: expectedConfig,
   514  	}
   515  
   516  	test.run(t)
   517  }
   518  
   519  func TestTokenLeavesCert(t *testing.T) {
   520  	authInfoWithCerts := clientcmdapi.NewAuthInfo()
   521  	authInfoWithCerts.ClientCertificate = "cert"
   522  	authInfoWithCerts.ClientCertificateData = []byte("certdata")
   523  	authInfoWithCerts.ClientKey = "key"
   524  	authInfoWithCerts.ClientKeyData = []byte("keydata")
   525  
   526  	authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
   527  	authInfoWithTokenAndCerts.Token = "token"
   528  	authInfoWithTokenAndCerts.ClientCertificate = "cert"
   529  	authInfoWithTokenAndCerts.ClientCertificateData = []byte("certdata")
   530  	authInfoWithTokenAndCerts.ClientKey = "key"
   531  	authInfoWithTokenAndCerts.ClientKeyData = []byte("keydata")
   532  
   533  	startingConfig := newRedFederalCowHammerConfig()
   534  	startingConfig.AuthInfos["another-user"] = authInfoWithCerts
   535  
   536  	expectedConfig := newRedFederalCowHammerConfig()
   537  	expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts
   538  
   539  	test := configCommandTest{
   540  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
   541  		startingConfig: startingConfig,
   542  		expectedConfig: expectedConfig,
   543  	}
   544  
   545  	test.run(t)
   546  }
   547  
   548  func TestCertLeavesToken(t *testing.T) {
   549  	authInfoWithToken := clientcmdapi.NewAuthInfo()
   550  	authInfoWithToken.Token = "token"
   551  
   552  	authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
   553  	authInfoWithTokenAndCerts.Token = "token"
   554  	authInfoWithTokenAndCerts.ClientCertificate = "/cert"
   555  	authInfoWithTokenAndCerts.ClientKey = "/key"
   556  
   557  	startingConfig := newRedFederalCowHammerConfig()
   558  	startingConfig.AuthInfos["another-user"] = authInfoWithToken
   559  
   560  	expectedConfig := newRedFederalCowHammerConfig()
   561  	expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts
   562  
   563  	test := configCommandTest{
   564  		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert", "--" + clientcmd.FlagKeyFile + "=/key"},
   565  		startingConfig: startingConfig,
   566  		expectedConfig: expectedConfig,
   567  	}
   568  
   569  	test.run(t)
   570  }
   571  
   572  func TestSetBytesBad(t *testing.T) {
   573  	startingConfig := newRedFederalCowHammerConfig()
   574  	startingConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
   575  
   576  	test := configCommandTest{
   577  		args:           []string{"set", "clusters.another-cluster.certificate-authority-data", "cadata"},
   578  		startingConfig: startingConfig,
   579  		expectedConfig: startingConfig,
   580  	}
   581  
   582  	func() {
   583  		defer func() {
   584  			// Restore cmdutil behavior.
   585  			cmdutil.DefaultBehaviorOnFatal()
   586  		}()
   587  
   588  		// Check exit code.
   589  		cmdutil.BehaviorOnFatal(func(e string, code int) {
   590  			if code != 1 {
   591  				t.Errorf("The exit code is %d, expected 1", code)
   592  			}
   593  		})
   594  
   595  		test.run(t)
   596  	}()
   597  }
   598  
   599  func TestSetBytes(t *testing.T) {
   600  	clusterInfoWithCAData := clientcmdapi.NewCluster()
   601  	clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
   602  
   603  	startingConfig := newRedFederalCowHammerConfig()
   604  	startingConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
   605  
   606  	expectedConfig := newRedFederalCowHammerConfig()
   607  	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
   608  
   609  	test := configCommandTest{
   610  		args:           []string{"set", "clusters.another-cluster.certificate-authority-data", "cadata", "--set-raw-bytes"},
   611  		startingConfig: startingConfig,
   612  		expectedConfig: expectedConfig,
   613  	}
   614  
   615  	test.run(t)
   616  }
   617  
   618  func TestSetBase64Bytes(t *testing.T) {
   619  	clusterInfoWithCAData := clientcmdapi.NewCluster()
   620  	clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
   621  
   622  	startingConfig := newRedFederalCowHammerConfig()
   623  	startingConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
   624  
   625  	expectedConfig := newRedFederalCowHammerConfig()
   626  	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
   627  
   628  	test := configCommandTest{
   629  		args:           []string{"set", "clusters.another-cluster.certificate-authority-data", "Y2FkYXRh"},
   630  		startingConfig: startingConfig,
   631  		expectedConfig: expectedConfig,
   632  	}
   633  
   634  	test.run(t)
   635  }
   636  
   637  func TestUnsetBytes(t *testing.T) {
   638  	clusterInfoWithCAData := clientcmdapi.NewCluster()
   639  	clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
   640  
   641  	startingConfig := newRedFederalCowHammerConfig()
   642  	startingConfig.Clusters["another-cluster"] = clusterInfoWithCAData
   643  
   644  	expectedConfig := newRedFederalCowHammerConfig()
   645  	expectedConfig.Clusters["another-cluster"] = clientcmdapi.NewCluster()
   646  
   647  	test := configCommandTest{
   648  		args:           []string{"unset", "clusters.another-cluster.certificate-authority-data"},
   649  		startingConfig: startingConfig,
   650  		expectedConfig: expectedConfig,
   651  	}
   652  
   653  	test.run(t)
   654  }
   655  
   656  func TestCAClearsInsecure(t *testing.T) {
   657  	fakeCAFile, _ := os.CreateTemp(os.TempDir(), "ca-file")
   658  	defer utiltesting.CloseAndRemove(t, fakeCAFile)
   659  	clusterInfoWithInsecure := clientcmdapi.NewCluster()
   660  	clusterInfoWithInsecure.InsecureSkipTLSVerify = true
   661  
   662  	clusterInfoWithCA := clientcmdapi.NewCluster()
   663  	clusterInfoWithCA.CertificateAuthority = path.Base(fakeCAFile.Name())
   664  
   665  	startingConfig := newRedFederalCowHammerConfig()
   666  	startingConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
   667  
   668  	expectedConfig := newRedFederalCowHammerConfig()
   669  	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA
   670  
   671  	test := configCommandTest{
   672  		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name()},
   673  		startingConfig: startingConfig,
   674  		expectedConfig: expectedConfig,
   675  	}
   676  
   677  	test.run(t)
   678  }
   679  
   680  func TestCAClearsCAData(t *testing.T) {
   681  	clusterInfoWithCAData := clientcmdapi.NewCluster()
   682  	clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
   683  
   684  	clusterInfoWithCA := clientcmdapi.NewCluster()
   685  	clusterInfoWithCA.CertificateAuthority = "/cafile"
   686  
   687  	startingConfig := newRedFederalCowHammerConfig()
   688  	startingConfig.Clusters["another-cluster"] = clusterInfoWithCAData
   689  
   690  	expectedConfig := newRedFederalCowHammerConfig()
   691  	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA
   692  
   693  	test := configCommandTest{
   694  		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=/cafile", "--" + clientcmd.FlagInsecure + "=false"},
   695  		startingConfig: startingConfig,
   696  		expectedConfig: expectedConfig,
   697  	}
   698  
   699  	test.run(t)
   700  }
   701  
   702  func TestInsecureClearsCA(t *testing.T) {
   703  	clusterInfoWithInsecure := clientcmdapi.NewCluster()
   704  	clusterInfoWithInsecure.InsecureSkipTLSVerify = true
   705  
   706  	clusterInfoWithCA := clientcmdapi.NewCluster()
   707  	clusterInfoWithCA.CertificateAuthority = "cafile"
   708  	clusterInfoWithCA.CertificateAuthorityData = []byte("cadata")
   709  
   710  	startingConfig := newRedFederalCowHammerConfig()
   711  	startingConfig.Clusters["another-cluster"] = clusterInfoWithCA
   712  
   713  	expectedConfig := newRedFederalCowHammerConfig()
   714  	expectedConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
   715  
   716  	test := configCommandTest{
   717  		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagInsecure + "=true"},
   718  		startingConfig: startingConfig,
   719  		expectedConfig: expectedConfig,
   720  	}
   721  
   722  	test.run(t)
   723  }
   724  
   725  func TestCADataClearsCA(t *testing.T) {
   726  	fakeCAFile, _ := os.CreateTemp(os.TempDir(), "")
   727  	defer utiltesting.CloseAndRemove(t, fakeCAFile)
   728  	fakeData := []byte("cadata")
   729  	os.WriteFile(fakeCAFile.Name(), fakeData, 0600)
   730  
   731  	clusterInfoWithCAData := clientcmdapi.NewCluster()
   732  	clusterInfoWithCAData.CertificateAuthorityData = fakeData
   733  
   734  	clusterInfoWithCA := clientcmdapi.NewCluster()
   735  	clusterInfoWithCA.CertificateAuthority = "cafile"
   736  
   737  	startingConfig := newRedFederalCowHammerConfig()
   738  	startingConfig.Clusters["another-cluster"] = clusterInfoWithCA
   739  
   740  	expectedConfig := newRedFederalCowHammerConfig()
   741  	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
   742  
   743  	test := configCommandTest{
   744  		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
   745  		startingConfig: startingConfig,
   746  		expectedConfig: expectedConfig,
   747  	}
   748  
   749  	test.run(t)
   750  }
   751  
   752  func TestEmbedNoCADisallowed(t *testing.T) {
   753  	expectedConfig := newRedFederalCowHammerConfig()
   754  	test := configCommandTest{
   755  		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagEmbedCerts + "=true"},
   756  		startingConfig: newRedFederalCowHammerConfig(),
   757  		expectedConfig: expectedConfig,
   758  	}
   759  
   760  	func() {
   761  		defer func() {
   762  			// Restore cmdutil behavior.
   763  			cmdutil.DefaultBehaviorOnFatal()
   764  		}()
   765  
   766  		// Check exit code.
   767  		cmdutil.BehaviorOnFatal(func(e string, code int) {
   768  			if code != 1 {
   769  				t.Errorf("The exit code is %d, expected 1", code)
   770  			}
   771  
   772  			expectedOutputs := []string{"--certificate-authority", "embed"}
   773  			test.checkOutput(e, expectedOutputs, t)
   774  		})
   775  
   776  		test.run(t)
   777  	}()
   778  }
   779  
   780  func TestCAAndInsecureDisallowed(t *testing.T) {
   781  	test := configCommandTest{
   782  		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=true"},
   783  		startingConfig: newRedFederalCowHammerConfig(),
   784  		expectedConfig: newRedFederalCowHammerConfig(),
   785  	}
   786  
   787  	func() {
   788  		defer func() {
   789  			// Restore cmdutil behavior.
   790  			cmdutil.DefaultBehaviorOnFatal()
   791  		}()
   792  
   793  		// Check exit code.
   794  		cmdutil.BehaviorOnFatal(func(e string, code int) {
   795  			if code != 1 {
   796  				t.Errorf("The exit code is %d, expected 1", code)
   797  			}
   798  
   799  			expectedOutputs := []string{"certificate", "insecure"}
   800  			test.checkOutput(e, expectedOutputs, t)
   801  		})
   802  
   803  		test.run(t)
   804  	}()
   805  }
   806  
   807  func TestMergeExistingAuth(t *testing.T) {
   808  	expectedConfig := newRedFederalCowHammerConfig()
   809  	authInfo := expectedConfig.AuthInfos["red-user"]
   810  	authInfo.ClientKey = "/key"
   811  	expectedConfig.AuthInfos["red-user"] = authInfo
   812  	test := configCommandTest{
   813  		args:           []string{"set-credentials", "red-user", "--" + clientcmd.FlagKeyFile + "=/key"},
   814  		startingConfig: newRedFederalCowHammerConfig(),
   815  		expectedConfig: expectedConfig,
   816  	}
   817  
   818  	test.run(t)
   819  }
   820  
   821  func TestNewEmptyCluster(t *testing.T) {
   822  	expectedConfig := *clientcmdapi.NewConfig()
   823  	expectedConfig.Clusters["new-cluster"] = clientcmdapi.NewCluster()
   824  	test := configCommandTest{
   825  		args:           []string{"set-cluster", "new-cluster"},
   826  		startingConfig: *clientcmdapi.NewConfig(),
   827  		expectedConfig: expectedConfig,
   828  	}
   829  
   830  	test.run(t)
   831  }
   832  
   833  func TestAdditionalCluster(t *testing.T) {
   834  	expectedConfig := newRedFederalCowHammerConfig()
   835  	cluster := clientcmdapi.NewCluster()
   836  	cluster.CertificateAuthority = "/ca-location"
   837  	cluster.InsecureSkipTLSVerify = false
   838  	cluster.Server = "serverlocation"
   839  	expectedConfig.Clusters["different-cluster"] = cluster
   840  	test := configCommandTest{
   841  		args:           []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=/ca-location"},
   842  		startingConfig: newRedFederalCowHammerConfig(),
   843  		expectedConfig: expectedConfig,
   844  	}
   845  
   846  	test.run(t)
   847  }
   848  
   849  func TestOverwriteExistingCluster(t *testing.T) {
   850  	expectedConfig := newRedFederalCowHammerConfig()
   851  	cluster := clientcmdapi.NewCluster()
   852  	cluster.Server = "serverlocation"
   853  	expectedConfig.Clusters["cow-cluster"] = cluster
   854  
   855  	test := configCommandTest{
   856  		args:           []string{"set-cluster", "cow-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation"},
   857  		startingConfig: newRedFederalCowHammerConfig(),
   858  		expectedConfig: expectedConfig,
   859  	}
   860  
   861  	test.run(t)
   862  }
   863  
   864  func TestNewEmptyContext(t *testing.T) {
   865  	expectedConfig := *clientcmdapi.NewConfig()
   866  	expectedConfig.Contexts["new-context"] = clientcmdapi.NewContext()
   867  	test := configCommandTest{
   868  		args:           []string{"set-context", "new-context"},
   869  		startingConfig: *clientcmdapi.NewConfig(),
   870  		expectedConfig: expectedConfig,
   871  	}
   872  
   873  	test.run(t)
   874  }
   875  
   876  func TestAdditionalContext(t *testing.T) {
   877  	expectedConfig := newRedFederalCowHammerConfig()
   878  	context := clientcmdapi.NewContext()
   879  	context.Cluster = "some-cluster"
   880  	context.AuthInfo = "some-user"
   881  	context.Namespace = "different-namespace"
   882  	expectedConfig.Contexts["different-context"] = context
   883  	test := configCommandTest{
   884  		args:           []string{"set-context", "different-context", "--" + clientcmd.FlagClusterName + "=some-cluster", "--" + clientcmd.FlagAuthInfoName + "=some-user", "--" + clientcmd.FlagNamespace + "=different-namespace"},
   885  		startingConfig: newRedFederalCowHammerConfig(),
   886  		expectedConfig: expectedConfig,
   887  	}
   888  
   889  	test.run(t)
   890  }
   891  
   892  func TestMergeExistingContext(t *testing.T) {
   893  	expectedConfig := newRedFederalCowHammerConfig()
   894  	context := expectedConfig.Contexts["federal-context"]
   895  	context.Namespace = "hammer"
   896  	expectedConfig.Contexts["federal-context"] = context
   897  
   898  	test := configCommandTest{
   899  		args:           []string{"set-context", "federal-context", "--" + clientcmd.FlagNamespace + "=hammer"},
   900  		startingConfig: newRedFederalCowHammerConfig(),
   901  		expectedConfig: expectedConfig,
   902  	}
   903  
   904  	test.run(t)
   905  }
   906  
   907  func TestToBool(t *testing.T) {
   908  	type test struct {
   909  		in  string
   910  		out bool
   911  		err string
   912  	}
   913  
   914  	tests := []test{
   915  		{"", false, ""},
   916  		{"true", true, ""},
   917  		{"on", false, `strconv.ParseBool: parsing "on": invalid syntax`},
   918  	}
   919  
   920  	for _, curr := range tests {
   921  		b, err := toBool(curr.in)
   922  		if (len(curr.err) != 0) && err == nil {
   923  			t.Errorf("Expected error: %v, but got nil", curr.err)
   924  		}
   925  		if (len(curr.err) == 0) && err != nil {
   926  			t.Errorf("Unexpected error: %v", err)
   927  		}
   928  		if (err != nil) && (err.Error() != curr.err) {
   929  			t.Errorf("Expected %v, got %v", curr.err, err)
   930  
   931  		}
   932  		if b != curr.out {
   933  			t.Errorf("Expected %v, got %v", curr.out, b)
   934  		}
   935  	}
   936  
   937  }
   938  
   939  func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *testing.T) (string, clientcmdapi.Config) {
   940  	fakeKubeFile, _ := os.CreateTemp(os.TempDir(), "")
   941  	defer utiltesting.CloseAndRemove(t, fakeKubeFile)
   942  	err := clientcmd.WriteToFile(startingConfig, fakeKubeFile.Name())
   943  	if err != nil {
   944  		t.Fatalf("unexpected error: %v", err)
   945  	}
   946  
   947  	argsToUse := make([]string, 0, 2+len(args))
   948  	argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name())
   949  	argsToUse = append(argsToUse, args...)
   950  
   951  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   952  	cmd := NewCmdConfig(clientcmd.NewDefaultPathOptions(), streams)
   953  	// "context" is a global flag, inherited from base kubectl command in the real world
   954  	cmd.PersistentFlags().String("context", "", "The name of the kubeconfig context to use")
   955  	cmd.SetArgs(argsToUse)
   956  	cmd.Execute()
   957  
   958  	config := clientcmd.GetConfigFromFileOrDie(fakeKubeFile.Name())
   959  	return buf.String(), *config
   960  }
   961  
   962  type configCommandTest struct {
   963  	args            []string
   964  	startingConfig  clientcmdapi.Config
   965  	expectedConfig  clientcmdapi.Config
   966  	expectedOutputs []string
   967  }
   968  
   969  func (test configCommandTest) checkOutput(out string, expectedOutputs []string, t *testing.T) {
   970  	for _, expectedOutput := range expectedOutputs {
   971  		if !strings.Contains(out, expectedOutput) {
   972  			t.Errorf("expected '%s' in output, got '%s'", expectedOutput, out)
   973  		}
   974  	}
   975  }
   976  
   977  func (test configCommandTest) run(t *testing.T) string {
   978  	out, actualConfig := testConfigCommand(test.args, test.startingConfig, t)
   979  
   980  	testSetNilMapsToEmpties(reflect.ValueOf(&test.expectedConfig))
   981  	testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig))
   982  	testClearLocationOfOrigin(&actualConfig)
   983  
   984  	if !apiequality.Semantic.DeepEqual(test.expectedConfig, actualConfig) {
   985  		t.Errorf("diff: %v", cmp.Diff(test.expectedConfig, actualConfig))
   986  		t.Errorf("expected: %#v\n actual:   %#v", test.expectedConfig, actualConfig)
   987  	}
   988  
   989  	test.checkOutput(out, test.expectedOutputs, t)
   990  
   991  	return out
   992  }
   993  func testClearLocationOfOrigin(config *clientcmdapi.Config) {
   994  	for key, obj := range config.AuthInfos {
   995  		obj.LocationOfOrigin = ""
   996  		config.AuthInfos[key] = obj
   997  	}
   998  	for key, obj := range config.Clusters {
   999  		obj.LocationOfOrigin = ""
  1000  		config.Clusters[key] = obj
  1001  	}
  1002  	for key, obj := range config.Contexts {
  1003  		obj.LocationOfOrigin = ""
  1004  		config.Contexts[key] = obj
  1005  	}
  1006  }
  1007  func testSetNilMapsToEmpties(curr reflect.Value) {
  1008  	actualCurrValue := curr
  1009  	if curr.Kind() == reflect.Pointer {
  1010  		actualCurrValue = curr.Elem()
  1011  	}
  1012  
  1013  	switch actualCurrValue.Kind() {
  1014  	case reflect.Map:
  1015  		for _, mapKey := range actualCurrValue.MapKeys() {
  1016  			currMapValue := actualCurrValue.MapIndex(mapKey)
  1017  			testSetNilMapsToEmpties(currMapValue)
  1018  		}
  1019  
  1020  	case reflect.Struct:
  1021  		for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
  1022  			currFieldValue := actualCurrValue.Field(fieldIndex)
  1023  
  1024  			if currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil() {
  1025  				newValue := reflect.MakeMap(currFieldValue.Type())
  1026  				currFieldValue.Set(newValue)
  1027  			} else {
  1028  				testSetNilMapsToEmpties(currFieldValue.Addr())
  1029  			}
  1030  		}
  1031  
  1032  	}
  1033  
  1034  }
  1035  

View as plain text