1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package errors
18
19import (
20	"encoding/json"
21	"fmt"
22	"net/http"
23	"reflect"
24	"strings"
25
26	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27	"k8s.io/apimachinery/pkg/runtime"
28	"k8s.io/apimachinery/pkg/runtime/schema"
29	"k8s.io/apimachinery/pkg/util/validation/field"
30)
31
32const (
33	// StatusTooManyRequests means the server experienced too many requests within a
34	// given window and that the client must wait to perform the action again.
35	// DEPRECATED: please use http.StatusTooManyRequests, this will be removed in
36	// the future version.
37	StatusTooManyRequests = http.StatusTooManyRequests
38)
39
40// StatusError is an error intended for consumption by a REST API server; it can also be
41// reconstructed by clients from a REST response. Public to allow easy type switches.
42type StatusError struct {
43	ErrStatus metav1.Status
44}
45
46// APIStatus is exposed by errors that can be converted to an api.Status object
47// for finer grained details.
48type APIStatus interface {
49	Status() metav1.Status
50}
51
52var _ error = &StatusError{}
53
54// Error implements the Error interface.
55func (e *StatusError) Error() string {
56	return e.ErrStatus.Message
57}
58
59// Status allows access to e's status without having to know the detailed workings
60// of StatusError.
61func (e *StatusError) Status() metav1.Status {
62	return e.ErrStatus
63}
64
65// DebugError reports extended info about the error to debug output.
66func (e *StatusError) DebugError() (string, []interface{}) {
67	if out, err := json.MarshalIndent(e.ErrStatus, "", "  "); err == nil {
68		return "server response object: %s", []interface{}{string(out)}
69	}
70	return "server response object: %#v", []interface{}{e.ErrStatus}
71}
72
73// HasStatusCause returns true if the provided error has a details cause
74// with the provided type name.
75func HasStatusCause(err error, name metav1.CauseType) bool {
76	_, ok := StatusCause(err, name)
77	return ok
78}
79
80// StatusCause returns the named cause from the provided error if it exists and
81// the error is of the type APIStatus. Otherwise it returns false.
82func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) {
83	apierr, ok := err.(APIStatus)
84	if !ok || apierr == nil || apierr.Status().Details == nil {
85		return metav1.StatusCause{}, false
86	}
87	for _, cause := range apierr.Status().Details.Causes {
88		if cause.Type == name {
89			return cause, true
90		}
91	}
92	return metav1.StatusCause{}, false
93}
94
95// UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
96type UnexpectedObjectError struct {
97	Object runtime.Object
98}
99
100// Error returns an error message describing 'u'.
101func (u *UnexpectedObjectError) Error() string {
102	return fmt.Sprintf("unexpected object: %v", u.Object)
103}
104
105// FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise,
106// returns an UnexpecteObjectError.
107func FromObject(obj runtime.Object) error {
108	switch t := obj.(type) {
109	case *metav1.Status:
110		return &StatusError{ErrStatus: *t}
111	case runtime.Unstructured:
112		var status metav1.Status
113		obj := t.UnstructuredContent()
114		if !reflect.DeepEqual(obj["kind"], "Status") {
115			break
116		}
117		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(t.UnstructuredContent(), &status); err != nil {
118			return err
119		}
120		if status.APIVersion != "v1" && status.APIVersion != "meta.k8s.io/v1" {
121			break
122		}
123		return &StatusError{ErrStatus: status}
124	}
125	return &UnexpectedObjectError{obj}
126}
127
128// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
129func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError {
130	return &StatusError{metav1.Status{
131		Status: metav1.StatusFailure,
132		Code:   http.StatusNotFound,
133		Reason: metav1.StatusReasonNotFound,
134		Details: &metav1.StatusDetails{
135			Group: qualifiedResource.Group,
136			Kind:  qualifiedResource.Resource,
137			Name:  name,
138		},
139		Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name),
140	}}
141}
142
143// NewAlreadyExists returns an error indicating the item requested exists by that identifier.
144func NewAlreadyExists(qualifiedResource schema.GroupResource, name string) *StatusError {
145	return &StatusError{metav1.Status{
146		Status: metav1.StatusFailure,
147		Code:   http.StatusConflict,
148		Reason: metav1.StatusReasonAlreadyExists,
149		Details: &metav1.StatusDetails{
150			Group: qualifiedResource.Group,
151			Kind:  qualifiedResource.Resource,
152			Name:  name,
153		},
154		Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name),
155	}}
156}
157
158// NewUnauthorized returns an error indicating the client is not authorized to perform the requested
159// action.
160func NewUnauthorized(reason string) *StatusError {
161	message := reason
162	if len(message) == 0 {
163		message = "not authorized"
164	}
165	return &StatusError{metav1.Status{
166		Status:  metav1.StatusFailure,
167		Code:    http.StatusUnauthorized,
168		Reason:  metav1.StatusReasonUnauthorized,
169		Message: message,
170	}}
171}
172
173// NewForbidden returns an error indicating the requested action was forbidden
174func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
175	var message string
176	if qualifiedResource.Empty() {
177		message = fmt.Sprintf("forbidden: %v", err)
178	} else if name == "" {
179		message = fmt.Sprintf("%s is forbidden: %v", qualifiedResource.String(), err)
180	} else {
181		message = fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err)
182	}
183	return &StatusError{metav1.Status{
184		Status: metav1.StatusFailure,
185		Code:   http.StatusForbidden,
186		Reason: metav1.StatusReasonForbidden,
187		Details: &metav1.StatusDetails{
188			Group: qualifiedResource.Group,
189			Kind:  qualifiedResource.Resource,
190			Name:  name,
191		},
192		Message: message,
193	}}
194}
195
196// NewConflict returns an error indicating the item can't be updated as provided.
197func NewConflict(qualifiedResource schema.GroupResource, name string, err error) *StatusError {
198	return &StatusError{metav1.Status{
199		Status: metav1.StatusFailure,
200		Code:   http.StatusConflict,
201		Reason: metav1.StatusReasonConflict,
202		Details: &metav1.StatusDetails{
203			Group: qualifiedResource.Group,
204			Kind:  qualifiedResource.Resource,
205			Name:  name,
206		},
207		Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err),
208	}}
209}
210
211// NewApplyConflict returns an error including details on the requests apply conflicts
212func NewApplyConflict(causes []metav1.StatusCause, message string) *StatusError {
213	return &StatusError{ErrStatus: metav1.Status{
214		Status: metav1.StatusFailure,
215		Code:   http.StatusConflict,
216		Reason: metav1.StatusReasonConflict,
217		Details: &metav1.StatusDetails{
218			// TODO: Get obj details here?
219			Causes: causes,
220		},
221		Message: message,
222	}}
223}
224
225// NewGone returns an error indicating the item no longer available at the server and no forwarding address is known.
226// DEPRECATED: Please use NewResourceExpired instead.
227func NewGone(message string) *StatusError {
228	return &StatusError{metav1.Status{
229		Status:  metav1.StatusFailure,
230		Code:    http.StatusGone,
231		Reason:  metav1.StatusReasonGone,
232		Message: message,
233	}}
234}
235
236// NewResourceExpired creates an error that indicates that the requested resource content has expired from
237// the server (usually due to a resourceVersion that is too old).
238func NewResourceExpired(message string) *StatusError {
239	return &StatusError{metav1.Status{
240		Status:  metav1.StatusFailure,
241		Code:    http.StatusGone,
242		Reason:  metav1.StatusReasonExpired,
243		Message: message,
244	}}
245}
246
247// NewInvalid returns an error indicating the item is invalid and cannot be processed.
248func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError {
249	causes := make([]metav1.StatusCause, 0, len(errs))
250	for i := range errs {
251		err := errs[i]
252		causes = append(causes, metav1.StatusCause{
253			Type:    metav1.CauseType(err.Type),
254			Message: err.ErrorBody(),
255			Field:   err.Field,
256		})
257	}
258	return &StatusError{metav1.Status{
259		Status: metav1.StatusFailure,
260		Code:   http.StatusUnprocessableEntity,
261		Reason: metav1.StatusReasonInvalid,
262		Details: &metav1.StatusDetails{
263			Group:  qualifiedKind.Group,
264			Kind:   qualifiedKind.Kind,
265			Name:   name,
266			Causes: causes,
267		},
268		Message: fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, errs.ToAggregate()),
269	}}
270}
271
272// NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
273func NewBadRequest(reason string) *StatusError {
274	return &StatusError{metav1.Status{
275		Status:  metav1.StatusFailure,
276		Code:    http.StatusBadRequest,
277		Reason:  metav1.StatusReasonBadRequest,
278		Message: reason,
279	}}
280}
281
282// NewTooManyRequests creates an error that indicates that the client must try again later because
283// the specified endpoint is not accepting requests. More specific details should be provided
284// if client should know why the failure was limited4.
285func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError {
286	return &StatusError{metav1.Status{
287		Status:  metav1.StatusFailure,
288		Code:    http.StatusTooManyRequests,
289		Reason:  metav1.StatusReasonTooManyRequests,
290		Message: message,
291		Details: &metav1.StatusDetails{
292			RetryAfterSeconds: int32(retryAfterSeconds),
293		},
294	}}
295}
296
297// NewServiceUnavailable creates an error that indicates that the requested service is unavailable.
298func NewServiceUnavailable(reason string) *StatusError {
299	return &StatusError{metav1.Status{
300		Status:  metav1.StatusFailure,
301		Code:    http.StatusServiceUnavailable,
302		Reason:  metav1.StatusReasonServiceUnavailable,
303		Message: reason,
304	}}
305}
306
307// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
308func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError {
309	return &StatusError{metav1.Status{
310		Status: metav1.StatusFailure,
311		Code:   http.StatusMethodNotAllowed,
312		Reason: metav1.StatusReasonMethodNotAllowed,
313		Details: &metav1.StatusDetails{
314			Group: qualifiedResource.Group,
315			Kind:  qualifiedResource.Resource,
316		},
317		Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()),
318	}}
319}
320
321// NewServerTimeout returns an error indicating the requested action could not be completed due to a
322// transient error, and the client should try again.
323func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError {
324	return &StatusError{metav1.Status{
325		Status: metav1.StatusFailure,
326		Code:   http.StatusInternalServerError,
327		Reason: metav1.StatusReasonServerTimeout,
328		Details: &metav1.StatusDetails{
329			Group:             qualifiedResource.Group,
330			Kind:              qualifiedResource.Resource,
331			Name:              operation,
332			RetryAfterSeconds: int32(retryAfterSeconds),
333		},
334		Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()),
335	}}
336}
337
338// NewServerTimeoutForKind should not exist.  Server timeouts happen when accessing resources, the Kind is just what we
339// happened to be looking at when the request failed.  This delegates to keep code sane, but we should work towards removing this.
340func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError {
341	return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds)
342}
343
344// NewInternalError returns an error indicating the item is invalid and cannot be processed.
345func NewInternalError(err error) *StatusError {
346	return &StatusError{metav1.Status{
347		Status: metav1.StatusFailure,
348		Code:   http.StatusInternalServerError,
349		Reason: metav1.StatusReasonInternalError,
350		Details: &metav1.StatusDetails{
351			Causes: []metav1.StatusCause{{Message: err.Error()}},
352		},
353		Message: fmt.Sprintf("Internal error occurred: %v", err),
354	}}
355}
356
357// NewTimeoutError returns an error indicating that a timeout occurred before the request
358// could be completed.  Clients may retry, but the operation may still complete.
359func NewTimeoutError(message string, retryAfterSeconds int) *StatusError {
360	return &StatusError{metav1.Status{
361		Status:  metav1.StatusFailure,
362		Code:    http.StatusGatewayTimeout,
363		Reason:  metav1.StatusReasonTimeout,
364		Message: fmt.Sprintf("Timeout: %s", message),
365		Details: &metav1.StatusDetails{
366			RetryAfterSeconds: int32(retryAfterSeconds),
367		},
368	}}
369}
370
371// NewTooManyRequestsError returns an error indicating that the request was rejected because
372// the server has received too many requests. Client should wait and retry. But if the request
373// is perishable, then the client should not retry the request.
374func NewTooManyRequestsError(message string) *StatusError {
375	return &StatusError{metav1.Status{
376		Status:  metav1.StatusFailure,
377		Code:    http.StatusTooManyRequests,
378		Reason:  metav1.StatusReasonTooManyRequests,
379		Message: fmt.Sprintf("Too many requests: %s", message),
380	}}
381}
382
383// NewRequestEntityTooLargeError returns an error indicating that the request
384// entity was too large.
385func NewRequestEntityTooLargeError(message string) *StatusError {
386	return &StatusError{metav1.Status{
387		Status:  metav1.StatusFailure,
388		Code:    http.StatusRequestEntityTooLarge,
389		Reason:  metav1.StatusReasonRequestEntityTooLarge,
390		Message: fmt.Sprintf("Request entity too large: %s", message),
391	}}
392}
393
394// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
395func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
396	reason := metav1.StatusReasonUnknown
397	message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
398	switch code {
399	case http.StatusConflict:
400		if verb == "POST" {
401			reason = metav1.StatusReasonAlreadyExists
402		} else {
403			reason = metav1.StatusReasonConflict
404		}
405		message = "the server reported a conflict"
406	case http.StatusNotFound:
407		reason = metav1.StatusReasonNotFound
408		message = "the server could not find the requested resource"
409	case http.StatusBadRequest:
410		reason = metav1.StatusReasonBadRequest
411		message = "the server rejected our request for an unknown reason"
412	case http.StatusUnauthorized:
413		reason = metav1.StatusReasonUnauthorized
414		message = "the server has asked for the client to provide credentials"
415	case http.StatusForbidden:
416		reason = metav1.StatusReasonForbidden
417		// the server message has details about who is trying to perform what action.  Keep its message.
418		message = serverMessage
419	case http.StatusNotAcceptable:
420		reason = metav1.StatusReasonNotAcceptable
421		// the server message has details about what types are acceptable
422		if len(serverMessage) == 0 || serverMessage == "unknown" {
423			message = "the server was unable to respond with a content type that the client supports"
424		} else {
425			message = serverMessage
426		}
427	case http.StatusUnsupportedMediaType:
428		reason = metav1.StatusReasonUnsupportedMediaType
429		// the server message has details about what types are acceptable
430		message = serverMessage
431	case http.StatusMethodNotAllowed:
432		reason = metav1.StatusReasonMethodNotAllowed
433		message = "the server does not allow this method on the requested resource"
434	case http.StatusUnprocessableEntity:
435		reason = metav1.StatusReasonInvalid
436		message = "the server rejected our request due to an error in our request"
437	case http.StatusServiceUnavailable:
438		reason = metav1.StatusReasonServiceUnavailable
439		message = "the server is currently unable to handle the request"
440	case http.StatusGatewayTimeout:
441		reason = metav1.StatusReasonTimeout
442		message = "the server was unable to return a response in the time allotted, but may still be processing the request"
443	case http.StatusTooManyRequests:
444		reason = metav1.StatusReasonTooManyRequests
445		message = "the server has received too many requests and has asked us to try again later"
446	default:
447		if code >= 500 {
448			reason = metav1.StatusReasonInternalError
449			message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage)
450		}
451	}
452	switch {
453	case !qualifiedResource.Empty() && len(name) > 0:
454		message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name)
455	case !qualifiedResource.Empty():
456		message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String())
457	}
458	var causes []metav1.StatusCause
459	if isUnexpectedResponse {
460		causes = []metav1.StatusCause{
461			{
462				Type:    metav1.CauseTypeUnexpectedServerResponse,
463				Message: serverMessage,
464			},
465		}
466	} else {
467		causes = nil
468	}
469	return &StatusError{metav1.Status{
470		Status: metav1.StatusFailure,
471		Code:   int32(code),
472		Reason: reason,
473		Details: &metav1.StatusDetails{
474			Group: qualifiedResource.Group,
475			Kind:  qualifiedResource.Resource,
476			Name:  name,
477
478			Causes:            causes,
479			RetryAfterSeconds: int32(retryAfterSeconds),
480		},
481		Message: message,
482	}}
483}
484
485// IsNotFound returns true if the specified error was created by NewNotFound.
486func IsNotFound(err error) bool {
487	return ReasonForError(err) == metav1.StatusReasonNotFound
488}
489
490// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
491func IsAlreadyExists(err error) bool {
492	return ReasonForError(err) == metav1.StatusReasonAlreadyExists
493}
494
495// IsConflict determines if the err is an error which indicates the provided update conflicts.
496func IsConflict(err error) bool {
497	return ReasonForError(err) == metav1.StatusReasonConflict
498}
499
500// IsInvalid determines if the err is an error which indicates the provided resource is not valid.
501func IsInvalid(err error) bool {
502	return ReasonForError(err) == metav1.StatusReasonInvalid
503}
504
505// IsGone is true if the error indicates the requested resource is no longer available.
506func IsGone(err error) bool {
507	return ReasonForError(err) == metav1.StatusReasonGone
508}
509
510// IsResourceExpired is true if the error indicates the resource has expired and the current action is
511// no longer possible.
512func IsResourceExpired(err error) bool {
513	return ReasonForError(err) == metav1.StatusReasonExpired
514}
515
516// IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header
517func IsNotAcceptable(err error) bool {
518	return ReasonForError(err) == metav1.StatusReasonNotAcceptable
519}
520
521// IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header
522func IsUnsupportedMediaType(err error) bool {
523	return ReasonForError(err) == metav1.StatusReasonUnsupportedMediaType
524}
525
526// IsMethodNotSupported determines if the err is an error which indicates the provided action could not
527// be performed because it is not supported by the server.
528func IsMethodNotSupported(err error) bool {
529	return ReasonForError(err) == metav1.StatusReasonMethodNotAllowed
530}
531
532// IsServiceUnavailable is true if the error indicates the underlying service is no longer available.
533func IsServiceUnavailable(err error) bool {
534	return ReasonForError(err) == metav1.StatusReasonServiceUnavailable
535}
536
537// IsBadRequest determines if err is an error which indicates that the request is invalid.
538func IsBadRequest(err error) bool {
539	return ReasonForError(err) == metav1.StatusReasonBadRequest
540}
541
542// IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
543// requires authentication by the user.
544func IsUnauthorized(err error) bool {
545	return ReasonForError(err) == metav1.StatusReasonUnauthorized
546}
547
548// IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
549// be completed as requested.
550func IsForbidden(err error) bool {
551	return ReasonForError(err) == metav1.StatusReasonForbidden
552}
553
554// IsTimeout determines if err is an error which indicates that request times out due to long
555// processing.
556func IsTimeout(err error) bool {
557	return ReasonForError(err) == metav1.StatusReasonTimeout
558}
559
560// IsServerTimeout determines if err is an error which indicates that the request needs to be retried
561// by the client.
562func IsServerTimeout(err error) bool {
563	return ReasonForError(err) == metav1.StatusReasonServerTimeout
564}
565
566// IsInternalError determines if err is an error which indicates an internal server error.
567func IsInternalError(err error) bool {
568	return ReasonForError(err) == metav1.StatusReasonInternalError
569}
570
571// IsTooManyRequests determines if err is an error which indicates that there are too many requests
572// that the server cannot handle.
573func IsTooManyRequests(err error) bool {
574	if ReasonForError(err) == metav1.StatusReasonTooManyRequests {
575		return true
576	}
577	switch t := err.(type) {
578	case APIStatus:
579		return t.Status().Code == http.StatusTooManyRequests
580	}
581	return false
582}
583
584// IsRequestEntityTooLargeError determines if err is an error which indicates
585// the request entity is too large.
586func IsRequestEntityTooLargeError(err error) bool {
587	if ReasonForError(err) == metav1.StatusReasonRequestEntityTooLarge {
588		return true
589	}
590	switch t := err.(type) {
591	case APIStatus:
592		return t.Status().Code == http.StatusRequestEntityTooLarge
593	}
594	return false
595}
596
597// IsUnexpectedServerError returns true if the server response was not in the expected API format,
598// and may be the result of another HTTP actor.
599func IsUnexpectedServerError(err error) bool {
600	switch t := err.(type) {
601	case APIStatus:
602		if d := t.Status().Details; d != nil {
603			for _, cause := range d.Causes {
604				if cause.Type == metav1.CauseTypeUnexpectedServerResponse {
605					return true
606				}
607			}
608		}
609	}
610	return false
611}
612
613// IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
614func IsUnexpectedObjectError(err error) bool {
615	_, ok := err.(*UnexpectedObjectError)
616	return err != nil && ok
617}
618
619// SuggestsClientDelay returns true if this error suggests a client delay as well as the
620// suggested seconds to wait, or false if the error does not imply a wait. It does not
621// address whether the error *should* be retried, since some errors (like a 3xx) may
622// request delay without retry.
623func SuggestsClientDelay(err error) (int, bool) {
624	switch t := err.(type) {
625	case APIStatus:
626		if t.Status().Details != nil {
627			switch t.Status().Reason {
628			// this StatusReason explicitly requests the caller to delay the action
629			case metav1.StatusReasonServerTimeout:
630				return int(t.Status().Details.RetryAfterSeconds), true
631			}
632			// If the client requests that we retry after a certain number of seconds
633			if t.Status().Details.RetryAfterSeconds > 0 {
634				return int(t.Status().Details.RetryAfterSeconds), true
635			}
636		}
637	}
638	return 0, false
639}
640
641// ReasonForError returns the HTTP status for a particular error.
642func ReasonForError(err error) metav1.StatusReason {
643	switch t := err.(type) {
644	case APIStatus:
645		return t.Status().Reason
646	}
647	return metav1.StatusReasonUnknown
648}
649
650// ErrorReporter converts generic errors into runtime.Object errors without
651// requiring the caller to take a dependency on meta/v1 (where Status lives).
652// This prevents circular dependencies in core watch code.
653type ErrorReporter struct {
654	code   int
655	verb   string
656	reason string
657}
658
659// NewClientErrorReporter will respond with valid v1.Status objects that report
660// unexpected server responses. Primarily used by watch to report errors when
661// we attempt to decode a response from the server and it is not in the form
662// we expect. Because watch is a dependency of the core api, we can't return
663// meta/v1.Status in that package and so much inject this interface to convert a
664// generic error as appropriate. The reason is passed as a unique status cause
665// on the returned status, otherwise the generic "ClientError" is returned.
666func NewClientErrorReporter(code int, verb string, reason string) *ErrorReporter {
667	return &ErrorReporter{
668		code:   code,
669		verb:   verb,
670		reason: reason,
671	}
672}
673
674// AsObject returns a valid error runtime.Object (a v1.Status) for the given
675// error, using the code and verb of the reporter type. The error is set to
676// indicate that this was an unexpected server response.
677func (r *ErrorReporter) AsObject(err error) runtime.Object {
678	status := NewGenericServerResponse(r.code, r.verb, schema.GroupResource{}, "", err.Error(), 0, true)
679	if status.ErrStatus.Details == nil {
680		status.ErrStatus.Details = &metav1.StatusDetails{}
681	}
682	reason := r.reason
683	if len(reason) == 0 {
684		reason = "ClientError"
685	}
686	status.ErrStatus.Details.Causes = append(status.ErrStatus.Details.Causes, metav1.StatusCause{
687		Type:    metav1.CauseType(reason),
688		Message: err.Error(),
689	})
690	return &status.ErrStatus
691}
692