Change:
I have a controller that creates objects. It uses to notice when create fails due to a deleting namespace. This check was working well until I moved it away from each call. In some cases, the error is wrapped using causing the function to always return false. I expected to work on both wrapped and unwrapped errors, like the functions do. This short program shows the issue: I am able to work around it by first calling . This type assertion should be replaced with a call to : /sig api-machinery v1.22.5, but I also see the affected code in master. <details</detailsn/a n/a k3s n/a n/a
/cc /triage accepted
The Kubernetes project currently lacks enough contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Mark this issue or PR as fresh with Mark this issue or PR as rotten with Close this issue or PR with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /lifecycle stale [1]:
The Kubernetes project currently lacks enough active contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Mark this issue or PR as fresh with Close this issue or PR with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /lifecycle rotten [1]:
/remove-lifecycle rotten

Discussion:
// HasStatusCause returns true if the provided error has a details cause // with the provided type name. <ins> // It supports wrapped errors and returns false when the error is nil. </ins> func HasStatusCause(err error, name metav1.CauseType) bool { _, ok := StatusCause(err, name) return ok } // StatusCause returns the named cause from the provided error if it exists and <del> // the error is of the type APIStatus. Otherwise it returns false. </del> <ins> // the error unwraps to the type APIStatus. Otherwise it returns false. </ins> func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) { <del> apierr, ok := err.(APIStatus) if !ok || apierr == nil || apierr.Status().Details == nil { return metav1.StatusCause{}, false } for _, cause := range apierr.Status().Details.Causes { if cause.Type == name { return cause, true </del> <ins> status, ok := err.(APIStatus) if (ok || errors.As(err, &status)) && status.Status().Details != nil { for _, cause := range status.Status().Details.Causes { if cause.Type == name { return cause, true } </ins> } } return metav1.StatusCause{}, false

// and may be the result of another HTTP actor. // It supports wrapped errors and returns false when the error is nil. func IsUnexpectedServerError(err error) bool { <del> if status := APIStatus(nil); errors.As(err, &status) && status.Status().Details != nil { </del> <ins> status, ok := err.(APIStatus) if (ok || errors.As(err, &status)) && status.Status().Details != nil { </ins> for _, cause := range status.Status().Details.Causes { if cause.Type == metav1.CauseTypeUnexpectedServerResponse { return true

// IsUnexpectedObjectError determines if err is due to an unexpected object from the master. // It supports wrapped errors and returns false when the error is nil. func IsUnexpectedObjectError(err error) bool { <del> uoe := &UnexpectedObjectError{} return err != nil && errors.As(err, &uoe) </del> <ins> uoe, ok := err.(*UnexpectedObjectError) return err != nil && (ok || errors.As(err, &uoe)) </ins> } // SuggestsClientDelay returns true if this error suggests a client delay as well as the

// request delay without retry. // It supports wrapped errors and returns false when the error is nil. func SuggestsClientDelay(err error) (int, bool) { <del> if t := APIStatus(nil); errors.As(err, &t) && t.Status().Details != nil { </del> <ins> t, ok := err.(APIStatus) if (ok || errors.As(err, &t)) && t.Status().Details != nil { </ins> switch t.Status().Reason { // this StatusReason explicitly requests the caller to delay the action case metav1.StatusReasonServerTimeout:

// It supports wrapped errors and returns StatusReasonUnknown when // the error is nil or doesn't have a status. func ReasonForError(err error) metav1.StatusReason { <del> if status := APIStatus(nil); errors.As(err, &status) { </del> <ins> if status, ok := err.(APIStatus); ok || errors.As(err, &status) { </ins> return status.Status().Reason } return metav1.StatusReasonUnknown } func reasonAndCodeForError(err error) (metav1.StatusReason, int32) { <del> if status := APIStatus(nil); errors.As(err, &status) { </del> <ins> if status, ok := err.(APIStatus); ok || errors.As(err, &status) { </ins> return status.Status().Reason, status.Status().Code } return metav1.StatusReasonUnknown, 0

} } <ins> func TestStatusCauseSupportsWrappedErrors(t *testing.T) { err := &StatusError{ErrStatus: metav1.Status{ Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{{Type: "SomeCause"}}, }, }} if cause, ok := StatusCause(nil, "SomeCause"); ok { t.Errorf("expected no cause for nil, got %v: %#v", ok, cause) } if cause, ok := StatusCause(errors.New("boom"), "SomeCause"); ok { t.Errorf("expected no cause for wrong type, got %v: %#v", ok, cause) } if cause, ok := StatusCause(err, "Other"); ok { t.Errorf("expected no cause for wrong name, got %v: %#v", ok, cause) } if cause, ok := StatusCause(err, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { t.Errorf("expected cause, got %v: %#v", ok, cause) } wrapped := fmt.Errorf("once: %w", err) if cause, ok := StatusCause(wrapped, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { t.Errorf("expected cause when wrapped, got %v: %#v", ok, cause) } nested := fmt.Errorf("twice: %w", wrapped) if cause, ok := StatusCause(nested, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { t.Errorf("expected cause when nested, got %v: %#v", ok, cause) } } func BenchmarkIsAlreadyExistsWrappedErrors(b *testing.B) { err := NewAlreadyExists(schema.GroupResource{}, "") wrapped := fmt.Errorf("once: %w", err) b.Run("Nil", func(b *testing.B) { for i := 0; i < b.N; i++ { IsAlreadyExists(nil) } }) b.Run("Bare", func(b *testing.B) { for i := 0; i < b.N; i++ { IsAlreadyExists(err) } }) b.Run("Wrapped", func(b *testing.B) { for i := 0; i < b.N; i++ { IsAlreadyExists(wrapped) } }) } func BenchmarkIsNotFoundWrappedErrors(b *testing.B) { err := NewNotFound(schema.GroupResource{}, "") wrapped := fmt.Errorf("once: %w", err) b.Run("Nil", func(b *testing.B) { for i := 0; i < b.N; i++ { IsNotFound(nil) } }) b.Run("Bare", func(b *testing.B) { for i := 0; i < b.N; i++ { IsNotFound(err) } }) b.Run("Wrapped", func(b *testing.B) { for i := 0; i < b.N; i++ { IsNotFound(wrapped) } }) } </ins>