...

Source file src/k8s.io/kubectl/pkg/cmd/taint/taint.go

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

     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 taint
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/spf13/cobra"
    25  	"k8s.io/klog/v2"
    26  	"k8s.io/kubectl/pkg/explain"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    34  	"k8s.io/apimachinery/pkg/util/validation"
    35  	"k8s.io/cli-runtime/pkg/genericclioptions"
    36  	"k8s.io/cli-runtime/pkg/genericiooptions"
    37  	"k8s.io/cli-runtime/pkg/printers"
    38  	"k8s.io/cli-runtime/pkg/resource"
    39  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    40  	"k8s.io/kubectl/pkg/scheme"
    41  	"k8s.io/kubectl/pkg/util/completion"
    42  	"k8s.io/kubectl/pkg/util/i18n"
    43  	"k8s.io/kubectl/pkg/util/templates"
    44  )
    45  
    46  // TaintOptions have the data required to perform the taint operation
    47  type TaintOptions struct {
    48  	PrintFlags *genericclioptions.PrintFlags
    49  	ToPrinter  func(string) (printers.ResourcePrinter, error)
    50  
    51  	DryRunStrategy      cmdutil.DryRunStrategy
    52  	ValidationDirective string
    53  
    54  	resources      []string
    55  	taintsToAdd    []v1.Taint
    56  	taintsToRemove []v1.Taint
    57  	builder        *resource.Builder
    58  	selector       string
    59  	overwrite      bool
    60  	all            bool
    61  	fieldManager   string
    62  
    63  	ClientForMapping func(*meta.RESTMapping) (resource.RESTClient, error)
    64  
    65  	genericiooptions.IOStreams
    66  
    67  	Mapper meta.RESTMapper
    68  }
    69  
    70  var (
    71  	taintLong = templates.LongDesc(i18n.T(`
    72  		Update the taints on one or more nodes.
    73  
    74  		* A taint consists of a key, value, and effect. As an argument here, it is expressed as key=value:effect.
    75  		* The key must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.
    76  		* Optionally, the key can begin with a DNS subdomain prefix and a single '/', like example.com/my-app.
    77  		* The value is optional. If given, it must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[2]d characters.
    78  		* The effect must be NoSchedule, PreferNoSchedule or NoExecute.
    79  		* Currently taint can only apply to node.`))
    80  
    81  	taintExample = templates.Examples(i18n.T(`
    82  		# Update node 'foo' with a taint with key 'dedicated' and value 'special-user' and effect 'NoSchedule'
    83  		# If a taint with that key and effect already exists, its value is replaced as specified
    84  		kubectl taint nodes foo dedicated=special-user:NoSchedule
    85  
    86  		# Remove from node 'foo' the taint with key 'dedicated' and effect 'NoSchedule' if one exists
    87  		kubectl taint nodes foo dedicated:NoSchedule-
    88  
    89  		# Remove from node 'foo' all the taints with key 'dedicated'
    90  		kubectl taint nodes foo dedicated-
    91  
    92  		# Add a taint with key 'dedicated' on nodes having label myLabel=X
    93  		kubectl taint node -l myLabel=X  dedicated=foo:PreferNoSchedule
    94  
    95  		# Add to node 'foo' a taint with key 'bar' and no value
    96  		kubectl taint nodes foo bar:NoSchedule`))
    97  )
    98  
    99  func NewCmdTaint(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   100  	options := &TaintOptions{
   101  		PrintFlags: genericclioptions.NewPrintFlags("tainted").WithTypeSetter(scheme.Scheme),
   102  		IOStreams:  streams,
   103  	}
   104  
   105  	validArgs := []string{"node"}
   106  
   107  	cmd := &cobra.Command{
   108  		Use:                   "taint NODE NAME KEY_1=VAL_1:TAINT_EFFECT_1 ... KEY_N=VAL_N:TAINT_EFFECT_N",
   109  		DisableFlagsInUseLine: true,
   110  		Short:                 i18n.T("Update the taints on one or more nodes"),
   111  		Long:                  fmt.Sprintf(taintLong, validation.DNS1123SubdomainMaxLength, validation.LabelValueMaxLength),
   112  		Example:               taintExample,
   113  		ValidArgsFunction:     completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs),
   114  		Run: func(cmd *cobra.Command, args []string) {
   115  			cmdutil.CheckErr(options.Complete(f, cmd, args))
   116  			cmdutil.CheckErr(options.Validate())
   117  			cmdutil.CheckErr(options.RunTaint())
   118  		},
   119  	}
   120  
   121  	options.PrintFlags.AddFlags(cmd)
   122  	cmdutil.AddDryRunFlag(cmd)
   123  	cmdutil.AddValidateFlags(cmd)
   124  	cmdutil.AddLabelSelectorFlagVar(cmd, &options.selector)
   125  	cmd.Flags().BoolVar(&options.overwrite, "overwrite", options.overwrite, "If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints.")
   126  	cmd.Flags().BoolVar(&options.all, "all", options.all, "Select all nodes in the cluster")
   127  	cmdutil.AddFieldManagerFlagVar(cmd, &options.fieldManager, "kubectl-taint")
   128  	return cmd
   129  }
   130  
   131  // Complete adapts from the command line args and factory to the data required.
   132  func (o *TaintOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) {
   133  	namespace, _, err := f.ToRawKubeConfigLoader().Namespace()
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	o.Mapper, err = f.ToRESTMapper()
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   148  
   149  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	// retrieves resource and taint args from args
   155  	// also checks args to verify that all resources are specified before taints
   156  	taintArgs := []string{}
   157  	metTaintArg := false
   158  	for _, s := range args {
   159  		isTaint := strings.Contains(s, "=") || strings.Contains(s, ":") || strings.HasSuffix(s, "-")
   160  		switch {
   161  		case !metTaintArg && isTaint:
   162  			metTaintArg = true
   163  			fallthrough
   164  		case metTaintArg && isTaint:
   165  			taintArgs = append(taintArgs, s)
   166  		case !metTaintArg && !isTaint:
   167  			o.resources = append(o.resources, s)
   168  		case metTaintArg && !isTaint:
   169  			return fmt.Errorf("all resources must be specified before taint changes: %s", s)
   170  		}
   171  	}
   172  
   173  	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
   174  		o.PrintFlags.NamePrintFlags.Operation = operation
   175  		return o.PrintFlags.ToPrinter()
   176  	}
   177  
   178  	if len(o.resources) < 1 {
   179  		return fmt.Errorf("one or more resources must be specified as <resource> <name>")
   180  	}
   181  	if len(taintArgs) < 1 {
   182  		return fmt.Errorf("at least one taint update is required")
   183  	}
   184  
   185  	if o.taintsToAdd, o.taintsToRemove, err = parseTaints(taintArgs); err != nil {
   186  		return cmdutil.UsageErrorf(cmd, err.Error())
   187  	}
   188  	o.builder = f.NewBuilder().
   189  		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
   190  		ContinueOnError().
   191  		NamespaceParam(namespace).DefaultNamespace()
   192  	if o.selector != "" {
   193  		o.builder = o.builder.LabelSelectorParam(o.selector).ResourceTypes("node")
   194  	}
   195  	if o.all {
   196  		o.builder = o.builder.SelectAllParam(o.all).ResourceTypes("node").Flatten().Latest()
   197  	}
   198  	if !o.all && o.selector == "" && len(o.resources) >= 2 {
   199  		o.builder = o.builder.ResourceNames("node", o.resources[1:]...)
   200  	}
   201  	o.builder = o.builder.LabelSelectorParam(o.selector).
   202  		Flatten().
   203  		Latest()
   204  
   205  	o.ClientForMapping = f.ClientForMapping
   206  	return nil
   207  }
   208  
   209  // validateFlags checks for the validation of flags for kubectl taints.
   210  func (o TaintOptions) validateFlags() error {
   211  	// Cannot have a non-empty selector and all flag set. They are mutually exclusive.
   212  	if o.all && o.selector != "" {
   213  		return fmt.Errorf("setting 'all' parameter with a non empty selector is prohibited")
   214  	}
   215  	// If both selector and all are not set.
   216  	if !o.all && o.selector == "" {
   217  		if len(o.resources) < 2 {
   218  			return fmt.Errorf("at least one resource name must be specified since 'all' parameter is not set")
   219  		} else {
   220  			return nil
   221  		}
   222  	}
   223  	return nil
   224  }
   225  
   226  // Validate checks to the TaintOptions to see if there is sufficient information run the command.
   227  func (o TaintOptions) Validate() error {
   228  	resourceType := strings.ToLower(o.resources[0])
   229  	fullySpecifiedGVR, _, err := explain.SplitAndParseResourceRequest(resourceType, o.Mapper)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	gvk, err := o.Mapper.KindFor(fullySpecifiedGVR)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	if gvk.Kind != "Node" {
   240  		return fmt.Errorf("invalid resource type %s, only node types are supported", resourceType)
   241  	}
   242  
   243  	// check the format of taint args and checks removed taints aren't in the new taints list
   244  	var conflictTaints []string
   245  	for _, taintAdd := range o.taintsToAdd {
   246  		for _, taintRemove := range o.taintsToRemove {
   247  			if taintAdd.Key != taintRemove.Key {
   248  				continue
   249  			}
   250  			if len(taintRemove.Effect) == 0 || taintAdd.Effect == taintRemove.Effect {
   251  				conflictTaint := fmt.Sprintf("%s=%s", taintRemove.Key, taintRemove.Effect)
   252  				conflictTaints = append(conflictTaints, conflictTaint)
   253  			}
   254  		}
   255  	}
   256  	if len(conflictTaints) > 0 {
   257  		return fmt.Errorf("can not both modify and remove the following taint(s) in the same command: %s", strings.Join(conflictTaints, ", "))
   258  	}
   259  	return o.validateFlags()
   260  }
   261  
   262  // RunTaint does the work
   263  func (o TaintOptions) RunTaint() error {
   264  	r := o.builder.Do()
   265  	if err := r.Err(); err != nil {
   266  		return err
   267  	}
   268  
   269  	return r.Visit(func(info *resource.Info, err error) error {
   270  		if err != nil {
   271  			return err
   272  		}
   273  
   274  		obj := info.Object
   275  		name, namespace := info.Name, info.Namespace
   276  		oldData, err := json.Marshal(obj)
   277  		if err != nil {
   278  			return err
   279  		}
   280  		operation, err := o.updateTaints(obj)
   281  		if err != nil {
   282  			return err
   283  		}
   284  		newData, err := json.Marshal(obj)
   285  		if err != nil {
   286  			return err
   287  		}
   288  		patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
   289  		createdPatch := err == nil
   290  		if err != nil {
   291  			klog.V(2).Infof("couldn't compute patch: %v", err)
   292  		}
   293  
   294  		printer, err := o.ToPrinter(operation)
   295  		if err != nil {
   296  			return err
   297  		}
   298  		if o.DryRunStrategy == cmdutil.DryRunClient {
   299  			if createdPatch {
   300  				typedObj, err := scheme.Scheme.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
   301  				if err != nil {
   302  					return err
   303  				}
   304  
   305  				nodeObj, ok := typedObj.(*v1.Node)
   306  				if !ok {
   307  					return fmt.Errorf("unexpected type %T", typedObj)
   308  				}
   309  
   310  				originalObjJS, err := json.Marshal(nodeObj)
   311  				if err != nil {
   312  					return err
   313  				}
   314  
   315  				originalPatchedObjJS, err := strategicpatch.StrategicMergePatch(originalObjJS, patchBytes, nodeObj)
   316  				if err != nil {
   317  					return err
   318  				}
   319  
   320  				targetObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalPatchedObjJS)
   321  				if err != nil {
   322  					return err
   323  				}
   324  				return printer.PrintObj(targetObj, o.Out)
   325  			}
   326  			return printer.PrintObj(obj, o.Out)
   327  		}
   328  
   329  		mapping := info.ResourceMapping()
   330  		client, err := o.ClientForMapping(mapping)
   331  		if err != nil {
   332  			return err
   333  		}
   334  		helper := resource.
   335  			NewHelper(client, mapping).
   336  			DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
   337  			WithFieldManager(o.fieldManager).
   338  			WithFieldValidation(o.ValidationDirective)
   339  
   340  		var outputObj runtime.Object
   341  		if createdPatch {
   342  			outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes, nil)
   343  		} else {
   344  			outputObj, err = helper.Replace(namespace, name, false, obj)
   345  		}
   346  		if err != nil {
   347  			return err
   348  		}
   349  
   350  		return printer.PrintObj(outputObj, o.Out)
   351  	})
   352  }
   353  
   354  // updateTaints applies a taint option(o) to a node in cluster after computing the net effect of operation(i.e. does it result in an overwrite?), it reports back the end result in a way that user can easily interpret.
   355  func (o TaintOptions) updateTaints(obj runtime.Object) (string, error) {
   356  	node, ok := obj.(*v1.Node)
   357  	if !ok {
   358  		return "", fmt.Errorf("unexpected type %T, expected Node", obj)
   359  	}
   360  	if !o.overwrite {
   361  		if exists := checkIfTaintsAlreadyExists(node.Spec.Taints, o.taintsToAdd); len(exists) != 0 {
   362  			return "", fmt.Errorf("node %s already has %v taint(s) with same effect(s) and --overwrite is false", node.Name, exists)
   363  		}
   364  	}
   365  	operation, newTaints, err := reorganizeTaints(node, o.overwrite, o.taintsToAdd, o.taintsToRemove)
   366  	if err != nil {
   367  		return "", err
   368  	}
   369  	node.Spec.Taints = newTaints
   370  	return operation, nil
   371  }
   372  

View as plain text