k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/pod/strategy.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 pod 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "net/http" 24 "net/url" 25 "strconv" 26 "strings" 27 "time" 28 29 apiv1 "k8s.io/api/core/v1" 30 apiequality "k8s.io/apimachinery/pkg/api/equality" 31 "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/fields" 34 "k8s.io/apimachinery/pkg/labels" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/types" 37 utilnet "k8s.io/apimachinery/pkg/util/net" 38 utilvalidation "k8s.io/apimachinery/pkg/util/validation" 39 "k8s.io/apimachinery/pkg/util/validation/field" 40 "k8s.io/apiserver/pkg/registry/generic" 41 "k8s.io/apiserver/pkg/storage" 42 "k8s.io/apiserver/pkg/storage/names" 43 utilfeature "k8s.io/apiserver/pkg/util/feature" 44 "k8s.io/apiserver/pkg/warning" 45 "k8s.io/client-go/tools/cache" 46 "k8s.io/kubernetes/pkg/api/legacyscheme" 47 podutil "k8s.io/kubernetes/pkg/api/pod" 48 api "k8s.io/kubernetes/pkg/apis/core" 49 "k8s.io/kubernetes/pkg/apis/core/helper/qos" 50 corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" 51 "k8s.io/kubernetes/pkg/features" 52 "k8s.io/kubernetes/pkg/kubelet/client" 53 netutils "k8s.io/utils/net" 54 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 55 ) 56 57 // podStrategy implements behavior for Pods 58 type podStrategy struct { 59 runtime.ObjectTyper 60 names.NameGenerator 61 } 62 63 // Strategy is the default logic that applies when creating and updating Pod 64 // objects via the REST API. 65 var Strategy = podStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} 66 67 // NamespaceScoped is true for pods. 68 func (podStrategy) NamespaceScoped() bool { 69 return true 70 } 71 72 // GetResetFields returns the set of fields that get reset by the strategy 73 // and should not be modified by the user. 74 func (podStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { 75 fields := map[fieldpath.APIVersion]*fieldpath.Set{ 76 "v1": fieldpath.NewSet( 77 fieldpath.MakePathOrDie("status"), 78 ), 79 } 80 81 return fields 82 } 83 84 // PrepareForCreate clears fields that are not allowed to be set by end users on creation. 85 func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { 86 pod := obj.(*api.Pod) 87 pod.Status = api.PodStatus{ 88 Phase: api.PodPending, 89 QOSClass: qos.GetPodQOS(pod), 90 } 91 92 podutil.DropDisabledPodFields(pod, nil) 93 94 applySchedulingGatedCondition(pod) 95 mutatePodAffinity(pod) 96 applyAppArmorVersionSkew(ctx, pod) 97 } 98 99 // PrepareForUpdate clears fields that are not allowed to be set by end users on update. 100 func (podStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { 101 newPod := obj.(*api.Pod) 102 oldPod := old.(*api.Pod) 103 newPod.Status = oldPod.Status 104 105 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { 106 // With support for in-place pod resizing, container resources are now mutable. 107 // If container resources are updated with new resource requests values, a pod resize is 108 // desired. The status of this request is reflected by setting Resize field to "Proposed" 109 // as a signal to the caller that the request is being considered. 110 podutil.MarkPodProposedForResize(oldPod, newPod) 111 } 112 113 podutil.DropDisabledPodFields(newPod, oldPod) 114 } 115 116 // Validate validates a new pod. 117 func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { 118 pod := obj.(*api.Pod) 119 opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, nil, &pod.ObjectMeta, nil) 120 opts.ResourceIsPod = true 121 return corevalidation.ValidatePodCreate(pod, opts) 122 } 123 124 // WarningsOnCreate returns warnings for the creation of the given object. 125 func (podStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { 126 newPod := obj.(*api.Pod) 127 var warnings []string 128 if msgs := utilvalidation.IsDNS1123Label(newPod.Name); len(msgs) != 0 { 129 warnings = append(warnings, fmt.Sprintf("metadata.name: this is used in the Pod's hostname, which can result in surprising behavior; a DNS label is recommended: %v", msgs)) 130 } 131 warnings = append(warnings, podutil.GetWarningsForPod(ctx, newPod, nil)...) 132 return warnings 133 } 134 135 // Canonicalize normalizes the object after validation. 136 func (podStrategy) Canonicalize(obj runtime.Object) { 137 } 138 139 // AllowCreateOnUpdate is false for pods. 140 func (podStrategy) AllowCreateOnUpdate() bool { 141 return false 142 } 143 144 // ValidateUpdate is the default update validation for an end user. 145 func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { 146 // Allow downward api usage of hugepages on pod update if feature is enabled or if the old pod already had used them. 147 pod := obj.(*api.Pod) 148 oldPod := old.(*api.Pod) 149 opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta) 150 opts.ResourceIsPod = true 151 return corevalidation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod), opts) 152 } 153 154 // WarningsOnUpdate returns warnings for the given update. 155 func (podStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 156 // skip warnings on pod update, since humans don't typically interact directly with pods, 157 // and we don't want to pay the evaluation cost on what might be a high-frequency update path 158 return nil 159 } 160 161 // AllowUnconditionalUpdate allows pods to be overwritten 162 func (podStrategy) AllowUnconditionalUpdate() bool { 163 return true 164 } 165 166 // CheckGracefulDelete allows a pod to be gracefully deleted. It updates the DeleteOptions to 167 // reflect the desired grace value. 168 func (podStrategy) CheckGracefulDelete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) bool { 169 if options == nil { 170 return false 171 } 172 pod := obj.(*api.Pod) 173 period := int64(0) 174 // user has specified a value 175 if options.GracePeriodSeconds != nil { 176 period = *options.GracePeriodSeconds 177 } else { 178 // use the default value if set, or deletes the pod immediately (0) 179 if pod.Spec.TerminationGracePeriodSeconds != nil { 180 period = *pod.Spec.TerminationGracePeriodSeconds 181 } 182 } 183 // if the pod is not scheduled, delete immediately 184 if len(pod.Spec.NodeName) == 0 { 185 period = 0 186 } 187 // if the pod is already terminated, delete immediately 188 if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded { 189 period = 0 190 } 191 192 if period < 0 { 193 period = 1 194 } 195 196 // ensure the options and the pod are in sync 197 options.GracePeriodSeconds = &period 198 return true 199 } 200 201 type podStatusStrategy struct { 202 podStrategy 203 } 204 205 // StatusStrategy wraps and exports the used podStrategy for the storage package. 206 var StatusStrategy = podStatusStrategy{Strategy} 207 208 // GetResetFields returns the set of fields that get reset by the strategy 209 // and should not be modified by the user. 210 func (podStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { 211 return map[fieldpath.APIVersion]*fieldpath.Set{ 212 "v1": fieldpath.NewSet( 213 fieldpath.MakePathOrDie("spec"), 214 fieldpath.MakePathOrDie("metadata", "deletionTimestamp"), 215 fieldpath.MakePathOrDie("metadata", "ownerReferences"), 216 ), 217 } 218 } 219 220 func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { 221 newPod := obj.(*api.Pod) 222 oldPod := old.(*api.Pod) 223 newPod.Spec = oldPod.Spec 224 newPod.DeletionTimestamp = nil 225 226 // don't allow the pods/status endpoint to touch owner references since old kubelets corrupt them in a way 227 // that breaks garbage collection 228 newPod.OwnerReferences = oldPod.OwnerReferences 229 } 230 231 func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { 232 pod := obj.(*api.Pod) 233 oldPod := old.(*api.Pod) 234 opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta) 235 opts.ResourceIsPod = true 236 237 return corevalidation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod), opts) 238 } 239 240 // WarningsOnUpdate returns warnings for the given update. 241 func (podStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 242 return nil 243 } 244 245 type podEphemeralContainersStrategy struct { 246 podStrategy 247 } 248 249 // EphemeralContainersStrategy wraps and exports the used podStrategy for the storage package. 250 var EphemeralContainersStrategy = podEphemeralContainersStrategy{Strategy} 251 252 // dropNonEphemeralContainerUpdates discards all changes except for pod.Spec.EphemeralContainers and certain metadata 253 func dropNonEphemeralContainerUpdates(newPod, oldPod *api.Pod) *api.Pod { 254 pod := oldPod.DeepCopy() 255 pod.Name = newPod.Name 256 pod.Namespace = newPod.Namespace 257 pod.ResourceVersion = newPod.ResourceVersion 258 pod.UID = newPod.UID 259 pod.Spec.EphemeralContainers = newPod.Spec.EphemeralContainers 260 return pod 261 } 262 263 func (podEphemeralContainersStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { 264 newPod := obj.(*api.Pod) 265 oldPod := old.(*api.Pod) 266 267 *newPod = *dropNonEphemeralContainerUpdates(newPod, oldPod) 268 podutil.DropDisabledPodFields(newPod, oldPod) 269 } 270 271 func (podEphemeralContainersStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { 272 newPod := obj.(*api.Pod) 273 oldPod := old.(*api.Pod) 274 opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&newPod.Spec, &oldPod.Spec, &newPod.ObjectMeta, &oldPod.ObjectMeta) 275 opts.ResourceIsPod = true 276 return corevalidation.ValidatePodEphemeralContainersUpdate(newPod, oldPod, opts) 277 } 278 279 // WarningsOnUpdate returns warnings for the given update. 280 func (podEphemeralContainersStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 281 return nil 282 } 283 284 // GetAttrs returns labels and fields of a given object for filtering purposes. 285 func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { 286 pod, ok := obj.(*api.Pod) 287 if !ok { 288 return nil, nil, fmt.Errorf("not a pod") 289 } 290 return labels.Set(pod.ObjectMeta.Labels), ToSelectableFields(pod), nil 291 } 292 293 // MatchPod returns a generic matcher for a given label and field selector. 294 func MatchPod(label labels.Selector, field fields.Selector) storage.SelectionPredicate { 295 var indexFields = []string{"spec.nodeName"} 296 if utilfeature.DefaultFeatureGate.Enabled(features.StorageNamespaceIndex) { 297 indexFields = append(indexFields, "metadata.namespace") 298 } 299 return storage.SelectionPredicate{ 300 Label: label, 301 Field: field, 302 GetAttrs: GetAttrs, 303 IndexFields: indexFields, 304 } 305 } 306 307 // NodeNameTriggerFunc returns value spec.nodename of given object. 308 func NodeNameTriggerFunc(obj runtime.Object) string { 309 return obj.(*api.Pod).Spec.NodeName 310 } 311 312 // NodeNameIndexFunc return value spec.nodename of given object. 313 func NodeNameIndexFunc(obj interface{}) ([]string, error) { 314 pod, ok := obj.(*api.Pod) 315 if !ok { 316 return nil, fmt.Errorf("not a pod") 317 } 318 return []string{pod.Spec.NodeName}, nil 319 } 320 321 // NamespaceIndexFunc return value name of given object. 322 func NamespaceIndexFunc(obj interface{}) ([]string, error) { 323 pod, ok := obj.(*api.Pod) 324 if !ok { 325 return nil, fmt.Errorf("not a pod") 326 } 327 return []string{pod.Namespace}, nil 328 } 329 330 // Indexers returns the indexers for pod storage. 331 func Indexers() *cache.Indexers { 332 var indexers = cache.Indexers{ 333 storage.FieldIndex("spec.nodeName"): NodeNameIndexFunc, 334 } 335 if utilfeature.DefaultFeatureGate.Enabled(features.StorageNamespaceIndex) { 336 indexers[storage.FieldIndex("metadata.namespace")] = NamespaceIndexFunc 337 } 338 return &indexers 339 } 340 341 // ToSelectableFields returns a field set that represents the object 342 // TODO: fields are not labels, and the validation rules for them do not apply. 343 func ToSelectableFields(pod *api.Pod) fields.Set { 344 // The purpose of allocation with a given number of elements is to reduce 345 // amount of allocations needed to create the fields.Set. If you add any 346 // field here or the number of object-meta related fields changes, this should 347 // be adjusted. 348 podSpecificFieldsSet := make(fields.Set, 10) 349 podSpecificFieldsSet["spec.nodeName"] = pod.Spec.NodeName 350 podSpecificFieldsSet["spec.restartPolicy"] = string(pod.Spec.RestartPolicy) 351 podSpecificFieldsSet["spec.schedulerName"] = string(pod.Spec.SchedulerName) 352 podSpecificFieldsSet["spec.serviceAccountName"] = string(pod.Spec.ServiceAccountName) 353 if pod.Spec.SecurityContext != nil { 354 podSpecificFieldsSet["spec.hostNetwork"] = strconv.FormatBool(pod.Spec.SecurityContext.HostNetwork) 355 } else { 356 // default to false 357 podSpecificFieldsSet["spec.hostNetwork"] = strconv.FormatBool(false) 358 } 359 podSpecificFieldsSet["status.phase"] = string(pod.Status.Phase) 360 // TODO: add podIPs as a downward API value(s) with proper format 361 podIP := "" 362 if len(pod.Status.PodIPs) > 0 { 363 podIP = string(pod.Status.PodIPs[0].IP) 364 } 365 podSpecificFieldsSet["status.podIP"] = podIP 366 podSpecificFieldsSet["status.nominatedNodeName"] = string(pod.Status.NominatedNodeName) 367 return generic.AddObjectMetaFieldsSet(podSpecificFieldsSet, &pod.ObjectMeta, true) 368 } 369 370 // ResourceGetter is an interface for retrieving resources by ResourceLocation. 371 type ResourceGetter interface { 372 Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error) 373 } 374 375 func getPod(ctx context.Context, getter ResourceGetter, name string) (*api.Pod, error) { 376 obj, err := getter.Get(ctx, name, &metav1.GetOptions{}) 377 if err != nil { 378 return nil, err 379 } 380 pod := obj.(*api.Pod) 381 if pod == nil { 382 return nil, fmt.Errorf("Unexpected object type: %#v", pod) 383 } 384 return pod, nil 385 } 386 387 // getPodIP returns primary IP for a Pod 388 func getPodIP(pod *api.Pod) string { 389 if pod == nil { 390 return "" 391 } 392 if len(pod.Status.PodIPs) > 0 { 393 return pod.Status.PodIPs[0].IP 394 } 395 396 return "" 397 } 398 399 // ResourceLocation returns a URL to which one can send traffic for the specified pod. 400 func ResourceLocation(ctx context.Context, getter ResourceGetter, rt http.RoundTripper, id string) (*url.URL, http.RoundTripper, error) { 401 // Allow ID as "podname" or "podname:port" or "scheme:podname:port". 402 // If port is not specified, try to use the first defined port on the pod. 403 scheme, name, port, valid := utilnet.SplitSchemeNamePort(id) 404 if !valid { 405 return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id)) 406 } 407 408 pod, err := getPod(ctx, getter, name) 409 if err != nil { 410 return nil, nil, err 411 } 412 413 // Try to figure out a port. 414 if port == "" { 415 for i := range pod.Spec.Containers { 416 if len(pod.Spec.Containers[i].Ports) > 0 { 417 port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort) 418 break 419 } 420 } 421 } 422 podIP := getPodIP(pod) 423 if ip := netutils.ParseIPSloppy(podIP); ip == nil || !ip.IsGlobalUnicast() { 424 return nil, nil, errors.NewBadRequest("address not allowed") 425 } 426 427 loc := &url.URL{ 428 Scheme: scheme, 429 } 430 if port == "" { 431 // when using an ipv6 IP as a hostname in a URL, it must be wrapped in [...] 432 // net.JoinHostPort does this for you. 433 if strings.Contains(podIP, ":") { 434 loc.Host = "[" + podIP + "]" 435 } else { 436 loc.Host = podIP 437 } 438 } else { 439 loc.Host = net.JoinHostPort(podIP, port) 440 } 441 return loc, rt, nil 442 } 443 444 // LogLocation returns the log URL for a pod container. If opts.Container is blank 445 // and only one container is present in the pod, that container is used. 446 func LogLocation( 447 ctx context.Context, getter ResourceGetter, 448 connInfo client.ConnectionInfoGetter, 449 name string, 450 opts *api.PodLogOptions, 451 ) (*url.URL, http.RoundTripper, error) { 452 pod, err := getPod(ctx, getter, name) 453 if err != nil { 454 return nil, nil, err 455 } 456 457 // Try to figure out a container 458 // If a container was provided, it must be valid 459 container := opts.Container 460 container, err = validateContainer(container, pod) 461 if err != nil { 462 return nil, nil, err 463 } 464 nodeName := types.NodeName(pod.Spec.NodeName) 465 if len(nodeName) == 0 { 466 // If pod has not been assigned a host, return an empty location 467 return nil, nil, nil 468 } 469 nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName) 470 if err != nil { 471 return nil, nil, err 472 } 473 params := url.Values{} 474 if opts.Follow { 475 params.Add("follow", "true") 476 } 477 if opts.Previous { 478 params.Add("previous", "true") 479 } 480 if opts.Timestamps { 481 params.Add("timestamps", "true") 482 } 483 if opts.SinceSeconds != nil { 484 params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10)) 485 } 486 if opts.SinceTime != nil { 487 params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339)) 488 } 489 if opts.TailLines != nil { 490 params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10)) 491 } 492 if opts.LimitBytes != nil { 493 params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10)) 494 } 495 loc := &url.URL{ 496 Scheme: nodeInfo.Scheme, 497 Host: net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port), 498 Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container), 499 RawQuery: params.Encode(), 500 } 501 502 if opts.InsecureSkipTLSVerifyBackend { 503 return loc, nodeInfo.InsecureSkipTLSVerifyTransport, nil 504 } 505 return loc, nodeInfo.Transport, nil 506 } 507 508 func podHasContainerWithName(pod *api.Pod, containerName string) bool { 509 var hasContainer bool 510 podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *api.Container, containerType podutil.ContainerType) bool { 511 if c.Name == containerName { 512 hasContainer = true 513 return false 514 } 515 return true 516 }) 517 return hasContainer 518 } 519 520 func streamParams(params url.Values, opts runtime.Object) error { 521 switch opts := opts.(type) { 522 case *api.PodExecOptions: 523 if opts.Stdin { 524 params.Add(api.ExecStdinParam, "1") 525 } 526 if opts.Stdout { 527 params.Add(api.ExecStdoutParam, "1") 528 } 529 if opts.Stderr { 530 params.Add(api.ExecStderrParam, "1") 531 } 532 if opts.TTY { 533 params.Add(api.ExecTTYParam, "1") 534 } 535 for _, c := range opts.Command { 536 params.Add("command", c) 537 } 538 case *api.PodAttachOptions: 539 if opts.Stdin { 540 params.Add(api.ExecStdinParam, "1") 541 } 542 if opts.Stdout { 543 params.Add(api.ExecStdoutParam, "1") 544 } 545 if opts.Stderr { 546 params.Add(api.ExecStderrParam, "1") 547 } 548 if opts.TTY { 549 params.Add(api.ExecTTYParam, "1") 550 } 551 case *api.PodPortForwardOptions: 552 if len(opts.Ports) > 0 { 553 ports := make([]string, len(opts.Ports)) 554 for i, p := range opts.Ports { 555 ports[i] = strconv.FormatInt(int64(p), 10) 556 } 557 params.Add(api.PortHeader, strings.Join(ports, ",")) 558 } 559 default: 560 return fmt.Errorf("Unknown object for streaming: %v", opts) 561 } 562 return nil 563 } 564 565 // AttachLocation returns the attach URL for a pod container. If opts.Container is blank 566 // and only one container is present in the pod, that container is used. 567 func AttachLocation( 568 ctx context.Context, 569 getter ResourceGetter, 570 connInfo client.ConnectionInfoGetter, 571 name string, 572 opts *api.PodAttachOptions, 573 ) (*url.URL, http.RoundTripper, error) { 574 return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "attach") 575 } 576 577 // ExecLocation returns the exec URL for a pod container. If opts.Container is blank 578 // and only one container is present in the pod, that container is used. 579 func ExecLocation( 580 ctx context.Context, 581 getter ResourceGetter, 582 connInfo client.ConnectionInfoGetter, 583 name string, 584 opts *api.PodExecOptions, 585 ) (*url.URL, http.RoundTripper, error) { 586 return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "exec") 587 } 588 589 func streamLocation( 590 ctx context.Context, 591 getter ResourceGetter, 592 connInfo client.ConnectionInfoGetter, 593 name string, 594 opts runtime.Object, 595 container, 596 path string, 597 ) (*url.URL, http.RoundTripper, error) { 598 pod, err := getPod(ctx, getter, name) 599 if err != nil { 600 return nil, nil, err 601 } 602 603 // Try to figure out a container 604 // If a container was provided, it must be valid 605 container, err = validateContainer(container, pod) 606 if err != nil { 607 return nil, nil, err 608 } 609 610 nodeName := types.NodeName(pod.Spec.NodeName) 611 if len(nodeName) == 0 { 612 // If pod has not been assigned a host, return an empty location 613 return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) 614 } 615 nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName) 616 if err != nil { 617 return nil, nil, err 618 } 619 params := url.Values{} 620 if err := streamParams(params, opts); err != nil { 621 return nil, nil, err 622 } 623 loc := &url.URL{ 624 Scheme: nodeInfo.Scheme, 625 Host: net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port), 626 Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container), 627 RawQuery: params.Encode(), 628 } 629 return loc, nodeInfo.Transport, nil 630 } 631 632 // PortForwardLocation returns the port-forward URL for a pod. 633 func PortForwardLocation( 634 ctx context.Context, 635 getter ResourceGetter, 636 connInfo client.ConnectionInfoGetter, 637 name string, 638 opts *api.PodPortForwardOptions, 639 ) (*url.URL, http.RoundTripper, error) { 640 pod, err := getPod(ctx, getter, name) 641 if err != nil { 642 return nil, nil, err 643 } 644 645 nodeName := types.NodeName(pod.Spec.NodeName) 646 if len(nodeName) == 0 { 647 // If pod has not been assigned a host, return an empty location 648 return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) 649 } 650 nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName) 651 if err != nil { 652 return nil, nil, err 653 } 654 params := url.Values{} 655 if err := streamParams(params, opts); err != nil { 656 return nil, nil, err 657 } 658 loc := &url.URL{ 659 Scheme: nodeInfo.Scheme, 660 Host: net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port), 661 Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name), 662 RawQuery: params.Encode(), 663 } 664 return loc, nodeInfo.Transport, nil 665 } 666 667 // validateContainer validate container is valid for pod, return valid container 668 func validateContainer(container string, pod *api.Pod) (string, error) { 669 if len(container) == 0 { 670 switch len(pod.Spec.Containers) { 671 case 1: 672 container = pod.Spec.Containers[0].Name 673 case 0: 674 return "", errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", pod.Name)) 675 default: 676 var containerNames []string 677 podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *api.Container, containerType podutil.ContainerType) bool { 678 containerNames = append(containerNames, c.Name) 679 return true 680 }) 681 errStr := fmt.Sprintf("a container name must be specified for pod %s, choose one of: %s", pod.Name, containerNames) 682 return "", errors.NewBadRequest(errStr) 683 } 684 } else { 685 if !podHasContainerWithName(pod, container) { 686 return "", errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, pod.Name)) 687 } 688 } 689 690 return container, nil 691 } 692 693 // applyLabelKeysToLabelSelector obtains the label value from the given label set by the key in labelKeys, 694 // and merge to LabelSelector with the given operator: 695 func applyLabelKeysToLabelSelector(labelSelector *metav1.LabelSelector, labelKeys []string, operator metav1.LabelSelectorOperator, podLabels map[string]string) { 696 for _, key := range labelKeys { 697 if value, ok := podLabels[key]; ok { 698 labelSelector.MatchExpressions = append(labelSelector.MatchExpressions, metav1.LabelSelectorRequirement{ 699 Key: key, 700 Operator: operator, 701 Values: []string{value}, 702 }) 703 } 704 } 705 } 706 707 // applyMatchLabelKeysAndMismatchLabelKeys obtains the labels from the pod labels by the key in matchLabelKeys or mismatchLabelKeys, 708 // and merge to LabelSelector of PodAffinityTerm depending on field: 709 // - If matchLabelKeys, key in (value) is merged with LabelSelector. 710 // - If mismatchLabelKeys, key notin (value) is merged with LabelSelector. 711 func applyMatchLabelKeysAndMismatchLabelKeys(term *api.PodAffinityTerm, label map[string]string) { 712 if (len(term.MatchLabelKeys) == 0 && len(term.MismatchLabelKeys) == 0) || term.LabelSelector == nil { 713 // If LabelSelector is nil, we don't need to apply label keys to it because nil-LabelSelector is match none. 714 return 715 } 716 717 applyLabelKeysToLabelSelector(term.LabelSelector, term.MatchLabelKeys, metav1.LabelSelectorOpIn, label) 718 applyLabelKeysToLabelSelector(term.LabelSelector, term.MismatchLabelKeys, metav1.LabelSelectorOpNotIn, label) 719 } 720 721 func mutatePodAffinity(pod *api.Pod) { 722 if !utilfeature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodAffinity) || pod.Spec.Affinity == nil { 723 return 724 } 725 if affinity := pod.Spec.Affinity.PodAffinity; affinity != nil { 726 for i := range affinity.PreferredDuringSchedulingIgnoredDuringExecution { 727 applyMatchLabelKeysAndMismatchLabelKeys(&affinity.PreferredDuringSchedulingIgnoredDuringExecution[i].PodAffinityTerm, pod.Labels) 728 } 729 for i := range affinity.RequiredDuringSchedulingIgnoredDuringExecution { 730 applyMatchLabelKeysAndMismatchLabelKeys(&affinity.RequiredDuringSchedulingIgnoredDuringExecution[i], pod.Labels) 731 } 732 } 733 if affinity := pod.Spec.Affinity.PodAntiAffinity; affinity != nil { 734 for i := range affinity.PreferredDuringSchedulingIgnoredDuringExecution { 735 applyMatchLabelKeysAndMismatchLabelKeys(&affinity.PreferredDuringSchedulingIgnoredDuringExecution[i].PodAffinityTerm, pod.Labels) 736 } 737 for i := range affinity.RequiredDuringSchedulingIgnoredDuringExecution { 738 applyMatchLabelKeysAndMismatchLabelKeys(&affinity.RequiredDuringSchedulingIgnoredDuringExecution[i], pod.Labels) 739 } 740 } 741 } 742 743 // applySchedulingGatedCondition adds a {type:PodScheduled, reason:SchedulingGated} condition 744 // to a new-created Pod if necessary. 745 func applySchedulingGatedCondition(pod *api.Pod) { 746 if len(pod.Spec.SchedulingGates) == 0 { 747 return 748 } 749 750 // If found a condition with type PodScheduled, return. 751 for _, condition := range pod.Status.Conditions { 752 if condition.Type == api.PodScheduled { 753 return 754 } 755 } 756 757 pod.Status.Conditions = append(pod.Status.Conditions, api.PodCondition{ 758 Type: api.PodScheduled, 759 Status: api.ConditionFalse, 760 Reason: apiv1.PodReasonSchedulingGated, 761 Message: "Scheduling is blocked due to non-empty scheduling gates", 762 }) 763 } 764 765 // applyAppArmorVersionSkew implements the version skew behavior described in: 766 // https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/24-apparmor#version-skew-strategy 767 func applyAppArmorVersionSkew(ctx context.Context, pod *api.Pod) { 768 if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) { 769 return 770 } 771 772 if pod.Spec.OS != nil && pod.Spec.OS.Name == api.Windows { 773 return 774 } 775 776 var podProfile *api.AppArmorProfile 777 if pod.Spec.SecurityContext != nil { 778 podProfile = pod.Spec.SecurityContext.AppArmorProfile 779 } 780 781 // Handle the containers of the pod 782 podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), 783 func(ctr *api.Container, _ podutil.ContainerType) bool { 784 // get possible annotation and field 785 key := api.DeprecatedAppArmorAnnotationKeyPrefix + ctr.Name 786 annotation, hasAnnotation := pod.Annotations[key] 787 788 var containerProfile *api.AppArmorProfile 789 if ctr.SecurityContext != nil { 790 containerProfile = ctr.SecurityContext.AppArmorProfile 791 } 792 793 // sync field and annotation 794 if !hasAnnotation { 795 newAnnotation := "" 796 if containerProfile != nil { 797 newAnnotation = appArmorAnnotationForField(containerProfile) 798 } else if podProfile != nil { 799 newAnnotation = appArmorAnnotationForField(podProfile) 800 } 801 802 if newAnnotation != "" { 803 if pod.Annotations == nil { 804 pod.Annotations = map[string]string{} 805 } 806 pod.Annotations[key] = newAnnotation 807 } 808 } else if containerProfile == nil { 809 newField := apparmorFieldForAnnotation(annotation) 810 if errs := corevalidation.ValidateAppArmorProfileField(newField, &field.Path{}); len(errs) > 0 { 811 // Skip copying invalid value. 812 newField = nil 813 } 814 815 // warn if we had an annotation that we couldn't derive a valid field from 816 deprecationWarning := newField == nil 817 818 // Only copy the annotation to the field if it is different from the pod-level profile. 819 if newField != nil && !apiequality.Semantic.DeepEqual(newField, podProfile) { 820 if ctr.SecurityContext == nil { 821 ctr.SecurityContext = &api.SecurityContext{} 822 } 823 ctr.SecurityContext.AppArmorProfile = newField 824 // warn if there was an annotation without a corresponding field 825 deprecationWarning = true 826 } 827 828 if deprecationWarning { 829 // Note: annotation deprecation warning must be added here rather than the 830 // typical WarningsOnCreate path to emit the warning before syncing the 831 // annotations & fields. 832 fldPath := field.NewPath("metadata", "annotations").Key(key) 833 warning.AddWarning(ctx, "", fmt.Sprintf(`%s: deprecated since v1.30; use the "appArmorProfile" field instead`, fldPath)) 834 } 835 } 836 837 return true 838 }) 839 } 840 841 // appArmorFieldForAnnotation takes a pod apparmor profile field and returns the 842 // converted annotation value 843 func appArmorAnnotationForField(field *api.AppArmorProfile) string { 844 // If only apparmor fields are specified, add the corresponding annotations. 845 // This ensures that the fields are enforced even if the node version 846 // trails the API version 847 switch field.Type { 848 case api.AppArmorProfileTypeUnconfined: 849 return api.DeprecatedAppArmorAnnotationValueUnconfined 850 851 case api.AppArmorProfileTypeRuntimeDefault: 852 return api.DeprecatedAppArmorAnnotationValueRuntimeDefault 853 854 case api.AppArmorProfileTypeLocalhost: 855 if field.LocalhostProfile != nil { 856 return api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + *field.LocalhostProfile 857 } 858 } 859 860 // we can only reach this code path if the LocalhostProfile is nil but the 861 // provided field type is AppArmorProfileTypeLocalhost or if an unrecognized 862 // type is specified 863 return "" 864 } 865 866 // apparmorFieldForAnnotation takes a pod annotation and returns the converted 867 // apparmor profile field. 868 func apparmorFieldForAnnotation(annotation string) *api.AppArmorProfile { 869 if annotation == api.DeprecatedAppArmorAnnotationValueUnconfined { 870 return &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined} 871 } 872 873 if annotation == api.DeprecatedAppArmorAnnotationValueRuntimeDefault { 874 return &api.AppArmorProfile{Type: api.AppArmorProfileTypeRuntimeDefault} 875 } 876 877 if strings.HasPrefix(annotation, api.DeprecatedAppArmorAnnotationValueLocalhostPrefix) { 878 localhostProfile := strings.TrimPrefix(annotation, api.DeprecatedAppArmorAnnotationValueLocalhostPrefix) 879 if localhostProfile != "" { 880 return &api.AppArmorProfile{ 881 Type: api.AppArmorProfileTypeLocalhost, 882 LocalhostProfile: &localhostProfile, 883 } 884 } 885 } 886 887 // we can only reach this code path if the localhostProfile name has a zero 888 // length or if the annotation has an unrecognized value 889 return nil 890 }