...

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

View as plain text