/*
|
Copyright 2014 The Kubernetes Authors.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
you may not use this file except in compliance with the License.
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
distributed under the License is distributed on an "AS IS" BASIS,
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
See the License for the specific language governing permissions and
|
limitations under the License.
|
*/
|
|
package clientcmd
|
|
import (
|
"errors"
|
"fmt"
|
"io/ioutil"
|
"os"
|
"strings"
|
"testing"
|
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
)
|
|
func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.Clusters["missing ca"] = &clientcmdapi.Cluster{
|
Server: "anything",
|
CertificateAuthority: "missing",
|
}
|
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
Username: "anything",
|
Token: "here",
|
}
|
config.Contexts["dirty"] = &clientcmdapi.Context{
|
Cluster: "missing ca",
|
AuthInfo: "error",
|
}
|
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
Server: "anything",
|
}
|
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
Token: "here",
|
}
|
config.Contexts["clean"] = &clientcmdapi.Context{
|
Cluster: "clean",
|
AuthInfo: "clean",
|
}
|
|
badValidation := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"unable to read certificate-authority"},
|
}
|
okTest := configValidationTest{
|
config: config,
|
}
|
|
okTest.testConfirmUsable("clean", t)
|
badValidation.testConfig(t)
|
}
|
|
func TestConfirmUsableBadInfoConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.Clusters["missing ca"] = &clientcmdapi.Cluster{
|
Server: "anything",
|
CertificateAuthority: "missing",
|
}
|
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
Username: "anything",
|
Token: "here",
|
}
|
config.Contexts["first"] = &clientcmdapi.Context{
|
Cluster: "missing ca",
|
AuthInfo: "error",
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"unable to read certificate-authority"},
|
}
|
|
test.testConfirmUsable("first", t)
|
}
|
|
func TestConfirmUsableEmptyConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
|
}
|
|
test.testConfirmUsable("", t)
|
}
|
|
func TestConfirmUsableMissingConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
|
}
|
|
test.testConfirmUsable("not-here", t)
|
}
|
|
func TestValidateEmptyConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"},
|
}
|
|
test.testConfig(t)
|
}
|
|
func TestValidateMissingCurrentContextConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.CurrentContext = "anything"
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"context was not found for specified "},
|
}
|
|
test.testConfig(t)
|
}
|
|
func TestIsContextNotFound(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.CurrentContext = "anything"
|
|
err := Validate(*config)
|
if !IsContextNotFound(err) {
|
t.Errorf("Expected context not found, but got %v", err)
|
}
|
if !IsConfigurationInvalid(err) {
|
t.Errorf("Expected configuration invalid, but got %v", err)
|
}
|
}
|
|
func TestIsEmptyConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
|
err := Validate(*config)
|
if !IsEmptyConfig(err) {
|
t.Errorf("Expected context not found, but got %v", err)
|
}
|
if !IsConfigurationInvalid(err) {
|
t.Errorf("Expected configuration invalid, but got %v", err)
|
}
|
}
|
|
func TestIsConfigurationInvalid(t *testing.T) {
|
if newErrConfigurationInvalid([]error{}) != nil {
|
t.Errorf("unexpected error")
|
}
|
if newErrConfigurationInvalid([]error{ErrNoContext}) == ErrNoContext {
|
t.Errorf("unexpected error")
|
}
|
if newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext}) == nil {
|
t.Errorf("unexpected error")
|
}
|
if !IsConfigurationInvalid(newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext})) {
|
t.Errorf("unexpected error")
|
}
|
}
|
|
func TestValidateMissingReferencesConfig(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.CurrentContext = "anything"
|
config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""},
|
}
|
|
test.testContext("anything", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateEmptyContext(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.CurrentContext = "anything"
|
config.Contexts["anything"] = &clientcmdapi.Context{}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""},
|
}
|
|
test.testContext("anything", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateEmptyContextName(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.CurrentContext = "anything"
|
config.Contexts[""] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"empty context name", "is not allowed"},
|
}
|
|
test.testContext("", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateEmptyClusterInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.Clusters["empty"] = clientcmdapi.NewCluster()
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"cluster has no server defined"},
|
}
|
|
test.testCluster("empty", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateClusterInfoErrEmptyCluster(t *testing.T) {
|
cluster := clientcmdapi.NewCluster()
|
errs := validateClusterInfo("", *cluster)
|
|
if len(errs) != 1 {
|
t.Fatalf("unexpected errors: %v", errs)
|
}
|
if errs[0] != ErrEmptyCluster {
|
t.Errorf("unexpected error: %v", errs[0])
|
}
|
}
|
|
func TestValidateMissingCAFileClusterInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.Clusters["missing ca"] = &clientcmdapi.Cluster{
|
Server: "anything",
|
CertificateAuthority: "missing",
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"unable to read certificate-authority"},
|
}
|
|
test.testCluster("missing ca", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateCleanClusterInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
Server: "anything",
|
}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testCluster("clean", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateCleanWithCAClusterInfo(t *testing.T) {
|
tempFile, _ := ioutil.TempFile("", "")
|
defer os.Remove(tempFile.Name())
|
|
config := clientcmdapi.NewConfig()
|
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
Server: "anything",
|
CertificateAuthority: tempFile.Name(),
|
}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testCluster("clean", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateEmptyAuthInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testAuthInfo("error", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
ClientCertificate: "missing",
|
ClientKey: "missing",
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"unable to read client-cert", "unable to read client-key"},
|
}
|
|
test.testAuthInfo("error", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateCertDataOverridesFiles(t *testing.T) {
|
tempFile, _ := ioutil.TempFile("", "")
|
defer os.Remove(tempFile.Name())
|
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
ClientCertificate: tempFile.Name(),
|
ClientCertificateData: []byte("certdata"),
|
ClientKey: tempFile.Name(),
|
ClientKeyData: []byte("keydata"),
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"client-cert-data and client-cert are both specified", "client-key-data and client-key are both specified"},
|
}
|
|
test.testAuthInfo("clean", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
|
tempFile, _ := ioutil.TempFile("", "")
|
defer os.Remove(tempFile.Name())
|
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
ClientCertificate: tempFile.Name(),
|
ClientKey: tempFile.Name(),
|
}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testAuthInfo("clean", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateCleanTokenAuthInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
Token: "any-value",
|
}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testAuthInfo("clean", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
|
Token: "token",
|
Username: "username",
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{"more than one authentication method", "token", "basicAuth"},
|
}
|
|
test.testAuthInfo("error", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateAuthInfoExec(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
|
Exec: &clientcmdapi.ExecConfig{
|
Command: "/bin/example",
|
APIVersion: "clientauthentication.k8s.io/v1alpha1",
|
Args: []string{"hello", "world"},
|
Env: []clientcmdapi.ExecEnvVar{
|
{Name: "foo", Value: "bar"},
|
},
|
},
|
}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testAuthInfo("user", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateAuthInfoExecNoVersion(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
|
Exec: &clientcmdapi.ExecConfig{
|
Command: "/bin/example",
|
},
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{
|
"apiVersion must be specified for user to use exec authentication plugin",
|
},
|
}
|
|
test.testAuthInfo("user", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateAuthInfoExecNoCommand(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
|
Exec: &clientcmdapi.ExecConfig{
|
APIVersion: "clientauthentication.k8s.io/v1alpha1",
|
},
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{
|
"command must be specified for user to use exec authentication plugin",
|
},
|
}
|
|
test.testAuthInfo("user", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateAuthInfoExecWithAuthProvider(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
|
AuthProvider: &clientcmdapi.AuthProviderConfig{
|
Name: "oidc",
|
},
|
Exec: &clientcmdapi.ExecConfig{
|
Command: "/bin/example",
|
APIVersion: "clientauthentication.k8s.io/v1alpha1",
|
},
|
}
|
test := configValidationTest{
|
config: config,
|
expectedErrorSubstring: []string{
|
"authProvider cannot be provided in combination with an exec plugin for user",
|
},
|
}
|
|
test.testAuthInfo("user", t)
|
test.testConfig(t)
|
}
|
|
func TestValidateAuthInfoExecNoEnv(t *testing.T) {
|
config := clientcmdapi.NewConfig()
|
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
|
Exec: &clientcmdapi.ExecConfig{
|
Command: "/bin/example",
|
APIVersion: "clientauthentication.k8s.io/v1alpha1",
|
Env: []clientcmdapi.ExecEnvVar{
|
{Name: "foo", Value: ""},
|
},
|
},
|
}
|
test := configValidationTest{
|
config: config,
|
}
|
|
test.testAuthInfo("user", t)
|
test.testConfig(t)
|
}
|
|
type configValidationTest struct {
|
config *clientcmdapi.Config
|
expectedErrorSubstring []string
|
}
|
|
func (c configValidationTest) testContext(contextName string, t *testing.T) {
|
errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config)
|
|
if len(c.expectedErrorSubstring) != 0 {
|
if len(errs) == 0 {
|
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
}
|
for _, curr := range c.expectedErrorSubstring {
|
if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
|
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
|
}
|
}
|
|
} else {
|
if len(errs) != 0 {
|
t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
|
}
|
}
|
}
|
|
func (c configValidationTest) testConfirmUsable(contextName string, t *testing.T) {
|
err := ConfirmUsable(*c.config, contextName)
|
|
if len(c.expectedErrorSubstring) != 0 {
|
if err == nil {
|
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
} else {
|
for _, curr := range c.expectedErrorSubstring {
|
if err != nil && !strings.Contains(err.Error(), curr) {
|
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err)
|
}
|
}
|
}
|
} else {
|
if err != nil {
|
t.Errorf("Unexpected error: %v", err)
|
}
|
}
|
}
|
|
func (c configValidationTest) testConfig(t *testing.T) {
|
err := Validate(*c.config)
|
|
if len(c.expectedErrorSubstring) != 0 {
|
if err == nil {
|
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
} else {
|
for _, curr := range c.expectedErrorSubstring {
|
if err != nil && !strings.Contains(err.Error(), curr) {
|
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err)
|
}
|
}
|
if !IsConfigurationInvalid(err) {
|
t.Errorf("all errors should be configuration invalid: %v", err)
|
}
|
}
|
} else {
|
if err != nil {
|
t.Errorf("Unexpected error: %v", err)
|
}
|
}
|
}
|
|
func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
|
errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName])
|
|
if len(c.expectedErrorSubstring) != 0 {
|
if len(errs) == 0 {
|
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
}
|
for _, curr := range c.expectedErrorSubstring {
|
if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
|
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
|
}
|
}
|
|
} else {
|
if len(errs) != 0 {
|
t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
|
}
|
}
|
}
|
|
func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) {
|
errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName])
|
|
if len(c.expectedErrorSubstring) != 0 {
|
if len(errs) == 0 {
|
t.Errorf("Expected error containing: %v", c.expectedErrorSubstring)
|
}
|
for _, curr := range c.expectedErrorSubstring {
|
if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) {
|
t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs))
|
}
|
}
|
|
} else {
|
if len(errs) != 0 {
|
t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs))
|
}
|
}
|
}
|
|
type alwaysMatchingError struct{}
|
|
func (_ alwaysMatchingError) Error() string {
|
return "error"
|
}
|
|
func (_ alwaysMatchingError) Is(_ error) bool {
|
return true
|
}
|
|
type someError struct{ msg string }
|
|
func (se someError) Error() string {
|
if se.msg != "" {
|
return se.msg
|
}
|
return "err"
|
}
|
|
func TestErrConfigurationInvalidWithErrorsIs(t *testing.T) {
|
testCases := []struct {
|
name string
|
err error
|
matchAgainst error
|
expectMatch bool
|
}{
|
{
|
name: "no match",
|
err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")},
|
matchAgainst: fmt.Errorf("no entry %s", "here"),
|
},
|
{
|
name: "match via .Is()",
|
err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}},
|
matchAgainst: errors.New("unauthorized"),
|
expectMatch: true,
|
},
|
{
|
name: "match via equality",
|
err: errConfigurationInvalid{errors.New("err"), someError{}},
|
matchAgainst: someError{},
|
expectMatch: true,
|
},
|
{
|
name: "match via nested aggregate",
|
err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}},
|
matchAgainst: someError{},
|
expectMatch: true,
|
},
|
{
|
name: "match via wrapped aggregate",
|
err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}),
|
matchAgainst: someError{},
|
expectMatch: true,
|
},
|
}
|
|
for _, tc := range testCases {
|
t.Run(tc.name, func(t *testing.T) {
|
result := errors.Is(tc.err, tc.matchAgainst)
|
if result != tc.expectMatch {
|
t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result)
|
}
|
})
|
}
|
}
|
|
type accessTrackingError struct {
|
wasAccessed bool
|
}
|
|
func (accessTrackingError) Error() string {
|
return "err"
|
}
|
|
func (ate *accessTrackingError) Is(_ error) bool {
|
ate.wasAccessed = true
|
return true
|
}
|
|
var _ error = &accessTrackingError{}
|
|
func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) {
|
errC := errConfigurationInvalid{&accessTrackingError{}, &accessTrackingError{}}
|
_ = errors.Is(errC, &accessTrackingError{})
|
|
var numAccessed int
|
for _, err := range errC {
|
if ate := err.(*accessTrackingError); ate.wasAccessed {
|
numAccessed++
|
}
|
}
|
if numAccessed != 1 {
|
t.Errorf("expected exactly one error to get accessed, got %d", numAccessed)
|
}
|
}
|