Change:
Is this a BUG REPORT or FEATURE REQUEST? (choose one): BUG <!-- If this is a BUG REPORT, please: Fill in as much of the template below as you can. If you leave out information, we can't help you as well. If this is a FEATURE REQUEST, please: Describe in detail the feature/behavior/change you'd like to see. In both cases, be ready for followup questions, and please respond in a timely manner. If we can't reproduce a bug or think a feature already exists, we might close your issue. If we're wrong, PLEASE feel free to reopen it and explain why. --Kubernetes version (use ): Client Version: version.Info{Major:"1", Minor:"6+", GitVersion:"v1.6.0-alpha.3.1185+-dirty Server Version: version.Info{Major:"1", Minor:"6+", GitVersion:"v1.6.0-alpha.3.1185+-dirty" What happened: Kubectl is always reporting as node <XYZis tainted irrespective of operation when used with local cluster. taint nodes XYZ dedicated- node "XYZ" tainted What you expected to happen: It should have shown exact operation like tainted, untainted modified and overwritten but because of it is always returning tainted. How to reproduce it (as minimally and precisely as possible): taint nodes XYZ dedicated-
cc/
Regarding Yes I agree we should provide more clearer output/feedback. But providing something that makes output more confusing than existing is something would be even more concerning. So as I stated , it would be good to discuss/decide how the output/feedback should like for various operations for one and multi nodes scenarios.
I think having 1 return values is an exponential improvement on what we have now, even if they aren't perfect for all cases. Rather then a disproportionately long debate over a relatively simple improvement.... Could you guys suggest something simple that you're confident it will be easy to agree on, and we can implement it as a second pass ? we are happy to implement anything with n 1 return values :).
I think we'd be happy to consider any reasonable PR. I'm not sure why was closed; I didn't look at it though so I don't know if it solves the problem.
(hit close accidentally)
, I am not sure why was closed. But I raised a PR around 15 days ago() to address this.

Discussion:
utiltaints "k8s.io/kubernetes/pkg/util/taints" ) <ins> const ( MODIFIED  = "modified" TAINTED   = "tainted" UNTAINTED = "untainted" ) </ins> // TaintOptions have the data required to perform the taint operation type TaintOptions struct { resources      []string

// 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. <del> 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) } </del> <ins> func reorganizeTaints(node *v1.Node, overwrite bool, taintsToAdd []v1.Taint, taintsToRemove []v1.Taint) (string, []v1.Taint, error) { </ins> newTaints := append([]v1.Taint{}, taintsToAdd...) oldTaints := node.Spec.Taints // add taints that already existing but not updated to newTaints <del> for _, oldTaint := range oldTaints { existsInNew := false for _, taint := range newTaints { if taint.MatchTaint(&oldTaint) { existsInNew = true break } } if !existsInNew { newTaints = append(newTaints, oldTaint) } </del> <ins> added := addTaints(oldTaints, &newTaints) allErrs, deleted := deleteTaints(taintsToRemove, &newTaints) if (added && deleted) || overwrite { return MODIFIED, newTaints, utilerrors.NewAggregate(allErrs) } else if added { return TAINTED, newTaints, utilerrors.NewAggregate(allErrs) </ins> } <ins> return UNTAINTED, newTaints, utilerrors.NewAggregate(allErrs) } // deleteTaints deletes the given taints from the node's taintlist. func deleteTaints(taintsToRemove []v1.Taint, newTaints *[]v1.Taint) ([]error, bool) { </ins> allErrs := []error{} <ins> var removed bool </ins> for _, taintToRemove := range taintsToRemove { <del> removed := false </del> <ins> removed = false </ins> if len(taintToRemove.Effect) > 0 { <del> newTaints, removed = v1helper.DeleteTaint(newTaints, &taintToRemove) </del> <ins> *newTaints, removed = v1helper.DeleteTaint(*newTaints, &taintToRemove) </ins> } else { <del> newTaints, removed = v1helper.DeleteTaintsByKey(newTaints, taintToRemove.Key) </del> <ins> *newTaints, removed = v1helper.DeleteTaintsByKey(*newTaints, taintToRemove.Key) </ins> } if !removed { allErrs = append(allErrs, fmt.Errorf("taint %q not found", taintToRemove.ToString())) } } <del> return newTaints, utilerrors.NewAggregate(allErrs) </del> <ins> return allErrs, removed } // addTaints adds the newTaints list to existing ones and updates the newTaints List. // TODO: This needs a rewrite to take only the new values instead of appended newTaints list to be consistent. func addTaints(oldTaints []v1.Taint, newTaints *[]v1.Taint) bool { for _, oldTaint := range oldTaints { existsInNew := false for _, taint := range *newTaints { if taint.MatchTaint(&oldTaint) { existsInNew = true break } } if !existsInNew { *newTaints = append(*newTaints, oldTaint) } } return len(oldTaints) != len(*newTaints) </ins> } func parseTaints(spec []string) ([]v1.Taint, []v1.Taint, error) {

if err != nil { return err } <del> if err := o.updateTaints(obj); err != nil { </del> <ins> operation, err := o.updateTaints(obj) if err != nil { </ins> return err } newData, err := json.Marshal(obj)

return o.f.PrintObject(o.cmd, mapper, outputObj, o.out) } <del> cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, false, "tainted") </del> <ins> cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, false, operation) </ins> return nil }) } // validateNoTaintOverwrites validates that when overwrite is false, to-be-updated taints don't exist in the node taint list (yet) <del> 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) } </del> <ins> func validateNoTaintOverwrites(node *v1.Node, taints []v1.Taint) error { </ins> allErrs := []error{} oldTaints := node.Spec.Taints for _, taint := range taints {

return utilerrors.NewAggregate(allErrs) } <del> // updateTaints updates taints of obj func (o TaintOptions) updateTaints(obj runtime.Object) error { </del> <ins> // 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) } </ins> if !o.overwrite { <del> if err := validateNoTaintOverwrites(obj, o.taintsToAdd); err != nil { return err </del> <ins> if err := validateNoTaintOverwrites(node, o.taintsToAdd); err != nil { return "", err </ins> } } <del> newTaints, err := reorganizeTaints(obj, o.overwrite, o.taintsToAdd, o.taintsToRemove) </del> <ins> operation, newTaints, err := reorganizeTaints(node, o.overwrite, o.taintsToAdd, o.taintsToRemove) </ins> if err != nil { <del> return err } node, ok := obj.(*v1.Node) if !ok { return fmt.Errorf("unexpected type %T, expected Node", obj) </del> <ins> return "", err </ins> } node.Spec.Taints = newTaints <del> return nil </del> <ins> return operation, nil </ins> }