...

Source file src/k8s.io/kubernetes/pkg/registry/core/pod/strategy.go

Documentation: k8s.io/kubernetes/pkg/registry/core/pod

     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  }
   891  

View as plain text