mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Make kubectl errors even more user-friendly
Omit glog prefix when v < 2, show multiline errors for configuration problems, add new generic messages for server errors that hide some complexity that is not relevant for users.
This commit is contained in:
parent
9b5b27a12e
commit
323a44e54a
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -58,6 +59,14 @@ func (e *StatusError) Status() api.Status {
|
||||
return e.ErrStatus
|
||||
}
|
||||
|
||||
// DebugError reports extended info about the error to debug output.
|
||||
func (e *StatusError) DebugError() (string, []interface{}) {
|
||||
if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil {
|
||||
return "server response object: %s", []interface{}{string(out)}
|
||||
}
|
||||
return "server response object: %#v", []interface{}{e.ErrStatus}
|
||||
}
|
||||
|
||||
// UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
|
||||
type UnexpectedObjectError struct {
|
||||
Object runtime.Object
|
||||
@ -355,16 +364,26 @@ func IsServerTimeout(err error) bool {
|
||||
return reasonForError(err) == api.StatusReasonServerTimeout
|
||||
}
|
||||
|
||||
// IsStatusError determines if err is an API Status error received from the master.
|
||||
func IsStatusError(err error) bool {
|
||||
_, ok := err.(*StatusError)
|
||||
return ok
|
||||
// IsUnexpectedServerError returns true if the server response was not in the expected API format,
|
||||
// and may be the result of another HTTP actor.
|
||||
func IsUnexpectedServerError(err error) bool {
|
||||
switch t := err.(type) {
|
||||
case *StatusError:
|
||||
if d := t.Status().Details; d != nil {
|
||||
for _, cause := range d.Causes {
|
||||
if cause.Type == api.CauseTypeUnexpectedServerResponse {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
|
||||
func IsUnexpectedObjectError(err error) bool {
|
||||
_, ok := err.(*UnexpectedObjectError)
|
||||
return ok
|
||||
return err != nil && ok
|
||||
}
|
||||
|
||||
// SuggestsClientDelay returns true if this error suggests a client delay as well as the
|
||||
|
@ -51,25 +51,6 @@ type HTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// UnexpectedStatusError is returned as an error if a response's body and HTTP code don't
|
||||
// make sense together.
|
||||
type UnexpectedStatusError struct {
|
||||
Request *http.Request
|
||||
Response *http.Response
|
||||
Body string
|
||||
}
|
||||
|
||||
// Error returns a textual description of 'u'.
|
||||
func (u *UnexpectedStatusError) Error() string {
|
||||
return fmt.Sprintf("request [%+v] failed (%d) %s: %s", u.Request, u.Response.StatusCode, u.Response.Status, u.Body)
|
||||
}
|
||||
|
||||
// IsUnexpectedStatusError determines if err is due to an unexpected status from the server.
|
||||
func IsUnexpectedStatusError(err error) bool {
|
||||
_, ok := err.(*UnexpectedStatusError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// RequestConstructionError is returned when there's an error assembling a request.
|
||||
type RequestConstructionError struct {
|
||||
Err error
|
||||
@ -661,7 +642,6 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request, body
|
||||
// initial contact, the presence of mismatched body contents from posted content types
|
||||
// - Give these a separate distinct error type and capture as much as possible of the original message
|
||||
//
|
||||
// TODO: introduce further levels of refinement that allow a client to distinguish between 1 and 2-3.
|
||||
// TODO: introduce transformation of generic http.Client.Do() errors that separates 4.
|
||||
func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error {
|
||||
if body == nil && resp.Body != nil {
|
||||
@ -669,43 +649,12 @@ func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *h
|
||||
body = data
|
||||
}
|
||||
}
|
||||
var err error = &UnexpectedStatusError{
|
||||
Request: req,
|
||||
Response: resp,
|
||||
Body: string(body),
|
||||
}
|
||||
message := "unknown"
|
||||
if isTextResponse(resp) {
|
||||
message = strings.TrimSpace(string(body))
|
||||
}
|
||||
// TODO: handle other error classes we know about
|
||||
switch resp.StatusCode {
|
||||
case http.StatusConflict:
|
||||
if req.Method == "POST" {
|
||||
err = errors.NewAlreadyExists(r.resource, r.resourceName)
|
||||
} else {
|
||||
err = errors.NewConflict(r.resource, r.resourceName, err)
|
||||
}
|
||||
case http.StatusNotFound:
|
||||
err = errors.NewNotFound(r.resource, r.resourceName)
|
||||
case http.StatusBadRequest:
|
||||
err = errors.NewBadRequest(message)
|
||||
case http.StatusUnauthorized:
|
||||
err = errors.NewUnauthorized(message)
|
||||
case http.StatusForbidden:
|
||||
err = errors.NewForbidden(r.resource, r.resourceName, err)
|
||||
case errors.StatusUnprocessableEntity:
|
||||
err = errors.NewInvalid(r.resource, r.resourceName, nil)
|
||||
case errors.StatusServerTimeout:
|
||||
retryAfterSeconds, _ := retryAfterSeconds(resp)
|
||||
err = errors.NewServerTimeout(r.resource, r.verb, retryAfterSeconds)
|
||||
case errors.StatusTooManyRequests:
|
||||
retryAfterSeconds, _ := retryAfterSeconds(resp)
|
||||
err = errors.NewServerTimeout(r.resource, r.verb, retryAfterSeconds)
|
||||
case http.StatusInternalServerError:
|
||||
err = errors.NewInternalError(fmt.Errorf(message))
|
||||
}
|
||||
return err
|
||||
retryAfter, _ := retryAfterSeconds(resp)
|
||||
return errors.NewGenericServerResponse(resp.StatusCode, req.Method, r.resource, r.resourceName, message, retryAfter)
|
||||
}
|
||||
|
||||
// isTextResponse returns true if the response appears to be a textual media type.
|
||||
|
@ -251,7 +251,7 @@ func TestTransformResponse(t *testing.T) {
|
||||
},
|
||||
Error: true,
|
||||
ErrFn: func(err error) bool {
|
||||
return err.Error() == "aaaaa" && apierrors.IsUnauthorized(err)
|
||||
return strings.Contains(err.Error(), "server has asked for the client to provide") && apierrors.IsUnauthorized(err)
|
||||
},
|
||||
},
|
||||
{Response: &http.Response{StatusCode: 403}, Error: true},
|
||||
|
@ -17,11 +17,13 @@ limitations under the License.
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -30,7 +32,9 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
utilerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
"github.com/evanphx/json-patch"
|
||||
|
||||
"github.com/golang/glog"
|
||||
@ -38,21 +42,69 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type debugError interface {
|
||||
DebugError() (msg string, args []interface{})
|
||||
}
|
||||
|
||||
func CheckErr(err error) {
|
||||
if err != nil {
|
||||
if errors.IsStatusError(err) {
|
||||
glog.FatalDepth(1, fmt.Sprintf("Error received from API: %s", err.Error()))
|
||||
if debugErr, ok := err.(debugError); ok {
|
||||
glog.V(4).Infof(debugErr.DebugError())
|
||||
}
|
||||
if errors.IsUnexpectedObjectError(err) {
|
||||
glog.FatalDepth(1, fmt.Sprintf("Unexpected object received from server: %s", err.Error()))
|
||||
_, isStatus := err.(client.APIStatus)
|
||||
switch {
|
||||
case clientcmd.IsConfigurationInvalid(err):
|
||||
fatal(MultilineError("Error in configuration: ", err))
|
||||
case isStatus:
|
||||
fatal(fmt.Sprintf("Error from server: %s", err.Error()))
|
||||
case errors.IsUnexpectedObjectError(err):
|
||||
fatal(fmt.Sprintf("Server returned an unexpected response: %s", err.Error()))
|
||||
}
|
||||
if client.IsUnexpectedStatusError(err) {
|
||||
glog.FatalDepth(1, fmt.Sprintf("Unexpected status received from server: %s", err.Error()))
|
||||
switch t := err.(type) {
|
||||
case *url.Error:
|
||||
glog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
|
||||
switch {
|
||||
case strings.Contains(t.Err.Error(), "connection refused"):
|
||||
host := t.URL
|
||||
if server, err := url.Parse(t.URL); err == nil {
|
||||
host = server.Host
|
||||
}
|
||||
fatal(fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host))
|
||||
}
|
||||
fatal(fmt.Sprintf("Unable to connect to the server: %v", t.Err))
|
||||
}
|
||||
glog.FatalDepth(1, fmt.Sprintf("Client error: %s", err.Error()))
|
||||
fatal(fmt.Sprintf("Error: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
func MultilineError(prefix string, err error) string {
|
||||
if agg, ok := err.(utilerrors.Aggregate); ok {
|
||||
errs := agg.Errors()
|
||||
buf := &bytes.Buffer{}
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
return fmt.Sprintf("%s%v", prefix, err)
|
||||
case 1:
|
||||
return fmt.Sprintf("%s%v", prefix, errs[0])
|
||||
default:
|
||||
fmt.Fprintln(buf, prefix)
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(buf, "* %v\n", err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s%s", prefix, err)
|
||||
}
|
||||
|
||||
func fatal(msg string) {
|
||||
if glog.V(2) {
|
||||
glog.FatalDepth(2, msg)
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func UsageError(cmd *cobra.Command, format string, args ...interface{}) error {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
return fmt.Errorf("%s\nsee '%s -h' for help.", msg, cmd.CommandPath())
|
||||
|
Loading…
Reference in New Issue
Block a user