replace.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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. "io/ioutil"
  18. "os"
  19. "path/filepath"
  20. "github.com/renstrom/dedent"
  21. "github.com/spf13/cobra"
  22. "github.com/golang/glog"
  23. "k8s.io/kubernetes/pkg/kubectl"
  24. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  25. "k8s.io/kubernetes/pkg/kubectl/resource"
  26. "k8s.io/kubernetes/pkg/runtime"
  27. )
  28. // ReplaceOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
  29. // referencing the cmd.Flags()
  30. type ReplaceOptions struct {
  31. Filenames []string
  32. Recursive bool
  33. }
  34. var (
  35. replace_long = dedent.Dedent(`
  36. Replace a resource by filename or stdin.
  37. JSON and YAML formats are accepted. If replacing an existing resource, the
  38. complete resource spec must be provided. This can be obtained by
  39. $ kubectl get TYPE NAME -o yaml
  40. 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.`)
  41. replace_example = dedent.Dedent(`
  42. # Replace a pod using the data in pod.json.
  43. kubectl replace -f ./pod.json
  44. # Replace a pod based on the JSON passed into stdin.
  45. cat pod.json | kubectl replace -f -
  46. # Update a single-container pod's image version (tag) to v4
  47. kubectl get pod mypod -o yaml | sed 's/\(image: myimage\):.*$/\1:v4/' | kubectl replace -f -
  48. # Force replace, delete and then re-create the resource
  49. kubectl replace --force -f ./pod.json`)
  50. )
  51. func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command {
  52. options := &ReplaceOptions{}
  53. cmd := &cobra.Command{
  54. Use: "replace -f FILENAME",
  55. // update is deprecated.
  56. Aliases: []string{"update"},
  57. Short: "Replace a resource by filename or stdin",
  58. Long: replace_long,
  59. Example: replace_example,
  60. Run: func(cmd *cobra.Command, args []string) {
  61. cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
  62. err := RunReplace(f, out, cmd, args, options)
  63. cmdutil.CheckErr(err)
  64. },
  65. }
  66. usage := "Filename, directory, or URL to file to use to replace the resource."
  67. kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
  68. cmd.MarkFlagRequired("filename")
  69. cmd.Flags().Bool("force", false, "Delete and re-create the specified resource")
  70. cmd.Flags().Bool("cascade", false, "Only relevant during a force replace. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController).")
  71. cmd.Flags().Int("grace-period", -1, "Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.")
  72. cmd.Flags().Duration("timeout", 0, "Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
  73. cmdutil.AddValidateFlags(cmd)
  74. cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
  75. cmdutil.AddOutputFlagsForMutation(cmd)
  76. cmdutil.AddApplyAnnotationFlags(cmd)
  77. cmdutil.AddRecordFlag(cmd)
  78. cmdutil.AddInclude3rdPartyFlags(cmd)
  79. return cmd
  80. }
  81. func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *ReplaceOptions) error {
  82. if len(os.Args) > 1 && os.Args[1] == "update" {
  83. printDeprecationWarning("replace", "update")
  84. }
  85. schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
  86. if err != nil {
  87. return err
  88. }
  89. cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
  90. if err != nil {
  91. return err
  92. }
  93. force := cmdutil.GetFlagBool(cmd, "force")
  94. if len(options.Filenames) == 0 {
  95. return cmdutil.UsageError(cmd, "Must specify --filename to replace")
  96. }
  97. shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
  98. if force {
  99. return forceReplace(f, out, cmd, args, shortOutput, options)
  100. }
  101. mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
  102. r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
  103. Schema(schema).
  104. ContinueOnError().
  105. NamespaceParam(cmdNamespace).DefaultNamespace().
  106. FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
  107. Flatten().
  108. Do()
  109. err = r.Err()
  110. if err != nil {
  111. return err
  112. }
  113. return r.Visit(func(info *resource.Info, err error) error {
  114. if err != nil {
  115. return err
  116. }
  117. if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil {
  118. return cmdutil.AddSourceToErr("replacing", info.Source, err)
  119. }
  120. if cmdutil.ShouldRecord(cmd, info) {
  121. if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil {
  122. return cmdutil.AddSourceToErr("replacing", info.Source, err)
  123. }
  124. }
  125. // Serialize the object with the annotation applied.
  126. obj, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, true, info.Object)
  127. if err != nil {
  128. return cmdutil.AddSourceToErr("replacing", info.Source, err)
  129. }
  130. info.Refresh(obj, true)
  131. f.PrintObjectSpecificMessage(obj, out)
  132. cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "replaced")
  133. return nil
  134. })
  135. }
  136. func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *ReplaceOptions) error {
  137. schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
  138. if err != nil {
  139. return err
  140. }
  141. cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
  142. if err != nil {
  143. return err
  144. }
  145. for i, filename := range options.Filenames {
  146. if filename == "-" {
  147. tempDir, err := ioutil.TempDir("", "kubectl_replace_")
  148. if err != nil {
  149. return err
  150. }
  151. defer os.RemoveAll(tempDir)
  152. tempFilename := filepath.Join(tempDir, "resource.stdin")
  153. err = cmdutil.DumpReaderToFile(os.Stdin, tempFilename)
  154. if err != nil {
  155. return err
  156. }
  157. options.Filenames[i] = tempFilename
  158. }
  159. }
  160. mapper, typer, err := f.UnstructuredObject()
  161. if err != nil {
  162. return err
  163. }
  164. r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
  165. ContinueOnError().
  166. NamespaceParam(cmdNamespace).DefaultNamespace().
  167. FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
  168. ResourceTypeOrNameArgs(false, args...).RequireObject(false).
  169. Flatten().
  170. Do()
  171. err = r.Err()
  172. if err != nil {
  173. return err
  174. }
  175. //Replace will create a resource if it doesn't exist already, so ignore not found error
  176. ignoreNotFound := true
  177. // By default use a reaper to delete all related resources.
  178. if cmdutil.GetFlagBool(cmd, "cascade") {
  179. glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.")
  180. err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper, false)
  181. } else {
  182. err = DeleteResult(r, out, ignoreNotFound, shortOutput, mapper)
  183. }
  184. if err != nil {
  185. return err
  186. }
  187. r = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
  188. Schema(schema).
  189. ContinueOnError().
  190. NamespaceParam(cmdNamespace).DefaultNamespace().
  191. FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
  192. Flatten().
  193. Do()
  194. err = r.Err()
  195. if err != nil {
  196. return err
  197. }
  198. count := 0
  199. err = r.Visit(func(info *resource.Info, err error) error {
  200. if err != nil {
  201. return err
  202. }
  203. if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil {
  204. return err
  205. }
  206. if cmdutil.ShouldRecord(cmd, info) {
  207. if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil {
  208. return cmdutil.AddSourceToErr("replacing", info.Source, err)
  209. }
  210. }
  211. obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
  212. if err != nil {
  213. return err
  214. }
  215. count++
  216. info.Refresh(obj, true)
  217. f.PrintObjectSpecificMessage(obj, out)
  218. cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "replaced")
  219. return nil
  220. })
  221. if err != nil {
  222. return err
  223. }
  224. if count == 0 {
  225. return fmt.Errorf("no objects passed to replace")
  226. }
  227. return nil
  228. }