k8s.io/client-go@v0.22.2/tools/clientcmd/loader_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  	"bytes"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"reflect"
    27  	"strings"
    28  	"testing"
    29  
    30  	"sigs.k8s.io/yaml"
    31  
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/util/diff"
    34  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    35  	clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest"
    36  )
    37  
    38  var (
    39  	testConfigAlfa = clientcmdapi.Config{
    40  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
    41  			"red-user": {Token: "red-token"}},
    42  		Clusters: map[string]*clientcmdapi.Cluster{
    43  			"cow-cluster": {Server: "http://cow.org:8080"}},
    44  		Contexts: map[string]*clientcmdapi.Context{
    45  			"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}},
    46  	}
    47  	testConfigBravo = clientcmdapi.Config{
    48  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
    49  			"black-user": {Token: "black-token"}},
    50  		Clusters: map[string]*clientcmdapi.Cluster{
    51  			"pig-cluster": {Server: "http://pig.org:8080"}},
    52  		Contexts: map[string]*clientcmdapi.Context{
    53  			"queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}},
    54  	}
    55  	testConfigCharlie = clientcmdapi.Config{
    56  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
    57  			"green-user": {Token: "green-token"}},
    58  		Clusters: map[string]*clientcmdapi.Cluster{
    59  			"horse-cluster": {Server: "http://horse.org:8080"}},
    60  		Contexts: map[string]*clientcmdapi.Context{
    61  			"shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}},
    62  	}
    63  	testConfigDelta = clientcmdapi.Config{
    64  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
    65  			"blue-user": {Token: "blue-token"}},
    66  		Clusters: map[string]*clientcmdapi.Cluster{
    67  			"chicken-cluster": {Server: "http://chicken.org:8080"}},
    68  		Contexts: map[string]*clientcmdapi.Context{
    69  			"gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}},
    70  	}
    71  
    72  	testConfigConflictAlfa = clientcmdapi.Config{
    73  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
    74  			"red-user":    {Token: "a-different-red-token"},
    75  			"yellow-user": {Token: "yellow-token"}},
    76  		Clusters: map[string]*clientcmdapi.Cluster{
    77  			"cow-cluster":    {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true},
    78  			"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}},
    79  		CurrentContext: "federal-context",
    80  	}
    81  )
    82  
    83  func TestNilOutMap(t *testing.T) {
    84  	var fakeKubeconfigData = `apiVersion: v1
    85  kind: Config
    86  clusters:
    87  - cluster:
    88      certificate-authority-data: UEhPTlkK
    89      server: https://1.1.1.1
    90    name: production
    91  contexts:
    92  - context:
    93      cluster: production
    94      user: production
    95    name: production
    96  current-context: production
    97  users:
    98  - name: production
    99    user:
   100      auth-provider:
   101        name: gcp`
   102  
   103  	_, _, err := clientcmdlatest.Codec.Decode([]byte(fakeKubeconfigData), nil, nil)
   104  	if err != nil {
   105  		t.Fatalf("unexpected error: %v", err)
   106  	}
   107  }
   108  
   109  func TestNonExistentCommandLineFile(t *testing.T) {
   110  	loadingRules := ClientConfigLoadingRules{
   111  		ExplicitPath: "bogus_file",
   112  	}
   113  
   114  	_, err := loadingRules.Load()
   115  	if err == nil {
   116  		t.Fatalf("Expected error for missing command-line file, got none")
   117  	}
   118  	if !strings.Contains(err.Error(), "bogus_file") {
   119  		t.Fatalf("Expected error about 'bogus_file', got %s", err.Error())
   120  	}
   121  }
   122  
   123  func TestToleratingMissingFiles(t *testing.T) {
   124  	loadingRules := ClientConfigLoadingRules{
   125  		Precedence: []string{"bogus1", "bogus2", "bogus3"},
   126  	}
   127  
   128  	_, err := loadingRules.Load()
   129  	if err != nil {
   130  		t.Fatalf("Unexpected error: %v", err)
   131  	}
   132  }
   133  
   134  func TestErrorReadingFile(t *testing.T) {
   135  	commandLineFile, _ := ioutil.TempFile("", "")
   136  	defer os.Remove(commandLineFile.Name())
   137  
   138  	if err := ioutil.WriteFile(commandLineFile.Name(), []byte("bogus value"), 0644); err != nil {
   139  		t.Fatalf("Error creating tempfile: %v", err)
   140  	}
   141  
   142  	loadingRules := ClientConfigLoadingRules{
   143  		ExplicitPath: commandLineFile.Name(),
   144  	}
   145  
   146  	_, err := loadingRules.Load()
   147  	if err == nil {
   148  		t.Fatalf("Expected error for unloadable file, got none")
   149  	}
   150  	if !strings.Contains(err.Error(), commandLineFile.Name()) {
   151  		t.Fatalf("Expected error about '%s', got %s", commandLineFile.Name(), err.Error())
   152  	}
   153  }
   154  
   155  func TestErrorReadingNonFile(t *testing.T) {
   156  	tmpdir, err := ioutil.TempDir("", "")
   157  	if err != nil {
   158  		t.Fatalf("Couldn't create tmpdir")
   159  	}
   160  	defer os.RemoveAll(tmpdir)
   161  
   162  	loadingRules := ClientConfigLoadingRules{
   163  		ExplicitPath: tmpdir,
   164  	}
   165  
   166  	_, err = loadingRules.Load()
   167  	if err == nil {
   168  		t.Fatalf("Expected error for non-file, got none")
   169  	}
   170  	if !strings.Contains(err.Error(), tmpdir) {
   171  		t.Fatalf("Expected error about '%s', got %s", tmpdir, err.Error())
   172  	}
   173  }
   174  
   175  func TestConflictingCurrentContext(t *testing.T) {
   176  	commandLineFile, _ := ioutil.TempFile("", "")
   177  	defer os.Remove(commandLineFile.Name())
   178  	envVarFile, _ := ioutil.TempFile("", "")
   179  	defer os.Remove(envVarFile.Name())
   180  
   181  	mockCommandLineConfig := clientcmdapi.Config{
   182  		CurrentContext: "any-context-value",
   183  	}
   184  	mockEnvVarConfig := clientcmdapi.Config{
   185  		CurrentContext: "a-different-context",
   186  	}
   187  
   188  	WriteToFile(mockCommandLineConfig, commandLineFile.Name())
   189  	WriteToFile(mockEnvVarConfig, envVarFile.Name())
   190  
   191  	loadingRules := ClientConfigLoadingRules{
   192  		ExplicitPath: commandLineFile.Name(),
   193  		Precedence:   []string{envVarFile.Name()},
   194  	}
   195  
   196  	mergedConfig, err := loadingRules.Load()
   197  	if err != nil {
   198  		t.Errorf("Unexpected error: %v", err)
   199  	}
   200  
   201  	if mergedConfig.CurrentContext != mockCommandLineConfig.CurrentContext {
   202  		t.Errorf("expected %v, got %v", mockCommandLineConfig.CurrentContext, mergedConfig.CurrentContext)
   203  	}
   204  }
   205  
   206  func TestEncodeYAML(t *testing.T) {
   207  	config := clientcmdapi.Config{
   208  		CurrentContext: "any-context-value",
   209  		Contexts: map[string]*clientcmdapi.Context{
   210  			"433e40": {
   211  				Cluster: "433e40",
   212  			},
   213  		},
   214  		Clusters: map[string]*clientcmdapi.Cluster{
   215  			"0": {
   216  				Server: "https://localhost:1234",
   217  			},
   218  			"1": {
   219  				Server: "https://localhost:1234",
   220  			},
   221  			"433e40": {
   222  				Server: "https://localhost:1234",
   223  			},
   224  		},
   225  	}
   226  	data, err := Write(config)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	expected := []byte(`apiVersion: v1
   231  clusters:
   232  - cluster:
   233      server: https://localhost:1234
   234    name: "0"
   235  - cluster:
   236      server: https://localhost:1234
   237    name: "1"
   238  - cluster:
   239      server: https://localhost:1234
   240    name: "433e40"
   241  contexts:
   242  - context:
   243      cluster: "433e40"
   244      user: ""
   245    name: "433e40"
   246  current-context: any-context-value
   247  kind: Config
   248  preferences: {}
   249  users: null
   250  `)
   251  	if !bytes.Equal(expected, data) {
   252  		t.Error(diff.ObjectReflectDiff(string(expected), string(data)))
   253  	}
   254  }
   255  
   256  func TestLoadingEmptyMaps(t *testing.T) {
   257  	configFile, _ := ioutil.TempFile("", "")
   258  	defer os.Remove(configFile.Name())
   259  
   260  	mockConfig := clientcmdapi.Config{
   261  		CurrentContext: "any-context-value",
   262  	}
   263  
   264  	WriteToFile(mockConfig, configFile.Name())
   265  
   266  	config, err := LoadFromFile(configFile.Name())
   267  	if err != nil {
   268  		t.Errorf("Unexpected error: %v", err)
   269  	}
   270  
   271  	if config.Clusters == nil {
   272  		t.Error("expected config.Clusters to be non-nil")
   273  	}
   274  	if config.AuthInfos == nil {
   275  		t.Error("expected config.AuthInfos to be non-nil")
   276  	}
   277  	if config.Contexts == nil {
   278  		t.Error("expected config.Contexts to be non-nil")
   279  	}
   280  }
   281  
   282  func TestDuplicateClusterName(t *testing.T) {
   283  	configFile, _ := ioutil.TempFile("", "")
   284  	defer os.Remove(configFile.Name())
   285  
   286  	err := ioutil.WriteFile(configFile.Name(), []byte(`
   287  kind: Config
   288  apiVersion: v1
   289  clusters:
   290  - cluster:
   291      api-version: v1
   292      server: https://kubernetes.default.svc:443
   293      certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
   294    name: kubeconfig-cluster
   295  - cluster:
   296      api-version: v2
   297      server: https://test.example.server:443
   298      certificate-authority: /var/run/secrets/test.example.io/serviceaccount/ca.crt
   299    name: kubeconfig-cluster
   300  contexts:
   301  - context:
   302      cluster: kubeconfig-cluster
   303      namespace: default
   304      user: kubeconfig-user
   305    name: kubeconfig-context
   306  current-context: kubeconfig-context
   307  users:
   308  - name: kubeconfig-user
   309    user:
   310      tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
   311  `), os.FileMode(0755))
   312  
   313  	if err != nil {
   314  		t.Errorf("Unexpected error: %v", err)
   315  	}
   316  
   317  	_, err = LoadFromFile(configFile.Name())
   318  	if err == nil || !strings.Contains(err.Error(),
   319  		"error converting *[]NamedCluster into *map[string]*api.Cluster: duplicate name \"kubeconfig-cluster\" in list") {
   320  		t.Error("Expected error in loading duplicate cluster name, got none")
   321  	}
   322  }
   323  
   324  func TestDuplicateContextName(t *testing.T) {
   325  	configFile, _ := ioutil.TempFile("", "")
   326  	defer os.Remove(configFile.Name())
   327  
   328  	err := ioutil.WriteFile(configFile.Name(), []byte(`
   329  kind: Config
   330  apiVersion: v1
   331  clusters:
   332  - cluster:
   333      api-version: v1
   334      server: https://kubernetes.default.svc:443
   335      certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
   336    name: kubeconfig-cluster
   337  contexts:
   338  - context:
   339      cluster: kubeconfig-cluster
   340      namespace: default
   341      user: kubeconfig-user
   342    name: kubeconfig-context
   343  - context:
   344      cluster: test-example-cluster
   345      namespace: test-example
   346      user: test-example-user
   347    name: kubeconfig-context
   348  current-context: kubeconfig-context
   349  users:
   350  - name: kubeconfig-user
   351    user:
   352      tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
   353  `), os.FileMode(0755))
   354  
   355  	if err != nil {
   356  		t.Errorf("Unexpected error: %v", err)
   357  	}
   358  
   359  	_, err = LoadFromFile(configFile.Name())
   360  	if err == nil || !strings.Contains(err.Error(),
   361  		"error converting *[]NamedContext into *map[string]*api.Context: duplicate name \"kubeconfig-context\" in list") {
   362  		t.Error("Expected error in loading duplicate context name, got none")
   363  	}
   364  }
   365  
   366  func TestDuplicateUserName(t *testing.T) {
   367  	configFile, _ := ioutil.TempFile("", "")
   368  	defer os.Remove(configFile.Name())
   369  
   370  	err := ioutil.WriteFile(configFile.Name(), []byte(`
   371  kind: Config
   372  apiVersion: v1
   373  clusters:
   374  - cluster:
   375      api-version: v1
   376      server: https://kubernetes.default.svc:443
   377      certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
   378    name: kubeconfig-cluster
   379  contexts:
   380  - context:
   381      cluster: kubeconfig-cluster
   382      namespace: default
   383      user: kubeconfig-user
   384    name: kubeconfig-context
   385  current-context: kubeconfig-context
   386  users:
   387  - name: kubeconfig-user
   388    user:
   389      tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
   390  - name: kubeconfig-user
   391    user:
   392      tokenFile: /var/run/secrets/test.example.com/serviceaccount/token
   393  `), os.FileMode(0755))
   394  
   395  	if err != nil {
   396  		t.Errorf("Unexpected error: %v", err)
   397  	}
   398  
   399  	_, err = LoadFromFile(configFile.Name())
   400  	if err == nil || !strings.Contains(err.Error(),
   401  		"error converting *[]NamedAuthInfo into *map[string]*api.AuthInfo: duplicate name \"kubeconfig-user\" in list") {
   402  		t.Error("Expected error in loading duplicate user name, got none")
   403  	}
   404  }
   405  
   406  func TestDuplicateExtensionName(t *testing.T) {
   407  	configFile, _ := ioutil.TempFile("", "")
   408  	defer os.Remove(configFile.Name())
   409  
   410  	err := ioutil.WriteFile(configFile.Name(), []byte(`
   411  kind: Config
   412  apiVersion: v1
   413  clusters:
   414  - cluster:
   415      api-version: v1
   416      server: https://kubernetes.default.svc:443
   417      certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
   418    name: kubeconfig-cluster
   419  contexts:
   420  - context:
   421      cluster: kubeconfig-cluster
   422      namespace: default
   423      user: kubeconfig-user
   424    name: kubeconfig-context
   425  current-context: kubeconfig-context
   426  users:
   427  - name: kubeconfig-user
   428    user:
   429      tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
   430  extensions:
   431  - extension:
   432      bytes: test
   433    name: test-extension
   434  - extension:
   435      bytes: some-example
   436    name: test-extension
   437  `), os.FileMode(0755))
   438  
   439  	if err != nil {
   440  		t.Errorf("Unexpected error: %v", err)
   441  	}
   442  
   443  	_, err = LoadFromFile(configFile.Name())
   444  	if err == nil || !strings.Contains(err.Error(),
   445  		"error converting *[]NamedExtension into *map[string]runtime.Object: duplicate name \"test-extension\" in list") {
   446  		t.Error("Expected error in loading duplicate extension name, got none")
   447  	}
   448  }
   449  
   450  func TestResolveRelativePaths(t *testing.T) {
   451  	pathResolutionConfig1 := clientcmdapi.Config{
   452  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
   453  			"relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"},
   454  			"absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"},
   455  			"relative-cmd-1":  {Exec: &clientcmdapi.ExecConfig{Command: "../relative/client/cmd"}},
   456  			"absolute-cmd-1":  {Exec: &clientcmdapi.ExecConfig{Command: "/absolute/client/cmd"}},
   457  			"PATH-cmd-1":      {Exec: &clientcmdapi.ExecConfig{Command: "cmd"}},
   458  		},
   459  		Clusters: map[string]*clientcmdapi.Cluster{
   460  			"relative-server-1": {CertificateAuthority: "../relative/ca"},
   461  			"absolute-server-1": {CertificateAuthority: "/absolute/ca"},
   462  		},
   463  	}
   464  	pathResolutionConfig2 := clientcmdapi.Config{
   465  		AuthInfos: map[string]*clientcmdapi.AuthInfo{
   466  			"relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2"},
   467  			"absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2"},
   468  		},
   469  		Clusters: map[string]*clientcmdapi.Cluster{
   470  			"relative-server-2": {CertificateAuthority: "../relative/ca2"},
   471  			"absolute-server-2": {CertificateAuthority: "/absolute/ca2"},
   472  		},
   473  	}
   474  
   475  	configDir1, _ := ioutil.TempDir("", "")
   476  	defer os.RemoveAll(configDir1)
   477  	configFile1 := path.Join(configDir1, ".kubeconfig")
   478  	configDir1, _ = filepath.Abs(configDir1)
   479  
   480  	configDir2, _ := ioutil.TempDir("", "")
   481  	defer os.RemoveAll(configDir2)
   482  	configDir2, _ = ioutil.TempDir(configDir2, "")
   483  	configFile2 := path.Join(configDir2, ".kubeconfig")
   484  	configDir2, _ = filepath.Abs(configDir2)
   485  
   486  	WriteToFile(pathResolutionConfig1, configFile1)
   487  	WriteToFile(pathResolutionConfig2, configFile2)
   488  
   489  	loadingRules := ClientConfigLoadingRules{
   490  		Precedence: []string{configFile1, configFile2},
   491  	}
   492  
   493  	mergedConfig, err := loadingRules.Load()
   494  	if err != nil {
   495  		t.Errorf("Unexpected error: %v", err)
   496  	}
   497  
   498  	foundClusterCount := 0
   499  	for key, cluster := range mergedConfig.Clusters {
   500  		if key == "relative-server-1" {
   501  			foundClusterCount++
   502  			matchStringArg(path.Join(configDir1, pathResolutionConfig1.Clusters["relative-server-1"].CertificateAuthority), cluster.CertificateAuthority, t)
   503  		}
   504  		if key == "relative-server-2" {
   505  			foundClusterCount++
   506  			matchStringArg(path.Join(configDir2, pathResolutionConfig2.Clusters["relative-server-2"].CertificateAuthority), cluster.CertificateAuthority, t)
   507  		}
   508  		if key == "absolute-server-1" {
   509  			foundClusterCount++
   510  			matchStringArg(pathResolutionConfig1.Clusters["absolute-server-1"].CertificateAuthority, cluster.CertificateAuthority, t)
   511  		}
   512  		if key == "absolute-server-2" {
   513  			foundClusterCount++
   514  			matchStringArg(pathResolutionConfig2.Clusters["absolute-server-2"].CertificateAuthority, cluster.CertificateAuthority, t)
   515  		}
   516  	}
   517  	if foundClusterCount != 4 {
   518  		t.Errorf("Expected 4 clusters, found %v: %v", foundClusterCount, mergedConfig.Clusters)
   519  	}
   520  
   521  	foundAuthInfoCount := 0
   522  	for key, authInfo := range mergedConfig.AuthInfos {
   523  		if key == "relative-user-1" {
   524  			foundAuthInfoCount++
   525  			matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientCertificate), authInfo.ClientCertificate, t)
   526  			matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientKey), authInfo.ClientKey, t)
   527  		}
   528  		if key == "relative-user-2" {
   529  			foundAuthInfoCount++
   530  			matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientCertificate), authInfo.ClientCertificate, t)
   531  			matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientKey), authInfo.ClientKey, t)
   532  		}
   533  		if key == "absolute-user-1" {
   534  			foundAuthInfoCount++
   535  			matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientCertificate, authInfo.ClientCertificate, t)
   536  			matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientKey, authInfo.ClientKey, t)
   537  		}
   538  		if key == "absolute-user-2" {
   539  			foundAuthInfoCount++
   540  			matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientCertificate, authInfo.ClientCertificate, t)
   541  			matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientKey, authInfo.ClientKey, t)
   542  		}
   543  		if key == "relative-cmd-1" {
   544  			foundAuthInfoCount++
   545  			matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos[key].Exec.Command), authInfo.Exec.Command, t)
   546  		}
   547  		if key == "absolute-cmd-1" {
   548  			foundAuthInfoCount++
   549  			matchStringArg(pathResolutionConfig1.AuthInfos[key].Exec.Command, authInfo.Exec.Command, t)
   550  		}
   551  		if key == "PATH-cmd-1" {
   552  			foundAuthInfoCount++
   553  			matchStringArg(pathResolutionConfig1.AuthInfos[key].Exec.Command, authInfo.Exec.Command, t)
   554  		}
   555  	}
   556  	if foundAuthInfoCount != 7 {
   557  		t.Errorf("Expected 7 users, found %v: %v", foundAuthInfoCount, mergedConfig.AuthInfos)
   558  	}
   559  
   560  }
   561  
   562  func TestMigratingFile(t *testing.T) {
   563  	sourceFile, _ := ioutil.TempFile("", "")
   564  	defer os.Remove(sourceFile.Name())
   565  	destinationFile, _ := ioutil.TempFile("", "")
   566  	// delete the file so that we'll write to it
   567  	os.Remove(destinationFile.Name())
   568  
   569  	WriteToFile(testConfigAlfa, sourceFile.Name())
   570  
   571  	loadingRules := ClientConfigLoadingRules{
   572  		MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()},
   573  	}
   574  
   575  	if _, err := loadingRules.Load(); err != nil {
   576  		t.Errorf("unexpected error %v", err)
   577  	}
   578  
   579  	// the load should have recreated this file
   580  	defer os.Remove(destinationFile.Name())
   581  
   582  	sourceContent, err := ioutil.ReadFile(sourceFile.Name())
   583  	if err != nil {
   584  		t.Errorf("unexpected error %v", err)
   585  	}
   586  	destinationContent, err := ioutil.ReadFile(destinationFile.Name())
   587  	if err != nil {
   588  		t.Errorf("unexpected error %v", err)
   589  	}
   590  
   591  	if !reflect.DeepEqual(sourceContent, destinationContent) {
   592  		t.Errorf("source and destination do not match")
   593  	}
   594  }
   595  
   596  func TestMigratingFileLeaveExistingFileAlone(t *testing.T) {
   597  	sourceFile, _ := ioutil.TempFile("", "")
   598  	defer os.Remove(sourceFile.Name())
   599  	destinationFile, _ := ioutil.TempFile("", "")
   600  	defer os.Remove(destinationFile.Name())
   601  
   602  	WriteToFile(testConfigAlfa, sourceFile.Name())
   603  
   604  	loadingRules := ClientConfigLoadingRules{
   605  		MigrationRules: map[string]string{destinationFile.Name(): sourceFile.Name()},
   606  	}
   607  
   608  	if _, err := loadingRules.Load(); err != nil {
   609  		t.Errorf("unexpected error %v", err)
   610  	}
   611  
   612  	destinationContent, err := ioutil.ReadFile(destinationFile.Name())
   613  	if err != nil {
   614  		t.Errorf("unexpected error %v", err)
   615  	}
   616  
   617  	if len(destinationContent) > 0 {
   618  		t.Errorf("destination should not have been touched")
   619  	}
   620  }
   621  
   622  func TestMigratingFileSourceMissingSkip(t *testing.T) {
   623  	sourceFilename := "some-missing-file"
   624  	destinationFile, _ := ioutil.TempFile("", "")
   625  	// delete the file so that we'll write to it
   626  	os.Remove(destinationFile.Name())
   627  
   628  	loadingRules := ClientConfigLoadingRules{
   629  		MigrationRules: map[string]string{destinationFile.Name(): sourceFilename},
   630  	}
   631  
   632  	if _, err := loadingRules.Load(); err != nil {
   633  		t.Errorf("unexpected error %v", err)
   634  	}
   635  
   636  	if _, err := os.Stat(destinationFile.Name()); !os.IsNotExist(err) {
   637  		t.Errorf("destination should not exist")
   638  	}
   639  }
   640  
   641  func TestFileLocking(t *testing.T) {
   642  	f, _ := ioutil.TempFile("", "")
   643  	defer os.Remove(f.Name())
   644  
   645  	err := lockFile(f.Name())
   646  	if err != nil {
   647  		t.Errorf("unexpected error while locking file: %v", err)
   648  	}
   649  	defer unlockFile(f.Name())
   650  
   651  	err = lockFile(f.Name())
   652  	if err == nil {
   653  		t.Error("expected error while locking file.")
   654  	}
   655  }
   656  
   657  func Example_noMergingOnExplicitPaths() {
   658  	commandLineFile, _ := ioutil.TempFile("", "")
   659  	defer os.Remove(commandLineFile.Name())
   660  	envVarFile, _ := ioutil.TempFile("", "")
   661  	defer os.Remove(envVarFile.Name())
   662  
   663  	WriteToFile(testConfigAlfa, commandLineFile.Name())
   664  	WriteToFile(testConfigConflictAlfa, envVarFile.Name())
   665  
   666  	loadingRules := ClientConfigLoadingRules{
   667  		ExplicitPath: commandLineFile.Name(),
   668  		Precedence:   []string{envVarFile.Name()},
   669  	}
   670  
   671  	mergedConfig, err := loadingRules.Load()
   672  
   673  	json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig)
   674  	if err != nil {
   675  		fmt.Printf("Unexpected error: %v", err)
   676  	}
   677  	output, err := yaml.JSONToYAML(json)
   678  	if err != nil {
   679  		fmt.Printf("Unexpected error: %v", err)
   680  	}
   681  
   682  	fmt.Printf("%v", string(output))
   683  	// Output:
   684  	// apiVersion: v1
   685  	// clusters:
   686  	// - cluster:
   687  	//     server: http://cow.org:8080
   688  	//   name: cow-cluster
   689  	// contexts:
   690  	// - context:
   691  	//     cluster: cow-cluster
   692  	//     namespace: hammer-ns
   693  	//     user: red-user
   694  	//   name: federal-context
   695  	// current-context: ""
   696  	// kind: Config
   697  	// preferences: {}
   698  	// users:
   699  	// - name: red-user
   700  	//   user:
   701  	//     token: red-token
   702  }
   703  
   704  func Example_mergingSomeWithConflict() {
   705  	commandLineFile, _ := ioutil.TempFile("", "")
   706  	defer os.Remove(commandLineFile.Name())
   707  	envVarFile, _ := ioutil.TempFile("", "")
   708  	defer os.Remove(envVarFile.Name())
   709  
   710  	WriteToFile(testConfigAlfa, commandLineFile.Name())
   711  	WriteToFile(testConfigConflictAlfa, envVarFile.Name())
   712  
   713  	loadingRules := ClientConfigLoadingRules{
   714  		Precedence: []string{commandLineFile.Name(), envVarFile.Name()},
   715  	}
   716  
   717  	mergedConfig, err := loadingRules.Load()
   718  
   719  	json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig)
   720  	if err != nil {
   721  		fmt.Printf("Unexpected error: %v", err)
   722  	}
   723  	output, err := yaml.JSONToYAML(json)
   724  	if err != nil {
   725  		fmt.Printf("Unexpected error: %v", err)
   726  	}
   727  
   728  	fmt.Printf("%v", string(output))
   729  	// Output:
   730  	// apiVersion: v1
   731  	// clusters:
   732  	// - cluster:
   733  	//     server: http://cow.org:8080
   734  	//   name: cow-cluster
   735  	// - cluster:
   736  	//     insecure-skip-tls-verify: true
   737  	//     server: http://donkey.org:8080
   738  	//   name: donkey-cluster
   739  	// contexts:
   740  	// - context:
   741  	//     cluster: cow-cluster
   742  	//     namespace: hammer-ns
   743  	//     user: red-user
   744  	//   name: federal-context
   745  	// current-context: federal-context
   746  	// kind: Config
   747  	// preferences: {}
   748  	// users:
   749  	// - name: red-user
   750  	//   user:
   751  	//     token: red-token
   752  	// - name: yellow-user
   753  	//   user:
   754  	//     token: yellow-token
   755  }
   756  
   757  func Example_mergingEverythingNoConflicts() {
   758  	commandLineFile, _ := ioutil.TempFile("", "")
   759  	defer os.Remove(commandLineFile.Name())
   760  	envVarFile, _ := ioutil.TempFile("", "")
   761  	defer os.Remove(envVarFile.Name())
   762  	currentDirFile, _ := ioutil.TempFile("", "")
   763  	defer os.Remove(currentDirFile.Name())
   764  	homeDirFile, _ := ioutil.TempFile("", "")
   765  	defer os.Remove(homeDirFile.Name())
   766  
   767  	WriteToFile(testConfigAlfa, commandLineFile.Name())
   768  	WriteToFile(testConfigBravo, envVarFile.Name())
   769  	WriteToFile(testConfigCharlie, currentDirFile.Name())
   770  	WriteToFile(testConfigDelta, homeDirFile.Name())
   771  
   772  	loadingRules := ClientConfigLoadingRules{
   773  		Precedence: []string{commandLineFile.Name(), envVarFile.Name(), currentDirFile.Name(), homeDirFile.Name()},
   774  	}
   775  
   776  	mergedConfig, err := loadingRules.Load()
   777  
   778  	json, err := runtime.Encode(clientcmdlatest.Codec, mergedConfig)
   779  	if err != nil {
   780  		fmt.Printf("Unexpected error: %v", err)
   781  	}
   782  	output, err := yaml.JSONToYAML(json)
   783  	if err != nil {
   784  		fmt.Printf("Unexpected error: %v", err)
   785  	}
   786  
   787  	fmt.Printf("%v", string(output))
   788  	// Output:
   789  	// 	apiVersion: v1
   790  	// clusters:
   791  	// - cluster:
   792  	//     server: http://chicken.org:8080
   793  	//   name: chicken-cluster
   794  	// - cluster:
   795  	//     server: http://cow.org:8080
   796  	//   name: cow-cluster
   797  	// - cluster:
   798  	//     server: http://horse.org:8080
   799  	//   name: horse-cluster
   800  	// - cluster:
   801  	//     server: http://pig.org:8080
   802  	//   name: pig-cluster
   803  	// contexts:
   804  	// - context:
   805  	//     cluster: cow-cluster
   806  	//     namespace: hammer-ns
   807  	//     user: red-user
   808  	//   name: federal-context
   809  	// - context:
   810  	//     cluster: chicken-cluster
   811  	//     namespace: plane-ns
   812  	//     user: blue-user
   813  	//   name: gothic-context
   814  	// - context:
   815  	//     cluster: pig-cluster
   816  	//     namespace: saw-ns
   817  	//     user: black-user
   818  	//   name: queen-anne-context
   819  	// - context:
   820  	//     cluster: horse-cluster
   821  	//     namespace: chisel-ns
   822  	//     user: green-user
   823  	//   name: shaker-context
   824  	// current-context: ""
   825  	// kind: Config
   826  	// preferences: {}
   827  	// users:
   828  	// - name: black-user
   829  	//   user:
   830  	//     token: black-token
   831  	// - name: blue-user
   832  	//   user:
   833  	//     token: blue-token
   834  	// - name: green-user
   835  	//   user:
   836  	//     token: green-token
   837  	// - name: red-user
   838  	//   user:
   839  	//     token: red-token
   840  }
   841  
   842  func TestDeduplicate(t *testing.T) {
   843  	testCases := []struct {
   844  		src    []string
   845  		expect []string
   846  	}{
   847  		{
   848  			src:    []string{"a", "b", "c", "d", "e", "f"},
   849  			expect: []string{"a", "b", "c", "d", "e", "f"},
   850  		},
   851  		{
   852  			src:    []string{"a", "b", "c", "b", "e", "f"},
   853  			expect: []string{"a", "b", "c", "e", "f"},
   854  		},
   855  		{
   856  			src:    []string{"a", "a", "b", "b", "c", "b"},
   857  			expect: []string{"a", "b", "c"},
   858  		},
   859  	}
   860  
   861  	for _, testCase := range testCases {
   862  		get := deduplicate(testCase.src)
   863  		if !reflect.DeepEqual(get, testCase.expect) {
   864  			t.Errorf("expect: %v, get: %v", testCase.expect, get)
   865  		}
   866  	}
   867  }
   868  
   869  func TestLoadingGetLoadingPrecedence(t *testing.T) {
   870  	testCases := map[string]struct {
   871  		rules      *ClientConfigLoadingRules
   872  		env        string
   873  		precedence []string
   874  	}{
   875  		"default": {
   876  			precedence: []string{filepath.Join(os.Getenv("HOME"), ".kube/config")},
   877  		},
   878  		"explicit": {
   879  			rules: &ClientConfigLoadingRules{
   880  				ExplicitPath: "/explicit/kubeconfig",
   881  			},
   882  			precedence: []string{"/explicit/kubeconfig"},
   883  		},
   884  		"envvar-single": {
   885  			env:        "/env/kubeconfig",
   886  			precedence: []string{"/env/kubeconfig"},
   887  		},
   888  		"envvar-multiple": {
   889  			env:        "/env/kubeconfig:/other/kubeconfig",
   890  			precedence: []string{"/env/kubeconfig", "/other/kubeconfig"},
   891  		},
   892  	}
   893  
   894  	kubeconfig := os.Getenv("KUBECONFIG")
   895  	defer os.Setenv("KUBECONFIG", kubeconfig)
   896  
   897  	for name, test := range testCases {
   898  		t.Run(name, func(t *testing.T) {
   899  			os.Setenv("KUBECONFIG", test.env)
   900  			rules := test.rules
   901  			if rules == nil {
   902  				rules = NewDefaultClientConfigLoadingRules()
   903  			}
   904  			actual := rules.GetLoadingPrecedence()
   905  			if !reflect.DeepEqual(actual, test.precedence) {
   906  				t.Errorf("expect %v, got %v", test.precedence, actual)
   907  			}
   908  		})
   909  	}
   910  }