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 .
* /
package cmd
import (
"fmt"
"io"
"strings"
"encoding/json"
2016-08-02 12:12:39 +00:00
2016-03-31 03:42:57 +00:00
"github.com/golang/glog"
"github.com/spf13/cobra"
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-11 14:09:48 +00:00
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
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"
2017-01-13 04:18:34 +00:00
"k8s.io/kubernetes/pkg/api/v1"
2016-06-23 14:49:31 +00:00
"k8s.io/kubernetes/pkg/kubectl"
2016-10-07 22:24:42 +00:00
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
2016-03-31 03:42:57 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
2017-01-25 01:00:32 +00:00
"k8s.io/kubernetes/pkg/util/i18n"
2016-08-29 22:00:02 +00:00
utiltaints "k8s.io/kubernetes/pkg/util/taints"
2016-03-31 03:42:57 +00:00
)
// TaintOptions have the data required to perform the taint operation
type TaintOptions struct {
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
2016-10-13 00:18:39 +00:00
f cmdutil . Factory
2016-08-12 08:46:40 +00:00
out io . Writer
cmd * cobra . Command
2016-03-31 03:42:57 +00:00
}
2016-05-20 17:49:56 +00:00
var (
2017-03-15 03:49:10 +00:00
taint_long = 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-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-03-15 03:49:10 +00:00
taint_example = 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-03-15 03:49:10 +00:00
kubectl taint nodes foo dedicated - ` ) )
2016-03-31 03:42:57 +00:00
)
2016-10-13 00:18:39 +00:00
func NewCmdTaint ( f cmdutil . Factory , out io . Writer ) * cobra . Command {
2016-03-31 03:42:57 +00:00
options := & TaintOptions { }
2016-08-22 02:46:50 +00:00
validArgs := [ ] string { "node" }
argAliases := kubectl . ResourceAliases ( validArgs )
2016-03-31 03:42:57 +00:00
cmd := & cobra . Command {
Use : "taint NODE NAME KEY_1=VAL_1:TAINT_EFFECT_1 ... KEY_N=VAL_N:TAINT_EFFECT_N" ,
2017-01-25 01:00:32 +00:00
Short : i18n . T ( "Update the taints on one or more nodes" ) ,
2016-03-31 03:42:57 +00:00
Long : fmt . Sprintf ( taint_long , validation . DNS1123SubdomainMaxLength , validation . LabelValueMaxLength ) ,
Example : taint_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
if err := options . Complete ( f , out , cmd , args ) ; err != nil {
cmdutil . CheckErr ( err )
}
if err := options . Validate ( args ) ; err != nil {
cmdutil . CheckErr ( cmdutil . UsageError ( cmd , err . Error ( ) ) )
}
if err := options . RunTaint ( ) ; err != nil {
cmdutil . CheckErr ( err )
}
} ,
2016-08-22 02:46:50 +00:00
ValidArgs : validArgs ,
ArgAliases : argAliases ,
2016-03-31 03:42:57 +00:00
}
cmdutil . AddValidateFlags ( cmd )
cmdutil . AddPrinterFlags ( cmd )
cmdutil . AddInclude3rdPartyFlags ( cmd )
2016-12-05 02:58:56 +00:00
cmd . Flags ( ) . StringVarP ( & options . selector , "selector" , "l" , "" , "Selector (label query) to filter on, supports '=', '==', and '!='." )
2016-03-31 03:42:57 +00:00
cmd . Flags ( ) . BoolVar ( & options . overwrite , "overwrite" , false , "If true, allow taints to be overwritten, otherwise reject taint updates that overwrite existing taints." )
cmd . Flags ( ) . BoolVar ( & options . all , "all" , false , "select all nodes in the cluster" )
return cmd
}
// reorganizeTaints returns the updated set of taints, taking into account old taints that were not updated,
// old taints that were updated, old taints that were deleted, and new taints.
2017-02-20 16:43:05 +00:00
func reorganizeTaints ( obj runtime . Object , overwrite bool , taintsToAdd [ ] v1 . Taint , taintsToRemove [ ] v1 . Taint ) ( [ ] v1 . Taint , error ) {
node , ok := obj . ( * v1 . Node )
if ! ok {
return nil , fmt . Errorf ( "unexpected type %T, expected Node" , obj )
2016-03-31 03:42:57 +00:00
}
2017-02-20 16:43:05 +00:00
newTaints := append ( [ ] v1 . Taint { } , taintsToAdd ... )
oldTaints := node . Spec . Taints
2016-03-31 03:42:57 +00:00
// add taints that already existing but not updated to newTaints
for _ , oldTaint := range oldTaints {
existsInNew := false
for _ , taint := range newTaints {
2017-02-06 12:59:50 +00:00
if taint . MatchTaint ( & oldTaint ) {
2016-03-31 03:42:57 +00:00
existsInNew = true
break
}
}
if ! existsInNew {
newTaints = append ( newTaints , oldTaint )
}
}
allErrs := [ ] error { }
2016-08-12 08:46:40 +00:00
for _ , taintToRemove := range taintsToRemove {
2017-01-14 08:18:14 +00:00
removed := false
if len ( taintToRemove . Effect ) > 0 {
newTaints , removed = v1 . DeleteTaint ( newTaints , & taintToRemove )
} else {
newTaints , removed = v1 . DeleteTaintsByKey ( newTaints , taintToRemove . Key )
}
if ! removed {
allErrs = append ( allErrs , fmt . Errorf ( "taint %q not found" , taintToRemove . ToString ( ) ) )
2016-03-31 03:42:57 +00:00
}
}
return newTaints , utilerrors . NewAggregate ( allErrs )
}
2017-01-13 04:18:34 +00:00
func parseTaints ( spec [ ] string ) ( [ ] v1 . Taint , [ ] v1 . Taint , error ) {
var taints , taintsToRemove [ ] v1 . Taint
uniqueTaints := map [ v1 . TaintEffect ] sets . String { }
2016-08-12 08:46:40 +00:00
2016-03-31 03:42:57 +00:00
for _ , taintSpec := range spec {
if strings . Index ( taintSpec , "=" ) != - 1 && strings . Index ( taintSpec , ":" ) != - 1 {
2016-08-29 22:00:02 +00:00
newTaint , err := utiltaints . ParseTaint ( taintSpec )
if err != nil {
return nil , nil , err
2016-03-31 03:42:57 +00:00
}
2016-08-12 08:46:40 +00:00
// validate if taint is unique by <key, effect>
if len ( uniqueTaints [ newTaint . Effect ] ) > 0 && uniqueTaints [ newTaint . Effect ] . Has ( newTaint . Key ) {
return nil , nil , fmt . Errorf ( "duplicated taints with the same key and effect: %v" , newTaint )
}
// add taint to existingTaints for uniqueness check
if len ( uniqueTaints [ newTaint . Effect ] ) == 0 {
uniqueTaints [ newTaint . Effect ] = sets . String { }
}
uniqueTaints [ newTaint . Effect ] . Insert ( newTaint . Key )
2016-03-31 03:42:57 +00:00
taints = append ( taints , newTaint )
} else if strings . HasSuffix ( taintSpec , "-" ) {
2016-08-12 08:46:40 +00:00
taintKey := taintSpec [ : len ( taintSpec ) - 1 ]
2017-01-13 04:18:34 +00:00
var effect v1 . TaintEffect
2016-08-12 08:46:40 +00:00
if strings . Index ( taintKey , ":" ) != - 1 {
parts := strings . Split ( taintKey , ":" )
taintKey = parts [ 0 ]
2017-01-13 04:18:34 +00:00
effect = v1 . TaintEffect ( parts [ 1 ] )
2016-08-12 08:46:40 +00:00
}
2017-01-13 04:18:34 +00:00
taintsToRemove = append ( taintsToRemove , v1 . Taint { Key : taintKey , Effect : effect } )
2016-03-31 03:42:57 +00:00
} else {
return nil , nil , fmt . Errorf ( "unknown taint spec: %v" , taintSpec )
}
}
2016-08-12 08:46:40 +00:00
return taints , taintsToRemove , nil
2016-03-31 03:42:57 +00:00
}
// Complete adapts from the command line args and factory to the data required.
2016-10-13 00:18:39 +00:00
func ( o * TaintOptions ) Complete ( f cmdutil . Factory , out io . Writer , cmd * cobra . Command , args [ ] string ) ( err error ) {
2016-03-31 03:42:57 +00:00
namespace , _ , err := f . DefaultNamespace ( )
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 )
}
}
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" )
}
2016-08-12 08:46:40 +00:00
if o . taintsToAdd , o . taintsToRemove , err = parseTaints ( taintArgs ) ; err != nil {
2016-03-31 03:42:57 +00:00
return cmdutil . UsageError ( cmd , err . Error ( ) )
}
2016-09-16 19:50:34 +00:00
mapper , typer := f . Object ( )
2017-03-24 12:02:10 +00:00
o . builder = resource . NewBuilder ( mapper , f . CategoryExpander ( ) , typer , resource . ClientMapperFunc ( f . ClientForMapping ) , f . Decoder ( true ) ) .
2016-03-31 03:42:57 +00:00
ContinueOnError ( ) .
NamespaceParam ( namespace ) . DefaultNamespace ( )
if o . all {
o . builder = o . builder . SelectAllParam ( o . all ) . ResourceTypes ( "node" )
} else {
if len ( o . resources ) < 2 {
return fmt . Errorf ( "at least one resource name must be specified since 'all' parameter is not set" )
}
o . builder = o . builder . ResourceNames ( "node" , o . resources [ 1 : ] ... )
}
o . builder = o . builder . SelectorParam ( o . selector ) .
Flatten ( ) .
Latest ( )
o . f = f
o . out = out
o . cmd = cmd
return nil
}
// Validate checks to the TaintOptions to see if there is sufficient information run the command.
func ( o TaintOptions ) Validate ( args [ ] string ) error {
resourceType := strings . ToLower ( o . resources [ 0 ] )
2016-08-22 02:46:50 +00:00
validResources , isValidResource := append ( kubectl . ResourceAliases ( [ ] string { "node" } ) , "node" ) , false
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
}
return nil
}
// 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
}
obj , err := info . Mapping . ConvertToVersion ( info . Object , info . Mapping . GroupVersionKind . GroupVersion ( ) )
if err != nil {
return err
}
name , namespace := info . Name , info . Namespace
oldData , err := json . Marshal ( obj )
if err != nil {
return err
}
if err := o . updateTaints ( obj ) ; err != nil {
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 {
glog . V ( 2 ) . Infof ( "couldn't compute patch: %v" , err )
}
mapping := info . ResourceMapping ( )
client , err := o . f . ClientForMapping ( mapping )
if err != nil {
return err
}
helper := resource . NewHelper ( client , mapping )
var outputObj runtime . Object
if createdPatch {
2017-01-16 20:13:59 +00:00
outputObj , err = helper . Patch ( namespace , name , types . StrategicMergePatchType , patchBytes )
2016-03-31 03:42:57 +00:00
} else {
outputObj , err = helper . Replace ( namespace , name , false , obj )
}
if err != nil {
return err
}
2016-09-16 19:50:34 +00:00
mapper , _ := o . f . Object ( )
2016-03-31 03:42:57 +00:00
outputFormat := cmdutil . GetFlagString ( o . cmd , "output" )
if outputFormat != "" {
return o . f . PrintObject ( o . cmd , mapper , outputObj , o . out )
}
2016-08-23 18:11:39 +00:00
cmdutil . PrintSuccess ( mapper , false , o . out , info . Mapping . Resource , info . Name , false , "tainted" )
2016-03-31 03:42:57 +00:00
return nil
} )
}
// validateNoTaintOverwrites validates that when overwrite is false, to-be-updated taints don't exist in the node taint list (yet)
2017-02-20 16:43:05 +00:00
func validateNoTaintOverwrites ( obj runtime . Object , taints [ ] v1 . Taint ) 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
}
allErrs := [ ] error { }
2017-02-20 16:43:05 +00:00
oldTaints := node . Spec . Taints
2016-03-31 03:42:57 +00:00
for _ , taint := range taints {
for _ , oldTaint := range oldTaints {
2016-08-12 08:46:40 +00:00
if taint . Key == oldTaint . Key && taint . Effect == oldTaint . Effect {
2017-02-20 16:43:05 +00:00
allErrs = append ( allErrs , fmt . Errorf ( "Node '%s' already has a taint with key (%s) and effect (%v), and --overwrite is false" , node . Name , taint . Key , taint . Effect ) )
2016-03-31 03:42:57 +00:00
break
}
}
}
return utilerrors . NewAggregate ( allErrs )
}
// updateTaints updates taints of obj
func ( o TaintOptions ) updateTaints ( obj runtime . Object ) error {
if ! o . overwrite {
2017-02-20 16:43:05 +00:00
if err := validateNoTaintOverwrites ( obj , o . taintsToAdd ) ; err != nil {
2016-03-31 03:42:57 +00:00
return err
}
}
2017-02-20 16:43:05 +00:00
newTaints , err := reorganizeTaints ( obj , o . overwrite , o . taintsToAdd , o . taintsToRemove )
2016-03-31 03:42:57 +00:00
if err != nil {
return err
}
2017-02-20 16:43:05 +00:00
node , ok := obj . ( * v1 . Node )
if ! ok {
return fmt . Errorf ( "unexpected type %T, expected Node" , obj )
2016-03-31 03:42:57 +00:00
}
2017-02-20 16:43:05 +00:00
node . Spec . Taints = newTaints
2016-03-31 03:42:57 +00:00
return nil
}