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