2016-03-31 03:42:57 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-03-31 03:42:57 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2018-10-05 11:06:12 +00:00
package taint
2016-03-31 03:42:57 +00:00
import (
2018-05-08 20:05:48 +00:00
"encoding/json"
2016-03-31 03:42:57 +00:00
"fmt"
"strings"
"github.com/spf13/cobra"
2018-11-09 18:49:10 +00:00
"k8s.io/klog"
2018-04-27 18:25:56 +00:00
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2018-04-24 04:40:35 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/runtime"
2017-01-16 20:13:59 +00:00
"k8s.io/apimachinery/pkg/types"
2017-01-24 14:35:22 +00:00
"k8s.io/apimachinery/pkg/util/strategicpatch"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/validation"
2018-08-21 10:46:39 +00:00
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
2016-03-31 03:42:57 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2018-05-16 18:19:21 +00:00
"k8s.io/kubernetes/pkg/kubectl/scheme"
2017-07-07 04:04:11 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
2018-10-10 18:29:30 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/templates"
2017-04-17 13:54:44 +00:00
)
2016-03-31 03:42:57 +00:00
// TaintOptions have the data required to perform the taint operation
type TaintOptions struct {
2018-05-02 19:15:47 +00:00
PrintFlags * genericclioptions . PrintFlags
ToPrinter func ( string ) ( printers . ResourcePrinter , error )
2018-04-24 04:40:35 +00:00
2016-08-12 08:46:40 +00:00
resources [ ] string
2017-01-13 04:18:34 +00:00
taintsToAdd [ ] v1 . Taint
taintsToRemove [ ] v1 . Taint
2016-08-12 08:46:40 +00:00
builder * resource . Builder
selector string
overwrite bool
all bool
2018-04-24 04:40:35 +00:00
ClientForMapping func ( * meta . RESTMapping ) ( resource . RESTClient , error )
genericclioptions . IOStreams
2016-03-31 03:42:57 +00:00
}
2016-05-20 17:49:56 +00:00
var (
2017-02-16 03:47:00 +00:00
taintLong = templates . LongDesc ( i18n . T ( `
2016-05-20 17:49:56 +00:00
Update the taints on one or more nodes .
2016-03-31 03:42:57 +00:00
2016-10-07 22:24:42 +00:00
* A taint consists of a key , value , and effect . As an argument here , it is expressed as key = value : effect .
* The key must begin with a letter or number , and may contain letters , numbers , hyphens , dots , and underscores , up to % [ 1 ] d characters .
2017-11-07 22:06:54 +00:00
* Optionally , the key can begin with a DNS subdomain prefix and a single '/' , like example . com / my - app
2017-01-04 03:14:25 +00:00
* The value must begin with a letter or number , and may contain letters , numbers , hyphens , dots , and underscores , up to % [ 2 ] d characters .
2017-03-09 02:17:33 +00:00
* The effect must be NoSchedule , PreferNoSchedule or NoExecute .
2017-03-15 03:49:10 +00:00
* Currently taint can only apply to node . ` ) )
2016-10-07 22:24:42 +00:00
2017-02-16 03:47:00 +00:00
taintExample = templates . Examples ( i18n . T ( `
2016-05-20 17:49:56 +00:00
# Update node ' foo ' with a taint with key ' dedicated ' and value ' special - user ' and effect ' NoSchedule ' .
2016-08-12 08:46:40 +00:00
# If a taint with that key and effect already exists , its value is replaced as specified .
2016-05-20 17:49:56 +00:00
kubectl taint nodes foo dedicated = special - user : NoSchedule
2016-08-12 08:46:40 +00:00
# Remove from node ' foo ' the taint with key ' dedicated ' and effect ' NoSchedule ' if one exists .
kubectl taint nodes foo dedicated : NoSchedule -
# Remove from node ' foo ' all the taints with key ' dedicated '
2017-04-20 21:27:09 +00:00
kubectl taint nodes foo dedicated -
# Add a taint with key ' dedicated ' on nodes having label mylabel = X
kubectl taint node - l myLabel = X dedicated = foo : PreferNoSchedule ` ) )
2016-03-31 03:42:57 +00:00
)
2018-04-24 04:40:35 +00:00
func NewCmdTaint ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
options := & TaintOptions {
2018-05-02 19:15:47 +00:00
PrintFlags : genericclioptions . NewPrintFlags ( "tainted" ) . WithTypeSetter ( scheme . Scheme ) ,
2018-04-24 04:40:35 +00:00
IOStreams : streams ,
}
2016-03-31 03:42:57 +00:00
2016-08-22 02:46:50 +00:00
validArgs := [ ] string { "node" }
2016-03-31 03:42:57 +00:00
cmd := & cobra . Command {
2018-10-05 19:59:38 +00:00
Use : "taint NODE NAME KEY_1=VAL_1:TAINT_EFFECT_1 ... KEY_N=VAL_N:TAINT_EFFECT_N" ,
2017-10-11 06:26:02 +00:00
DisableFlagsInUseLine : true ,
2018-10-05 19:59:38 +00:00
Short : i18n . T ( "Update the taints on one or more nodes" ) ,
Long : fmt . Sprintf ( taintLong , validation . DNS1123SubdomainMaxLength , validation . LabelValueMaxLength ) ,
Example : taintExample ,
2016-03-31 03:42:57 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2018-11-09 08:22:00 +00:00
cmdutil . CheckErr ( options . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( options . Validate ( ) )
cmdutil . CheckErr ( options . RunTaint ( ) )
2016-03-31 03:42:57 +00:00
} ,
2018-05-07 19:25:40 +00:00
ValidArgs : validArgs ,
2016-03-31 03:42:57 +00:00
}
2018-04-24 04:40:35 +00:00
options . PrintFlags . AddFlags ( cmd )
cmdutil . AddValidateFlags ( cmd )
2018-02-24 08:37:19 +00:00
cmd . Flags ( ) . StringVarP ( & options . selector , "selector" , "l" , options . selector , "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)" )
cmd . Flags ( ) . BoolVar ( & options . overwrite , "overwrite" , options . overwrite , "If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints." )
cmd . Flags ( ) . BoolVar ( & options . all , "all" , options . all , "Select all nodes in the cluster" )
2016-03-31 03:42:57 +00:00
return cmd
}
// Complete adapts from the command line args and factory to the data required.
2018-04-24 04:40:35 +00:00
func ( o * TaintOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) ( err error ) {
2018-05-24 13:33:36 +00:00
namespace , _ , err := f . ToRawKubeConfigLoader ( ) . Namespace ( )
2016-03-31 03:42:57 +00:00
if err != nil {
return err
}
// retrieves resource and taint args from args
// also checks args to verify that all resources are specified before taints
taintArgs := [ ] string { }
metTaintArg := false
for _ , s := range args {
isTaint := strings . Contains ( s , "=" ) || strings . HasSuffix ( s , "-" )
switch {
case ! metTaintArg && isTaint :
metTaintArg = true
fallthrough
case metTaintArg && isTaint :
taintArgs = append ( taintArgs , s )
case ! metTaintArg && ! isTaint :
o . resources = append ( o . resources , s )
case metTaintArg && ! isTaint :
return fmt . Errorf ( "all resources must be specified before taint changes: %s" , s )
}
}
2018-05-02 19:15:47 +00:00
o . ToPrinter = func ( operation string ) ( printers . ResourcePrinter , error ) {
2018-04-24 04:40:35 +00:00
o . PrintFlags . NamePrintFlags . Operation = operation
2018-05-02 19:15:47 +00:00
return o . PrintFlags . ToPrinter ( )
2018-04-24 04:40:35 +00:00
}
2016-03-31 03:42:57 +00:00
if len ( o . resources ) < 1 {
return fmt . Errorf ( "one or more resources must be specified as <resource> <name>" )
}
if len ( taintArgs ) < 1 {
return fmt . Errorf ( "at least one taint update is required" )
}
2018-10-04 22:56:43 +00:00
if o . taintsToAdd , o . taintsToRemove , err = parseTaints ( taintArgs ) ; err != nil {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , err . Error ( ) )
2016-03-31 03:42:57 +00:00
}
2017-08-02 20:23:07 +00:00
o . builder = f . NewBuilder ( ) .
2018-08-03 11:51:44 +00:00
WithScheme ( scheme . Scheme , scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... ) .
2016-03-31 03:42:57 +00:00
ContinueOnError ( ) .
NamespaceParam ( namespace ) . DefaultNamespace ( )
2017-04-20 21:27:09 +00:00
if o . selector != "" {
2017-08-04 06:54:17 +00:00
o . builder = o . builder . LabelSelectorParam ( o . selector ) . ResourceTypes ( "node" )
2017-04-20 21:27:09 +00:00
}
2016-03-31 03:42:57 +00:00
if o . all {
2017-04-20 21:27:09 +00:00
o . builder = o . builder . SelectAllParam ( o . all ) . ResourceTypes ( "node" ) . Flatten ( ) . Latest ( )
}
if ! o . all && o . selector == "" && len ( o . resources ) >= 2 {
2016-03-31 03:42:57 +00:00
o . builder = o . builder . ResourceNames ( "node" , o . resources [ 1 : ] ... )
}
2017-08-04 06:54:17 +00:00
o . builder = o . builder . LabelSelectorParam ( o . selector ) .
2016-03-31 03:42:57 +00:00
Flatten ( ) .
Latest ( )
2018-04-24 04:40:35 +00:00
o . ClientForMapping = f . ClientForMapping
2017-04-20 21:27:09 +00:00
return nil
}
2016-03-31 03:42:57 +00:00
2017-04-20 21:27:09 +00:00
// validateFlags checks for the validation of flags for kubectl taints.
func ( o TaintOptions ) validateFlags ( ) error {
// Cannot have a non-empty selector and all flag set. They are mutually exclusive.
if o . all && o . selector != "" {
return fmt . Errorf ( "setting 'all' parameter with a non empty selector is prohibited." )
}
// If both selector and all are not set.
if ! o . all && o . selector == "" {
if len ( o . resources ) < 2 {
return fmt . Errorf ( "at least one resource name must be specified since 'all' parameter is not set" )
} else {
return nil
}
}
2016-03-31 03:42:57 +00:00
return nil
}
// Validate checks to the TaintOptions to see if there is sufficient information run the command.
2017-03-17 13:55:46 +00:00
func ( o TaintOptions ) Validate ( ) error {
2016-03-31 03:42:57 +00:00
resourceType := strings . ToLower ( o . resources [ 0 ] )
2018-05-07 19:25:40 +00:00
validResources , isValidResource := [ ] string { "node" , "nodes" } , false
2016-08-22 02:46:50 +00:00
for _ , validResource := range validResources {
if resourceType == validResource {
isValidResource = true
break
}
}
if ! isValidResource {
return fmt . Errorf ( "invalid resource type %s, only %q are supported" , o . resources [ 0 ] , validResources )
2016-03-31 03:42:57 +00:00
}
// check the format of taint args and checks removed taints aren't in the new taints list
2016-08-12 08:46:40 +00:00
var conflictTaints [ ] string
for _ , taintAdd := range o . taintsToAdd {
for _ , taintRemove := range o . taintsToRemove {
if taintAdd . Key != taintRemove . Key {
continue
}
if len ( taintRemove . Effect ) == 0 || taintAdd . Effect == taintRemove . Effect {
conflictTaint := fmt . Sprintf ( "{\"%s\":\"%s\"}" , taintRemove . Key , taintRemove . Effect )
conflictTaints = append ( conflictTaints , conflictTaint )
}
2016-03-31 03:42:57 +00:00
}
}
2016-08-12 08:46:40 +00:00
if len ( conflictTaints ) > 0 {
return fmt . Errorf ( "can not both modify and remove the following taint(s) in the same command: %s" , strings . Join ( conflictTaints , ", " ) )
2016-03-31 03:42:57 +00:00
}
2017-04-20 21:27:09 +00:00
return o . validateFlags ( )
2016-03-31 03:42:57 +00:00
}
// RunTaint does the work
func ( o TaintOptions ) RunTaint ( ) error {
r := o . builder . Do ( )
if err := r . Err ( ) ; err != nil {
return err
}
return r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
2018-08-03 11:51:44 +00:00
obj := info . Object
2016-03-31 03:42:57 +00:00
name , namespace := info . Name , info . Namespace
oldData , err := json . Marshal ( obj )
if err != nil {
return err
}
2017-04-17 13:54:44 +00:00
operation , err := o . updateTaints ( obj )
if err != nil {
2016-03-31 03:42:57 +00:00
return err
}
newData , err := json . Marshal ( obj )
if err != nil {
return err
}
2016-11-23 05:06:36 +00:00
patchBytes , err := strategicpatch . CreateTwoWayMergePatch ( oldData , newData , obj )
2016-03-31 03:42:57 +00:00
createdPatch := err == nil
if err != nil {
2018-11-09 18:49:10 +00:00
klog . V ( 2 ) . Infof ( "couldn't compute patch: %v" , err )
2016-03-31 03:42:57 +00:00
}
mapping := info . ResourceMapping ( )
2018-04-24 04:40:35 +00:00
client , err := o . ClientForMapping ( mapping )
2016-03-31 03:42:57 +00:00
if err != nil {
return err
}
helper := resource . NewHelper ( client , mapping )
var outputObj runtime . Object
if createdPatch {
2018-08-30 13:33:34 +00:00
outputObj , err = helper . Patch ( namespace , name , types . StrategicMergePatchType , patchBytes , nil )
2016-03-31 03:42:57 +00:00
} else {
outputObj , err = helper . Replace ( namespace , name , false , obj )
}
if err != nil {
return err
}
2018-04-24 04:40:35 +00:00
printer , err := o . ToPrinter ( operation )
if err != nil {
return err
2016-03-31 03:42:57 +00:00
}
2018-04-24 04:40:35 +00:00
return printer . PrintObj ( outputObj , o . Out )
2016-03-31 03:42:57 +00:00
} )
}
2017-04-17 13:54:44 +00:00
// 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.
func ( o TaintOptions ) updateTaints ( obj runtime . Object ) ( string , error ) {
node , ok := obj . ( * v1 . Node )
if ! ok {
return "" , fmt . Errorf ( "unexpected type %T, expected Node" , obj )
}
2016-03-31 03:42:57 +00:00
if ! o . overwrite {
2018-10-04 22:56:43 +00:00
if exists := checkIfTaintsAlreadyExists ( node . Spec . Taints , o . taintsToAdd ) ; len ( exists ) != 0 {
2017-07-06 13:13:13 +00:00
return "" , fmt . Errorf ( "Node %s already has %v taint(s) with same effect(s) and --overwrite is false" , node . Name , exists )
2016-03-31 03:42:57 +00:00
}
}
2018-10-04 22:56:43 +00:00
operation , newTaints , err := reorganizeTaints ( node , o . overwrite , o . taintsToAdd , o . taintsToRemove )
2016-03-31 03:42:57 +00:00
if err != nil {
2017-04-17 13:54:44 +00:00
return "" , err
2016-03-31 03:42:57 +00:00
}
2017-02-20 16:43:05 +00:00
node . Spec . Taints = newTaints
2017-04-17 13:54:44 +00:00
return operation , nil
2016-03-31 03:42:57 +00:00
}