2015-02-07 00:33:42 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-02-07 00:33:42 +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"
2015-11-30 16:47:08 +00:00
"reflect"
2015-02-07 00:33:42 +00:00
"strings"
2017-01-22 18:54:46 +00:00
jsonpatch "github.com/evanphx/json-patch"
2016-01-25 16:57:34 +00:00
"github.com/golang/glog"
2015-08-05 22:05:17 +00:00
"github.com/spf13/cobra"
2017-01-22 18:54:46 +00:00
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2017-01-17 03:38:19 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-05-21 19:27:11 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
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"
2017-01-22 18:54:46 +00:00
"k8s.io/apimachinery/pkg/util/json"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/validation"
2017-01-22 18:54:46 +00:00
2016-10-07 22:24:42 +00:00
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
2015-08-05 22:03:47 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2018-04-19 14:41:17 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
2018-05-02 19:15:47 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
2018-05-10 12:20:27 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
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"
2015-02-07 00:33:42 +00:00
)
2016-10-06 07:17:40 +00:00
// LabelOptions have the data required to perform the label operation
type LabelOptions struct {
// Filename options
resource . FilenameOptions
2018-04-19 14:41:17 +00:00
RecordFlags * genericclioptions . RecordFlags
2016-10-06 07:17:40 +00:00
2018-05-02 19:15:47 +00:00
PrintFlags * genericclioptions . PrintFlags
ToPrinter func ( string ) ( printers . ResourcePrinter , error )
2018-04-19 00:02:37 +00:00
2016-10-06 07:17:40 +00:00
// Common user flags
overwrite bool
2017-09-05 19:28:32 +00:00
list bool
2016-10-06 07:17:40 +00:00
local bool
dryrun bool
all bool
resourceVersion string
selector string
2018-03-02 16:33:02 +00:00
fieldSelector string
2016-10-06 07:17:40 +00:00
outputFormat string
// results of arg parsing
resources [ ] string
newLabels map [ string ] string
removeLabels [ ] string
2018-04-19 14:41:17 +00:00
Recorder genericclioptions . Recorder
2018-04-26 13:30:21 +00:00
namespace string
enforceNamespace bool
includeUninitialized bool
builder * resource . Builder
unstructuredClientForMapping func ( mapping * meta . RESTMapping ) ( resource . RESTClient , error )
2016-10-06 07:17:40 +00:00
// Common shared fields
2018-04-19 00:02:37 +00:00
genericclioptions . IOStreams
2016-10-06 07:17:40 +00:00
}
2016-05-20 17:49:56 +00:00
var (
2017-02-16 03:47:00 +00:00
labelLong = templates . LongDesc ( i18n . T ( `
2016-05-20 17:49:56 +00:00
Update the labels on a resource .
2015-02-07 00:33:42 +00:00
2017-11-07 22:06:54 +00:00
* A label key and value must begin with a letter or number , and may contain letters , numbers , hyphens , dots , and underscores , up to % [ 1 ] d characters each .
* Optionally , the key can begin with a DNS subdomain prefix and a single '/' , like example . com / my - app
2016-10-07 22:24:42 +00:00
* If -- overwrite is true , then existing labels can be overwritten , otherwise attempting to overwrite a label will result in an error .
2017-03-15 03:49:10 +00:00
* If -- resource - version is specified , then updates will use this resource version , otherwise the existing resource - version will be used . ` ) )
2016-10-07 22:24:42 +00:00
2017-02-16 03:47:00 +00:00
labelExample = templates . Examples ( i18n . T ( `
2016-05-20 17:49:56 +00:00
# Update pod ' foo ' with the label ' unhealthy ' and the value ' true ' .
kubectl label pods foo unhealthy = true
2015-02-07 00:33:42 +00:00
2016-05-20 17:49:56 +00:00
# Update pod ' foo ' with the label ' status ' and the value ' unhealthy ' , overwriting any existing value .
kubectl label -- overwrite pods foo status = unhealthy
2015-02-07 00:33:42 +00:00
2016-05-20 17:49:56 +00:00
# Update all pods in the namespace
kubectl label pods -- all status = unhealthy
2015-03-26 01:52:12 +00:00
2016-05-20 17:49:56 +00:00
# Update a pod identified by the type and name in "pod.json"
kubectl label - f pod . json status = unhealthy
2015-08-05 03:43:48 +00:00
2016-05-20 17:49:56 +00:00
# Update pod ' foo ' only if the resource is unchanged from version 1.
kubectl label pods foo status = unhealthy -- resource - version = 1
2015-02-20 21:28:43 +00:00
2016-05-20 17:49:56 +00:00
# Update pod ' foo ' by removing a label named ' bar ' if it exists .
# Does not require the -- overwrite flag .
2017-03-15 03:49:10 +00:00
kubectl label pods foo bar - ` ) )
2015-02-20 21:28:43 +00:00
)
2018-04-19 00:02:37 +00:00
func NewLabelOptions ( ioStreams genericclioptions . IOStreams ) * LabelOptions {
2018-04-19 14:41:17 +00:00
return & LabelOptions {
RecordFlags : genericclioptions . NewRecordFlags ( ) ,
2018-04-19 00:02:37 +00:00
Recorder : genericclioptions . NoopRecorder { } ,
2018-04-19 14:41:17 +00:00
2018-05-02 19:15:47 +00:00
PrintFlags : genericclioptions . NewPrintFlags ( "labeled" ) . WithTypeSetter ( scheme . Scheme ) ,
2018-04-19 14:41:17 +00:00
2018-04-19 00:02:37 +00:00
IOStreams : ioStreams ,
2018-01-16 19:09:09 +00:00
}
2018-04-19 14:41:17 +00:00
}
2018-04-19 00:02:37 +00:00
func NewCmdLabel ( f cmdutil . Factory , ioStreams genericclioptions . IOStreams ) * cobra . Command {
o := NewLabelOptions ( ioStreams )
2018-01-16 19:09:09 +00:00
2015-02-20 21:28:43 +00:00
cmd := & cobra . Command {
2017-10-11 06:26:02 +00:00
Use : "label [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]" ,
DisableFlagsInUseLine : true ,
2017-01-25 01:00:32 +00:00
Short : i18n . T ( "Update the labels on a resource" ) ,
2017-02-16 03:47:00 +00:00
Long : fmt . Sprintf ( labelLong , validation . LabelValueMaxLength ) ,
Example : labelExample ,
2015-02-07 00:33:42 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2018-04-26 13:30:21 +00:00
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . RunLabel ( ) )
2015-02-07 00:33:42 +00:00
} ,
}
2018-04-19 14:41:17 +00:00
o . RecordFlags . AddFlags ( cmd )
2018-04-19 00:02:37 +00:00
o . PrintFlags . AddFlags ( cmd )
2018-04-19 14:41:17 +00:00
cmd . Flags ( ) . BoolVar ( & o . overwrite , "overwrite" , o . overwrite , "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels." )
cmd . Flags ( ) . BoolVar ( & o . list , "list" , o . list , "If true, display the labels for a given resource." )
cmd . Flags ( ) . BoolVar ( & o . local , "local" , o . local , "If true, label will NOT contact api-server but run locally." )
cmd . Flags ( ) . StringVarP ( & o . selector , "selector" , "l" , o . selector , "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)." )
2018-03-02 16:33:02 +00:00
cmd . Flags ( ) . StringVar ( & o . fieldSelector , "field-selector" , o . fieldSelector , "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type." )
2018-04-19 14:41:17 +00:00
cmd . Flags ( ) . BoolVar ( & o . all , "all" , o . all , "Select all resources, including uninitialized ones, in the namespace of the specified resource types" )
cmd . Flags ( ) . StringVar ( & o . resourceVersion , "resource-version" , o . resourceVersion , i18n . T ( "If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource." ) )
2016-08-17 18:28:07 +00:00
usage := "identifying the resource to update the labels"
2018-04-19 14:41:17 +00:00
cmdutil . AddFilenameOptionFlags ( cmd , & o . FilenameOptions , usage )
2016-05-11 00:26:39 +00:00
cmdutil . AddDryRunFlag ( cmd )
2017-08-11 06:21:44 +00:00
cmdutil . AddIncludeUninitializedFlag ( cmd )
2015-08-31 12:52:04 +00:00
2015-02-07 00:33:42 +00:00
return cmd
}
2016-10-06 07:17:40 +00:00
// Complete adapts from the command line args and factory to the data required.
2018-04-19 14:41:17 +00:00
func ( o * LabelOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
var err error
2018-05-21 19:27:11 +00:00
o . RecordFlags . Complete ( cmd )
2018-04-19 14:41:17 +00:00
o . Recorder , err = o . RecordFlags . ToRecorder ( )
if err != nil {
return err
}
2016-10-06 07:17:40 +00:00
o . outputFormat = cmdutil . GetFlagString ( cmd , "output" )
o . dryrun = cmdutil . GetDryRunFlag ( cmd )
2015-02-07 00:33:42 +00:00
2018-05-02 19:15:47 +00:00
o . ToPrinter = func ( operation string ) ( printers . ResourcePrinter , error ) {
2018-04-19 00:02:37 +00:00
o . PrintFlags . NamePrintFlags . Operation = operation
if o . dryrun {
o . PrintFlags . Complete ( "%s (dry run)" )
}
2018-05-02 19:15:47 +00:00
return o . PrintFlags . ToPrinter ( )
2018-04-19 00:02:37 +00:00
}
2016-10-06 07:17:40 +00:00
resources , labelArgs , err := cmdutil . GetResourcesAndPairs ( args , "label" )
2015-02-07 00:33:42 +00:00
if err != nil {
2015-07-23 22:43:48 +00:00
return err
2015-02-07 00:33:42 +00:00
}
2016-10-06 07:17:40 +00:00
o . resources = resources
o . newLabels , o . removeLabels , err = parseLabels ( labelArgs )
2017-09-05 19:28:32 +00:00
if o . list && len ( o . outputFormat ) > 0 {
2018-04-26 13:30:21 +00:00
return fmt . Errorf ( "--list and --output may not be specified together" )
}
2018-05-24 13:33:36 +00:00
o . namespace , o . enforceNamespace , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
2018-04-26 13:30:21 +00:00
if err != nil {
return err
2017-09-05 19:28:32 +00:00
}
2018-04-26 13:30:21 +00:00
o . includeUninitialized = cmdutil . ShouldIncludeUninitialized ( cmd , false )
o . builder = f . NewBuilder ( )
o . unstructuredClientForMapping = f . UnstructuredClientForMapping
2017-09-05 19:28:32 +00:00
2018-04-26 13:30:21 +00:00
return nil
2016-10-06 07:17:40 +00:00
}
2015-02-11 11:05:41 +00:00
2016-10-06 07:17:40 +00:00
// Validate checks to the LabelOptions to see if there is sufficient information run the command.
func ( o * LabelOptions ) Validate ( ) error {
2017-08-25 08:28:21 +00:00
if o . all && len ( o . selector ) > 0 {
return fmt . Errorf ( "cannot set --all and --selector at the same time" )
}
2018-03-02 16:33:02 +00:00
if o . all && len ( o . fieldSelector ) > 0 {
return fmt . Errorf ( "cannot set --all and --field-selector at the same time" )
}
2017-06-14 21:14:42 +00:00
if len ( o . resources ) < 1 && cmdutil . IsFilenameSliceEmpty ( o . FilenameOptions . Filenames ) {
2016-10-06 07:17:40 +00:00
return fmt . Errorf ( "one or more resources must be specified as <resource> <name> or <resource>/<name>" )
2015-02-07 00:33:42 +00:00
}
2017-09-05 19:28:32 +00:00
if len ( o . newLabels ) < 1 && len ( o . removeLabels ) < 1 && ! o . list {
2016-10-06 07:17:40 +00:00
return fmt . Errorf ( "at least one label update is required" )
2015-02-07 00:33:42 +00:00
}
2015-07-23 22:43:48 +00:00
return nil
2015-02-07 00:33:42 +00:00
}
2015-03-09 22:08:16 +00:00
2016-10-06 07:17:40 +00:00
// RunLabel does the work
2018-04-26 13:30:21 +00:00
func ( o * LabelOptions ) RunLabel ( ) error {
b := o . builder .
2017-11-15 04:03:06 +00:00
Unstructured ( ) .
LocalParam ( o . local ) .
2017-11-14 03:43:58 +00:00
ContinueOnError ( ) .
2018-04-26 13:30:21 +00:00
NamespaceParam ( o . namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . enforceNamespace , & o . FilenameOptions ) .
IncludeUninitialized ( o . includeUninitialized ) .
2017-11-14 03:43:58 +00:00
Flatten ( )
if ! o . local {
b = b . LabelSelectorParam ( o . selector ) .
2018-03-02 16:33:02 +00:00
FieldSelectorParam ( o . fieldSelector ) .
2016-10-06 07:17:40 +00:00
ResourceTypeOrNameArgs ( o . all , o . resources ... ) .
2016-10-05 05:45:27 +00:00
Latest ( )
}
2017-08-02 20:23:07 +00:00
2015-03-26 01:52:12 +00:00
one := false
2017-11-14 03:43:58 +00:00
r := b . Do ( ) . IntoSingleItemImplied ( & one )
2015-03-26 01:52:12 +00:00
if err := r . Err ( ) ; err != nil {
2015-03-09 22:08:16 +00:00
return err
}
2015-08-05 03:43:48 +00:00
2015-03-26 01:52:12 +00:00
// only apply resource version locking on a single resource
2016-10-06 07:17:40 +00:00
if ! one && len ( o . resourceVersion ) > 0 {
return fmt . Errorf ( "--resource-version may only be used with a single resource" )
2015-03-26 01:52:12 +00:00
}
2015-03-09 22:08:16 +00:00
2015-03-26 01:52:12 +00:00
// TODO: support bulk generic output a la Get
2015-06-15 02:48:56 +00:00
return r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
2015-08-31 12:52:04 +00:00
var outputObj runtime . Object
2015-11-30 16:47:08 +00:00
dataChangeMsg := "not labeled"
2017-09-05 19:28:32 +00:00
if o . dryrun || o . local || o . list {
2016-10-06 07:17:40 +00:00
err = labelFunc ( info . Object , o . overwrite , o . resourceVersion , o . newLabels , o . removeLabels )
2015-08-31 12:52:04 +00:00
if err != nil {
return err
}
2017-05-16 21:52:51 +00:00
dataChangeMsg = "labeled"
2015-08-31 12:52:04 +00:00
outputObj = info . Object
} else {
2017-01-22 18:54:46 +00:00
obj := info . Object
2016-01-22 12:39:18 +00:00
name , namespace := info . Name , info . Namespace
2015-10-14 18:07:47 +00:00
oldData , err := json . Marshal ( obj )
if err != nil {
return err
}
2016-03-25 08:57:45 +00:00
accessor , err := meta . Accessor ( obj )
if err != nil {
return err
}
2016-10-06 07:17:40 +00:00
for _ , label := range o . removeLabels {
2016-03-25 08:57:45 +00:00
if _ , ok := accessor . GetLabels ( ) [ label ] ; ! ok {
2018-04-19 00:02:37 +00:00
fmt . Fprintf ( o . Out , "label %q not found.\n" , label )
2015-11-30 16:47:08 +00:00
}
}
2016-10-06 07:17:40 +00:00
if err := labelFunc ( obj , o . overwrite , o . resourceVersion , o . newLabels , o . removeLabels ) ; err != nil {
2015-10-14 18:07:47 +00:00
return err
}
2018-04-19 14:41:17 +00:00
if err := o . Recorder . Record ( obj ) ; err != nil {
glog . V ( 4 ) . Infof ( "error recording current command: %v" , err )
2016-01-22 18:33:23 +00:00
}
2015-10-14 18:07:47 +00:00
newData , err := json . Marshal ( obj )
if err != nil {
return err
}
2015-11-30 16:47:08 +00:00
if ! reflect . DeepEqual ( oldData , newData ) {
dataChangeMsg = "labeled"
}
2017-01-22 18:54:46 +00:00
patchBytes , err := jsonpatch . CreateMergePatch ( oldData , newData )
2016-01-25 16:57:34 +00:00
createdPatch := err == nil
2015-10-14 18:07:47 +00:00
if err != nil {
2016-01-25 16:57:34 +00:00
glog . V ( 2 ) . Infof ( "couldn't compute patch: %v" , err )
2015-10-14 18:07:47 +00:00
}
mapping := info . ResourceMapping ( )
2018-04-26 13:30:21 +00:00
client , err := o . unstructuredClientForMapping ( mapping )
2015-10-14 18:07:47 +00:00
if err != nil {
return err
}
helper := resource . NewHelper ( client , mapping )
2016-01-25 16:57:34 +00:00
if createdPatch {
2017-01-22 18:54:46 +00:00
outputObj , err = helper . Patch ( namespace , name , types . MergePatchType , patchBytes )
2016-01-25 16:57:34 +00:00
} else {
outputObj , err = helper . Replace ( namespace , name , false , obj )
}
2015-03-26 01:52:12 +00:00
if err != nil {
2015-07-23 22:43:48 +00:00
return err
2015-03-26 01:52:12 +00:00
}
2015-03-09 22:08:16 +00:00
}
2017-05-16 21:52:51 +00:00
2017-09-05 19:28:32 +00:00
if o . list {
accessor , err := meta . Accessor ( outputObj )
if err != nil {
return err
}
2018-01-16 19:09:09 +00:00
indent := ""
if ! one {
indent = " "
2018-04-27 18:25:56 +00:00
gvks , _ , err := unstructuredscheme . NewUnstructuredObjectTyper ( ) . ObjectKinds ( info . Object )
if err != nil {
return err
}
fmt . Fprintf ( o . ErrOut , "Listing labels for %s.%s/%s:\n" , gvks [ 0 ] . Kind , gvks [ 0 ] . Group , info . Name )
2018-01-16 19:09:09 +00:00
}
2017-09-05 19:28:32 +00:00
for k , v := range accessor . GetLabels ( ) {
2018-04-19 00:02:37 +00:00
fmt . Fprintf ( o . Out , "%s%s=%s\n" , indent , k , v )
2017-09-05 19:28:32 +00:00
}
return nil
}
2018-04-19 00:02:37 +00:00
printer , err := o . ToPrinter ( dataChangeMsg )
if err != nil {
return err
2015-03-26 01:52:12 +00:00
}
2018-04-26 15:21:41 +00:00
printer . PrintObj ( info . Object , o . Out )
2015-08-16 12:22:35 +00:00
return nil
2015-03-26 01:52:12 +00:00
} )
2015-03-09 22:08:16 +00:00
}
2016-10-06 07:17:40 +00:00
2017-01-11 20:28:46 +00:00
func validateNoOverwrites ( accessor metav1 . Object , labels map [ string ] string ) error {
2016-10-06 07:17:40 +00:00
allErrs := [ ] error { }
for key := range labels {
if value , found := accessor . GetLabels ( ) [ key ] ; found {
allErrs = append ( allErrs , fmt . Errorf ( "'%s' already has a value (%s), and --overwrite is false" , key , value ) )
}
}
return utilerrors . NewAggregate ( allErrs )
}
func parseLabels ( spec [ ] string ) ( map [ string ] string , [ ] string , error ) {
labels := map [ string ] string { }
var remove [ ] string
for _ , labelSpec := range spec {
2017-05-25 20:37:56 +00:00
if strings . Contains ( labelSpec , "=" ) {
2016-10-06 07:17:40 +00:00
parts := strings . Split ( labelSpec , "=" )
if len ( parts ) != 2 {
return nil , nil , fmt . Errorf ( "invalid label spec: %v" , labelSpec )
}
if errs := validation . IsValidLabelValue ( parts [ 1 ] ) ; len ( errs ) != 0 {
return nil , nil , fmt . Errorf ( "invalid label value: %q: %s" , labelSpec , strings . Join ( errs , ";" ) )
}
labels [ parts [ 0 ] ] = parts [ 1 ]
} else if strings . HasSuffix ( labelSpec , "-" ) {
remove = append ( remove , labelSpec [ : len ( labelSpec ) - 1 ] )
} else {
return nil , nil , fmt . Errorf ( "unknown label spec: %v" , labelSpec )
}
}
for _ , removeLabel := range remove {
if _ , found := labels [ removeLabel ] ; found {
return nil , nil , fmt . Errorf ( "can not both modify and remove a label in the same command" )
}
}
return labels , remove , nil
}
func labelFunc ( obj runtime . Object , overwrite bool , resourceVersion string , labels map [ string ] string , remove [ ] string ) error {
accessor , err := meta . Accessor ( obj )
if err != nil {
return err
}
if ! overwrite {
if err := validateNoOverwrites ( accessor , labels ) ; err != nil {
return err
}
}
objLabels := accessor . GetLabels ( )
if objLabels == nil {
objLabels = make ( map [ string ] string )
}
for key , value := range labels {
objLabels [ key ] = value
}
for _ , label := range remove {
delete ( objLabels , label )
}
accessor . SetLabels ( objLabels )
if len ( resourceVersion ) != 0 {
accessor . SetResourceVersion ( resourceVersion )
}
return nil
}