k8s.io/client-go@v0.31.1/rest/request.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rest 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/hex" 23 "fmt" 24 "io" 25 "mime" 26 "net/http" 27 "net/http/httptrace" 28 "net/url" 29 "os" 30 "path" 31 "reflect" 32 "strconv" 33 "strings" 34 "sync" 35 "time" 36 37 "golang.org/x/net/http2" 38 39 "k8s.io/apimachinery/pkg/api/errors" 40 "k8s.io/apimachinery/pkg/api/meta" 41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 "k8s.io/apimachinery/pkg/conversion" 43 "k8s.io/apimachinery/pkg/runtime" 44 "k8s.io/apimachinery/pkg/runtime/schema" 45 "k8s.io/apimachinery/pkg/runtime/serializer/streaming" 46 "k8s.io/apimachinery/pkg/util/net" 47 "k8s.io/apimachinery/pkg/watch" 48 clientfeatures "k8s.io/client-go/features" 49 restclientwatch "k8s.io/client-go/rest/watch" 50 "k8s.io/client-go/tools/metrics" 51 "k8s.io/client-go/util/flowcontrol" 52 "k8s.io/klog/v2" 53 "k8s.io/utils/clock" 54 ) 55 56 var ( 57 // longThrottleLatency defines threshold for logging requests. All requests being 58 // throttled (via the provided rateLimiter) for more than longThrottleLatency will 59 // be logged. 60 longThrottleLatency = 50 * time.Millisecond 61 62 // extraLongThrottleLatency defines the threshold for logging requests at log level 2. 63 extraLongThrottleLatency = 1 * time.Second 64 ) 65 66 // HTTPClient is an interface for testing a request object. 67 type HTTPClient interface { 68 Do(req *http.Request) (*http.Response, error) 69 } 70 71 // ResponseWrapper is an interface for getting a response. 72 // The response may be either accessed as a raw data (the whole output is put into memory) or as a stream. 73 type ResponseWrapper interface { 74 DoRaw(context.Context) ([]byte, error) 75 Stream(context.Context) (io.ReadCloser, error) 76 } 77 78 // RequestConstructionError is returned when there's an error assembling a request. 79 type RequestConstructionError struct { 80 Err error 81 } 82 83 // Error returns a textual description of 'r'. 84 func (r *RequestConstructionError) Error() string { 85 return fmt.Sprintf("request construction error: '%v'", r.Err) 86 } 87 88 var noBackoff = &NoBackoff{} 89 90 type requestRetryFunc func(maxRetries int) WithRetry 91 92 func defaultRequestRetryFn(maxRetries int) WithRetry { 93 return &withRetry{maxRetries: maxRetries} 94 } 95 96 // Request allows for building up a request to a server in a chained fashion. 97 // Any errors are stored until the end of your call, so you only have to 98 // check once. 99 type Request struct { 100 c *RESTClient 101 102 warningHandler WarningHandler 103 104 rateLimiter flowcontrol.RateLimiter 105 backoff BackoffManager 106 timeout time.Duration 107 maxRetries int 108 109 // generic components accessible via method setters 110 verb string 111 pathPrefix string 112 subpath string 113 params url.Values 114 headers http.Header 115 116 // structural elements of the request that are part of the Kubernetes API conventions 117 namespace string 118 namespaceSet bool 119 resource string 120 resourceName string 121 subresource string 122 123 // output 124 err error 125 126 // only one of body / bodyBytes may be set. requests using body are not retriable. 127 body io.Reader 128 bodyBytes []byte 129 130 retryFn requestRetryFunc 131 } 132 133 // NewRequest creates a new request helper object for accessing runtime.Objects on a server. 134 func NewRequest(c *RESTClient) *Request { 135 var backoff BackoffManager 136 if c.createBackoffMgr != nil { 137 backoff = c.createBackoffMgr() 138 } 139 if backoff == nil { 140 backoff = noBackoff 141 } 142 143 var pathPrefix string 144 if c.base != nil { 145 pathPrefix = path.Join("/", c.base.Path, c.versionedAPIPath) 146 } else { 147 pathPrefix = path.Join("/", c.versionedAPIPath) 148 } 149 150 var timeout time.Duration 151 if c.Client != nil { 152 timeout = c.Client.Timeout 153 } 154 155 r := &Request{ 156 c: c, 157 rateLimiter: c.rateLimiter, 158 backoff: backoff, 159 timeout: timeout, 160 pathPrefix: pathPrefix, 161 maxRetries: 10, 162 retryFn: defaultRequestRetryFn, 163 warningHandler: c.warningHandler, 164 } 165 166 switch { 167 case len(c.content.AcceptContentTypes) > 0: 168 r.SetHeader("Accept", c.content.AcceptContentTypes) 169 case len(c.content.ContentType) > 0: 170 r.SetHeader("Accept", c.content.ContentType+", */*") 171 } 172 return r 173 } 174 175 // NewRequestWithClient creates a Request with an embedded RESTClient for use in test scenarios. 176 func NewRequestWithClient(base *url.URL, versionedAPIPath string, content ClientContentConfig, client *http.Client) *Request { 177 return NewRequest(&RESTClient{ 178 base: base, 179 versionedAPIPath: versionedAPIPath, 180 content: content, 181 Client: client, 182 }) 183 } 184 185 // Verb sets the verb this request will use. 186 func (r *Request) Verb(verb string) *Request { 187 r.verb = verb 188 return r 189 } 190 191 // Prefix adds segments to the relative beginning to the request path. These 192 // items will be placed before the optional Namespace, Resource, or Name sections. 193 // Setting AbsPath will clear any previously set Prefix segments 194 func (r *Request) Prefix(segments ...string) *Request { 195 if r.err != nil { 196 return r 197 } 198 r.pathPrefix = path.Join(r.pathPrefix, path.Join(segments...)) 199 return r 200 } 201 202 // Suffix appends segments to the end of the path. These items will be placed after the prefix and optional 203 // Namespace, Resource, or Name sections. 204 func (r *Request) Suffix(segments ...string) *Request { 205 if r.err != nil { 206 return r 207 } 208 r.subpath = path.Join(r.subpath, path.Join(segments...)) 209 return r 210 } 211 212 // Resource sets the resource to access (<resource>/[ns/<namespace>/]<name>) 213 func (r *Request) Resource(resource string) *Request { 214 if r.err != nil { 215 return r 216 } 217 if len(r.resource) != 0 { 218 r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource) 219 return r 220 } 221 if msgs := IsValidPathSegmentName(resource); len(msgs) != 0 { 222 r.err = fmt.Errorf("invalid resource %q: %v", resource, msgs) 223 return r 224 } 225 r.resource = resource 226 return r 227 } 228 229 // BackOff sets the request's backoff manager to the one specified, 230 // or defaults to the stub implementation if nil is provided 231 func (r *Request) BackOff(manager BackoffManager) *Request { 232 if manager == nil { 233 r.backoff = &NoBackoff{} 234 return r 235 } 236 237 r.backoff = manager 238 return r 239 } 240 241 // WarningHandler sets the handler this client uses when warning headers are encountered. 242 // If set to nil, this client will use the default warning handler (see SetDefaultWarningHandler). 243 func (r *Request) WarningHandler(handler WarningHandler) *Request { 244 r.warningHandler = handler 245 return r 246 } 247 248 // Throttle receives a rate-limiter and sets or replaces an existing request limiter 249 func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request { 250 r.rateLimiter = limiter 251 return r 252 } 253 254 // SubResource sets a sub-resource path which can be multiple segments after the resource 255 // name but before the suffix. 256 func (r *Request) SubResource(subresources ...string) *Request { 257 if r.err != nil { 258 return r 259 } 260 subresource := path.Join(subresources...) 261 if len(r.subresource) != 0 { 262 r.err = fmt.Errorf("subresource already set to %q, cannot change to %q", r.subresource, subresource) 263 return r 264 } 265 for _, s := range subresources { 266 if msgs := IsValidPathSegmentName(s); len(msgs) != 0 { 267 r.err = fmt.Errorf("invalid subresource %q: %v", s, msgs) 268 return r 269 } 270 } 271 r.subresource = subresource 272 return r 273 } 274 275 // Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>) 276 func (r *Request) Name(resourceName string) *Request { 277 if r.err != nil { 278 return r 279 } 280 if len(resourceName) == 0 { 281 r.err = fmt.Errorf("resource name may not be empty") 282 return r 283 } 284 if len(r.resourceName) != 0 { 285 r.err = fmt.Errorf("resource name already set to %q, cannot change to %q", r.resourceName, resourceName) 286 return r 287 } 288 if msgs := IsValidPathSegmentName(resourceName); len(msgs) != 0 { 289 r.err = fmt.Errorf("invalid resource name %q: %v", resourceName, msgs) 290 return r 291 } 292 r.resourceName = resourceName 293 return r 294 } 295 296 // Namespace applies the namespace scope to a request (<resource>/[ns/<namespace>/]<name>) 297 func (r *Request) Namespace(namespace string) *Request { 298 if r.err != nil { 299 return r 300 } 301 if r.namespaceSet { 302 r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace) 303 return r 304 } 305 if msgs := IsValidPathSegmentName(namespace); len(msgs) != 0 { 306 r.err = fmt.Errorf("invalid namespace %q: %v", namespace, msgs) 307 return r 308 } 309 r.namespaceSet = true 310 r.namespace = namespace 311 return r 312 } 313 314 // NamespaceIfScoped is a convenience function to set a namespace if scoped is true 315 func (r *Request) NamespaceIfScoped(namespace string, scoped bool) *Request { 316 if scoped { 317 return r.Namespace(namespace) 318 } 319 return r 320 } 321 322 // AbsPath overwrites an existing path with the segments provided. Trailing slashes are preserved 323 // when a single segment is passed. 324 func (r *Request) AbsPath(segments ...string) *Request { 325 if r.err != nil { 326 return r 327 } 328 r.pathPrefix = path.Join(r.c.base.Path, path.Join(segments...)) 329 if len(segments) == 1 && (len(r.c.base.Path) > 1 || len(segments[0]) > 1) && strings.HasSuffix(segments[0], "/") { 330 // preserve any trailing slashes for legacy behavior 331 r.pathPrefix += "/" 332 } 333 return r 334 } 335 336 // RequestURI overwrites existing path and parameters with the value of the provided server relative 337 // URI. 338 func (r *Request) RequestURI(uri string) *Request { 339 if r.err != nil { 340 return r 341 } 342 locator, err := url.Parse(uri) 343 if err != nil { 344 r.err = err 345 return r 346 } 347 r.pathPrefix = locator.Path 348 if len(locator.Query()) > 0 { 349 if r.params == nil { 350 r.params = make(url.Values) 351 } 352 for k, v := range locator.Query() { 353 r.params[k] = v 354 } 355 } 356 return r 357 } 358 359 // Param creates a query parameter with the given string value. 360 func (r *Request) Param(paramName, s string) *Request { 361 if r.err != nil { 362 return r 363 } 364 return r.setParam(paramName, s) 365 } 366 367 // VersionedParams will take the provided object, serialize it to a map[string][]string using the 368 // implicit RESTClient API version and the default parameter codec, and then add those as parameters 369 // to the request. Use this to provide versioned query parameters from client libraries. 370 // VersionedParams will not write query parameters that have omitempty set and are empty. If a 371 // parameter has already been set it is appended to (Params and VersionedParams are additive). 372 func (r *Request) VersionedParams(obj runtime.Object, codec runtime.ParameterCodec) *Request { 373 return r.SpecificallyVersionedParams(obj, codec, r.c.content.GroupVersion) 374 } 375 376 func (r *Request) SpecificallyVersionedParams(obj runtime.Object, codec runtime.ParameterCodec, version schema.GroupVersion) *Request { 377 if r.err != nil { 378 return r 379 } 380 params, err := codec.EncodeParameters(obj, version) 381 if err != nil { 382 r.err = err 383 return r 384 } 385 for k, v := range params { 386 if r.params == nil { 387 r.params = make(url.Values) 388 } 389 r.params[k] = append(r.params[k], v...) 390 } 391 return r 392 } 393 394 func (r *Request) setParam(paramName, value string) *Request { 395 if r.params == nil { 396 r.params = make(url.Values) 397 } 398 r.params[paramName] = append(r.params[paramName], value) 399 return r 400 } 401 402 func (r *Request) SetHeader(key string, values ...string) *Request { 403 if r.headers == nil { 404 r.headers = http.Header{} 405 } 406 r.headers.Del(key) 407 for _, value := range values { 408 r.headers.Add(key, value) 409 } 410 return r 411 } 412 413 // Timeout makes the request use the given duration as an overall timeout for the 414 // request. Additionally, if set passes the value as "timeout" parameter in URL. 415 func (r *Request) Timeout(d time.Duration) *Request { 416 if r.err != nil { 417 return r 418 } 419 r.timeout = d 420 return r 421 } 422 423 // MaxRetries makes the request use the given integer as a ceiling of retrying upon receiving 424 // "Retry-After" headers and 429 status-code in the response. The default is 10 unless this 425 // function is specifically called with a different value. 426 // A zero maxRetries prevent it from doing retires and return an error immediately. 427 func (r *Request) MaxRetries(maxRetries int) *Request { 428 if maxRetries < 0 { 429 maxRetries = 0 430 } 431 r.maxRetries = maxRetries 432 return r 433 } 434 435 // Body makes the request use obj as the body. Optional. 436 // If obj is a string, try to read a file of that name. 437 // If obj is a []byte, send it directly. 438 // If obj is an io.Reader, use it directly. 439 // If obj is a runtime.Object, marshal it correctly, and set Content-Type header. 440 // If obj is a runtime.Object and nil, do nothing. 441 // Otherwise, set an error. 442 func (r *Request) Body(obj interface{}) *Request { 443 if r.err != nil { 444 return r 445 } 446 switch t := obj.(type) { 447 case string: 448 data, err := os.ReadFile(t) 449 if err != nil { 450 r.err = err 451 return r 452 } 453 glogBody("Request Body", data) 454 r.body = nil 455 r.bodyBytes = data 456 case []byte: 457 glogBody("Request Body", t) 458 r.body = nil 459 r.bodyBytes = t 460 case io.Reader: 461 r.body = t 462 r.bodyBytes = nil 463 case runtime.Object: 464 // callers may pass typed interface pointers, therefore we must check nil with reflection 465 if reflect.ValueOf(t).IsNil() { 466 return r 467 } 468 encoder, err := r.c.content.Negotiator.Encoder(r.c.content.ContentType, nil) 469 if err != nil { 470 r.err = err 471 return r 472 } 473 data, err := runtime.Encode(encoder, t) 474 if err != nil { 475 r.err = err 476 return r 477 } 478 glogBody("Request Body", data) 479 r.body = nil 480 r.bodyBytes = data 481 r.SetHeader("Content-Type", r.c.content.ContentType) 482 default: 483 r.err = fmt.Errorf("unknown type used for body: %+v", obj) 484 } 485 return r 486 } 487 488 // Error returns any error encountered constructing the request, if any. 489 func (r *Request) Error() error { 490 return r.err 491 } 492 493 // URL returns the current working URL. Check the result of Error() to ensure 494 // that the returned URL is valid. 495 func (r *Request) URL() *url.URL { 496 p := r.pathPrefix 497 if r.namespaceSet && len(r.namespace) > 0 { 498 p = path.Join(p, "namespaces", r.namespace) 499 } 500 if len(r.resource) != 0 { 501 p = path.Join(p, strings.ToLower(r.resource)) 502 } 503 // Join trims trailing slashes, so preserve r.pathPrefix's trailing slash for backwards compatibility if nothing was changed 504 if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 { 505 p = path.Join(p, r.resourceName, r.subresource, r.subpath) 506 } 507 508 finalURL := &url.URL{} 509 if r.c.base != nil { 510 *finalURL = *r.c.base 511 } 512 finalURL.Path = p 513 514 query := url.Values{} 515 for key, values := range r.params { 516 for _, value := range values { 517 query.Add(key, value) 518 } 519 } 520 521 // timeout is handled specially here. 522 if r.timeout != 0 { 523 query.Set("timeout", r.timeout.String()) 524 } 525 finalURL.RawQuery = query.Encode() 526 return finalURL 527 } 528 529 // finalURLTemplate is similar to URL(), but will make all specific parameter values equal 530 // - instead of name or namespace, "{name}" and "{namespace}" will be used, and all query 531 // parameters will be reset. This creates a copy of the url so as not to change the 532 // underlying object. 533 func (r Request) finalURLTemplate() url.URL { 534 newParams := url.Values{} 535 v := []string{"{value}"} 536 for k := range r.params { 537 newParams[k] = v 538 } 539 r.params = newParams 540 u := r.URL() 541 if u == nil { 542 return url.URL{} 543 } 544 545 segments := strings.Split(u.Path, "/") 546 groupIndex := 0 547 index := 0 548 trimmedBasePath := "" 549 if r.c.base != nil && strings.Contains(u.Path, r.c.base.Path) { 550 p := strings.TrimPrefix(u.Path, r.c.base.Path) 551 if !strings.HasPrefix(p, "/") { 552 p = "/" + p 553 } 554 // store the base path that we have trimmed so we can append it 555 // before returning the URL 556 trimmedBasePath = r.c.base.Path 557 segments = strings.Split(p, "/") 558 groupIndex = 1 559 } 560 if len(segments) <= 2 { 561 return *u 562 } 563 564 const CoreGroupPrefix = "api" 565 const NamedGroupPrefix = "apis" 566 isCoreGroup := segments[groupIndex] == CoreGroupPrefix 567 isNamedGroup := segments[groupIndex] == NamedGroupPrefix 568 if isCoreGroup { 569 // checking the case of core group with /api/v1/... format 570 index = groupIndex + 2 571 } else if isNamedGroup { 572 // checking the case of named group with /apis/apps/v1/... format 573 index = groupIndex + 3 574 } else { 575 // this should not happen that the only two possibilities are /api... and /apis..., just want to put an 576 // outlet here in case more API groups are added in future if ever possible: 577 // https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups 578 // if a wrong API groups name is encountered, return the {prefix} for url.Path 579 u.Path = "/{prefix}" 580 u.RawQuery = "" 581 return *u 582 } 583 // switch segLength := len(segments) - index; segLength { 584 switch { 585 // case len(segments) - index == 1: 586 // resource (with no name) do nothing 587 case len(segments)-index == 2: 588 // /$RESOURCE/$NAME: replace $NAME with {name} 589 segments[index+1] = "{name}" 590 case len(segments)-index == 3: 591 if segments[index+2] == "finalize" || segments[index+2] == "status" { 592 // /$RESOURCE/$NAME/$SUBRESOURCE: replace $NAME with {name} 593 segments[index+1] = "{name}" 594 } else { 595 // /namespace/$NAMESPACE/$RESOURCE: replace $NAMESPACE with {namespace} 596 segments[index+1] = "{namespace}" 597 } 598 case len(segments)-index >= 4: 599 segments[index+1] = "{namespace}" 600 // /namespace/$NAMESPACE/$RESOURCE/$NAME: replace $NAMESPACE with {namespace}, $NAME with {name} 601 if segments[index+3] != "finalize" && segments[index+3] != "status" { 602 // /$RESOURCE/$NAME/$SUBRESOURCE: replace $NAME with {name} 603 segments[index+3] = "{name}" 604 } 605 } 606 u.Path = path.Join(trimmedBasePath, path.Join(segments...)) 607 return *u 608 } 609 610 func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) error { 611 if r.rateLimiter == nil { 612 return nil 613 } 614 615 now := time.Now() 616 617 err := r.rateLimiter.Wait(ctx) 618 if err != nil { 619 err = fmt.Errorf("client rate limiter Wait returned an error: %w", err) 620 } 621 latency := time.Since(now) 622 623 var message string 624 switch { 625 case len(retryInfo) > 0: 626 message = fmt.Sprintf("Waited for %v, %s - request: %s:%s", latency, retryInfo, r.verb, r.URL().String()) 627 default: 628 message = fmt.Sprintf("Waited for %v due to client-side throttling, not priority and fairness, request: %s:%s", latency, r.verb, r.URL().String()) 629 } 630 631 if latency > longThrottleLatency { 632 klog.V(3).Info(message) 633 } 634 if latency > extraLongThrottleLatency { 635 // If the rate limiter latency is very high, the log message should be printed at a higher log level, 636 // but we use a throttled logger to prevent spamming. 637 globalThrottledLogger.Infof("%s", message) 638 } 639 metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency) 640 641 return err 642 } 643 644 func (r *Request) tryThrottle(ctx context.Context) error { 645 return r.tryThrottleWithInfo(ctx, "") 646 } 647 648 type throttleSettings struct { 649 logLevel klog.Level 650 minLogInterval time.Duration 651 652 lastLogTime time.Time 653 lock sync.RWMutex 654 } 655 656 type throttledLogger struct { 657 clock clock.PassiveClock 658 settings []*throttleSettings 659 } 660 661 var globalThrottledLogger = &throttledLogger{ 662 clock: clock.RealClock{}, 663 settings: []*throttleSettings{ 664 { 665 logLevel: 2, 666 minLogInterval: 1 * time.Second, 667 }, { 668 logLevel: 0, 669 minLogInterval: 10 * time.Second, 670 }, 671 }, 672 } 673 674 func (b *throttledLogger) attemptToLog() (klog.Level, bool) { 675 for _, setting := range b.settings { 676 if bool(klog.V(setting.logLevel).Enabled()) { 677 // Return early without write locking if possible. 678 if func() bool { 679 setting.lock.RLock() 680 defer setting.lock.RUnlock() 681 return b.clock.Since(setting.lastLogTime) >= setting.minLogInterval 682 }() { 683 setting.lock.Lock() 684 defer setting.lock.Unlock() 685 if b.clock.Since(setting.lastLogTime) >= setting.minLogInterval { 686 setting.lastLogTime = b.clock.Now() 687 return setting.logLevel, true 688 } 689 } 690 return -1, false 691 } 692 } 693 return -1, false 694 } 695 696 // Infof will write a log message at each logLevel specified by the receiver's throttleSettings 697 // as long as it hasn't written a log message more recently than minLogInterval. 698 func (b *throttledLogger) Infof(message string, args ...interface{}) { 699 if logLevel, ok := b.attemptToLog(); ok { 700 klog.V(logLevel).Infof(message, args...) 701 } 702 } 703 704 // Watch attempts to begin watching the requested location. 705 // Returns a watch.Interface, or an error. 706 func (r *Request) Watch(ctx context.Context) (watch.Interface, error) { 707 // We specifically don't want to rate limit watches, so we 708 // don't use r.rateLimiter here. 709 if r.err != nil { 710 return nil, r.err 711 } 712 713 client := r.c.Client 714 if client == nil { 715 client = http.DefaultClient 716 } 717 718 isErrRetryableFunc := func(request *http.Request, err error) bool { 719 // The watch stream mechanism handles many common partial data errors, so closed 720 // connections can be retried in many cases. 721 if net.IsProbableEOF(err) || net.IsTimeout(err) { 722 return true 723 } 724 return false 725 } 726 retry := r.retryFn(r.maxRetries) 727 url := r.URL().String() 728 for { 729 if err := retry.Before(ctx, r); err != nil { 730 return nil, retry.WrapPreviousError(err) 731 } 732 733 req, err := r.newHTTPRequest(ctx) 734 if err != nil { 735 return nil, err 736 } 737 738 resp, err := client.Do(req) 739 retry.After(ctx, r, resp, err) 740 if err == nil && resp.StatusCode == http.StatusOK { 741 return r.newStreamWatcher(resp) 742 } 743 744 done, transformErr := func() (bool, error) { 745 defer readAndCloseResponseBody(resp) 746 747 if retry.IsNextRetry(ctx, r, req, resp, err, isErrRetryableFunc) { 748 return false, nil 749 } 750 751 if resp == nil { 752 // the server must have sent us an error in 'err' 753 return true, nil 754 } 755 if result := r.transformResponse(resp, req); result.err != nil { 756 return true, result.err 757 } 758 return true, fmt.Errorf("for request %s, got status: %v", url, resp.StatusCode) 759 }() 760 if done { 761 if isErrRetryableFunc(req, err) { 762 return watch.NewEmptyWatch(), nil 763 } 764 if err == nil { 765 // if the server sent us an HTTP Response object, 766 // we need to return the error object from that. 767 err = transformErr 768 } 769 return nil, retry.WrapPreviousError(err) 770 } 771 } 772 } 773 774 type WatchListResult struct { 775 // err holds any errors we might have received 776 // during streaming. 777 err error 778 779 // items hold the collected data 780 items []runtime.Object 781 782 // initialEventsEndBookmarkRV holds the resource version 783 // extracted from the bookmark event that marks 784 // the end of the stream. 785 initialEventsEndBookmarkRV string 786 787 // gv represents the API version 788 // it is used to construct the final list response 789 // normally this information is filled by the server 790 gv schema.GroupVersion 791 } 792 793 func (r WatchListResult) Into(obj runtime.Object) error { 794 if r.err != nil { 795 return r.err 796 } 797 798 listPtr, err := meta.GetItemsPtr(obj) 799 if err != nil { 800 return err 801 } 802 listVal, err := conversion.EnforcePtr(listPtr) 803 if err != nil { 804 return err 805 } 806 if listVal.Kind() != reflect.Slice { 807 return fmt.Errorf("need a pointer to slice, got %v", listVal.Kind()) 808 } 809 810 if len(r.items) == 0 { 811 listVal.Set(reflect.MakeSlice(listVal.Type(), 0, 0)) 812 } else { 813 listVal.Set(reflect.MakeSlice(listVal.Type(), len(r.items), len(r.items))) 814 for i, o := range r.items { 815 if listVal.Type().Elem() != reflect.TypeOf(o).Elem() { 816 return fmt.Errorf("received object type = %v at index = %d, doesn't match the list item type = %v", reflect.TypeOf(o).Elem(), i, listVal.Type().Elem()) 817 } 818 listVal.Index(i).Set(reflect.ValueOf(o).Elem()) 819 } 820 } 821 822 listMeta, err := meta.ListAccessor(obj) 823 if err != nil { 824 return err 825 } 826 listMeta.SetResourceVersion(r.initialEventsEndBookmarkRV) 827 828 typeMeta, err := meta.TypeAccessor(obj) 829 if err != nil { 830 return err 831 } 832 version := r.gv.String() 833 typeMeta.SetAPIVersion(version) 834 typeMeta.SetKind(reflect.TypeOf(obj).Elem().Name()) 835 836 return nil 837 } 838 839 // WatchList establishes a stream to get a consistent snapshot of data 840 // from the server as described in https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3157-watch-list#proposal 841 // 842 // Note that the watchlist requires properly setting the ListOptions 843 // otherwise it just establishes a regular watch with the server. 844 // Check the documentation https://kubernetes.io/docs/reference/using-api/api-concepts/#streaming-lists 845 // to see what parameters are currently required. 846 func (r *Request) WatchList(ctx context.Context) WatchListResult { 847 if !clientfeatures.FeatureGates().Enabled(clientfeatures.WatchListClient) { 848 return WatchListResult{err: fmt.Errorf("%q feature gate is not enabled", clientfeatures.WatchListClient)} 849 } 850 // TODO(#115478): consider validating request parameters (i.e sendInitialEvents). 851 // Most users use the generated client, which handles the proper setting of parameters. 852 // We don't have validation for other methods (e.g., the Watch) 853 // thus, for symmetry, we haven't added additional checks for the WatchList method. 854 w, err := r.Watch(ctx) 855 if err != nil { 856 return WatchListResult{err: err} 857 } 858 return r.handleWatchList(ctx, w) 859 } 860 861 // handleWatchList holds the actual logic for easier unit testing. 862 // Note that this function will close the passed watch. 863 func (r *Request) handleWatchList(ctx context.Context, w watch.Interface) WatchListResult { 864 defer w.Stop() 865 var lastKey string 866 var items []runtime.Object 867 868 for { 869 select { 870 case <-ctx.Done(): 871 return WatchListResult{err: ctx.Err()} 872 case event, ok := <-w.ResultChan(): 873 if !ok { 874 return WatchListResult{err: fmt.Errorf("unexpected watch close")} 875 } 876 if event.Type == watch.Error { 877 return WatchListResult{err: errors.FromObject(event.Object)} 878 } 879 meta, err := meta.Accessor(event.Object) 880 if err != nil { 881 return WatchListResult{err: fmt.Errorf("failed to parse watch event: %#v", event)} 882 } 883 884 switch event.Type { 885 case watch.Added: 886 // the following check ensures that the response is ordered. 887 // earlier servers had a bug that caused them to not sort the output. 888 // in such cases, return an error which can trigger fallback logic. 889 key := objectKeyFromMeta(meta) 890 if len(lastKey) > 0 && lastKey > key { 891 return WatchListResult{err: fmt.Errorf("cannot add the obj (%#v) with the key = %s, as it violates the ordering guarantees provided by the watchlist feature in beta phase, lastInsertedKey was = %s", event.Object, key, lastKey)} 892 } 893 items = append(items, event.Object) 894 lastKey = key 895 case watch.Bookmark: 896 if meta.GetAnnotations()[metav1.InitialEventsAnnotationKey] == "true" { 897 return WatchListResult{ 898 items: items, 899 initialEventsEndBookmarkRV: meta.GetResourceVersion(), 900 gv: r.c.content.GroupVersion, 901 } 902 } 903 default: 904 return WatchListResult{err: fmt.Errorf("unexpected watch event %#v, expected to only receive watch.Added and watch.Bookmark events", event)} 905 } 906 } 907 } 908 } 909 910 func (r *Request) newStreamWatcher(resp *http.Response) (watch.Interface, error) { 911 contentType := resp.Header.Get("Content-Type") 912 mediaType, params, err := mime.ParseMediaType(contentType) 913 if err != nil { 914 klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err) 915 } 916 objectDecoder, streamingSerializer, framer, err := r.c.content.Negotiator.StreamDecoder(mediaType, params) 917 if err != nil { 918 return nil, err 919 } 920 921 handleWarnings(resp.Header, r.warningHandler) 922 923 frameReader := framer.NewFrameReader(resp.Body) 924 watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer) 925 926 return watch.NewStreamWatcher( 927 restclientwatch.NewDecoder(watchEventDecoder, objectDecoder), 928 // use 500 to indicate that the cause of the error is unknown - other error codes 929 // are more specific to HTTP interactions, and set a reason 930 errors.NewClientErrorReporter(http.StatusInternalServerError, r.verb, "ClientWatchDecoding"), 931 ), nil 932 } 933 934 // updateRequestResultMetric increments the RequestResult metric counter, 935 // it should be called with the (response, err) tuple from the final 936 // reply from the server. 937 func updateRequestResultMetric(ctx context.Context, req *Request, resp *http.Response, err error) { 938 code, host := sanitize(req, resp, err) 939 metrics.RequestResult.Increment(ctx, code, req.verb, host) 940 } 941 942 // updateRequestRetryMetric increments the RequestRetry metric counter, 943 // it should be called with the (response, err) tuple for each retry 944 // except for the final attempt. 945 func updateRequestRetryMetric(ctx context.Context, req *Request, resp *http.Response, err error) { 946 code, host := sanitize(req, resp, err) 947 metrics.RequestRetry.IncrementRetry(ctx, code, req.verb, host) 948 } 949 950 func sanitize(req *Request, resp *http.Response, err error) (string, string) { 951 host := "none" 952 if req.c.base != nil { 953 host = req.c.base.Host 954 } 955 956 // Errors can be arbitrary strings. Unbound label cardinality is not suitable for a metric 957 // system so we just report them as `<error>`. 958 code := "<error>" 959 if resp != nil { 960 code = strconv.Itoa(resp.StatusCode) 961 } 962 963 return code, host 964 } 965 966 // Stream formats and executes the request, and offers streaming of the response. 967 // Returns io.ReadCloser which could be used for streaming of the response, or an error 968 // Any non-2xx http status code causes an error. If we get a non-2xx code, we try to convert the body into an APIStatus object. 969 // If we can, we return that as an error. Otherwise, we create an error that lists the http status and the content of the response. 970 func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) { 971 if r.err != nil { 972 return nil, r.err 973 } 974 975 if err := r.tryThrottle(ctx); err != nil { 976 return nil, err 977 } 978 979 client := r.c.Client 980 if client == nil { 981 client = http.DefaultClient 982 } 983 984 retry := r.retryFn(r.maxRetries) 985 url := r.URL().String() 986 for { 987 if err := retry.Before(ctx, r); err != nil { 988 return nil, err 989 } 990 991 req, err := r.newHTTPRequest(ctx) 992 if err != nil { 993 return nil, err 994 } 995 resp, err := client.Do(req) 996 retry.After(ctx, r, resp, err) 997 if err != nil { 998 // we only retry on an HTTP response with 'Retry-After' header 999 return nil, err 1000 } 1001 1002 switch { 1003 case (resp.StatusCode >= 200) && (resp.StatusCode < 300): 1004 handleWarnings(resp.Header, r.warningHandler) 1005 return resp.Body, nil 1006 1007 default: 1008 done, transformErr := func() (bool, error) { 1009 defer resp.Body.Close() 1010 1011 if retry.IsNextRetry(ctx, r, req, resp, err, neverRetryError) { 1012 return false, nil 1013 } 1014 result := r.transformResponse(resp, req) 1015 if err := result.Error(); err != nil { 1016 return true, err 1017 } 1018 return true, fmt.Errorf("%d while accessing %v: %s", result.statusCode, url, string(result.body)) 1019 }() 1020 if done { 1021 return nil, transformErr 1022 } 1023 } 1024 } 1025 } 1026 1027 // requestPreflightCheck looks for common programmer errors on Request. 1028 // 1029 // We tackle here two programmer mistakes. The first one is to try to create 1030 // something(POST) using an empty string as namespace with namespaceSet as 1031 // true. If namespaceSet is true then namespace should also be defined. The 1032 // second mistake is, when under the same circumstances, the programmer tries 1033 // to GET, PUT or DELETE a named resource(resourceName != ""), again, if 1034 // namespaceSet is true then namespace must not be empty. 1035 func (r *Request) requestPreflightCheck() error { 1036 if !r.namespaceSet { 1037 return nil 1038 } 1039 if len(r.namespace) > 0 { 1040 return nil 1041 } 1042 1043 switch r.verb { 1044 case "POST": 1045 return fmt.Errorf("an empty namespace may not be set during creation") 1046 case "GET", "PUT", "DELETE": 1047 if len(r.resourceName) > 0 { 1048 return fmt.Errorf("an empty namespace may not be set when a resource name is provided") 1049 } 1050 } 1051 return nil 1052 } 1053 1054 func (r *Request) newHTTPRequest(ctx context.Context) (*http.Request, error) { 1055 var body io.Reader 1056 switch { 1057 case r.body != nil && r.bodyBytes != nil: 1058 return nil, fmt.Errorf("cannot set both body and bodyBytes") 1059 case r.body != nil: 1060 body = r.body 1061 case r.bodyBytes != nil: 1062 // Create a new reader specifically for this request. 1063 // Giving each request a dedicated reader allows retries to avoid races resetting the request body. 1064 body = bytes.NewReader(r.bodyBytes) 1065 } 1066 1067 url := r.URL().String() 1068 req, err := http.NewRequestWithContext(httptrace.WithClientTrace(ctx, newDNSMetricsTrace(ctx)), r.verb, url, body) 1069 if err != nil { 1070 return nil, err 1071 } 1072 req.Header = r.headers 1073 return req, nil 1074 } 1075 1076 // newDNSMetricsTrace returns an HTTP trace that tracks time spent on DNS lookups per host. 1077 // This metric is available in client as "rest_client_dns_resolution_duration_seconds". 1078 func newDNSMetricsTrace(ctx context.Context) *httptrace.ClientTrace { 1079 type dnsMetric struct { 1080 start time.Time 1081 host string 1082 sync.Mutex 1083 } 1084 dns := &dnsMetric{} 1085 return &httptrace.ClientTrace{ 1086 DNSStart: func(info httptrace.DNSStartInfo) { 1087 dns.Lock() 1088 defer dns.Unlock() 1089 dns.start = time.Now() 1090 dns.host = info.Host 1091 }, 1092 DNSDone: func(info httptrace.DNSDoneInfo) { 1093 dns.Lock() 1094 defer dns.Unlock() 1095 metrics.ResolverLatency.Observe(ctx, dns.host, time.Since(dns.start)) 1096 }, 1097 } 1098 } 1099 1100 // request connects to the server and invokes the provided function when a server response is 1101 // received. It handles retry behavior and up front validation of requests. It will invoke 1102 // fn at most once. It will return an error if a problem occurred prior to connecting to the 1103 // server - the provided function is responsible for handling server errors. 1104 func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error { 1105 // Metrics for total request latency 1106 start := time.Now() 1107 defer func() { 1108 metrics.RequestLatency.Observe(ctx, r.verb, r.finalURLTemplate(), time.Since(start)) 1109 }() 1110 1111 if r.err != nil { 1112 klog.V(4).Infof("Error in request: %v", r.err) 1113 return r.err 1114 } 1115 1116 if err := r.requestPreflightCheck(); err != nil { 1117 return err 1118 } 1119 1120 client := r.c.Client 1121 if client == nil { 1122 client = http.DefaultClient 1123 } 1124 1125 // Throttle the first try before setting up the timeout configured on the 1126 // client. We don't want a throttled client to return timeouts to callers 1127 // before it makes a single request. 1128 if err := r.tryThrottle(ctx); err != nil { 1129 return err 1130 } 1131 1132 if r.timeout > 0 { 1133 var cancel context.CancelFunc 1134 ctx, cancel = context.WithTimeout(ctx, r.timeout) 1135 defer cancel() 1136 } 1137 1138 isErrRetryableFunc := func(req *http.Request, err error) bool { 1139 // "Connection reset by peer" or "apiserver is shutting down" are usually a transient errors. 1140 // Thus in case of "GET" operations, we simply retry it. 1141 // We are not automatically retrying "write" operations, as they are not idempotent. 1142 if req.Method != "GET" { 1143 return false 1144 } 1145 // For connection errors and apiserver shutdown errors retry. 1146 if net.IsConnectionReset(err) || net.IsProbableEOF(err) { 1147 return true 1148 } 1149 return false 1150 } 1151 1152 // Right now we make about ten retry attempts if we get a Retry-After response. 1153 retry := r.retryFn(r.maxRetries) 1154 for { 1155 if err := retry.Before(ctx, r); err != nil { 1156 return retry.WrapPreviousError(err) 1157 } 1158 req, err := r.newHTTPRequest(ctx) 1159 if err != nil { 1160 return err 1161 } 1162 resp, err := client.Do(req) 1163 // The value -1 or a value of 0 with a non-nil Body indicates that the length is unknown. 1164 // https://pkg.go.dev/net/http#Request 1165 if req.ContentLength >= 0 && !(req.Body != nil && req.ContentLength == 0) { 1166 metrics.RequestSize.Observe(ctx, r.verb, r.URL().Host, float64(req.ContentLength)) 1167 } 1168 retry.After(ctx, r, resp, err) 1169 1170 done := func() bool { 1171 defer readAndCloseResponseBody(resp) 1172 1173 // if the server returns an error in err, the response will be nil. 1174 f := func(req *http.Request, resp *http.Response) { 1175 if resp == nil { 1176 return 1177 } 1178 fn(req, resp) 1179 } 1180 1181 if retry.IsNextRetry(ctx, r, req, resp, err, isErrRetryableFunc) { 1182 return false 1183 } 1184 1185 f(req, resp) 1186 return true 1187 }() 1188 if done { 1189 return retry.WrapPreviousError(err) 1190 } 1191 } 1192 } 1193 1194 // Do formats and executes the request. Returns a Result object for easy response 1195 // processing. 1196 // 1197 // Error type: 1198 // - If the server responds with a status: *errors.StatusError or *errors.UnexpectedObjectError 1199 // - http.Client.Do errors are returned directly. 1200 func (r *Request) Do(ctx context.Context) Result { 1201 var result Result 1202 err := r.request(ctx, func(req *http.Request, resp *http.Response) { 1203 result = r.transformResponse(resp, req) 1204 }) 1205 if err != nil { 1206 return Result{err: err} 1207 } 1208 if result.err == nil || len(result.body) > 0 { 1209 metrics.ResponseSize.Observe(ctx, r.verb, r.URL().Host, float64(len(result.body))) 1210 } 1211 return result 1212 } 1213 1214 // DoRaw executes the request but does not process the response body. 1215 func (r *Request) DoRaw(ctx context.Context) ([]byte, error) { 1216 var result Result 1217 err := r.request(ctx, func(req *http.Request, resp *http.Response) { 1218 result.body, result.err = io.ReadAll(resp.Body) 1219 glogBody("Response Body", result.body) 1220 if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent { 1221 result.err = r.transformUnstructuredResponseError(resp, req, result.body) 1222 } 1223 }) 1224 if err != nil { 1225 return nil, err 1226 } 1227 if result.err == nil || len(result.body) > 0 { 1228 metrics.ResponseSize.Observe(ctx, r.verb, r.URL().Host, float64(len(result.body))) 1229 } 1230 return result.body, result.err 1231 } 1232 1233 // transformResponse converts an API response into a structured API object 1234 func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result { 1235 var body []byte 1236 if resp.Body != nil { 1237 data, err := io.ReadAll(resp.Body) 1238 switch err.(type) { 1239 case nil: 1240 body = data 1241 case http2.StreamError: 1242 // This is trying to catch the scenario that the server may close the connection when sending the 1243 // response body. This can be caused by server timeout due to a slow network connection. 1244 // TODO: Add test for this. Steps may be: 1245 // 1. client-go (or kubectl) sends a GET request. 1246 // 2. Apiserver sends back the headers and then part of the body 1247 // 3. Apiserver closes connection. 1248 // 4. client-go should catch this and return an error. 1249 klog.V(2).Infof("Stream error %#v when reading response body, may be caused by closed connection.", err) 1250 streamErr := fmt.Errorf("stream error when reading response body, may be caused by closed connection. Please retry. Original error: %w", err) 1251 return Result{ 1252 err: streamErr, 1253 } 1254 default: 1255 klog.Errorf("Unexpected error when reading response body: %v", err) 1256 unexpectedErr := fmt.Errorf("unexpected error when reading response body. Please retry. Original error: %w", err) 1257 return Result{ 1258 err: unexpectedErr, 1259 } 1260 } 1261 } 1262 1263 glogBody("Response Body", body) 1264 1265 // verify the content type is accurate 1266 var decoder runtime.Decoder 1267 contentType := resp.Header.Get("Content-Type") 1268 if len(contentType) == 0 { 1269 contentType = r.c.content.ContentType 1270 } 1271 if len(contentType) > 0 { 1272 var err error 1273 mediaType, params, err := mime.ParseMediaType(contentType) 1274 if err != nil { 1275 return Result{err: errors.NewInternalError(err)} 1276 } 1277 decoder, err = r.c.content.Negotiator.Decoder(mediaType, params) 1278 if err != nil { 1279 // if we fail to negotiate a decoder, treat this as an unstructured error 1280 switch { 1281 case resp.StatusCode == http.StatusSwitchingProtocols: 1282 // no-op, we've been upgraded 1283 case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: 1284 return Result{err: r.transformUnstructuredResponseError(resp, req, body)} 1285 } 1286 return Result{ 1287 body: body, 1288 contentType: contentType, 1289 statusCode: resp.StatusCode, 1290 warnings: handleWarnings(resp.Header, r.warningHandler), 1291 } 1292 } 1293 } 1294 1295 switch { 1296 case resp.StatusCode == http.StatusSwitchingProtocols: 1297 // no-op, we've been upgraded 1298 case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: 1299 // calculate an unstructured error from the response which the Result object may use if the caller 1300 // did not return a structured error. 1301 retryAfter, _ := retryAfterSeconds(resp) 1302 err := r.newUnstructuredResponseError(body, isTextResponse(resp), resp.StatusCode, req.Method, retryAfter) 1303 return Result{ 1304 body: body, 1305 contentType: contentType, 1306 statusCode: resp.StatusCode, 1307 decoder: decoder, 1308 err: err, 1309 warnings: handleWarnings(resp.Header, r.warningHandler), 1310 } 1311 } 1312 1313 return Result{ 1314 body: body, 1315 contentType: contentType, 1316 statusCode: resp.StatusCode, 1317 decoder: decoder, 1318 warnings: handleWarnings(resp.Header, r.warningHandler), 1319 } 1320 } 1321 1322 // truncateBody decides if the body should be truncated, based on the glog Verbosity. 1323 func truncateBody(body string) string { 1324 max := 0 1325 switch { 1326 case bool(klog.V(10).Enabled()): 1327 return body 1328 case bool(klog.V(9).Enabled()): 1329 max = 10240 1330 case bool(klog.V(8).Enabled()): 1331 max = 1024 1332 } 1333 1334 if len(body) <= max { 1335 return body 1336 } 1337 1338 return body[:max] + fmt.Sprintf(" [truncated %d chars]", len(body)-max) 1339 } 1340 1341 // glogBody logs a body output that could be either JSON or protobuf. It explicitly guards against 1342 // allocating a new string for the body output unless necessary. Uses a simple heuristic to determine 1343 // whether the body is printable. 1344 func glogBody(prefix string, body []byte) { 1345 if klogV := klog.V(8); klogV.Enabled() { 1346 if bytes.IndexFunc(body, func(r rune) bool { 1347 return r < 0x0a 1348 }) != -1 { 1349 klogV.Infof("%s:\n%s", prefix, truncateBody(hex.Dump(body))) 1350 } else { 1351 klogV.Infof("%s: %s", prefix, truncateBody(string(body))) 1352 } 1353 } 1354 } 1355 1356 // maxUnstructuredResponseTextBytes is an upper bound on how much output to include in the unstructured error. 1357 const maxUnstructuredResponseTextBytes = 2048 1358 1359 // transformUnstructuredResponseError handles an error from the server that is not in a structured form. 1360 // It is expected to transform any response that is not recognizable as a clear server sent error from the 1361 // K8S API using the information provided with the request. In practice, HTTP proxies and client libraries 1362 // introduce a level of uncertainty to the responses returned by servers that in common use result in 1363 // unexpected responses. The rough structure is: 1364 // 1365 // 1. Assume the server sends you something sane - JSON + well defined error objects + proper codes 1366 // - this is the happy path 1367 // - when you get this output, trust what the server sends 1368 // 2. Guard against empty fields / bodies in received JSON and attempt to cull sufficient info from them to 1369 // generate a reasonable facsimile of the original failure. 1370 // - Be sure to use a distinct error type or flag that allows a client to distinguish between this and error 1 above 1371 // 3. Handle true disconnect failures / completely malformed data by moving up to a more generic client error 1372 // 4. Distinguish between various connection failures like SSL certificates, timeouts, proxy errors, unexpected 1373 // initial contact, the presence of mismatched body contents from posted content types 1374 // - Give these a separate distinct error type and capture as much as possible of the original message 1375 // 1376 // TODO: introduce transformation of generic http.Client.Do() errors that separates 4. 1377 func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error { 1378 if body == nil && resp.Body != nil { 1379 if data, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: maxUnstructuredResponseTextBytes}); err == nil { 1380 body = data 1381 } 1382 } 1383 retryAfter, _ := retryAfterSeconds(resp) 1384 return r.newUnstructuredResponseError(body, isTextResponse(resp), resp.StatusCode, req.Method, retryAfter) 1385 } 1386 1387 // newUnstructuredResponseError instantiates the appropriate generic error for the provided input. It also logs the body. 1388 func (r *Request) newUnstructuredResponseError(body []byte, isTextResponse bool, statusCode int, method string, retryAfter int) error { 1389 // cap the amount of output we create 1390 if len(body) > maxUnstructuredResponseTextBytes { 1391 body = body[:maxUnstructuredResponseTextBytes] 1392 } 1393 1394 message := "unknown" 1395 if isTextResponse { 1396 message = strings.TrimSpace(string(body)) 1397 } 1398 var groupResource schema.GroupResource 1399 if len(r.resource) > 0 { 1400 groupResource.Group = r.c.content.GroupVersion.Group 1401 groupResource.Resource = r.resource 1402 } 1403 return errors.NewGenericServerResponse( 1404 statusCode, 1405 method, 1406 groupResource, 1407 r.resourceName, 1408 message, 1409 retryAfter, 1410 true, 1411 ) 1412 } 1413 1414 // isTextResponse returns true if the response appears to be a textual media type. 1415 func isTextResponse(resp *http.Response) bool { 1416 contentType := resp.Header.Get("Content-Type") 1417 if len(contentType) == 0 { 1418 return true 1419 } 1420 media, _, err := mime.ParseMediaType(contentType) 1421 if err != nil { 1422 return false 1423 } 1424 return strings.HasPrefix(media, "text/") 1425 } 1426 1427 // retryAfterSeconds returns the value of the Retry-After header and true, or 0 and false if 1428 // the header was missing or not a valid number. 1429 func retryAfterSeconds(resp *http.Response) (int, bool) { 1430 if h := resp.Header.Get("Retry-After"); len(h) > 0 { 1431 if i, err := strconv.Atoi(h); err == nil { 1432 return i, true 1433 } 1434 } 1435 return 0, false 1436 } 1437 1438 // Result contains the result of calling Request.Do(). 1439 type Result struct { 1440 body []byte 1441 warnings []net.WarningHeader 1442 contentType string 1443 err error 1444 statusCode int 1445 1446 decoder runtime.Decoder 1447 } 1448 1449 // Raw returns the raw result. 1450 func (r Result) Raw() ([]byte, error) { 1451 return r.body, r.err 1452 } 1453 1454 // Get returns the result as an object, which means it passes through the decoder. 1455 // If the returned object is of type Status and has .Status != StatusSuccess, the 1456 // additional information in Status will be used to enrich the error. 1457 func (r Result) Get() (runtime.Object, error) { 1458 if r.err != nil { 1459 // Check whether the result has a Status object in the body and prefer that. 1460 return nil, r.Error() 1461 } 1462 if r.decoder == nil { 1463 return nil, fmt.Errorf("serializer for %s doesn't exist", r.contentType) 1464 } 1465 1466 // decode, but if the result is Status return that as an error instead. 1467 out, _, err := r.decoder.Decode(r.body, nil, nil) 1468 if err != nil { 1469 return nil, err 1470 } 1471 switch t := out.(type) { 1472 case *metav1.Status: 1473 // any status besides StatusSuccess is considered an error. 1474 if t.Status != metav1.StatusSuccess { 1475 return nil, errors.FromObject(t) 1476 } 1477 } 1478 return out, nil 1479 } 1480 1481 // StatusCode returns the HTTP status code of the request. (Only valid if no 1482 // error was returned.) 1483 func (r Result) StatusCode(statusCode *int) Result { 1484 *statusCode = r.statusCode 1485 return r 1486 } 1487 1488 // ContentType returns the "Content-Type" response header into the passed 1489 // string, returning the Result for possible chaining. (Only valid if no 1490 // error code was returned.) 1491 func (r Result) ContentType(contentType *string) Result { 1492 *contentType = r.contentType 1493 return r 1494 } 1495 1496 // Into stores the result into obj, if possible. If obj is nil it is ignored. 1497 // If the returned object is of type Status and has .Status != StatusSuccess, the 1498 // additional information in Status will be used to enrich the error. 1499 func (r Result) Into(obj runtime.Object) error { 1500 if r.err != nil { 1501 // Check whether the result has a Status object in the body and prefer that. 1502 return r.Error() 1503 } 1504 if r.decoder == nil { 1505 return fmt.Errorf("serializer for %s doesn't exist", r.contentType) 1506 } 1507 if len(r.body) == 0 { 1508 return fmt.Errorf("0-length response with status code: %d and content type: %s", 1509 r.statusCode, r.contentType) 1510 } 1511 1512 out, _, err := r.decoder.Decode(r.body, nil, obj) 1513 if err != nil || out == obj { 1514 return err 1515 } 1516 // if a different object is returned, see if it is Status and avoid double decoding 1517 // the object. 1518 switch t := out.(type) { 1519 case *metav1.Status: 1520 // any status besides StatusSuccess is considered an error. 1521 if t.Status != metav1.StatusSuccess { 1522 return errors.FromObject(t) 1523 } 1524 } 1525 return nil 1526 } 1527 1528 // WasCreated updates the provided bool pointer to whether the server returned 1529 // 201 created or a different response. 1530 func (r Result) WasCreated(wasCreated *bool) Result { 1531 *wasCreated = r.statusCode == http.StatusCreated 1532 return r 1533 } 1534 1535 // Error returns the error executing the request, nil if no error occurred. 1536 // If the returned object is of type Status and has Status != StatusSuccess, the 1537 // additional information in Status will be used to enrich the error. 1538 // See the Request.Do() comment for what errors you might get. 1539 func (r Result) Error() error { 1540 // if we have received an unexpected server error, and we have a body and decoder, we can try to extract 1541 // a Status object. 1542 if r.err == nil || !errors.IsUnexpectedServerError(r.err) || len(r.body) == 0 || r.decoder == nil { 1543 return r.err 1544 } 1545 1546 // attempt to convert the body into a Status object 1547 // to be backwards compatible with old servers that do not return a version, default to "v1" 1548 out, _, err := r.decoder.Decode(r.body, &schema.GroupVersionKind{Version: "v1"}, nil) 1549 if err != nil { 1550 klog.V(5).Infof("body was not decodable (unable to check for Status): %v", err) 1551 return r.err 1552 } 1553 switch t := out.(type) { 1554 case *metav1.Status: 1555 // because we default the kind, we *must* check for StatusFailure 1556 if t.Status == metav1.StatusFailure { 1557 return errors.FromObject(t) 1558 } 1559 } 1560 return r.err 1561 } 1562 1563 // Warnings returns any warning headers received in the response 1564 func (r Result) Warnings() []net.WarningHeader { 1565 return r.warnings 1566 } 1567 1568 // NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store) 1569 var NameMayNotBe = []string{".", ".."} 1570 1571 // NameMayNotContain specifies substrings that cannot be used in names specified as path segments (like the REST API or etcd store) 1572 var NameMayNotContain = []string{"/", "%"} 1573 1574 // IsValidPathSegmentName validates the name can be safely encoded as a path segment 1575 func IsValidPathSegmentName(name string) []string { 1576 for _, illegalName := range NameMayNotBe { 1577 if name == illegalName { 1578 return []string{fmt.Sprintf(`may not be '%s'`, illegalName)} 1579 } 1580 } 1581 1582 var errors []string 1583 for _, illegalContent := range NameMayNotContain { 1584 if strings.Contains(name, illegalContent) { 1585 errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) 1586 } 1587 } 1588 1589 return errors 1590 } 1591 1592 // IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment 1593 // It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid 1594 func IsValidPathSegmentPrefix(name string) []string { 1595 var errors []string 1596 for _, illegalContent := range NameMayNotContain { 1597 if strings.Contains(name, illegalContent) { 1598 errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) 1599 } 1600 } 1601 1602 return errors 1603 } 1604 1605 // ValidatePathSegmentName validates the name can be safely encoded as a path segment 1606 func ValidatePathSegmentName(name string, prefix bool) []string { 1607 if prefix { 1608 return IsValidPathSegmentPrefix(name) 1609 } 1610 return IsValidPathSegmentName(name) 1611 } 1612 1613 func objectKeyFromMeta(objMeta metav1.Object) string { 1614 if len(objMeta.GetNamespace()) > 0 { 1615 return fmt.Sprintf("%s/%s", objMeta.GetNamespace(), objMeta.GetName()) 1616 } 1617 return objMeta.GetName() 1618 }