diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 5c47c608..f9fddfe4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -352,7 +352,7 @@ }, { "ImportPath": "k8s.io/apimachinery", - "Rev": "6584f51ae935" + "Rev": "003f3e63f669" }, { "ImportPath": "k8s.io/gengo", diff --git a/go.mod b/go.mod index 76ccb399..7d6abbc4 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 google.golang.org/appengine v1.5.0 // indirect k8s.io/api v0.0.0-20200306002249-bf5c8537bc90 - k8s.io/apimachinery v0.0.0-20200303201514-6584f51ae935 + k8s.io/apimachinery v0.0.0-20200306042133-003f3e63f669 k8s.io/klog v1.0.0 k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab sigs.k8s.io/yaml v1.2.0 @@ -39,5 +39,5 @@ replace ( golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // pinned to release-branch.go1.13 golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13 k8s.io/api => k8s.io/api v0.0.0-20200306002249-bf5c8537bc90 - k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200303201514-6584f51ae935 + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200306042133-003f3e63f669 ) diff --git a/go.sum b/go.sum index 354c1f2b..44d0a2c9 100644 --- a/go.sum +++ b/go.sum @@ -183,7 +183,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20200306002249-bf5c8537bc90/go.mod h1:EFuendCidCp9DUXAn3QXS0nWIaAgQYL8VaCqs8KTZBA= -k8s.io/apimachinery v0.0.0-20200303201514-6584f51ae935/go.mod h1:5X8oEhnd931nEg6/Nkumo00nT6ZsCLp2h7Xwd7Ym6P4= +k8s.io/apimachinery v0.0.0-20200306042133-003f3e63f669/go.mod h1:5X8oEhnd931nEg6/Nkumo00nT6ZsCLp2h7Xwd7Ym6P4= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/tools/clientcmd/validation.go b/tools/clientcmd/validation.go index 02dd1130..afe6f80b 100644 --- a/tools/clientcmd/validation.go +++ b/tools/clientcmd/validation.go @@ -86,11 +86,41 @@ func (e errConfigurationInvalid) Error() string { return fmt.Sprintf("invalid configuration: %v", utilerrors.NewAggregate(e).Error()) } -// Errors implements the AggregateError interface +// Errors implements the utilerrors.Aggregate interface func (e errConfigurationInvalid) Errors() []error { return e } +// Is implements the utilerrors.Aggregate interface +func (e errConfigurationInvalid) Is(target error) bool { + return e.visit(func(err error) bool { + return errors.Is(err, target) + }) +} + +func (e errConfigurationInvalid) visit(f func(err error) bool) bool { + for _, err := range e { + switch err := err.(type) { + case errConfigurationInvalid: + if match := err.visit(f); match { + return match + } + case utilerrors.Aggregate: + for _, nestedErr := range err.Errors() { + if match := f(nestedErr); match { + return match + } + } + default: + if match := f(err); match { + return match + } + } + } + + return false +} + // IsConfigurationInvalid returns true if the provided error indicates the configuration is invalid. func IsConfigurationInvalid(err error) bool { switch err.(type) { diff --git a/tools/clientcmd/validation_test.go b/tools/clientcmd/validation_test.go index 1680eeb1..caf67054 100644 --- a/tools/clientcmd/validation_test.go +++ b/tools/clientcmd/validation_test.go @@ -17,6 +17,8 @@ limitations under the License. package clientcmd import ( + "errors" + "fmt" "io/ioutil" "os" "strings" @@ -569,3 +571,100 @@ func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) { } } } + +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) + } +}