2015-02-07 00:33:42 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
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 (
2015-10-14 18:07:47 +00:00
"encoding/json"
2015-02-07 00:33:42 +00:00
"fmt"
"io"
2015-11-30 16:47:08 +00:00
"reflect"
2015-02-07 00:33:42 +00:00
"strings"
2016-01-25 16:57:34 +00:00
"github.com/golang/glog"
2015-08-05 22:05:17 +00:00
"github.com/spf13/cobra"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
2016-03-25 08:57:45 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2015-08-05 03:43:48 +00:00
"k8s.io/kubernetes/pkg/kubectl"
2015-08-05 22:03:47 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
2015-10-14 05:18:37 +00:00
utilerrors "k8s.io/kubernetes/pkg/util/errors"
2015-10-14 18:07:47 +00:00
"k8s.io/kubernetes/pkg/util/strategicpatch"
2015-09-10 22:48:28 +00:00
"k8s.io/kubernetes/pkg/util/validation"
2015-02-07 00:33:42 +00:00
)
2015-08-14 18:46:43 +00:00
// LabelOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type LabelOptions struct {
Filenames [ ] string
2016-03-28 19:44:21 +00:00
Recursive bool
2015-08-14 18:46:43 +00:00
}
2015-02-20 21:28:43 +00:00
const (
label_long = ` Update the labels on a resource .
2015-02-07 00:33:42 +00:00
2015-05-20 09:02:30 +00:00
A label must begin with a letter or number , and may contain letters , numbers , hyphens , dots , and underscores , up to % [ 1 ] d characters .
2015-02-07 00:33:42 +00:00
If -- overwrite is true , then existing labels can be overwritten , otherwise attempting to overwrite a label will result in an error .
2015-02-20 21:28:43 +00:00
If -- resource - version is specified , then updates will use this resource version , otherwise the existing resource - version will be used . `
2015-08-12 16:50:09 +00:00
label_example = ` # Update pod ' foo ' with the label ' unhealthy ' and the value ' true ' .
2016-02-29 14:41:09 +00:00
kubectl label pods foo unhealthy = true
2015-02-07 00:33:42 +00:00
2015-08-12 16:50:09 +00:00
# Update pod ' foo ' with the label ' status ' and the value ' unhealthy ' , overwriting any existing value .
2016-02-29 14:41:09 +00:00
kubectl label -- overwrite pods foo status = unhealthy
2015-02-07 00:33:42 +00:00
2015-08-12 16:50:09 +00:00
# Update all pods in the namespace
2016-02-29 14:41:09 +00:00
kubectl label pods -- all status = unhealthy
2015-03-26 01:52:12 +00:00
2015-08-05 03:43:48 +00:00
# Update a pod identified by the type and name in "pod.json"
2016-02-29 14:41:09 +00:00
kubectl label - f pod . json status = unhealthy
2015-08-05 03:43:48 +00:00
2015-08-12 16:50:09 +00:00
# Update pod ' foo ' only if the resource is unchanged from version 1.
2016-02-29 14:41:09 +00:00
kubectl label pods foo status = unhealthy -- resource - version = 1
2015-02-20 21:28:43 +00:00
2015-08-12 16:50:09 +00:00
# Update pod ' foo ' by removing a label named ' bar ' if it exists .
# Does not require the -- overwrite flag .
2016-02-29 14:41:09 +00:00
kubectl label pods foo bar - `
2015-02-20 21:28:43 +00:00
)
2015-04-07 18:21:25 +00:00
func NewCmdLabel ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
2015-08-14 18:46:43 +00:00
options := & LabelOptions { }
2015-09-02 22:38:40 +00:00
// retrieve a list of handled resources from printer as valid args
2016-03-20 18:14:25 +00:00
validArgs , argAliases := [ ] string { } , [ ] string { }
2016-02-04 07:08:44 +00:00
p , err := f . Printer ( nil , false , false , false , false , false , false , [ ] string { } )
2015-08-31 19:04:12 +00:00
cmdutil . CheckErr ( err )
if p != nil {
validArgs = p . HandledResources ( )
2016-03-20 18:14:25 +00:00
argAliases = kubectl . ResourceAliases ( validArgs )
2015-08-31 19:04:12 +00:00
}
2015-08-14 18:46:43 +00:00
2015-02-20 21:28:43 +00:00
cmd := & cobra . Command {
2015-08-05 03:43:48 +00:00
Use : "label [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]" ,
2015-02-20 21:28:43 +00:00
Short : "Update the labels on a resource" ,
2015-09-10 22:48:28 +00:00
Long : fmt . Sprintf ( label_long , validation . LabelValueMaxLength ) ,
2015-02-20 21:28:43 +00:00
Example : label_example ,
2015-02-07 00:33:42 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-08-14 18:46:43 +00:00
err := RunLabel ( f , out , cmd , args , options )
2015-04-07 18:21:25 +00:00
cmdutil . CheckErr ( err )
2015-02-07 00:33:42 +00:00
} ,
2016-03-20 18:14:25 +00:00
ValidArgs : validArgs ,
ArgAliases : argAliases ,
2015-02-07 00:33:42 +00:00
}
2015-04-07 18:21:25 +00:00
cmdutil . AddPrinterFlags ( cmd )
2015-02-07 00:33:42 +00:00
cmd . Flags ( ) . Bool ( "overwrite" , false , "If true, allow labels to be overwritten, otherwise reject label updates that overwrite existing labels." )
2015-03-26 01:52:12 +00:00
cmd . Flags ( ) . StringP ( "selector" , "l" , "" , "Selector (label query) to filter on" )
cmd . Flags ( ) . Bool ( "all" , false , "select all resources in the namespace of the specified resource types" )
cmd . Flags ( ) . String ( "resource-version" , "" , "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." )
2015-08-05 03:43:48 +00:00
usage := "Filename, directory, or URL to a file identifying the resource to update the labels"
2015-08-14 18:46:43 +00:00
kubectl . AddJsonFilenameFlag ( cmd , & options . Filenames , usage )
2016-03-28 19:44:21 +00:00
cmdutil . AddRecursiveFlag ( cmd , & options . Recursive )
2016-05-11 00:26:39 +00:00
cmdutil . AddDryRunFlag ( cmd )
2016-01-22 18:33:23 +00:00
cmdutil . AddRecordFlag ( cmd )
2016-03-10 01:27:19 +00:00
cmdutil . AddInclude3rdPartyFlags ( cmd )
2015-08-31 12:52:04 +00:00
2015-02-07 00:33:42 +00:00
return cmd
}
2016-03-25 08:57:45 +00:00
func validateNoOverwrites ( accessor meta . Object , labels map [ string ] string ) error {
2015-09-01 06:08:40 +00:00
allErrs := [ ] error { }
2015-02-07 00:33:42 +00:00
for key := range labels {
2016-03-25 08:57:45 +00:00
if value , found := accessor . GetLabels ( ) [ key ] ; found {
2015-09-01 06:08:40 +00:00
allErrs = append ( allErrs , fmt . Errorf ( "'%s' already has a value (%s), and --overwrite is false" , key , value ) )
2015-02-07 00:33:42 +00:00
}
}
2015-10-14 05:18:37 +00:00
return utilerrors . NewAggregate ( allErrs )
2015-02-07 00:33:42 +00:00
}
func parseLabels ( spec [ ] string ) ( map [ string ] string , [ ] string , error ) {
labels := map [ string ] string { }
var remove [ ] string
for _ , labelSpec := range spec {
if strings . Index ( labelSpec , "=" ) != - 1 {
parts := strings . Split ( labelSpec , "=" )
2015-12-16 06:27:13 +00:00
if len ( parts ) != 2 || len ( parts [ 1 ] ) == 0 {
2015-02-07 00:33:42 +00:00
return nil , nil , fmt . Errorf ( "invalid label spec: %v" , labelSpec )
}
2015-12-16 06:27:13 +00:00
if errs := validation . IsValidLabelValue ( parts [ 1 ] ) ; len ( errs ) != 0 {
return nil , nil , fmt . Errorf ( "invalid label value: %q: %s" , labelSpec , strings . Join ( errs , ";" ) )
}
2015-02-07 00:33:42 +00:00
labels [ parts [ 0 ] ] = parts [ 1 ]
} else if strings . HasSuffix ( labelSpec , "-" ) {
remove = append ( remove , labelSpec [ : len ( labelSpec ) - 1 ] )
} else {
2015-03-31 22:32:02 +00:00
return nil , nil , fmt . Errorf ( "unknown label spec: %v" , labelSpec )
2015-02-07 00:33:42 +00:00
}
}
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
}
2015-07-23 22:43:48 +00:00
func labelFunc ( obj runtime . Object , overwrite bool , resourceVersion string , labels map [ string ] string , remove [ ] string ) error {
2016-03-25 08:57:45 +00:00
accessor , err := meta . Accessor ( obj )
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
}
if ! overwrite {
2016-03-25 08:57:45 +00:00
if err := validateNoOverwrites ( accessor , labels ) ; err != nil {
2015-07-23 22:43:48 +00:00
return err
2015-02-07 00:33:42 +00:00
}
}
2016-03-25 08:57:45 +00:00
objLabels := accessor . GetLabels ( )
if objLabels == nil {
objLabels = make ( map [ string ] string )
2015-02-11 11:05:41 +00:00
}
2015-02-07 00:33:42 +00:00
for key , value := range labels {
2016-03-25 08:57:45 +00:00
objLabels [ key ] = value
2015-02-07 00:33:42 +00:00
}
for _ , label := range remove {
2016-03-25 08:57:45 +00:00
delete ( objLabels , label )
2015-02-07 00:33:42 +00:00
}
2016-03-25 08:57:45 +00:00
accessor . SetLabels ( objLabels )
2015-02-07 00:33:42 +00:00
if len ( resourceVersion ) != 0 {
2016-03-25 08:57:45 +00:00
accessor . SetResourceVersion ( resourceVersion )
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
2015-08-14 18:46:43 +00:00
func RunLabel ( f * cmdutil . Factory , out io . Writer , cmd * cobra . Command , args [ ] string , options * LabelOptions ) error {
2016-05-11 00:26:39 +00:00
resources , labelArgs , err := cmdutil . GetResourcesAndPairs ( args , "label" )
if err != nil {
return err
2015-03-09 22:08:16 +00:00
}
2015-08-14 18:46:43 +00:00
if len ( resources ) < 1 && len ( options . Filenames ) == 0 {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "one or more resources must be specified as <resource> <name> or <resource>/<name>" )
2015-03-09 22:08:16 +00:00
}
2015-03-26 01:52:12 +00:00
if len ( labelArgs ) < 1 {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "at least one label update is required" )
2015-03-09 22:08:16 +00:00
}
2015-04-07 18:21:25 +00:00
selector := cmdutil . GetFlagString ( cmd , "selector" )
all := cmdutil . GetFlagBool ( cmd , "all" )
overwrite := cmdutil . GetFlagBool ( cmd , "overwrite" )
resourceVersion := cmdutil . GetFlagString ( cmd , "resource-version" )
2015-03-26 01:52:12 +00:00
2015-08-05 03:43:48 +00:00
cmdNamespace , enforceNamespace , err := f . DefaultNamespace ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-03-26 01:52:12 +00:00
2015-08-16 12:22:35 +00:00
lbls , remove , err := parseLabels ( labelArgs )
2015-03-09 22:08:16 +00:00
if err != nil {
2015-05-20 09:02:30 +00:00
return cmdutil . UsageError ( cmd , err . Error ( ) )
2015-03-09 22:08:16 +00:00
}
2016-03-10 01:27:19 +00:00
mapper , typer := f . Object ( cmdutil . GetIncludeThirdPartyAPIs ( cmd ) )
2015-12-21 05:37:49 +00:00
b := resource . NewBuilder ( mapper , typer , resource . ClientMapperFunc ( f . ClientForMapping ) , f . Decoder ( true ) ) .
2015-03-26 01:52:12 +00:00
ContinueOnError ( ) .
NamespaceParam ( cmdNamespace ) . DefaultNamespace ( ) .
2016-03-28 19:44:21 +00:00
FilenameParam ( enforceNamespace , options . Recursive , options . Filenames ... ) .
2015-03-26 01:52:12 +00:00
SelectorParam ( selector ) .
ResourceTypeOrNameArgs ( all , resources ... ) .
Flatten ( ) .
Latest ( )
one := false
r := b . Do ( ) . IntoSingular ( & one )
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
if ! one && len ( resourceVersion ) > 0 {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "--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"
2016-05-11 00:26:39 +00:00
if cmdutil . GetDryRunFlag ( cmd ) {
2015-08-31 12:52:04 +00:00
err = labelFunc ( info . Object , overwrite , resourceVersion , lbls , remove )
if err != nil {
return err
}
outputObj = info . Object
} else {
2016-05-01 00:43:30 +00:00
obj , err := info . Mapping . ConvertToVersion ( info . Object , info . Mapping . GroupVersionKind . GroupVersion ( ) )
2016-01-22 12:39:18 +00:00
if err != nil {
return err
}
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
}
2015-11-30 16:47:08 +00:00
for _ , label := range remove {
2016-03-25 08:57:45 +00:00
if _ , ok := accessor . GetLabels ( ) [ label ] ; ! ok {
2015-11-30 16:47:08 +00:00
fmt . Fprintf ( out , "label %q not found.\n" , label )
}
}
2015-10-14 18:07:47 +00:00
if err := labelFunc ( obj , overwrite , resourceVersion , lbls , remove ) ; err != nil {
return err
}
2016-01-22 18:33:23 +00:00
if cmdutil . ShouldRecord ( cmd , info ) {
if err := cmdutil . RecordChangeCause ( obj , f . Command ( ) ) ; err != nil {
return err
}
}
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"
}
2015-10-14 18:07:47 +00:00
patchBytes , err := strategicpatch . CreateTwoWayMergePatch ( oldData , newData , obj )
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 ( )
2015-12-21 05:37:49 +00:00
client , err := f . ClientForMapping ( 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 {
outputObj , err = helper . Patch ( namespace , name , api . StrategicMergePatchType , patchBytes )
} 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
}
2015-08-16 12:22:35 +00:00
outputFormat := cmdutil . GetFlagString ( cmd , "output" )
2015-08-26 08:37:16 +00:00
if outputFormat != "" {
2016-03-10 01:27:19 +00:00
return f . PrintObject ( cmd , mapper , outputObj , out )
2015-03-26 01:52:12 +00:00
}
2015-11-30 16:47:08 +00:00
cmdutil . PrintSuccess ( mapper , false , out , info . Mapping . Resource , info . Name , dataChangeMsg )
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
}