...

Source file src/k8s.io/client-go/tools/clientcmd/validation_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  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"testing"
    25  
    26  	utiltesting "k8s.io/client-go/util/testing"
    27  
    28  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    29  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    30  )
    31  
    32  func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
    33  	config := clientcmdapi.NewConfig()
    34  	config.Clusters["missing ca"] = &clientcmdapi.Cluster{
    35  		Server:               "anything",
    36  		CertificateAuthority: "missing",
    37  	}
    38  	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
    39  		Username: "anything",
    40  		Token:    "here",
    41  	}
    42  	config.Contexts["dirty"] = &clientcmdapi.Context{
    43  		Cluster:  "missing ca",
    44  		AuthInfo: "error",
    45  	}
    46  	config.Clusters["clean"] = &clientcmdapi.Cluster{
    47  		Server: "anything",
    48  	}
    49  	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
    50  		Token: "here",
    51  	}
    52  	config.Contexts["clean"] = &clientcmdapi.Context{
    53  		Cluster:  "clean",
    54  		AuthInfo: "clean",
    55  	}
    56  
    57  	badValidation := configValidationTest{
    58  		config:                 config,
    59  		expectedErrorSubstring: []string{"unable to read certificate-authority"},
    60  	}
    61  	okTest := configValidationTest{
    62  		config: config,
    63  	}
    64  
    65  	okTest.testConfirmUsable("clean", t)
    66  	badValidation.testConfig(t)
    67  }
    68  
    69  func TestConfirmUsableMissingObjects(t *testing.T) {
    70  	config := clientcmdapi.NewConfig()
    71  	config.Clusters["kind-cluster"] = &clientcmdapi.Cluster{
    72  		Server: "anything",
    73  	}
    74  	config.AuthInfos["kind-user"] = &clientcmdapi.AuthInfo{
    75  		Token: "any-value",
    76  	}
    77  	config.Contexts["missing-user"] = &clientcmdapi.Context{
    78  		Cluster:  "kind-cluster",
    79  		AuthInfo: "garbage",
    80  	}
    81  	config.Contexts["missing-cluster"] = &clientcmdapi.Context{
    82  		Cluster:  "garbage",
    83  		AuthInfo: "kind-user",
    84  	}
    85  
    86  	missingUser := configValidationTest{
    87  		config: config,
    88  		expectedErrorSubstring: []string{
    89  			`user "garbage" was not found for context "missing-user"`,
    90  		},
    91  	}
    92  	missingUser.testConfirmUsable("missing-user", t)
    93  	missingUser.testConfig(t)
    94  
    95  	missingCluster := configValidationTest{
    96  		config: config,
    97  		expectedErrorSubstring: []string{
    98  			`cluster "garbage" was not found for context "missing-cluster"`,
    99  		},
   100  	}
   101  	missingCluster.testConfirmUsable("missing-cluster", t)
   102  	missingCluster.testConfig(t)
   103  }
   104  
   105  func TestConfirmUsableBadInfoConfig(t *testing.T) {
   106  	config := clientcmdapi.NewConfig()
   107  	config.Clusters["missing ca"] = &clientcmdapi.Cluster{
   108  		Server:               "anything",
   109  		CertificateAuthority: "missing",
   110  	}
   111  	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
   112  		Username: "anything",
   113  		Token:    "here",
   114  	}
   115  	config.Contexts["first"] = &clientcmdapi.Context{
   116  		Cluster:  "missing ca",
   117  		AuthInfo: "error",
   118  	}
   119  	test := configValidationTest{
   120  		config:                 config,
   121  		expectedErrorSubstring: []string{"unable to read certificate-authority"},
   122  	}
   123  
   124  	test.testConfirmUsable("first", t)
   125  }
   126  
   127  func TestConfirmUsableEmptyConfig(t *testing.T) {
   128  	config := clientcmdapi.NewConfig()
   129  	test := configValidationTest{
   130  		config:                 config,
   131  		expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
   132  	}
   133  
   134  	test.testConfirmUsable("", t)
   135  }
   136  
   137  func TestConfirmUsableMissingConfig(t *testing.T) {
   138  	config := clientcmdapi.NewConfig()
   139  	test := configValidationTest{
   140  		config:                 config,
   141  		expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
   142  	}
   143  
   144  	test.testConfirmUsable("not-here", t)
   145  }
   146  
   147  func TestValidateEmptyConfig(t *testing.T) {
   148  	config := clientcmdapi.NewConfig()
   149  	test := configValidationTest{
   150  		config:                 config,
   151  		expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
   152  	}
   153  
   154  	test.testConfig(t)
   155  }
   156  
   157  func TestValidateMissingCurrentContextConfig(t *testing.T) {
   158  	config := clientcmdapi.NewConfig()
   159  	config.CurrentContext = "anything"
   160  	test := configValidationTest{
   161  		config:                 config,
   162  		expectedErrorSubstring: []string{"context was not found for specified "},
   163  	}
   164  
   165  	test.testConfig(t)
   166  }
   167  
   168  func TestIsContextNotFound(t *testing.T) {
   169  	config := clientcmdapi.NewConfig()
   170  	config.CurrentContext = "anything"
   171  
   172  	err := Validate(*config)
   173  	if !IsContextNotFound(err) {
   174  		t.Errorf("Expected context not found, but got %v", err)
   175  	}
   176  	if !IsConfigurationInvalid(err) {
   177  		t.Errorf("Expected configuration invalid, but got %v", err)
   178  	}
   179  }
   180  
   181  func TestIsEmptyConfig(t *testing.T) {
   182  	config := clientcmdapi.NewConfig()
   183  
   184  	err := Validate(*config)
   185  	if !IsEmptyConfig(err) {
   186  		t.Errorf("Expected context not found, but got %v", err)
   187  	}
   188  	if !IsConfigurationInvalid(err) {
   189  		t.Errorf("Expected configuration invalid, but got %v", err)
   190  	}
   191  }
   192  
   193  func TestIsConfigurationInvalid(t *testing.T) {
   194  	if newErrConfigurationInvalid([]error{}) != nil {
   195  		t.Errorf("unexpected error")
   196  	}
   197  	if newErrConfigurationInvalid([]error{ErrNoContext}) == ErrNoContext {
   198  		t.Errorf("unexpected error")
   199  	}
   200  	if newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext}) == nil {
   201  		t.Errorf("unexpected error")
   202  	}
   203  	if !IsConfigurationInvalid(newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext})) {
   204  		t.Errorf("unexpected error")
   205  	}
   206  }
   207  
   208  func TestValidateMissingReferencesConfig(t *testing.T) {
   209  	config := clientcmdapi.NewConfig()
   210  	config.CurrentContext = "anything"
   211  	config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
   212  	test := configValidationTest{
   213  		config:                 config,
   214  		expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""},
   215  	}
   216  
   217  	test.testContext("anything", t)
   218  	test.testConfig(t)
   219  }
   220  
   221  func TestValidateEmptyContext(t *testing.T) {
   222  	config := clientcmdapi.NewConfig()
   223  	config.CurrentContext = "anything"
   224  	config.Contexts["anything"] = &clientcmdapi.Context{}
   225  	test := configValidationTest{
   226  		config:                 config,
   227  		expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""},
   228  	}
   229  
   230  	test.testContext("anything", t)
   231  	test.testConfig(t)
   232  }
   233  
   234  func TestValidateEmptyContextName(t *testing.T) {
   235  	config := clientcmdapi.NewConfig()
   236  	config.CurrentContext = "anything"
   237  	config.Contexts[""] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
   238  	test := configValidationTest{
   239  		config:                 config,
   240  		expectedErrorSubstring: []string{"empty context name", "is not allowed"},
   241  	}
   242  
   243  	test.testContext("", t)
   244  	test.testConfig(t)
   245  }
   246  
   247  func TestValidateEmptyClusterInfo(t *testing.T) {
   248  	config := clientcmdapi.NewConfig()
   249  	config.Clusters["empty"] = clientcmdapi.NewCluster()
   250  	test := configValidationTest{
   251  		config:                 config,
   252  		expectedErrorSubstring: []string{"cluster has no server defined"},
   253  	}
   254  
   255  	test.testCluster("empty", t)
   256  	test.testConfig(t)
   257  }
   258  
   259  func TestValidateClusterInfoErrEmptyCluster(t *testing.T) {
   260  	cluster := clientcmdapi.NewCluster()
   261  	errs := validateClusterInfo("", *cluster)
   262  
   263  	if len(errs) != 1 {
   264  		t.Fatalf("unexpected errors: %v", errs)
   265  	}
   266  	if errs[0] != ErrEmptyCluster {
   267  		t.Errorf("unexpected error: %v", errs[0])
   268  	}
   269  }
   270  
   271  func TestValidateMissingCAFileClusterInfo(t *testing.T) {
   272  	config := clientcmdapi.NewConfig()
   273  	config.Clusters["missing ca"] = &clientcmdapi.Cluster{
   274  		Server:               "anything",
   275  		CertificateAuthority: "missing",
   276  	}
   277  	test := configValidationTest{
   278  		config:                 config,
   279  		expectedErrorSubstring: []string{"unable to read certificate-authority"},
   280  	}
   281  
   282  	test.testCluster("missing ca", t)
   283  	test.testConfig(t)
   284  }
   285  
   286  func TestValidateCleanClusterInfo(t *testing.T) {
   287  	config := clientcmdapi.NewConfig()
   288  	config.Clusters["clean"] = &clientcmdapi.Cluster{
   289  		Server: "anything",
   290  	}
   291  	test := configValidationTest{
   292  		config: config,
   293  	}
   294  
   295  	test.testCluster("clean", t)
   296  	test.testConfig(t)
   297  }
   298  
   299  func TestValidateCleanWithCAClusterInfo(t *testing.T) {
   300  	tempFile, _ := os.CreateTemp("", "")
   301  	defer utiltesting.CloseAndRemove(t, tempFile)
   302  
   303  	config := clientcmdapi.NewConfig()
   304  	config.Clusters["clean"] = &clientcmdapi.Cluster{
   305  		Server:               "anything",
   306  		CertificateAuthority: tempFile.Name(),
   307  	}
   308  	test := configValidationTest{
   309  		config: config,
   310  	}
   311  
   312  	test.testCluster("clean", t)
   313  	test.testConfig(t)
   314  }
   315  
   316  func TestValidateEmptyAuthInfo(t *testing.T) {
   317  	config := clientcmdapi.NewConfig()
   318  	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{}
   319  	test := configValidationTest{
   320  		config: config,
   321  	}
   322  
   323  	test.testAuthInfo("error", t)
   324  	test.testConfig(t)
   325  }
   326  
   327  func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
   328  	config := clientcmdapi.NewConfig()
   329  	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
   330  		ClientCertificate: "missing",
   331  		ClientKey:         "missing",
   332  	}
   333  	test := configValidationTest{
   334  		config:                 config,
   335  		expectedErrorSubstring: []string{"unable to read client-cert", "unable to read client-key"},
   336  	}
   337  
   338  	test.testAuthInfo("error", t)
   339  	test.testConfig(t)
   340  }
   341  
   342  func TestValidateCertDataOverridesFiles(t *testing.T) {
   343  	tempFile, _ := os.CreateTemp("", "")
   344  	defer utiltesting.CloseAndRemove(t, tempFile)
   345  
   346  	config := clientcmdapi.NewConfig()
   347  	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
   348  		ClientCertificate:     tempFile.Name(),
   349  		ClientCertificateData: []byte("certdata"),
   350  		ClientKey:             tempFile.Name(),
   351  		ClientKeyData:         []byte("keydata"),
   352  	}
   353  	test := configValidationTest{
   354  		config:                 config,
   355  		expectedErrorSubstring: []string{"client-cert-data and client-cert are both specified", "client-key-data and client-key are both specified"},
   356  	}
   357  
   358  	test.testAuthInfo("clean", t)
   359  	test.testConfig(t)
   360  }
   361  
   362  func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
   363  	tempFile, _ := os.CreateTemp("", "")
   364  	defer utiltesting.CloseAndRemove(t, tempFile)
   365  
   366  	config := clientcmdapi.NewConfig()
   367  	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
   368  		ClientCertificate: tempFile.Name(),
   369  		ClientKey:         tempFile.Name(),
   370  	}
   371  	test := configValidationTest{
   372  		config: config,
   373  	}
   374  
   375  	test.testAuthInfo("clean", t)
   376  	test.testConfig(t)
   377  }
   378  
   379  func TestValidateCleanTokenAuthInfo(t *testing.T) {
   380  	config := clientcmdapi.NewConfig()
   381  	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
   382  		Token: "any-value",
   383  	}
   384  	test := configValidationTest{
   385  		config: config,
   386  	}
   387  
   388  	test.testAuthInfo("clean", t)
   389  	test.testConfig(t)
   390  }
   391  
   392  func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
   393  	config := clientcmdapi.NewConfig()
   394  	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
   395  		Token:    "token",
   396  		Username: "username",
   397  	}
   398  	test := configValidationTest{
   399  		config:                 config,
   400  		expectedErrorSubstring: []string{"more than one authentication method", "token", "basicAuth"},
   401  	}
   402  
   403  	test.testAuthInfo("error", t)
   404  	test.testConfig(t)
   405  }
   406  
   407  func TestValidateAuthInfoExec(t *testing.T) {
   408  	config := clientcmdapi.NewConfig()
   409  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   410  		Exec: &clientcmdapi.ExecConfig{
   411  			Command:    "/bin/example",
   412  			APIVersion: "clientauthentication.k8s.io/v1alpha1",
   413  			Args:       []string{"hello", "world"},
   414  			Env: []clientcmdapi.ExecEnvVar{
   415  				{Name: "foo", Value: "bar"},
   416  			},
   417  			InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
   418  		},
   419  	}
   420  	test := configValidationTest{
   421  		config: config,
   422  	}
   423  
   424  	test.testAuthInfo("user", t)
   425  	test.testConfig(t)
   426  }
   427  
   428  func TestValidateAuthInfoExecNoVersion(t *testing.T) {
   429  	config := clientcmdapi.NewConfig()
   430  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   431  		Exec: &clientcmdapi.ExecConfig{
   432  			Command:         "/bin/example",
   433  			InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
   434  		},
   435  	}
   436  	test := configValidationTest{
   437  		config: config,
   438  		expectedErrorSubstring: []string{
   439  			"apiVersion must be specified for user to use exec authentication plugin",
   440  		},
   441  	}
   442  
   443  	test.testAuthInfo("user", t)
   444  	test.testConfig(t)
   445  }
   446  
   447  func TestValidateAuthInfoExecNoCommand(t *testing.T) {
   448  	config := clientcmdapi.NewConfig()
   449  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   450  		Exec: &clientcmdapi.ExecConfig{
   451  			APIVersion:      "clientauthentication.k8s.io/v1alpha1",
   452  			InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
   453  		},
   454  	}
   455  	test := configValidationTest{
   456  		config: config,
   457  		expectedErrorSubstring: []string{
   458  			"command must be specified for user to use exec authentication plugin",
   459  		},
   460  	}
   461  
   462  	test.testAuthInfo("user", t)
   463  	test.testConfig(t)
   464  }
   465  
   466  func TestValidateAuthInfoExecWithAuthProvider(t *testing.T) {
   467  	config := clientcmdapi.NewConfig()
   468  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   469  		AuthProvider: &clientcmdapi.AuthProviderConfig{
   470  			Name: "oidc",
   471  		},
   472  		Exec: &clientcmdapi.ExecConfig{
   473  			Command:         "/bin/example",
   474  			APIVersion:      "clientauthentication.k8s.io/v1alpha1",
   475  			InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
   476  		},
   477  	}
   478  	test := configValidationTest{
   479  		config: config,
   480  		expectedErrorSubstring: []string{
   481  			"authProvider cannot be provided in combination with an exec plugin for user",
   482  		},
   483  	}
   484  
   485  	test.testAuthInfo("user", t)
   486  	test.testConfig(t)
   487  }
   488  
   489  func TestValidateAuthInfoExecNoEnv(t *testing.T) {
   490  	config := clientcmdapi.NewConfig()
   491  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   492  		Exec: &clientcmdapi.ExecConfig{
   493  			Command:    "/bin/example",
   494  			APIVersion: "clientauthentication.k8s.io/v1alpha1",
   495  			Env: []clientcmdapi.ExecEnvVar{
   496  				{Name: "foo", Value: ""},
   497  			},
   498  			InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
   499  		},
   500  	}
   501  	test := configValidationTest{
   502  		config: config,
   503  	}
   504  
   505  	test.testAuthInfo("user", t)
   506  	test.testConfig(t)
   507  }
   508  
   509  func TestValidateAuthInfoExecInteractiveModeMissing(t *testing.T) {
   510  	config := clientcmdapi.NewConfig()
   511  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   512  		Exec: &clientcmdapi.ExecConfig{
   513  			Command:    "/bin/example",
   514  			APIVersion: "clientauthentication.k8s.io/v1alpha1",
   515  		},
   516  	}
   517  	test := configValidationTest{
   518  		config: config,
   519  		expectedErrorSubstring: []string{
   520  			"interactiveMode must be specified for user to use exec authentication plugin",
   521  		},
   522  	}
   523  
   524  	test.testAuthInfo("user", t)
   525  	test.testConfig(t)
   526  }
   527  
   528  func TestValidateAuthInfoExecInteractiveModeInvalid(t *testing.T) {
   529  	config := clientcmdapi.NewConfig()
   530  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   531  		Exec: &clientcmdapi.ExecConfig{
   532  			Command:         "/bin/example",
   533  			APIVersion:      "clientauthentication.k8s.io/v1alpha1",
   534  			InteractiveMode: "invalid",
   535  		},
   536  	}
   537  	test := configValidationTest{
   538  		config: config,
   539  		expectedErrorSubstring: []string{
   540  			`invalid interactiveMode for user: "invalid"`,
   541  		},
   542  	}
   543  
   544  	test.testAuthInfo("user", t)
   545  	test.testConfig(t)
   546  }
   547  
   548  func TestValidateAuthInfoImpersonateUser(t *testing.T) {
   549  	config := clientcmdapi.NewConfig()
   550  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   551  		Impersonate: "user",
   552  	}
   553  	test := configValidationTest{
   554  		config: config,
   555  	}
   556  	test.testAuthInfo("user", t)
   557  	test.testConfig(t)
   558  }
   559  
   560  func TestValidateAuthInfoImpersonateEverything(t *testing.T) {
   561  	config := clientcmdapi.NewConfig()
   562  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   563  		Impersonate:          "user",
   564  		ImpersonateUID:       "abc123",
   565  		ImpersonateGroups:    []string{"group-1", "group-2"},
   566  		ImpersonateUserExtra: map[string][]string{"key": {"val1", "val2"}},
   567  	}
   568  	test := configValidationTest{
   569  		config: config,
   570  	}
   571  	test.testAuthInfo("user", t)
   572  	test.testConfig(t)
   573  }
   574  
   575  func TestValidateAuthInfoImpersonateGroupsWithoutUserInvalid(t *testing.T) {
   576  	config := clientcmdapi.NewConfig()
   577  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   578  		ImpersonateGroups: []string{"group-1", "group-2"},
   579  	}
   580  	test := configValidationTest{
   581  		config: config,
   582  		expectedErrorSubstring: []string{
   583  			`requesting uid, groups or user-extra for user without impersonating a user`,
   584  		},
   585  	}
   586  	test.testAuthInfo("user", t)
   587  	test.testConfig(t)
   588  }
   589  
   590  func TestValidateAuthInfoImpersonateExtraWithoutUserInvalid(t *testing.T) {
   591  	config := clientcmdapi.NewConfig()
   592  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   593  		ImpersonateUserExtra: map[string][]string{"key": {"val1", "val2"}},
   594  	}
   595  	test := configValidationTest{
   596  		config: config,
   597  		expectedErrorSubstring: []string{
   598  			`requesting uid, groups or user-extra for user without impersonating a user`,
   599  		},
   600  	}
   601  	test.testAuthInfo("user", t)
   602  	test.testConfig(t)
   603  }
   604  
   605  func TestValidateAuthInfoImpersonateUIDWithoutUserInvalid(t *testing.T) {
   606  	config := clientcmdapi.NewConfig()
   607  	config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
   608  		ImpersonateUID: "abc123",
   609  	}
   610  	test := configValidationTest{
   611  		config: config,
   612  		expectedErrorSubstring: []string{
   613  			`requesting uid, groups or user-extra for user without impersonating a user`,
   614  		},
   615  	}
   616  	test.testAuthInfo("user", t)
   617  	test.testConfig(t)
   618  }
   619  
   620  type configValidationTest struct {
   621  	config                 *clientcmdapi.Config
   622  	expectedErrorSubstring []string
   623  }
   624  
   625  func (c configValidationTest) testContext(contextName string, t *testing.T) {
   626  	errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config)
   627  
   628  	if len(c.expectedErrorSubstring) != 0 {
   629  		if len(errs) == 0 {
   630  			t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
   631  		}
   632  		for _, curr := range c.expectedErrorSubstring {
   633  			if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
   634  				t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
   635  			}
   636  		}
   637  
   638  	} else {
   639  		if len(errs) != 0 {
   640  			t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
   641  		}
   642  	}
   643  }
   644  
   645  func (c configValidationTest) testConfirmUsable(contextName string, t *testing.T) {
   646  	err := ConfirmUsable(*c.config, contextName)
   647  
   648  	if len(c.expectedErrorSubstring) != 0 {
   649  		if err == nil {
   650  			t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
   651  		} else {
   652  			for _, curr := range c.expectedErrorSubstring {
   653  				if err != nil && !strings.Contains(err.Error(), curr) {
   654  					t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err)
   655  				}
   656  			}
   657  		}
   658  	} else {
   659  		if err != nil {
   660  			t.Errorf("Unexpected error: %v", err)
   661  		}
   662  	}
   663  }
   664  
   665  func (c configValidationTest) testConfig(t *testing.T) {
   666  	err := Validate(*c.config)
   667  
   668  	if len(c.expectedErrorSubstring) != 0 {
   669  		if err == nil {
   670  			t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
   671  		} else {
   672  			for _, curr := range c.expectedErrorSubstring {
   673  				if err != nil && !strings.Contains(err.Error(), curr) {
   674  					t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err)
   675  				}
   676  			}
   677  			if !IsConfigurationInvalid(err) {
   678  				t.Errorf("all errors should be configuration invalid: %v", err)
   679  			}
   680  		}
   681  	} else {
   682  		if err != nil {
   683  			t.Errorf("Unexpected error: %v", err)
   684  		}
   685  	}
   686  }
   687  
   688  func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
   689  	errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName])
   690  
   691  	if len(c.expectedErrorSubstring) != 0 {
   692  		if len(errs) == 0 {
   693  			t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
   694  		}
   695  		for _, curr := range c.expectedErrorSubstring {
   696  			if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
   697  				t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
   698  			}
   699  		}
   700  
   701  	} else {
   702  		if len(errs) != 0 {
   703  			t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
   704  		}
   705  	}
   706  }
   707  
   708  func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) {
   709  	errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName])
   710  
   711  	if len(c.expectedErrorSubstring) != 0 {
   712  		if len(errs) == 0 {
   713  			t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
   714  		}
   715  		for _, curr := range c.expectedErrorSubstring {
   716  			if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
   717  				t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
   718  			}
   719  		}
   720  
   721  	} else {
   722  		if len(errs) != 0 {
   723  			t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
   724  		}
   725  	}
   726  }
   727  
   728  type alwaysMatchingError struct{}
   729  
   730  func (_ alwaysMatchingError) Error() string {
   731  	return "error"
   732  }
   733  
   734  func (_ alwaysMatchingError) Is(_ error) bool {
   735  	return true
   736  }
   737  
   738  type someError struct{ msg string }
   739  
   740  func (se someError) Error() string {
   741  	if se.msg != "" {
   742  		return se.msg
   743  	}
   744  	return "err"
   745  }
   746  
   747  func TestErrConfigurationInvalidWithErrorsIs(t *testing.T) {
   748  	testCases := []struct {
   749  		name         string
   750  		err          error
   751  		matchAgainst error
   752  		expectMatch  bool
   753  	}{{
   754  		name:         "no match",
   755  		err:          errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")},
   756  		matchAgainst: fmt.Errorf("no entry %s", "here"),
   757  	}, {
   758  		name:         "match via .Is()",
   759  		err:          errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}},
   760  		matchAgainst: errors.New("unauthorized"),
   761  		expectMatch:  true,
   762  	}, {
   763  		name:         "match via equality",
   764  		err:          errConfigurationInvalid{errors.New("err"), someError{}},
   765  		matchAgainst: someError{},
   766  		expectMatch:  true,
   767  	}, {
   768  		name:         "match via nested aggregate",
   769  		err:          errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}},
   770  		matchAgainst: someError{},
   771  		expectMatch:  true,
   772  	}, {
   773  		name:         "match via wrapped aggregate",
   774  		err:          fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}),
   775  		matchAgainst: someError{},
   776  		expectMatch:  true,
   777  	}}
   778  
   779  	for _, tc := range testCases {
   780  		t.Run(tc.name, func(t *testing.T) {
   781  			result := errors.Is(tc.err, tc.matchAgainst)
   782  			if result != tc.expectMatch {
   783  				t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result)
   784  			}
   785  		})
   786  	}
   787  }
   788  
   789  type accessTrackingError struct {
   790  	wasAccessed bool
   791  }
   792  
   793  func (accessTrackingError) Error() string {
   794  	return "err"
   795  }
   796  
   797  func (ate *accessTrackingError) Is(_ error) bool {
   798  	ate.wasAccessed = true
   799  	return true
   800  }
   801  
   802  var _ error = &accessTrackingError{}
   803  
   804  func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) {
   805  	errC := errConfigurationInvalid{&accessTrackingError{}, &accessTrackingError{}}
   806  	_ = errors.Is(errC, &accessTrackingError{})
   807  
   808  	var numAccessed int
   809  	for _, err := range errC {
   810  		if ate := err.(*accessTrackingError); ate.wasAccessed {
   811  			numAccessed++
   812  		}
   813  	}
   814  	if numAccessed != 1 {
   815  		t.Errorf("expected exactly one error to get accessed, got %d", numAccessed)
   816  	}
   817  }
   818  

View as plain text