patch.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cmd
  14. import (
  15. "fmt"
  16. "io"
  17. "strings"
  18. "github.com/evanphx/json-patch"
  19. "github.com/renstrom/dedent"
  20. "github.com/spf13/cobra"
  21. "k8s.io/kubernetes/pkg/api"
  22. "k8s.io/kubernetes/pkg/kubectl"
  23. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  24. "k8s.io/kubernetes/pkg/kubectl/resource"
  25. "k8s.io/kubernetes/pkg/runtime"
  26. "k8s.io/kubernetes/pkg/util/sets"
  27. "k8s.io/kubernetes/pkg/util/strategicpatch"
  28. "k8s.io/kubernetes/pkg/util/yaml"
  29. )
  30. var patchTypes = map[string]api.PatchType{"json": api.JSONPatchType, "merge": api.MergePatchType, "strategic": api.StrategicMergePatchType}
  31. // PatchOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
  32. // referencing the cmd.Flags()
  33. type PatchOptions struct {
  34. Filenames []string
  35. Recursive bool
  36. Local bool
  37. OutputFormat string
  38. }
  39. var (
  40. patch_long = dedent.Dedent(`
  41. Update field(s) of a resource using strategic merge patch
  42. JSON and YAML formats are accepted.
  43. Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/release-1.4/docs/api-reference/v1/definitions.html to find if a field is mutable.`)
  44. patch_example = dedent.Dedent(`
  45. # Partially update a node using strategic merge patch
  46. kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'
  47. # Partially update a node identified by the type and name specified in "node.json" using strategic merge patch
  48. kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'
  49. # Update a container's image; spec.containers[*].name is required because it's a merge key
  50. kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'
  51. # Update a container's image using a json patch with positional arrays
  52. kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'`)
  53. )
  54. func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
  55. options := &PatchOptions{}
  56. // retrieve a list of handled resources from printer as valid args
  57. validArgs, argAliases := []string{}, []string{}
  58. p, err := f.Printer(nil, kubectl.PrintOptions{
  59. ColumnLabels: []string{},
  60. })
  61. cmdutil.CheckErr(err)
  62. if p != nil {
  63. validArgs = p.HandledResources()
  64. argAliases = kubectl.ResourceAliases(validArgs)
  65. }
  66. cmd := &cobra.Command{
  67. Use: "patch (-f FILENAME | TYPE NAME) -p PATCH",
  68. Short: "Update field(s) of a resource using strategic merge patch",
  69. Long: patch_long,
  70. Example: patch_example,
  71. Run: func(cmd *cobra.Command, args []string) {
  72. options.OutputFormat = cmdutil.GetFlagString(cmd, "output")
  73. err := RunPatch(f, out, cmd, args, options)
  74. cmdutil.CheckErr(err)
  75. },
  76. ValidArgs: validArgs,
  77. ArgAliases: argAliases,
  78. }
  79. cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
  80. cmd.MarkFlagRequired("patch")
  81. cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
  82. cmdutil.AddPrinterFlags(cmd)
  83. cmdutil.AddRecordFlag(cmd)
  84. cmdutil.AddInclude3rdPartyFlags(cmd)
  85. usage := "Filename, directory, or URL to a file identifying the resource to update"
  86. kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
  87. cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
  88. cmd.Flags().BoolVar(&options.Local, "local", false, "If true, patch will operate on the content of the file, not the server-side resource.")
  89. return cmd
  90. }
  91. func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error {
  92. switch {
  93. case options.Local && len(args) != 0:
  94. return fmt.Errorf("cannot specify --local and server resources")
  95. }
  96. cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
  97. if err != nil {
  98. return err
  99. }
  100. patchType := api.StrategicMergePatchType
  101. patchTypeString := strings.ToLower(cmdutil.GetFlagString(cmd, "type"))
  102. if len(patchTypeString) != 0 {
  103. ok := false
  104. patchType, ok = patchTypes[patchTypeString]
  105. if !ok {
  106. return cmdutil.UsageError(cmd, fmt.Sprintf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), patchTypeString))
  107. }
  108. }
  109. patch := cmdutil.GetFlagString(cmd, "patch")
  110. if len(patch) == 0 {
  111. return cmdutil.UsageError(cmd, "Must specify -p to patch")
  112. }
  113. patchBytes, err := yaml.ToJSON([]byte(patch))
  114. if err != nil {
  115. return fmt.Errorf("unable to parse %q: %v", patch, err)
  116. }
  117. mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
  118. r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
  119. ContinueOnError().
  120. NamespaceParam(cmdNamespace).DefaultNamespace().
  121. FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
  122. ResourceTypeOrNameArgs(false, args...).
  123. Flatten().
  124. Do()
  125. err = r.Err()
  126. if err != nil {
  127. return err
  128. }
  129. count := 0
  130. err = r.Visit(func(info *resource.Info, err error) error {
  131. if err != nil {
  132. return err
  133. }
  134. name, namespace := info.Name, info.Namespace
  135. mapping := info.ResourceMapping()
  136. client, err := f.ClientForMapping(mapping)
  137. if err != nil {
  138. return err
  139. }
  140. if !options.Local {
  141. helper := resource.NewHelper(client, mapping)
  142. _, err := helper.Patch(namespace, name, patchType, patchBytes)
  143. if err != nil {
  144. return err
  145. }
  146. if cmdutil.ShouldRecord(cmd, info) {
  147. // don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed
  148. // don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command
  149. // also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this
  150. // record hint is likely to be invalid anyway, so avoid the bad hint
  151. patch, err := cmdutil.ChangeResourcePatch(info, f.Command())
  152. if err == nil {
  153. helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
  154. }
  155. }
  156. count++
  157. if options.OutputFormat == "name" || len(options.OutputFormat) == 0 {
  158. cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, "", name, "patched")
  159. }
  160. return nil
  161. }
  162. count++
  163. patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject)
  164. if err != nil {
  165. return err
  166. }
  167. originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object))
  168. if err != nil {
  169. return err
  170. }
  171. originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object))
  172. if err != nil {
  173. return err
  174. }
  175. targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS)
  176. if err != nil {
  177. return err
  178. }
  179. // TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything
  180. // rawExtension := &runtime.Unknown{
  181. // Raw: originalPatchedObjJS,
  182. // }
  183. printer, err := f.PrinterForMapping(cmd, mapping, false)
  184. if err != nil {
  185. return err
  186. }
  187. if err := printer.PrintObj(targetObj, out); err != nil {
  188. return err
  189. }
  190. return nil
  191. })
  192. if err != nil {
  193. return err
  194. }
  195. if count == 0 {
  196. return fmt.Errorf("no objects passed to patch")
  197. }
  198. return nil
  199. }
  200. func getPatchedJSON(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
  201. switch patchType {
  202. case api.JSONPatchType:
  203. patchObj, err := jsonpatch.DecodePatch(patchJS)
  204. if err != nil {
  205. return nil, err
  206. }
  207. return patchObj.Apply(originalJS)
  208. case api.MergePatchType:
  209. return jsonpatch.MergePatch(originalJS, patchJS)
  210. case api.StrategicMergePatchType:
  211. return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj)
  212. default:
  213. // only here as a safety net - go-restful filters content-type
  214. return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType)
  215. }
  216. }