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