Change:
kubectl apply displays different errors in v1.21 (and 1.22) vs v1.23 (and above) for the same webhook response, with the error message in v1.23 not being helpful at all. For example, if a webhook returns a status with code is not equal to / the and fields are not populated In v1.21.0, we see the following error. This is a helpful error message with plenty of context. In v1.23.5, for the same webhook status, we'll see: This is not helpful at all. To get an equivalent helpful error message in v1.23.5 Make a webhook return a status with code with or not populated, or not equivalent to . Looking at the code, it looks like this was a consequence of Since this PR was introduced in v1.23, this issue would exist for v1.22 as well. kubectl first checks whether the error message is (in both and ) The definition of was changed in . In 1.21 (and 1.22) just reason "Invalid". Given that the webhook status does not include a , the is executed and kubectl shows a helpful error message. In 1.23 (and above) if OR if the status code is , which is true for the webhook status. Since it’s true, this is executed. is nil, so we just see the response. This error message is not helpful. We should probably update how kubectl handles "invalid" errors and ensure that they display the field too (instead of just restricting to check the ). Verified with v1.21.0 and v1.23.5. Have not verified this for v1.22, but it should also affect v1.22 due to the above reasons. N/A No response No response No response No response
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/sig cli
/assign
Hi So this purely a client-side issue, right? The server is still sending back the exact same response/data structure? Thanks!
FYI, I posted in Kube slack related to this. We were hoping to alter our webhook behavior based on the originating user agent by passing the into the Go context given to our webhook server's request handler. However, it appears we are not able to access the originating user-agent info. As indicated in the Slack thread: In other words, the sooner this issue is fixed, the better.
checked with nikhita offline, will reroute the issue /assign /unassign
fix open in

Discussion:
}) } <ins> // isInvalidReasonStatusError returns true if this is an API Status error with reason=Invalid. // This is distinct from generic 422 errors we want to fall back to generic error handling. func isInvalidReasonStatusError(err error) bool { if !apierrors.IsInvalid(err) { return false } statusError, isStatusError := err.(*apierrors.StatusError) if !isStatusError { return false } status := statusError.Status() return status.Reason == metav1.StatusReasonInvalid } </ins> // checkErr formats a given error as a string and calls the passed handleErr // func with that string and an kubectl exit code. func checkErr(err error, handleErr func(string, int)) {

switch { case err == ErrExit: handleErr("", DefaultErrorExitCode) <del> case apierrors.IsInvalid(err): details := err.(*apierrors.StatusError).Status().Details </del> <ins> case isInvalidReasonStatusError(err): status := err.(*apierrors.StatusError).Status() details := status.Details </ins> s := "The request is invalid" if details == nil { <ins> // if we have no other details, include the message from the server if present if len(status.Message) > 0 { s += ": " + status.Message } </ins> handleErr(s, DefaultErrorExitCode) return } if len(details.Kind) != 0 || len(details.Name) != 0 { s = fmt.Sprintf("The %s %q is invalid", details.Kind, details.Name) <ins> } else if len(status.Message) > 0 && len(details.Causes) == 0 { // only append the message if we have no kind/name details and no causes, // since default invalid error constructors duplicate that information in the message s += ": " + status.Message </ins> } if len(details.Causes) > 0 { errs := statusCausesToAggrError(details.Causes) handleErr(MultilineError(s+": ", errs), DefaultErrorExitCode)

"The request is invalid", DefaultErrorExitCode, }, <ins> // invalid error that that includes a message but no details { &errors.StatusError{metav1.Status{ Status: metav1.StatusFailure, Code:   http.StatusUnprocessableEntity, Reason: metav1.StatusReasonInvalid, // Details is nil. Message: "Some message", }}, "The request is invalid: Some message", DefaultErrorExitCode, }, // webhook response that sets code=422 with no reason { &errors.StatusError{metav1.Status{ Status:  "Failure", Message: `admission webhook "my.webhook" denied the request without explanation`, Code:    422, }}, `Error from server: admission webhook "my.webhook" denied the request without explanation`, DefaultErrorExitCode, }, // webhook response that sets code=422 with no reason and non-nil details { &errors.StatusError{metav1.Status{ Status:  "Failure", Message: `admission webhook "my.webhook" denied the request without explanation`, Code:    422, Details: &metav1.StatusDetails{}, }}, `Error from server: admission webhook "my.webhook" denied the request without explanation`, DefaultErrorExitCode, }, // source-wrapped webhook response that sets code=422 with no reason { AddSourceToErr("creating", "configmap.yaml", &errors.StatusError{metav1.Status{ Status:  "Failure", Message: `admission webhook "my.webhook" denied the request without explanation`, Code:    422, }}), `Error from server: error when creating "configmap.yaml": admission webhook "my.webhook" denied the request without explanation`, DefaultErrorExitCode, }, // webhook response that sets reason=Invalid and code=422 and a message { &errors.StatusError{metav1.Status{ Status:  "Failure", Reason:  "Invalid", Message: `admission webhook "my.webhook" denied the request without explanation`, Code:    422, }}, `The request is invalid: admission webhook "my.webhook" denied the request without explanation`, DefaultErrorExitCode, }, </ins> }) }