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