replace.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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 replace
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "path/filepath"
  19. "time"
  20. "github.com/spf13/cobra"
  21. "k8s.io/klog"
  22. "k8s.io/apimachinery/pkg/api/errors"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. "k8s.io/cli-runtime/pkg/genericclioptions"
  26. "k8s.io/cli-runtime/pkg/resource"
  27. "k8s.io/kubernetes/pkg/kubectl"
  28. "k8s.io/kubernetes/pkg/kubectl/cmd/delete"
  29. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  30. "k8s.io/kubernetes/pkg/kubectl/scheme"
  31. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  32. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  33. "k8s.io/kubernetes/pkg/kubectl/validation"
  34. )
  35. var (
  36. replaceLong = templates.LongDesc(i18n.T(`
  37. Replace a resource by filename or stdin.
  38. JSON and YAML formats are accepted. If replacing an existing resource, the
  39. complete resource spec must be provided. This can be obtained by
  40. $ kubectl get TYPE NAME -o yaml`))
  41. replaceExample = templates.Examples(i18n.T(`
  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. type ReplaceOptions struct {
  52. PrintFlags *genericclioptions.PrintFlags
  53. RecordFlags *genericclioptions.RecordFlags
  54. DeleteFlags *delete.DeleteFlags
  55. DeleteOptions *delete.DeleteOptions
  56. PrintObj func(obj runtime.Object) error
  57. createAnnotation bool
  58. validate bool
  59. Schema validation.Schema
  60. Builder func() *resource.Builder
  61. BuilderArgs []string
  62. Namespace string
  63. EnforceNamespace bool
  64. Recorder genericclioptions.Recorder
  65. genericclioptions.IOStreams
  66. }
  67. func NewReplaceOptions(streams genericclioptions.IOStreams) *ReplaceOptions {
  68. return &ReplaceOptions{
  69. PrintFlags: genericclioptions.NewPrintFlags("replaced"),
  70. DeleteFlags: delete.NewDeleteFlags("to use to replace the resource."),
  71. IOStreams: streams,
  72. }
  73. }
  74. func NewCmdReplace(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
  75. o := NewReplaceOptions(streams)
  76. cmd := &cobra.Command{
  77. Use: "replace -f FILENAME",
  78. DisableFlagsInUseLine: true,
  79. Short: i18n.T("Replace a resource by filename or stdin"),
  80. Long: replaceLong,
  81. Example: replaceExample,
  82. Run: func(cmd *cobra.Command, args []string) {
  83. cmdutil.CheckErr(o.Complete(f, cmd, args))
  84. cmdutil.CheckErr(o.Validate(cmd))
  85. cmdutil.CheckErr(o.Run())
  86. },
  87. }
  88. o.PrintFlags.AddFlags(cmd)
  89. o.DeleteFlags.AddFlags(cmd)
  90. o.RecordFlags.AddFlags(cmd)
  91. cmdutil.AddValidateFlags(cmd)
  92. cmdutil.AddApplyAnnotationFlags(cmd)
  93. return cmd
  94. }
  95. func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  96. var err error
  97. o.RecordFlags.Complete(cmd)
  98. o.Recorder, err = o.RecordFlags.ToRecorder()
  99. if err != nil {
  100. return err
  101. }
  102. o.validate = cmdutil.GetFlagBool(cmd, "validate")
  103. o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
  104. printer, err := o.PrintFlags.ToPrinter()
  105. if err != nil {
  106. return err
  107. }
  108. o.PrintObj = func(obj runtime.Object) error {
  109. return printer.PrintObj(obj, o.Out)
  110. }
  111. dynamicClient, err := f.DynamicClient()
  112. if err != nil {
  113. return err
  114. }
  115. deleteOpts := o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
  116. //Replace will create a resource if it doesn't exist already, so ignore not found error
  117. deleteOpts.IgnoreNotFound = true
  118. if o.PrintFlags.OutputFormat != nil {
  119. deleteOpts.Output = *o.PrintFlags.OutputFormat
  120. }
  121. if deleteOpts.GracePeriod == 0 {
  122. // To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
  123. // into --grace-period=1 and wait until the object is successfully deleted.
  124. deleteOpts.GracePeriod = 1
  125. deleteOpts.WaitForDeletion = true
  126. }
  127. o.DeleteOptions = deleteOpts
  128. err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize()
  129. if err != nil {
  130. return err
  131. }
  132. schema, err := f.Validator(o.validate)
  133. if err != nil {
  134. return err
  135. }
  136. o.Schema = schema
  137. o.Builder = f.NewBuilder
  138. o.BuilderArgs = args
  139. o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
  140. if err != nil {
  141. return err
  142. }
  143. return nil
  144. }
  145. func (o *ReplaceOptions) Validate(cmd *cobra.Command) error {
  146. if o.DeleteOptions.GracePeriod >= 0 && !o.DeleteOptions.ForceDeletion {
  147. return fmt.Errorf("--grace-period must have --force specified")
  148. }
  149. if o.DeleteOptions.Timeout != 0 && !o.DeleteOptions.ForceDeletion {
  150. return fmt.Errorf("--timeout must have --force specified")
  151. }
  152. if cmdutil.IsFilenameSliceEmpty(o.DeleteOptions.FilenameOptions.Filenames, o.DeleteOptions.FilenameOptions.Kustomize) {
  153. return cmdutil.UsageErrorf(cmd, "Must specify --filename to replace")
  154. }
  155. return nil
  156. }
  157. func (o *ReplaceOptions) Run() error {
  158. if o.DeleteOptions.ForceDeletion {
  159. return o.forceReplace()
  160. }
  161. r := o.Builder().
  162. Unstructured().
  163. Schema(o.Schema).
  164. ContinueOnError().
  165. NamespaceParam(o.Namespace).DefaultNamespace().
  166. FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
  167. Flatten().
  168. Do()
  169. if err := r.Err(); err != nil {
  170. return err
  171. }
  172. return r.Visit(func(info *resource.Info, err error) error {
  173. if err != nil {
  174. return err
  175. }
  176. if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, info.Object, scheme.DefaultJSONEncoder()); err != nil {
  177. return cmdutil.AddSourceToErr("replacing", info.Source, err)
  178. }
  179. if err := o.Recorder.Record(info.Object); err != nil {
  180. klog.V(4).Infof("error recording current command: %v", err)
  181. }
  182. // Serialize the object with the annotation applied.
  183. obj, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, true, info.Object)
  184. if err != nil {
  185. return cmdutil.AddSourceToErr("replacing", info.Source, err)
  186. }
  187. info.Refresh(obj, true)
  188. return o.PrintObj(info.Object)
  189. })
  190. }
  191. func (o *ReplaceOptions) forceReplace() error {
  192. for i, filename := range o.DeleteOptions.FilenameOptions.Filenames {
  193. if filename == "-" {
  194. tempDir, err := ioutil.TempDir("", "kubectl_replace_")
  195. if err != nil {
  196. return err
  197. }
  198. defer os.RemoveAll(tempDir)
  199. tempFilename := filepath.Join(tempDir, "resource.stdin")
  200. err = cmdutil.DumpReaderToFile(os.Stdin, tempFilename)
  201. if err != nil {
  202. return err
  203. }
  204. o.DeleteOptions.FilenameOptions.Filenames[i] = tempFilename
  205. }
  206. }
  207. r := o.Builder().
  208. Unstructured().
  209. ContinueOnError().
  210. NamespaceParam(o.Namespace).DefaultNamespace().
  211. ResourceTypeOrNameArgs(false, o.BuilderArgs...).RequireObject(false).
  212. FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
  213. Flatten().
  214. Do()
  215. if err := r.Err(); err != nil {
  216. return err
  217. }
  218. if err := o.DeleteOptions.DeleteResult(r); err != nil {
  219. return err
  220. }
  221. timeout := o.DeleteOptions.Timeout
  222. if timeout == 0 {
  223. timeout = 5 * time.Minute
  224. }
  225. err := r.Visit(func(info *resource.Info, err error) error {
  226. if err != nil {
  227. return err
  228. }
  229. return wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
  230. if err := info.Get(); !errors.IsNotFound(err) {
  231. return false, err
  232. }
  233. return true, nil
  234. })
  235. })
  236. if err != nil {
  237. return err
  238. }
  239. r = o.Builder().
  240. Unstructured().
  241. Schema(o.Schema).
  242. ContinueOnError().
  243. NamespaceParam(o.Namespace).DefaultNamespace().
  244. FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
  245. Flatten().
  246. Do()
  247. err = r.Err()
  248. if err != nil {
  249. return err
  250. }
  251. count := 0
  252. err = r.Visit(func(info *resource.Info, err error) error {
  253. if err != nil {
  254. return err
  255. }
  256. if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, info.Object, scheme.DefaultJSONEncoder()); err != nil {
  257. return err
  258. }
  259. if err := o.Recorder.Record(info.Object); err != nil {
  260. klog.V(4).Infof("error recording current command: %v", err)
  261. }
  262. obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
  263. if err != nil {
  264. return err
  265. }
  266. count++
  267. info.Refresh(obj, true)
  268. return o.PrintObj(info.Object)
  269. })
  270. if err != nil {
  271. return err
  272. }
  273. if count == 0 {
  274. return fmt.Errorf("no objects passed to replace")
  275. }
  276. return nil
  277. }