apply_set_last_applied.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. Copyright 2017 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 apply
  14. import (
  15. "bytes"
  16. "fmt"
  17. "github.com/spf13/cobra"
  18. "k8s.io/apimachinery/pkg/api/errors"
  19. "k8s.io/apimachinery/pkg/api/meta"
  20. "k8s.io/apimachinery/pkg/runtime"
  21. "k8s.io/apimachinery/pkg/types"
  22. "k8s.io/cli-runtime/pkg/genericclioptions"
  23. "k8s.io/cli-runtime/pkg/printers"
  24. "k8s.io/cli-runtime/pkg/resource"
  25. "k8s.io/kubernetes/pkg/kubectl"
  26. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  27. "k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
  28. "k8s.io/kubernetes/pkg/kubectl/scheme"
  29. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  30. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  31. )
  32. // SetLastAppliedOptions defines options for the `apply set-last-applied` command.`
  33. type SetLastAppliedOptions struct {
  34. CreateAnnotation bool
  35. PrintFlags *genericclioptions.PrintFlags
  36. PrintObj printers.ResourcePrinterFunc
  37. FilenameOptions resource.FilenameOptions
  38. infoList []*resource.Info
  39. namespace string
  40. enforceNamespace bool
  41. dryRun bool
  42. shortOutput bool
  43. output string
  44. patchBufferList []PatchBuffer
  45. builder *resource.Builder
  46. unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
  47. genericclioptions.IOStreams
  48. }
  49. // PatchBuffer caches changes that are to be applied.
  50. type PatchBuffer struct {
  51. Patch []byte
  52. PatchType types.PatchType
  53. }
  54. var (
  55. applySetLastAppliedLong = templates.LongDesc(i18n.T(`
  56. Set the latest last-applied-configuration annotations by setting it to match the contents of a file.
  57. This results in the last-applied-configuration being updated as though 'kubectl apply -f <file>' was run,
  58. without updating any other parts of the object.`))
  59. applySetLastAppliedExample = templates.Examples(i18n.T(`
  60. # Set the last-applied-configuration of a resource to match the contents of a file.
  61. kubectl apply set-last-applied -f deploy.yaml
  62. # Execute set-last-applied against each configuration file in a directory.
  63. kubectl apply set-last-applied -f path/
  64. # Set the last-applied-configuration of a resource to match the contents of a file, will create the annotation if it does not already exist.
  65. kubectl apply set-last-applied -f deploy.yaml --create-annotation=true
  66. `))
  67. )
  68. // NewSetLastAppliedOptions takes option arguments from a CLI stream and returns it at SetLastAppliedOptions type.
  69. func NewSetLastAppliedOptions(ioStreams genericclioptions.IOStreams) *SetLastAppliedOptions {
  70. return &SetLastAppliedOptions{
  71. PrintFlags: genericclioptions.NewPrintFlags("configured").WithTypeSetter(scheme.Scheme),
  72. IOStreams: ioStreams,
  73. }
  74. }
  75. // NewCmdApplySetLastApplied creates the cobra CLI `apply` subcommand `set-last-applied`.`
  76. func NewCmdApplySetLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  77. o := NewSetLastAppliedOptions(ioStreams)
  78. cmd := &cobra.Command{
  79. Use: "set-last-applied -f FILENAME",
  80. DisableFlagsInUseLine: true,
  81. Short: i18n.T("Set the last-applied-configuration annotation on a live object to match the contents of a file."),
  82. Long: applySetLastAppliedLong,
  83. Example: applySetLastAppliedExample,
  84. Run: func(cmd *cobra.Command, args []string) {
  85. cmdutil.CheckErr(o.Complete(f, cmd))
  86. cmdutil.CheckErr(o.Validate())
  87. cmdutil.CheckErr(o.RunSetLastApplied())
  88. },
  89. }
  90. o.PrintFlags.AddFlags(cmd)
  91. cmdutil.AddDryRunFlag(cmd)
  92. cmd.Flags().BoolVar(&o.CreateAnnotation, "create-annotation", o.CreateAnnotation, "Will create 'last-applied-configuration' annotations if current objects doesn't have one")
  93. cmdutil.AddJsonFilenameFlag(cmd.Flags(), &o.FilenameOptions.Filenames, "Filename, directory, or URL to files that contains the last-applied-configuration annotations")
  94. return cmd
  95. }
  96. // Complete populates dry-run and output flag options.
  97. func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
  98. o.dryRun = cmdutil.GetDryRunFlag(cmd)
  99. o.output = cmdutil.GetFlagString(cmd, "output")
  100. o.shortOutput = o.output == "name"
  101. var err error
  102. o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
  103. if err != nil {
  104. return err
  105. }
  106. o.builder = f.NewBuilder()
  107. o.unstructuredClientForMapping = f.UnstructuredClientForMapping
  108. if o.dryRun {
  109. // TODO(juanvallejo): This can be cleaned up even further by creating
  110. // a PrintFlags struct that binds the --dry-run flag, and whose
  111. // ToPrinter method returns a printer that understands how to print
  112. // this success message.
  113. o.PrintFlags.Complete("%s (dry run)")
  114. }
  115. printer, err := o.PrintFlags.ToPrinter()
  116. if err != nil {
  117. return err
  118. }
  119. o.PrintObj = printer.PrintObj
  120. return nil
  121. }
  122. // Validate checks SetLastAppliedOptions for validity.
  123. func (o *SetLastAppliedOptions) Validate() error {
  124. r := o.builder.
  125. Unstructured().
  126. NamespaceParam(o.namespace).DefaultNamespace().
  127. FilenameParam(o.enforceNamespace, &o.FilenameOptions).
  128. Flatten().
  129. Do()
  130. err := r.Visit(func(info *resource.Info, err error) error {
  131. if err != nil {
  132. return err
  133. }
  134. patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.Object.(runtime.Unstructured))
  135. if err != nil {
  136. return err
  137. }
  138. // Verify the object exists in the cluster before trying to patch it.
  139. if err := info.Get(); err != nil {
  140. if errors.IsNotFound(err) {
  141. return err
  142. }
  143. return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
  144. }
  145. originalBuf, err := kubectl.GetOriginalConfiguration(info.Object)
  146. if err != nil {
  147. return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
  148. }
  149. if originalBuf == nil && !o.CreateAnnotation {
  150. return fmt.Errorf("no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name)
  151. }
  152. //only add to PatchBufferList when changed
  153. if !bytes.Equal(cmdutil.StripComments(originalBuf), cmdutil.StripComments(diffBuf)) {
  154. p := PatchBuffer{Patch: patchBuf, PatchType: patchType}
  155. o.patchBufferList = append(o.patchBufferList, p)
  156. o.infoList = append(o.infoList, info)
  157. } else {
  158. fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name)
  159. }
  160. return nil
  161. })
  162. return err
  163. }
  164. // RunSetLastApplied executes the `set-last-applied` command according to SetLastAppliedOptions.
  165. func (o *SetLastAppliedOptions) RunSetLastApplied() error {
  166. for i, patch := range o.patchBufferList {
  167. info := o.infoList[i]
  168. finalObj := info.Object
  169. if !o.dryRun {
  170. mapping := info.ResourceMapping()
  171. client, err := o.unstructuredClientForMapping(mapping)
  172. if err != nil {
  173. return err
  174. }
  175. helper := resource.NewHelper(client, mapping)
  176. finalObj, err = helper.Patch(o.namespace, info.Name, patch.PatchType, patch.Patch, nil)
  177. if err != nil {
  178. return err
  179. }
  180. }
  181. if err := o.PrintObj(finalObj, o.Out); err != nil {
  182. return err
  183. }
  184. }
  185. return nil
  186. }