2015-07-23 22:43:48 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-07-23 22:43:48 +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 (
"bytes"
"fmt"
"io"
2017-01-22 18:54:39 +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:39 +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"
2017-01-22 18:54:39 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
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-22 18:54:39 +00:00
"k8s.io/apimachinery/pkg/util/json"
2015-08-21 07:08:29 +00:00
"k8s.io/kubernetes/pkg/kubectl"
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"
"k8s.io/kubernetes/pkg/kubectl/resource"
2016-12-14 07:18:11 +00:00
"k8s.io/kubernetes/pkg/util/i18n"
2015-07-23 22:43:48 +00:00
)
// AnnotateOptions have the data required to perform the annotate operation
type AnnotateOptions struct {
2016-10-06 07:17:40 +00:00
// Filename options
2016-08-17 18:28:07 +00:00
resource . FilenameOptions
2016-10-06 07:17:40 +00:00
// Common user flags
overwrite bool
local bool
dryrun bool
all bool
resourceVersion string
selector string
outputFormat string
recordChangeCause bool
// results of arg parsing
2015-07-23 22:43:48 +00:00
resources [ ] string
newAnnotations map [ string ] string
removeAnnotations [ ] string
2015-11-13 19:07:21 +00:00
2016-10-13 00:18:39 +00:00
// Common share fields
2015-11-13 19:07:21 +00:00
out io . Writer
2015-07-23 22:43:48 +00:00
}
2016-05-20 17:49:56 +00:00
var (
2016-10-07 22:24:42 +00:00
annotate_long = templates . LongDesc ( `
2016-05-20 17:49:56 +00:00
Update the annotations on one or more resources .
2015-07-23 22:43:48 +00:00
2016-10-07 22:24:42 +00:00
* An annotation is a key / value pair that can hold larger ( compared to a label ) , and possibly not human - readable , data .
* It is intended to store non - identifying auxiliary data , especially data manipulated by tools and system extensions .
* If -- overwrite is true , then existing annotations can be overwritten , otherwise attempting to overwrite an annotation will result in an error .
* If -- resource - version is specified , then updates will use this resource version , otherwise the existing resource - version will be used .
2015-07-23 22:43:48 +00:00
2016-10-07 22:24:42 +00:00
` + valid_resources )
2016-04-03 10:13:51 +00:00
2016-10-07 22:24:42 +00:00
annotate_example = templates . Examples ( `
# Update pod ' foo ' with the annotation ' description ' and the value ' my frontend ' .
# If the same annotation is set multiple times , only the last value will be applied
kubectl annotate pods foo description = ' my frontend '
2015-07-23 22:43:48 +00:00
2016-10-07 22:24:42 +00:00
# Update a pod identified by type and name in "pod.json"
kubectl annotate - f pod . json description = ' my frontend '
2015-08-21 07:08:29 +00:00
2016-10-07 22:24:42 +00:00
# Update pod ' foo ' with the annotation ' description ' and the value ' my frontend running nginx ' , overwriting any existing value .
kubectl annotate -- overwrite pods foo description = ' my frontend running nginx '
2015-07-23 22:43:48 +00:00
2016-10-07 22:24:42 +00:00
# Update all pods in the namespace
kubectl annotate pods -- all description = ' my frontend running nginx '
2015-07-23 22:43:48 +00:00
2016-10-07 22:24:42 +00:00
# Update pod ' foo ' only if the resource is unchanged from version 1.
kubectl annotate pods foo description = ' my frontend running nginx ' -- resource - version = 1
2015-07-23 22:43:48 +00:00
2016-10-07 22:24:42 +00:00
# Update pod ' foo ' by removing an annotation named ' description ' if it exists .
# Does not require the -- overwrite flag .
kubectl annotate pods foo description - ` )
2015-07-23 22:43:48 +00:00
)
2016-10-13 00:18:39 +00:00
func NewCmdAnnotate ( f cmdutil . Factory , out io . Writer ) * cobra . Command {
2015-07-23 22:43:48 +00:00
options := & AnnotateOptions { }
2016-10-06 07:17:40 +00:00
// retrieve a list of handled resources from printer as valid args
2016-04-03 10:13:51 +00:00
validArgs , argAliases := [ ] string { } , [ ] string { }
2016-09-01 08:33:51 +00:00
p , err := f . Printer ( nil , kubectl . PrintOptions {
ColumnLabels : [ ] string { } ,
} )
cmdutil . CheckErr ( err )
if p != nil {
validArgs = p . HandledResources ( )
2016-04-03 10:13:51 +00:00
argAliases = kubectl . ResourceAliases ( validArgs )
}
2015-07-23 22:43:48 +00:00
cmd := & cobra . Command {
2015-08-21 07:08:29 +00:00
Use : "annotate [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]" ,
2016-12-14 07:18:11 +00:00
Short : i18n . T ( "Update the annotations on a resource" ) ,
2015-07-23 22:43:48 +00:00
Long : annotate_long ,
Example : annotate_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-11-13 19:07:21 +00:00
if err := options . Complete ( f , out , cmd , args ) ; err != nil {
2015-07-23 22:43:48 +00:00
cmdutil . CheckErr ( cmdutil . UsageError ( cmd , err . Error ( ) ) )
}
2016-10-06 07:17:40 +00:00
if err := options . Validate ( ) ; err != nil {
cmdutil . CheckErr ( cmdutil . UsageError ( cmd , err . Error ( ) ) )
2015-07-23 22:43:48 +00:00
}
2016-10-06 07:17:40 +00:00
cmdutil . CheckErr ( options . RunAnnotate ( f , cmd ) )
2015-07-23 22:43:48 +00:00
} ,
2016-04-03 10:13:51 +00:00
ValidArgs : validArgs ,
ArgAliases : argAliases ,
2015-07-23 22:43:48 +00:00
}
2015-11-13 19:07:21 +00:00
cmdutil . AddPrinterFlags ( cmd )
2016-10-06 07:17:40 +00:00
cmd . Flags ( ) . Bool ( "overwrite" , false , "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations." )
cmd . Flags ( ) . Bool ( "local" , false , "If true, annotation will NOT contact api-server but run locally." )
2016-12-05 02:58:56 +00:00
cmd . Flags ( ) . StringP ( "selector" , "l" , "" , "Selector (label query) to filter on, supports '=', '==', and '!='." )
2016-10-06 07:17:40 +00:00
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 annotation 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 annotation"
cmdutil . AddFilenameOptionFlags ( cmd , & options . FilenameOptions , usage )
2016-10-06 07:17:40 +00:00
cmdutil . AddDryRunFlag ( cmd )
2016-01-22 18:33:23 +00:00
cmdutil . AddRecordFlag ( cmd )
2016-10-06 07:17:40 +00:00
cmdutil . AddInclude3rdPartyFlags ( cmd )
2015-07-23 22:43:48 +00:00
return cmd
}
// Complete adapts from the command line args and factory to the data required.
2016-10-13 00:18:39 +00:00
func ( o * AnnotateOptions ) Complete ( f cmdutil . Factory , out io . Writer , cmd * cobra . Command , args [ ] string ) ( err error ) {
2016-10-06 07:17:40 +00:00
o . out = out
o . local = cmdutil . GetFlagBool ( cmd , "local" )
o . overwrite = cmdutil . GetFlagBool ( cmd , "overwrite" )
o . all = cmdutil . GetFlagBool ( cmd , "all" )
o . resourceVersion = cmdutil . GetFlagString ( cmd , "resource-version" )
o . selector = cmdutil . GetFlagString ( cmd , "selector" )
o . outputFormat = cmdutil . GetFlagString ( cmd , "output" )
o . dryrun = cmdutil . GetDryRunFlag ( cmd )
o . recordChangeCause = cmdutil . GetRecordFlag ( cmd )
2015-07-23 22:43:48 +00:00
// retrieves resource and annotation args from args
// also checks args to verify that all resources are specified before annotations
2016-05-11 00:26:39 +00:00
resources , annotationArgs , err := cmdutil . GetResourcesAndPairs ( args , "annotation" )
if err != nil {
return err
2015-07-23 22:43:48 +00:00
}
2016-05-11 00:26:39 +00:00
o . resources = resources
2016-10-06 07:17:40 +00:00
o . newAnnotations , o . removeAnnotations , err = parseAnnotations ( annotationArgs )
return err
}
// Validate checks to the AnnotateOptions to see if there is sufficient information run the command.
func ( o AnnotateOptions ) Validate ( ) error {
2016-08-17 18:28:07 +00:00
if len ( o . resources ) < 1 && cmdutil . IsFilenameEmpty ( o . Filenames ) {
2015-07-23 22:43:48 +00:00
return fmt . Errorf ( "one or more resources must be specified as <resource> <name> or <resource>/<name>" )
}
2016-10-06 07:17:40 +00:00
if len ( o . newAnnotations ) < 1 && len ( o . removeAnnotations ) < 1 {
2015-07-23 22:43:48 +00:00
return fmt . Errorf ( "at least one annotation update is required" )
}
2016-10-06 07:17:40 +00:00
return validateAnnotations ( o . removeAnnotations , o . newAnnotations )
}
2015-07-23 22:43:48 +00:00
2016-10-06 07:17:40 +00:00
// RunAnnotate does the work
2016-10-13 00:18:39 +00:00
func ( o AnnotateOptions ) RunAnnotate ( f cmdutil . Factory , cmd * cobra . Command ) error {
2016-10-06 07:17:40 +00:00
namespace , enforceNamespace , err := f . DefaultNamespace ( )
if err != nil {
2015-07-23 22:43:48 +00:00
return err
}
2016-10-06 07:17:40 +00:00
changeCause := f . Command ( )
2017-01-22 18:54:39 +00:00
mapper , typer , err := f . UnstructuredObject ( )
if err != nil {
return err
}
b := resource . NewBuilder ( mapper , typer , resource . ClientMapperFunc ( f . UnstructuredClientForMapping ) , unstructured . UnstructuredJSONScheme ) .
2015-07-23 22:43:48 +00:00
ContinueOnError ( ) .
NamespaceParam ( namespace ) . DefaultNamespace ( ) .
2016-08-17 18:28:07 +00:00
FilenameParam ( enforceNamespace , & o . FilenameOptions ) .
2016-10-05 04:39:45 +00:00
Flatten ( )
2016-10-06 07:17:40 +00:00
2016-10-05 04:39:45 +00:00
if ! o . local {
2016-10-06 07:17:40 +00:00
b = b . SelectorParam ( o . selector ) .
2016-10-05 04:39:45 +00:00
ResourceTypeOrNameArgs ( o . all , o . resources ... ) .
Latest ( )
}
2016-10-06 07:17:40 +00:00
r := b . Do ( )
2015-07-23 22:43:48 +00:00
if err := r . Err ( ) ; err != nil {
return err
}
2015-10-14 05:25:18 +00:00
2017-01-09 19:21:24 +00:00
var singleItemImpliedResource bool
r . IntoSingleItemImplied ( & singleItemImpliedResource )
2016-07-20 20:24:29 +00:00
// only apply resource version locking on a single resource.
// we must perform this check after o.builder.Do() as
// []o.resources can not not accurately return the proper number
// of resources when they are not passed in "resource/name" format.
2017-01-09 19:21:24 +00:00
if ! singleItemImpliedResource && len ( o . resourceVersion ) > 0 {
2016-07-20 20:24:29 +00:00
return fmt . Errorf ( "--resource-version may only be used with a single resource" )
}
2015-06-15 02:48:56 +00:00
return r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
2015-10-14 05:25:18 +00:00
2016-10-05 04:39:45 +00:00
var outputObj runtime . Object
2016-04-27 20:54:15 +00:00
obj , err := cmdutil . MaybeConvertObject ( info . Object , info . Mapping . GroupVersionKind . GroupVersion ( ) , info . Mapping )
2016-01-22 12:39:18 +00:00
if err != nil {
return err
}
2016-10-06 07:17:40 +00:00
if o . dryrun || o . local {
2016-10-05 04:39:45 +00:00
if err := o . updateAnnotations ( obj ) ; err != nil {
return err
}
outputObj = obj
} else {
name , namespace := info . Name , info . Namespace
oldData , err := json . Marshal ( obj )
if err != nil {
return err
}
// If we should record change-cause, add it to new annotations
if cmdutil . ContainsChangeCause ( info ) || o . recordChangeCause {
2016-10-06 07:17:40 +00:00
o . newAnnotations [ kubectl . ChangeCauseAnnotation ] = changeCause
2016-10-05 04:39:45 +00:00
}
if err := o . updateAnnotations ( obj ) ; err != nil {
return err
}
newData , err := json . Marshal ( obj )
if err != nil {
return err
}
2017-01-22 18:54:39 +00:00
patchBytes , err := jsonpatch . CreateMergePatch ( oldData , newData )
2016-10-05 04:39:45 +00:00
createdPatch := err == nil
if err != nil {
glog . V ( 2 ) . Infof ( "couldn't compute patch: %v" , err )
}
2015-10-14 05:25:18 +00:00
2016-10-05 04:39:45 +00:00
mapping := info . ResourceMapping ( )
2017-01-22 18:54:39 +00:00
client , err := f . UnstructuredClientForMapping ( mapping )
2016-10-05 04:39:45 +00:00
if err != nil {
return err
}
helper := resource . NewHelper ( client , mapping )
2015-10-14 05:25:18 +00:00
2016-10-05 04:39:45 +00:00
if createdPatch {
2017-01-22 18:54:39 +00:00
outputObj , err = helper . Patch ( namespace , name , types . MergePatchType , patchBytes )
2016-10-05 04:39:45 +00:00
} else {
outputObj , err = helper . Replace ( namespace , name , false , obj )
}
if err != nil {
return err
}
}
2016-10-06 07:17:40 +00:00
if o . outputFormat != "" {
return f . PrintObject ( cmd , mapper , outputObj , o . out )
2015-11-13 19:07:21 +00:00
}
2016-10-06 07:17:40 +00:00
cmdutil . PrintSuccess ( mapper , false , o . out , info . Mapping . Resource , info . Name , o . dryrun , "annotated" )
2015-11-13 19:07:21 +00:00
return nil
2015-07-23 22:43:48 +00:00
} )
}
// parseAnnotations retrieves new and remove annotations from annotation args
func parseAnnotations ( annotationArgs [ ] string ) ( map [ string ] string , [ ] string , error ) {
2016-05-11 00:26:39 +00:00
return cmdutil . ParsePairs ( annotationArgs , "annotation" , true )
2015-07-23 22:43:48 +00:00
}
// validateAnnotations checks the format of annotation args and checks removed annotations aren't in the new annotations map
func validateAnnotations ( removeAnnotations [ ] string , newAnnotations map [ string ] string ) error {
var modifyRemoveBuf bytes . Buffer
for _ , removeAnnotation := range removeAnnotations {
if _ , found := newAnnotations [ removeAnnotation ] ; found {
if modifyRemoveBuf . Len ( ) > 0 {
modifyRemoveBuf . WriteString ( ", " )
}
modifyRemoveBuf . WriteString ( fmt . Sprintf ( removeAnnotation ) )
}
}
if modifyRemoveBuf . Len ( ) > 0 {
return fmt . Errorf ( "can not both modify and remove the following annotation(s) in the same command: %s" , modifyRemoveBuf . String ( ) )
}
return nil
}
// validateNoAnnotationOverwrites validates that when overwrite is false, to-be-updated annotations don't exist in the object annotation map (yet)
2017-01-11 20:28:46 +00:00
func validateNoAnnotationOverwrites ( accessor metav1 . Object , annotations map [ string ] string ) error {
2015-07-23 22:43:48 +00:00
var buf bytes . Buffer
for key := range annotations {
2016-01-22 18:33:23 +00:00
// change-cause annotation can always be overwritten
if key == kubectl . ChangeCauseAnnotation {
continue
}
2016-03-25 08:57:45 +00:00
if value , found := accessor . GetAnnotations ( ) [ key ] ; found {
2015-07-23 22:43:48 +00:00
if buf . Len ( ) > 0 {
buf . WriteString ( "; " )
}
buf . WriteString ( fmt . Sprintf ( "'%s' already has a value (%s)" , key , value ) )
}
}
if buf . Len ( ) > 0 {
return fmt . Errorf ( "--overwrite is false but found the following declared annotation(s): %s" , buf . String ( ) )
}
return nil
}
// updateAnnotations updates annotations of obj
func ( o AnnotateOptions ) updateAnnotations ( obj runtime . Object ) error {
2016-03-25 08:57:45 +00:00
accessor , err := meta . Accessor ( obj )
2015-07-23 22:43:48 +00:00
if err != nil {
return err
}
if ! o . overwrite {
2016-03-25 08:57:45 +00:00
if err := validateNoAnnotationOverwrites ( accessor , o . newAnnotations ) ; err != nil {
2015-07-23 22:43:48 +00:00
return err
}
}
2016-03-25 08:57:45 +00:00
annotations := accessor . GetAnnotations ( )
if annotations == nil {
annotations = make ( map [ string ] string )
2015-07-23 22:43:48 +00:00
}
for key , value := range o . newAnnotations {
2016-03-25 08:57:45 +00:00
annotations [ key ] = value
2015-07-23 22:43:48 +00:00
}
for _ , annotation := range o . removeAnnotations {
2016-03-25 08:57:45 +00:00
delete ( annotations , annotation )
2015-07-23 22:43:48 +00:00
}
2016-03-25 08:57:45 +00:00
accessor . SetAnnotations ( annotations )
2015-07-23 22:43:48 +00:00
if len ( o . resourceVersion ) != 0 {
2016-03-25 08:57:45 +00:00
accessor . SetResourceVersion ( o . resourceVersion )
2015-07-23 22:43:48 +00:00
}
return nil
}