2019-08-01 14:01:40 -04:00
/ *
Copyright 2014 The Kubernetes Authors .
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 patch
import (
"fmt"
2020-07-29 18:40:55 -04:00
"io/ioutil"
2019-08-01 14:01:40 -04:00
"reflect"
"strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/spf13/cobra"
2020-04-17 15:25:06 -04:00
"k8s.io/klog/v2"
2019-08-01 14:01:40 -04:00
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
2022-03-24 09:06:05 -04:00
"k8s.io/kubectl/pkg/util/completion"
2019-08-01 14:01:40 -04:00
"k8s.io/kubectl/pkg/util/i18n"
2021-07-07 11:30:59 -04:00
"k8s.io/kubectl/pkg/util/slice"
2019-08-01 14:01:40 -04:00
"k8s.io/kubectl/pkg/util/templates"
)
var patchTypes = map [ string ] types . PatchType { "json" : types . JSONPatchType , "merge" : types . MergePatchType , "strategic" : types . StrategicMergePatchType }
// 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 {
resource . FilenameOptions
RecordFlags * genericclioptions . RecordFlags
PrintFlags * genericclioptions . PrintFlags
ToPrinter func ( string ) ( printers . ResourcePrinter , error )
Recorder genericclioptions . Recorder
2021-07-07 11:30:59 -04:00
Local bool
PatchType string
Patch string
PatchFile string
Subresource string
2019-08-01 14:01:40 -04:00
namespace string
enforceNamespace bool
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
dryRunStrategy cmdutil . DryRunStrategy
2022-03-09 09:51:50 -05:00
dryRunVerifier * resource . QueryParamVerifier
2019-08-01 14:01:40 -04:00
outputFormat string
args [ ] string
builder * resource . Builder
unstructuredClientForMapping func ( mapping * meta . RESTMapping ) ( resource . RESTClient , error )
2020-03-04 22:04:01 -05:00
fieldManager string
2019-08-01 14:01:40 -04:00
genericclioptions . IOStreams
}
var (
patchLong = templates . LongDesc ( i18n . T ( `
2021-07-06 15:05:26 -04:00
Update fields of a resource using strategic merge patch , a JSON merge patch , or a JSON patch .
2019-08-01 14:01:40 -04:00
JSON and YAML formats are accepted . ` ) )
patchExample = templates . Examples ( i18n . T ( `
2021-07-06 15:05:26 -04:00
# Partially update a node using a strategic merge patch , specifying the patch as JSON
2019-08-01 14:01:40 -04:00
kubectl patch node k8s - node - 1 - p ' { "spec" : { "unschedulable" : true } } '
2021-07-06 15:05:26 -04:00
# Partially update a node using a strategic merge patch , specifying the patch as YAML
2019-08-01 14:01:40 -04:00
kubectl patch node k8s - node - 1 - p $ ' spec : \ n unschedulable : true '
2021-07-06 15:05:26 -04:00
# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch
2019-08-01 14:01:40 -04:00
kubectl patch - f node . json - p ' { "spec" : { "unschedulable" : true } } '
2021-07-06 15:05:26 -04:00
# Update a container ' s image ; spec . containers [ * ] . name is required because it ' s a merge key
2019-08-01 14:01:40 -04:00
kubectl patch pod valid - pod - p ' { "spec" : { "containers" : [ { "name" : "kubernetes-serve-hostname" , "image" : "new image" } ] } } '
2021-07-06 15:05:26 -04:00
# Update a container ' s image using a JSON patch with positional arrays
2021-07-07 11:30:59 -04:00
kubectl patch pod valid - pod -- type = ' json ' - p = ' [ { "op" : "replace" , "path" : "/spec/containers/0/image" , "value" : "new image" } ] '
# Update a deployment ' s replicas through the scale subresource using a merge patch .
kubectl patch deployment nginx - deployment -- subresource = ' scale ' -- type = ' merge ' - p ' { "spec" : { "replicas" : 2 } } ' ` ) )
2019-08-01 14:01:40 -04:00
)
2021-07-07 11:30:59 -04:00
var supportedSubresources = [ ] string { "status" , "scale" }
2019-08-01 14:01:40 -04:00
func NewPatchOptions ( ioStreams genericclioptions . IOStreams ) * PatchOptions {
return & PatchOptions {
RecordFlags : genericclioptions . NewRecordFlags ( ) ,
Recorder : genericclioptions . NoopRecorder { } ,
PrintFlags : genericclioptions . NewPrintFlags ( "patched" ) . WithTypeSetter ( scheme . Scheme ) ,
IOStreams : ioStreams ,
}
}
func NewCmdPatch ( f cmdutil . Factory , ioStreams genericclioptions . IOStreams ) * cobra . Command {
o := NewPatchOptions ( ioStreams )
cmd := & cobra . Command {
2020-07-29 18:40:55 -04:00
Use : "patch (-f FILENAME | TYPE NAME) [-p PATCH|--patch-file FILE]" ,
2019-08-01 14:01:40 -04:00
DisableFlagsInUseLine : true ,
2021-07-06 15:05:26 -04:00
Short : i18n . T ( "Update fields of a resource" ) ,
2019-08-01 14:01:40 -04:00
Long : patchLong ,
Example : patchExample ,
2022-03-24 09:06:05 -04:00
ValidArgsFunction : completion . ResourceTypeAndNameCompletionFunc ( f ) ,
2019-08-01 14:01:40 -04:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . RunPatch ( ) )
} ,
}
o . RecordFlags . AddFlags ( cmd )
o . PrintFlags . AddFlags ( cmd )
cmd . Flags ( ) . StringVarP ( & o . Patch , "patch" , "p" , "" , "The patch to be applied to the resource JSON file." )
2020-07-29 18:40:55 -04:00
cmd . Flags ( ) . StringVar ( & o . PatchFile , "patch-file" , "" , "A file containing a patch to be applied to the resource." )
2019-08-01 14:01:40 -04:00
cmd . Flags ( ) . StringVar ( & o . PatchType , "type" , "strategic" , fmt . Sprintf ( "The type of patch being provided; one of %v" , sets . StringKeySet ( patchTypes ) . List ( ) ) )
cmdutil . AddDryRunFlag ( cmd )
cmdutil . AddFilenameOptionFlags ( cmd , & o . FilenameOptions , "identifying the resource to update" )
cmd . Flags ( ) . BoolVar ( & o . Local , "local" , o . Local , "If true, patch will operate on the content of the file, not the server-side resource." )
2020-03-04 22:04:01 -05:00
cmdutil . AddFieldManagerFlagVar ( cmd , & o . fieldManager , "kubectl-patch" )
2021-07-07 11:30:59 -04:00
cmdutil . AddSubresourceFlags ( cmd , & o . Subresource , "If specified, patch will operate on the subresource of the requested object." , supportedSubresources ... )
2019-08-01 14:01:40 -04:00
return cmd
}
func ( o * PatchOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
var err error
o . RecordFlags . Complete ( cmd )
o . Recorder , err = o . RecordFlags . ToRecorder ( )
if err != nil {
return err
}
o . outputFormat = cmdutil . GetFlagString ( cmd , "output" )
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
o . dryRunStrategy , err = cmdutil . GetDryRunStrategy ( cmd )
if err != nil {
return err
}
2019-08-01 14:01:40 -04:00
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
cmdutil . PrintFlagsWithDryRunStrategy ( o . PrintFlags , o . dryRunStrategy )
2019-08-01 14:01:40 -04:00
o . ToPrinter = func ( operation string ) ( printers . ResourcePrinter , error ) {
o . PrintFlags . NamePrintFlags . Operation = operation
return o . PrintFlags . ToPrinter ( )
}
o . namespace , o . enforceNamespace , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return err
}
o . args = args
o . builder = f . NewBuilder ( )
o . unstructuredClientForMapping = f . UnstructuredClientForMapping
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
dynamicClient , err := f . DynamicClient ( )
if err != nil {
return err
}
2022-03-09 09:51:50 -05:00
o . dryRunVerifier = resource . NewQueryParamVerifier ( dynamicClient , f . OpenAPIGetter ( ) , resource . QueryParamDryRun )
2019-08-01 14:01:40 -04:00
return nil
}
func ( o * PatchOptions ) Validate ( ) error {
2020-07-29 18:40:55 -04:00
if len ( o . Patch ) > 0 && len ( o . PatchFile ) > 0 {
return fmt . Errorf ( "cannot specify --patch and --patch-file together" )
}
if len ( o . Patch ) == 0 && len ( o . PatchFile ) == 0 {
return fmt . Errorf ( "must specify --patch or --patch-file containing the contents of the patch" )
}
2019-08-01 14:01:40 -04:00
if o . Local && len ( o . args ) != 0 {
return fmt . Errorf ( "cannot specify --local and server resources" )
}
2020-02-13 16:44:14 -05:00
if o . Local && o . dryRunStrategy == cmdutil . DryRunServer {
return fmt . Errorf ( "cannot specify --local and --dry-run=server - did you mean --dry-run=client?" )
}
2019-08-01 14:01:40 -04: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 )
}
}
2021-07-07 11:30:59 -04:00
if len ( o . Subresource ) > 0 && ! slice . ContainsString ( supportedSubresources , o . Subresource , nil ) {
return fmt . Errorf ( "invalid subresource value: %q. Must be one of %v" , o . Subresource , supportedSubresources )
}
2019-08-01 14:01:40 -04:00
return nil
}
func ( o * PatchOptions ) RunPatch ( ) error {
patchType := types . StrategicMergePatchType
if len ( o . PatchType ) != 0 {
patchType = patchTypes [ strings . ToLower ( o . PatchType ) ]
}
2020-07-29 18:40:55 -04:00
var patchBytes [ ] byte
if len ( o . PatchFile ) > 0 {
var err error
patchBytes , err = ioutil . ReadFile ( o . PatchFile )
if err != nil {
return fmt . Errorf ( "unable to read patch file: %v" , err )
}
} else {
patchBytes = [ ] byte ( o . Patch )
}
patchBytes , err := yaml . ToJSON ( patchBytes )
2019-08-01 14:01:40 -04:00
if err != nil {
return fmt . Errorf ( "unable to parse %q: %v" , o . Patch , err )
}
r := o . builder .
Unstructured ( ) .
ContinueOnError ( ) .
LocalParam ( o . Local ) .
NamespaceParam ( o . namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . enforceNamespace , & o . FilenameOptions ) .
2021-07-07 11:30:59 -04:00
Subresource ( o . Subresource ) .
2019-08-01 14:01:40 -04:00
ResourceTypeOrNameArgs ( false , o . args ... ) .
Flatten ( ) .
Do ( )
err = r . Err ( )
if err != nil {
return err
}
count := 0
err = r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
count ++
name , namespace := info . Name , info . Namespace
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
if ! o . Local && o . dryRunStrategy != cmdutil . DryRunClient {
2019-08-01 14:01:40 -04:00
mapping := info . ResourceMapping ( )
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
if o . dryRunStrategy == cmdutil . DryRunServer {
if err := o . dryRunVerifier . HasSupport ( mapping . GroupVersionKind ) ; err != nil {
return err
}
}
2019-08-01 14:01:40 -04:00
client , err := o . unstructuredClientForMapping ( mapping )
if err != nil {
return err
}
Use --dry-run=client,server in kubectl.
- Support --dry-run=server for subcommands apply, run, create, annotate,
expose, patch, label, autoscale, apply set-last-applied, drain, rollout undo
- Support --dry-run=server for set subcommands
- image
- resources
- serviceaccount
- selector
- env
- subject
- Support --dry-run=server for create subcommands.
- clusterrole
- clusterrolebinding
- configmap
- cronjob
- job
- deployment
- namespace
- poddisruptionbudget
- priorityclass
- quota
- role
- rolebinding
- service
- secret
- serviceaccount
- Remove GetClientSideDryRun
Kubernetes-commit: 13b80b48cd02b8263d910f2423a1f5b92cdf7644
2020-01-30 20:23:33 -05:00
helper := resource .
NewHelper ( client , mapping ) .
2020-03-04 22:04:01 -05:00
DryRun ( o . dryRunStrategy == cmdutil . DryRunServer ) .
2021-07-07 11:30:59 -04:00
WithFieldManager ( o . fieldManager ) .
WithSubresource ( o . Subresource )
2019-08-01 14:01:40 -04:00
patchedObj , err := helper . Patch ( namespace , name , patchType , patchBytes , nil )
if err != nil {
return err
}
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 {
klog . V ( 4 ) . Infof ( "error recording current command: %v" , err )
} else if len ( mergePatch ) > 0 {
2021-03-13 02:49:44 -05:00
if recordedObj , err := helper . Patch ( namespace , name , types . MergePatchType , mergePatch , nil ) ; err != nil {
2019-08-01 14:01:40 -04:00
klog . V ( 4 ) . Infof ( "error recording reason: %v" , err )
} else {
patchedObj = recordedObj
}
}
printer , err := o . ToPrinter ( patchOperation ( didPatch ) )
if err != nil {
return err
}
return printer . PrintObj ( patchedObj , o . Out )
}
originalObjJS , err := runtime . Encode ( unstructured . UnstructuredJSONScheme , info . Object )
if err != nil {
return err
}
originalPatchedObjJS , err := getPatchedJSON ( patchType , originalObjJS , patchBytes , info . Object . GetObjectKind ( ) . GroupVersionKind ( ) , scheme . Scheme )
if err != nil {
return err
}
targetObj , err := runtime . Decode ( unstructured . UnstructuredJSONScheme , originalPatchedObjJS )
if err != nil {
return err
}
didPatch := ! reflect . DeepEqual ( info . Object , targetObj )
printer , err := o . ToPrinter ( patchOperation ( didPatch ) )
if err != nil {
return err
}
return printer . PrintObj ( targetObj , o . Out )
} )
if err != nil {
return err
}
if count == 0 {
return fmt . Errorf ( "no objects passed to patch" )
}
return nil
}
func getPatchedJSON ( patchType types . PatchType , originalJS , patchJS [ ] byte , gvk schema . GroupVersionKind , creater runtime . ObjectCreater ) ( [ ] byte , error ) {
switch patchType {
case types . JSONPatchType :
patchObj , err := jsonpatch . DecodePatch ( patchJS )
if err != nil {
return nil , err
}
bytes , err := patchObj . Apply ( originalJS )
// TODO: This is pretty hacky, we need a better structured error from the json-patch
if err != nil && strings . Contains ( err . Error ( ) , "doc is missing key" ) {
msg := err . Error ( )
ix := strings . Index ( msg , "key:" )
key := msg [ ix + 5 : ]
return bytes , fmt . Errorf ( "Object to be patched is missing field (%s)" , key )
}
return bytes , err
case types . MergePatchType :
return jsonpatch . MergePatch ( originalJS , patchJS )
case types . StrategicMergePatchType :
// get a typed object for this GVK if we need to apply a strategic merge patch
obj , err := creater . New ( gvk )
if err != nil {
2021-02-17 03:14:36 -05:00
return nil , fmt . Errorf ( "strategic merge patch is not supported for %s locally, try --type merge" , gvk . String ( ) )
2019-08-01 14:01:40 -04:00
}
return strategicpatch . StrategicMergePatch ( originalJS , patchJS , obj )
default :
// only here as a safety net - go-restful filters content-type
return nil , fmt . Errorf ( "unknown Content-Type header for patch: %v" , patchType )
}
}
func patchOperation ( didPatch bool ) string {
if didPatch {
return "patched"
}
return "patched (no change)"
}