2015-06-25 22:56:30 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-06-25 22:56:30 +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-08-06 09:24:02 +00:00
"fmt"
2016-12-10 00:01:27 +00:00
"reflect"
2016-02-11 20:52:42 +00:00
"strings"
2015-06-25 22:56:30 +00:00
2016-12-10 00:01:27 +00:00
jsonpatch "github.com/evanphx/json-patch"
2017-01-23 02:15:10 +00:00
"github.com/golang/glog"
2015-06-25 22:56:30 +00:00
"github.com/spf13/cobra"
2015-09-30 15:58:42 +00:00
2018-04-26 13:30:21 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2017-01-22 16:38:05 +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-26 02:21:47 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2017-01-16 20:13:59 +00:00
"k8s.io/apimachinery/pkg/types"
2017-01-11 14:09:48 +00:00
"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/yaml"
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"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/kubectl/resource"
2017-10-28 01:31:42 +00:00
"k8s.io/kubernetes/pkg/kubectl/scheme"
2017-07-07 04:04:11 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
2018-04-19 00:02:37 +00:00
"k8s.io/kubernetes/pkg/printers"
2015-06-25 22:56:30 +00:00
)
2017-01-16 20:13:59 +00:00
var patchTypes = map [ string ] types . PatchType { "json" : types . JSONPatchType , "merge" : types . MergePatchType , "strategic" : types . StrategicMergePatchType }
2016-02-11 20:52:42 +00:00
2015-08-14 18:46:43 +00:00
// PatchOptions 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 PatchOptions struct {
2016-08-17 18:28:07 +00:00
resource . FilenameOptions
2018-04-26 13:30:21 +00:00
2018-04-19 14:41:17 +00:00
RecordFlags * genericclioptions . RecordFlags
2018-04-19 00:02:37 +00:00
PrintFlags * printers . PrintFlags
ToPrinter func ( string ) ( printers . ResourcePrinterFunc , error )
2018-04-26 13:30:21 +00:00
Recorder genericclioptions . Recorder
2016-08-17 18:28:07 +00:00
2018-04-26 13:30:21 +00:00
Local bool
PatchType string
Patch string
2016-06-02 17:50:52 +00:00
2018-04-26 13:30:21 +00:00
namespace string
enforceNamespace bool
dryRun bool
outputFormat string
args [ ] string
builder * resource . Builder
unstructuredClientForMapping func ( mapping * meta . RESTMapping ) ( resource . RESTClient , error )
2018-04-19 14:41:17 +00:00
2018-04-26 13:30:21 +00:00
genericclioptions . IOStreams
2015-08-14 18:46:43 +00:00
}
2016-05-20 17:49:56 +00:00
var (
2017-02-16 03:47:00 +00:00
patchLong = templates . LongDesc ( i18n . T ( `
2017-08-11 18:54:33 +00:00
Update field ( s ) of a resource using strategic merge patch , a JSON merge patch , or a JSON patch .
2015-06-25 22:56:30 +00:00
2016-05-20 17:49:56 +00:00
JSON and YAML formats are accepted .
2015-07-13 20:46:51 +00:00
2017-03-15 03:49:10 +00:00
Please refer to the models in https : //htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`))
2015-07-13 20:46:51 +00:00
2017-02-16 03:47:00 +00:00
patchExample = templates . Examples ( i18n . T ( `
2017-08-11 18:54:33 +00:00
# Partially update a node using a strategic merge patch . Specify the patch as JSON .
2016-05-20 17:49:56 +00:00
kubectl patch node k8s - node - 1 - p ' { "spec" : { "unschedulable" : true } } '
2015-08-06 09:24:02 +00:00
2017-08-11 18:54:33 +00:00
# Partially update a node using a strategic merge patch . Specify the patch as YAML .
kubectl patch node k8s - node - 1 - p $ ' spec : \ n unschedulable : true '
# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch .
2016-05-20 17:49:56 +00:00
kubectl patch - f node . json - p ' { "spec" : { "unschedulable" : true } } '
2016-02-11 20:52:42 +00:00
2017-08-11 18:54:33 +00:00
# Update a container ' s image ; spec . containers [ * ] . name is required because it ' s a merge key .
2016-05-20 17:49:56 +00:00
kubectl patch pod valid - pod - p ' { "spec" : { "containers" : [ { "name" : "kubernetes-serve-hostname" , "image" : "new image" } ] } } '
2017-08-11 18:54:33 +00:00
# Update a container ' s image using a json patch with positional arrays .
2017-03-15 03:49:10 +00:00
kubectl patch pod valid - pod -- type = ' json ' - p = ' [ { "op" : "replace" , "path" : "/spec/containers/0/image" , "value" : "new image" } ] ' ` ) )
2015-06-25 22:56:30 +00:00
)
2018-04-26 13:30:21 +00:00
func NewPatchOptions ( ioStreams genericclioptions . IOStreams ) * PatchOptions {
2018-04-19 14:41:17 +00:00
return & PatchOptions {
RecordFlags : genericclioptions . NewRecordFlags ( ) ,
2018-04-19 00:02:37 +00:00
Recorder : genericclioptions . NoopRecorder { } ,
PrintFlags : printers . NewPrintFlags ( "patched" ) ,
2018-04-26 13:30:21 +00:00
IOStreams : ioStreams ,
2018-04-19 14:41:17 +00:00
}
}
2018-04-26 13:30:21 +00:00
func NewCmdPatch ( f cmdutil . Factory , ioStreams genericclioptions . IOStreams ) * cobra . Command {
o := NewPatchOptions ( ioStreams )
2016-04-03 06:57:51 +00:00
2015-06-25 22:56:30 +00:00
cmd := & cobra . Command {
2017-10-11 06:26:02 +00:00
Use : "patch (-f FILENAME | TYPE NAME) -p PATCH" ,
DisableFlagsInUseLine : true ,
2017-01-25 01:00:32 +00:00
Short : i18n . T ( "Update field(s) of a resource using strategic merge patch" ) ,
2017-02-16 03:47:00 +00:00
Long : patchLong ,
Example : patchExample ,
2015-06-25 22:56:30 +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 . RunPatch ( ) )
2015-06-25 22:56:30 +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
2018-04-26 13:30:21 +00:00
cmd . Flags ( ) . StringVarP ( & o . Patch , "patch" , "p" , "" , "The patch to be applied to the resource JSON file." )
2015-06-25 22:56:30 +00:00
cmd . MarkFlagRequired ( "patch" )
2018-04-26 13:30:21 +00:00
cmd . Flags ( ) . StringVar ( & o . PatchType , "type" , "strategic" , fmt . Sprintf ( "The type of patch being provided; one of %v" , sets . StringKeySet ( patchTypes ) . List ( ) ) )
2018-03-02 00:19:51 +00:00
cmdutil . AddDryRunFlag ( cmd )
2018-04-26 13:30:21 +00:00
cmdutil . AddFilenameOptionFlags ( cmd , & o . FilenameOptions , "identifying the resource to update" )
2018-04-19 14:41:17 +00:00
cmd . Flags ( ) . BoolVar ( & o . Local , "local" , o . Local , "If true, patch will operate on the content of the file, not the server-side resource." )
2016-06-02 17:50:52 +00:00
2015-06-25 22:56:30 +00:00
return cmd
}
2018-04-26 13:30:21 +00:00
func ( o * PatchOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
2018-04-19 14:41:17 +00:00
var err error
o . RecordFlags . Complete ( f . Command ( cmd , false ) )
o . Recorder , err = o . RecordFlags . ToRecorder ( )
if err != nil {
return err
}
2018-04-26 13:30:21 +00:00
o . outputFormat = cmdutil . GetFlagString ( cmd , "output" )
o . dryRun = cmdutil . GetFlagBool ( cmd , "dry-run" )
2018-04-19 14:41:17 +00:00
2018-04-19 00:02:37 +00:00
o . ToPrinter = func ( operation string ) ( printers . ResourcePrinterFunc , error ) {
o . PrintFlags . NamePrintFlags . Operation = operation
2018-04-26 13:30:21 +00:00
if o . dryRun {
2018-04-19 00:02:37 +00:00
o . PrintFlags . Complete ( "%s (dry run)" )
}
printer , err := o . PrintFlags . ToPrinter ( )
if err != nil {
return nil , err
}
return printer . PrintObj , nil
}
2018-04-26 13:30:21 +00:00
o . namespace , o . enforceNamespace , err = f . DefaultNamespace ( )
if err != nil {
return err
}
o . args = args
o . builder = f . NewBuilder ( )
o . unstructuredClientForMapping = f . UnstructuredClientForMapping
return nil
2018-04-19 14:41:17 +00:00
}
2018-04-26 13:30:21 +00:00
func ( o * PatchOptions ) Validate ( ) error {
if o . Local && len ( o . args ) != 0 {
2016-06-02 17:50:52 +00:00
return fmt . Errorf ( "cannot specify --local and server resources" )
}
2018-04-26 13:30:21 +00:00
if len ( o . Patch ) == 0 {
return fmt . Errorf ( "must specify -p to patch" )
2015-06-25 22:56:30 +00:00
}
2018-04-26 13:30:21 +00:00
if len ( o . PatchType ) != 0 {
if _ , ok := patchTypes [ strings . ToLower ( o . PatchType ) ] ; ! ok {
return fmt . Errorf ( "--type must be one of %v, not %q" , sets . StringKeySet ( patchTypes ) . List ( ) , o . PatchType )
2016-02-11 20:52:42 +00:00
}
}
2018-04-26 13:30:21 +00:00
return nil
}
func ( o * PatchOptions ) RunPatch ( ) error {
patchType := types . StrategicMergePatchType
if len ( o . PatchType ) != 0 {
patchType = patchTypes [ strings . ToLower ( o . PatchType ) ]
2015-06-25 22:56:30 +00:00
}
2018-04-26 13:30:21 +00:00
patchBytes , err := yaml . ToJSON ( [ ] byte ( o . Patch ) )
2015-09-30 15:58:42 +00:00
if err != nil {
2018-04-26 13:30:21 +00:00
return fmt . Errorf ( "unable to parse %q: %v" , o . Patch , err )
2015-09-30 15:58:42 +00:00
}
2015-06-25 22:56:30 +00:00
2018-04-26 13:30:21 +00:00
r := o . builder .
2017-11-14 04:01:51 +00:00
Unstructured ( ) .
2015-06-25 22:56:30 +00:00
ContinueOnError ( ) .
2018-04-26 13:30:21 +00:00
NamespaceParam ( o . namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . enforceNamespace , & o . FilenameOptions ) .
ResourceTypeOrNameArgs ( false , o . args ... ) .
2015-06-25 22:56:30 +00:00
Flatten ( ) .
Do ( )
err = r . Err ( )
if err != nil {
return err
}
2015-08-06 09:24:02 +00:00
2016-04-14 22:00:40 +00:00
count := 0
err = r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
name , namespace := info . Name , info . Namespace
mapping := info . ResourceMapping ( )
2018-04-26 13:30:21 +00:00
client , err := o . unstructuredClientForMapping ( mapping )
2016-04-14 22:00:40 +00:00
if err != nil {
return err
}
2015-06-25 22:56:30 +00:00
2018-04-26 13:30:21 +00:00
if ! o . Local && ! o . dryRun {
2016-06-02 17:50:52 +00:00
helper := resource . NewHelper ( client , mapping )
2016-12-10 00:01:27 +00:00
patchedObj , err := helper . Patch ( namespace , name , patchType , patchBytes )
2016-06-02 17:50:52 +00:00
if err != nil {
return err
}
2018-04-19 14:41:17 +00:00
didPatch := ! reflect . DeepEqual ( info . Object , patchedObj )
// if the recorder makes a change, compute and create another patch
if mergePatch , err := o . Recorder . MakeRecordMergePatch ( patchedObj ) ; err != nil {
glog . V ( 4 ) . Infof ( "error recording current command: %v" , err )
} else if len ( mergePatch ) > 0 {
if recordedObj , err := helper . Patch ( info . Namespace , info . Name , types . MergePatchType , mergePatch ) ; err != nil {
glog . V ( 4 ) . Infof ( "error recording reason: %v" , err )
} else {
patchedObj = recordedObj
2016-06-02 17:50:52 +00:00
}
}
count ++
2017-03-02 06:00:48 +00:00
// After computing whether we changed data, refresh the resource info with the resulting object
if err := info . Refresh ( patchedObj , true ) ; err != nil {
return err
}
2018-04-19 00:02:37 +00:00
printer , err := o . ToPrinter ( patchOperation ( didPatch ) )
if err != nil {
return err
2016-06-02 17:50:52 +00:00
}
2018-04-26 13:30:21 +00:00
printer . PrintObj ( info . Object , o . Out )
2017-09-13 21:55:26 +00:00
// if object was not successfully patched, exit with error code 1
if ! didPatch {
return cmdutil . ErrExit
}
2016-06-02 17:50:52 +00:00
return nil
}
count ++
2017-11-14 04:01:51 +00:00
originalObjJS , err := runtime . Encode ( unstructured . UnstructuredJSONScheme , info . Object )
2016-04-14 22:00:40 +00:00
if err != nil {
return err
}
2017-05-16 21:52:51 +00:00
2017-10-28 01:31:42 +00:00
originalPatchedObjJS , err := getPatchedJSON ( patchType , originalObjJS , patchBytes , mapping . GroupVersionKind , scheme . Scheme )
2016-06-02 17:50:52 +00:00
if err != nil {
return err
2016-04-14 22:00:40 +00:00
}
2017-05-16 21:52:51 +00:00
2017-01-26 02:21:47 +00:00
targetObj , err := runtime . Decode ( unstructured . UnstructuredJSONScheme , originalPatchedObjJS )
2016-06-02 17:50:52 +00:00
if err != nil {
return err
}
2017-05-16 21:52:51 +00:00
2018-03-02 00:19:51 +00:00
didPatch := ! reflect . DeepEqual ( info . Object , targetObj )
2016-06-02 17:50:52 +00:00
// TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything
// rawExtension := &runtime.Unknown{
2016-05-19 10:58:36 +00:00
// Raw: originalPatchedObjJS,
2016-06-02 17:50:52 +00:00
// }
2018-03-02 00:19:51 +00:00
if didPatch {
if err := info . Refresh ( targetObj , true ) ; err != nil {
return err
}
}
2018-04-19 00:02:37 +00:00
printer , err := o . ToPrinter ( patchOperation ( didPatch ) )
if err != nil {
return err
2016-06-02 17:50:52 +00:00
}
2018-04-26 13:30:21 +00:00
return printer . PrintObj ( info . Object , o . Out )
2016-04-14 22:00:40 +00:00
} )
2015-06-25 22:56:30 +00:00
if err != nil {
return err
}
2016-04-14 22:00:40 +00:00
if count == 0 {
return fmt . Errorf ( "no objects passed to patch" )
2016-01-22 18:33:23 +00:00
}
2015-06-25 22:56:30 +00:00
return nil
}
2016-06-02 17:50:52 +00:00
2017-01-26 02:21:47 +00:00
func getPatchedJSON ( patchType types . PatchType , originalJS , patchJS [ ] byte , gvk schema . GroupVersionKind , creater runtime . ObjectCreater ) ( [ ] byte , error ) {
2016-06-02 17:50:52 +00:00
switch patchType {
2017-01-16 20:13:59 +00:00
case types . JSONPatchType :
2016-06-02 17:50:52 +00:00
patchObj , err := jsonpatch . DecodePatch ( patchJS )
if err != nil {
return nil , err
}
return patchObj . Apply ( originalJS )
2017-01-16 20:13:59 +00:00
case types . MergePatchType :
2016-06-02 17:50:52 +00:00
return jsonpatch . MergePatch ( originalJS , patchJS )
2017-01-16 20:13:59 +00:00
case types . StrategicMergePatchType :
2017-01-26 02:21:47 +00:00
// get a typed object for this GVK if we need to apply a strategic merge patch
obj , err := creater . New ( gvk )
if err != nil {
return nil , fmt . Errorf ( "cannot apply strategic merge patch for %s locally, try --type merge" , gvk . String ( ) )
}
2016-12-30 10:19:22 +00:00
return strategicpatch . StrategicMergePatch ( originalJS , patchJS , obj )
2016-06-02 17:50:52 +00:00
default :
// only here as a safety net - go-restful filters content-type
return nil , fmt . Errorf ( "unknown Content-Type header for patch: %v" , patchType )
}
}
2018-03-02 00:19:51 +00:00
func patchOperation ( didPatch bool ) string {
if didPatch {
return "patched"
}
return "not patched"
}