...

Source file src/k8s.io/kubectl/pkg/cmd/set/set_selector.go

Documentation: k8s.io/kubectl/pkg/cmd/set

     1  /*
     2  Copyright 2016 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 set
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/spf13/cobra"
    23  	"k8s.io/klog/v2"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/apimachinery/pkg/util/validation"
    31  	"k8s.io/cli-runtime/pkg/genericclioptions"
    32  	"k8s.io/cli-runtime/pkg/genericiooptions"
    33  	"k8s.io/cli-runtime/pkg/printers"
    34  	"k8s.io/cli-runtime/pkg/resource"
    35  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    36  	"k8s.io/kubectl/pkg/scheme"
    37  	"k8s.io/kubectl/pkg/util/i18n"
    38  	"k8s.io/kubectl/pkg/util/templates"
    39  )
    40  
    41  // SetSelectorOptions is the start of the data required to perform the operation.  As new fields are added, add them here instead of
    42  // referencing the cmd.Flags()
    43  type SetSelectorOptions struct {
    44  	// Bound
    45  	ResourceBuilderFlags *genericclioptions.ResourceBuilderFlags
    46  	PrintFlags           *genericclioptions.PrintFlags
    47  	RecordFlags          *genericclioptions.RecordFlags
    48  	dryRunStrategy       cmdutil.DryRunStrategy
    49  	fieldManager         string
    50  
    51  	// set by args
    52  	resources       []string
    53  	selector        *metav1.LabelSelector
    54  	resourceVersion string
    55  
    56  	// computed
    57  	WriteToServer  bool
    58  	PrintObj       printers.ResourcePrinterFunc
    59  	Recorder       genericclioptions.Recorder
    60  	ResourceFinder genericclioptions.ResourceFinder
    61  
    62  	// set at initialization
    63  	genericiooptions.IOStreams
    64  }
    65  
    66  var (
    67  	selectorLong = templates.LongDesc(i18n.T(`
    68  		Set the selector on a resource. Note that the new selector will overwrite the old selector if the resource had one prior to the invocation
    69  		of 'set selector'.
    70  
    71  		A selector must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.
    72  		If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.
    73          Note: currently selectors can only be set on Service objects.`))
    74  	selectorExample = templates.Examples(`
    75          # Set the labels and selector before creating a deployment/service pair
    76          kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run=client | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f -
    77          kubectl create deployment my-dep -o yaml --dry-run=client | kubectl label --local -f - environment=qa -o yaml | kubectl create -f -`)
    78  )
    79  
    80  // NewSelectorOptions returns an initialized SelectorOptions instance
    81  func NewSelectorOptions(streams genericiooptions.IOStreams) *SetSelectorOptions {
    82  	return &SetSelectorOptions{
    83  		ResourceBuilderFlags: genericclioptions.NewResourceBuilderFlags().
    84  			WithScheme(scheme.Scheme).
    85  			WithAll(false).
    86  			WithLocal(false).
    87  			WithLatest(),
    88  		PrintFlags:  genericclioptions.NewPrintFlags("selector updated").WithTypeSetter(scheme.Scheme),
    89  		RecordFlags: genericclioptions.NewRecordFlags(),
    90  
    91  		Recorder: genericclioptions.NoopRecorder{},
    92  
    93  		IOStreams: streams,
    94  	}
    95  }
    96  
    97  // NewCmdSelector is the "set selector" command.
    98  func NewCmdSelector(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    99  	o := NewSelectorOptions(streams)
   100  
   101  	cmd := &cobra.Command{
   102  		Use:                   "selector (-f FILENAME | TYPE NAME) EXPRESSIONS [--resource-version=version]",
   103  		DisableFlagsInUseLine: true,
   104  		Short:                 i18n.T("Set the selector on a resource"),
   105  		Long:                  fmt.Sprintf(selectorLong, validation.LabelValueMaxLength),
   106  		Example:               selectorExample,
   107  		Run: func(cmd *cobra.Command, args []string) {
   108  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   109  			cmdutil.CheckErr(o.Validate())
   110  			cmdutil.CheckErr(o.RunSelector())
   111  		},
   112  	}
   113  
   114  	o.ResourceBuilderFlags.AddFlags(cmd.Flags())
   115  	o.PrintFlags.AddFlags(cmd)
   116  	o.RecordFlags.AddFlags(cmd)
   117  	cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-set")
   118  
   119  	cmd.Flags().StringVarP(&o.resourceVersion, "resource-version", "", o.resourceVersion, "If non-empty, the selectors update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")
   120  	cmdutil.AddDryRunFlag(cmd)
   121  
   122  	return cmd
   123  }
   124  
   125  // Complete assigns the SelectorOptions from args.
   126  func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   127  	var err error
   128  
   129  	o.RecordFlags.Complete(cmd)
   130  	o.Recorder, err = o.RecordFlags.ToRecorder()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	o.resources, o.selector, err = getResourcesAndSelector(args)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	o.ResourceFinder = o.ResourceBuilderFlags.ToBuilder(f, o.resources)
   146  	o.WriteToServer = !(*o.ResourceBuilderFlags.Local || o.dryRunStrategy == cmdutil.DryRunClient)
   147  
   148  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
   149  	printer, err := o.PrintFlags.ToPrinter()
   150  	if err != nil {
   151  		return err
   152  	}
   153  	o.PrintObj = printer.PrintObj
   154  
   155  	return err
   156  }
   157  
   158  // Validate basic inputs
   159  func (o *SetSelectorOptions) Validate() error {
   160  	if o.selector == nil {
   161  		return fmt.Errorf("one selector is required")
   162  	}
   163  	return nil
   164  }
   165  
   166  // RunSelector executes the command.
   167  func (o *SetSelectorOptions) RunSelector() error {
   168  	r := o.ResourceFinder.Do()
   169  
   170  	return r.Visit(func(info *resource.Info, err error) error {
   171  		if err != nil {
   172  			return err
   173  		}
   174  		patch := &Patch{Info: info}
   175  
   176  		if len(o.resourceVersion) != 0 {
   177  			// ensure resourceVersion is always sent in the patch by clearing it from the starting JSON
   178  			accessor, err := meta.Accessor(info.Object)
   179  			if err != nil {
   180  				return err
   181  			}
   182  			accessor.SetResourceVersion("")
   183  		}
   184  
   185  		CalculatePatch(patch, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
   186  
   187  			if len(o.resourceVersion) != 0 {
   188  				accessor, err := meta.Accessor(info.Object)
   189  				if err != nil {
   190  					return nil, err
   191  				}
   192  				accessor.SetResourceVersion(o.resourceVersion)
   193  			}
   194  
   195  			selectErr := updateSelectorForObject(info.Object, *o.selector)
   196  			if selectErr != nil {
   197  				return nil, selectErr
   198  			}
   199  
   200  			// record this change (for rollout history)
   201  			if err := o.Recorder.Record(patch.Info.Object); err != nil {
   202  				klog.V(4).Infof("error recording current command: %v", err)
   203  			}
   204  
   205  			return runtime.Encode(scheme.DefaultJSONEncoder(), info.Object)
   206  		})
   207  
   208  		if patch.Err != nil {
   209  			return patch.Err
   210  		}
   211  		if !o.WriteToServer {
   212  			return o.PrintObj(info.Object, o.Out)
   213  		}
   214  
   215  		actual, err := resource.
   216  			NewHelper(info.Client, info.Mapping).
   217  			DryRun(o.dryRunStrategy == cmdutil.DryRunServer).
   218  			WithFieldManager(o.fieldManager).
   219  			Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
   220  		if err != nil {
   221  			return err
   222  		}
   223  
   224  		return o.PrintObj(actual, o.Out)
   225  	})
   226  }
   227  
   228  func updateSelectorForObject(obj runtime.Object, selector metav1.LabelSelector) error {
   229  	copyOldSelector := func() (map[string]string, error) {
   230  		if len(selector.MatchExpressions) > 0 {
   231  			return nil, fmt.Errorf("match expression %v not supported on this object", selector.MatchExpressions)
   232  		}
   233  		dst := make(map[string]string)
   234  		for label, value := range selector.MatchLabels {
   235  			dst[label] = value
   236  		}
   237  		return dst, nil
   238  	}
   239  	var err error
   240  	switch t := obj.(type) {
   241  	case *v1.Service:
   242  		t.Spec.Selector, err = copyOldSelector()
   243  	default:
   244  		err = fmt.Errorf("setting a selector is only supported for Services")
   245  	}
   246  	return err
   247  }
   248  
   249  // getResourcesAndSelector retrieves resources and the selector expression from the given args (assuming selectors the last arg)
   250  func getResourcesAndSelector(args []string) (resources []string, selector *metav1.LabelSelector, err error) {
   251  	if len(args) == 0 {
   252  		return []string{}, nil, nil
   253  	}
   254  	resources = args[:len(args)-1]
   255  	selector, err = metav1.ParseToLabelSelector(args[len(args)-1])
   256  	return resources, selector, err
   257  }
   258  

View as plain text