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