Change:
Checked a method error using when the error was caused by a validating webhook that returns a 403 response. In this particular case, the error was generated from a validating webhook implemented with controller-runtime that returns controller-runtime's response. to return true. returned false Many (but not all) of the other family of functions in suffer the same kind of problem. I have changes ready to submit in a PR if we can get agreement that this is a problem and that it should be fixed in the functions that ought to check the of an erorr value that is or wraps an Kubernetes version (use ): 19.3 for the server
/sig api-machinery
are you going to send a PR for this? The issue description looks reasonable. /triage accepted
Thanks, PR is submitted.
/assign

Discussion:
var _ error = &StatusError{} <ins> var knownReasons = map[metav1.StatusReason]struct{}{ // metav1.StatusReasonUnknown : {} metav1.StatusReasonUnauthorized:          {}, metav1.StatusReasonForbidden:             {}, metav1.StatusReasonNotFound:              {}, metav1.StatusReasonAlreadyExists:         {}, metav1.StatusReasonConflict:              {}, metav1.StatusReasonGone:                  {}, metav1.StatusReasonInvalid:               {}, metav1.StatusReasonServerTimeout:         {}, metav1.StatusReasonTimeout:               {}, metav1.StatusReasonTooManyRequests:       {}, metav1.StatusReasonBadRequest:            {}, metav1.StatusReasonMethodNotAllowed:      {}, metav1.StatusReasonNotAcceptable:         {}, metav1.StatusReasonRequestEntityTooLarge: {}, metav1.StatusReasonUnsupportedMediaType:  {}, metav1.StatusReasonInternalError:         {}, metav1.StatusReasonExpired:               {}, metav1.StatusReasonServiceUnavailable:    {}, } </ins> // Error implements the Error interface. func (e *StatusError) Error() string { return e.ErrStatus.Message

// IsNotFound returns true if the specified error was created by NewNotFound. // It supports wrapped errors. func IsNotFound(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonNotFound </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonNotFound { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusNotFound { return true } return false </ins> } // IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.

// IsConflict determines if the err is an error which indicates the provided update conflicts. // It supports wrapped errors. func IsConflict(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonConflict </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonConflict { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusConflict { return true } return false </ins> } // IsInvalid determines if the err is an error which indicates the provided resource is not valid. // It supports wrapped errors. func IsInvalid(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonInvalid </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonInvalid { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnprocessableEntity { return true } return false </ins> } // IsGone is true if the error indicates the requested resource is no longer available. // It supports wrapped errors. func IsGone(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonGone </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonGone { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusGone { return true } return false </ins> } // IsResourceExpired is true if the error indicates the resource has expired and the current action is

// IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header // It supports wrapped errors. func IsNotAcceptable(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonNotAcceptable </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonNotAcceptable { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusNotAcceptable { return true } return false </ins> } // IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header // It supports wrapped errors. func IsUnsupportedMediaType(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonUnsupportedMediaType </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonUnsupportedMediaType { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnsupportedMediaType { return true } return false </ins> } // IsMethodNotSupported determines if the err is an error which indicates the provided action could not // be performed because it is not supported by the server. // It supports wrapped errors. func IsMethodNotSupported(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonMethodNotAllowed </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonMethodNotAllowed { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusMethodNotAllowed { return true } return false </ins> } // IsServiceUnavailable is true if the error indicates the underlying service is no longer available. // It supports wrapped errors. func IsServiceUnavailable(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonServiceUnavailable </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonServiceUnavailable { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusServiceUnavailable { return true } return false </ins> } // IsBadRequest determines if err is an error which indicates that the request is invalid. // It supports wrapped errors. func IsBadRequest(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonBadRequest </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonBadRequest { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusBadRequest { return true } return false </ins> } // IsUnauthorized determines if err is an error which indicates that the request is unauthorized and // requires authentication by the user. // It supports wrapped errors. func IsUnauthorized(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonUnauthorized </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonUnauthorized { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnauthorized { return true } return false </ins> } // IsForbidden determines if err is an error which indicates that the request is forbidden and cannot // be completed as requested. // It supports wrapped errors. func IsForbidden(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonForbidden </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonForbidden { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusForbidden { return true } return false </ins> } // IsTimeout determines if err is an error which indicates that request times out due to long // processing. // It supports wrapped errors. func IsTimeout(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonTimeout </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonTimeout { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusGatewayTimeout { return true } return false </ins> } // IsServerTimeout determines if err is an error which indicates that the request needs to be retried // by the client. // It supports wrapped errors. func IsServerTimeout(err error) bool { <ins> // do not check the status code, because no https status code exists that can // be scoped to retryable timeouts. </ins> return ReasonForError(err) == metav1.StatusReasonServerTimeout } // IsInternalError determines if err is an error which indicates an internal server error. // It supports wrapped errors. func IsInternalError(err error) bool { <del> return ReasonForError(err) == metav1.StatusReasonInternalError </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonInternalError { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusInternalServerError { return true } return false </ins> } // IsTooManyRequests determines if err is an error which indicates that there are too many requests // that the server cannot handle. // It supports wrapped errors. func IsTooManyRequests(err error) bool { <del> if ReasonForError(err) == metav1.StatusReasonTooManyRequests { </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonTooManyRequests { </ins> return true } <del> if status := APIStatus(nil); errors.As(err, &status) { return status.Status().Code == http.StatusTooManyRequests </del> <ins> // IsTooManyRequests' checking of code predates the checking of the code in // the other Is* functions. In order to maintain backward compatibility, this // does not check that the reason is unknown. if code == http.StatusTooManyRequests { return true </ins> } return false }

// the request entity is too large. // It supports wrapped errors. func IsRequestEntityTooLargeError(err error) bool { <del> if ReasonForError(err) == metav1.StatusReasonRequestEntityTooLarge { </del> <ins> reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonRequestEntityTooLarge { </ins> return true } <del> if status := APIStatus(nil); errors.As(err, &status) { return status.Status().Code == http.StatusRequestEntityTooLarge </del> <ins> // IsRequestEntityTooLargeError's checking of code predates the checking of // the code in the other Is* functions. In order to maintain backward // compatibility, this does not check that the reason is unknown. if code == http.StatusRequestEntityTooLarge { return true </ins> } return false }

return metav1.StatusReasonUnknown } <ins> func reasonAndCodeForError(err error) (metav1.StatusReason, int32) { if status := APIStatus(nil); errors.As(err, &status) { return status.Status().Reason, status.Status().Code } return metav1.StatusReasonUnknown, 0 } </ins> // ErrorReporter converts generic errors into runtime.Object errors without // requiring the caller to take a dependency on meta/v1 (where Status lives). // This prevents circular dependencies in core watch code.

}) } } <ins> func TestIsErrorTypesByReasonAndCode(t *testing.T) { testCases := []struct { name                  string knownReason           metav1.StatusReason otherReason           metav1.StatusReason otherReasonConsidered bool code                  int32 fn                    func(error) bool }{ { name:                  "IsRequestEntityTooLarge", knownReason:           metav1.StatusReasonRequestEntityTooLarge, otherReason:           metav1.StatusReasonForbidden, otherReasonConsidered: false, code:                  http.StatusRequestEntityTooLarge, fn:                    IsRequestEntityTooLargeError, }, { name:                  "TooManyRequests", knownReason:           metav1.StatusReasonTooManyRequests, otherReason:           metav1.StatusReasonForbidden, otherReasonConsidered: false, code:                  http.StatusTooManyRequests, fn:                    IsTooManyRequests, }, { name:                  "Forbidden", knownReason:           metav1.StatusReasonForbidden, otherReason:           metav1.StatusReasonNotFound, otherReasonConsidered: true, code:                  http.StatusForbidden, fn:                    IsForbidden, }, { name:                  "NotFound", knownReason:           metav1.StatusReasonNotFound, otherReason:           metav1.StatusReasonForbidden, otherReasonConsidered: true, code:                  http.StatusNotFound, fn:                    IsNotFound, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Run("by known reason", func(t *testing.T) { err := &StatusError{ metav1.Status{ Reason: tc.knownReason, }, } got := tc.fn(err) if !got { t.Errorf("expected reason %s to match", tc.knownReason) } }) t.Run("by code and unknown reason", func(t *testing.T) { err := &StatusError{ metav1.Status{ Reason: metav1.StatusReasonUnknown, // this could be _any_ reason that isn't in knownReasons. Code:   tc.code, }, } got := tc.fn(err) if !got { t.Errorf("expected code %d with reason %s to match", tc.code, tc.otherReason) } }) if !tc.otherReasonConsidered { return } t.Run("by code and other known reason", func(t *testing.T) { err := &StatusError{ metav1.Status{ Reason: tc.otherReason, Code:   tc.code, }, } got := tc.fn(err) if got { t.Errorf("expected code %d with reason %s to not match", tc.code, tc.otherReason) } }) }) } } </ins>