1
16
17 package apply
18
19 import (
20 "bytes"
21 "fmt"
22
23 "github.com/spf13/cobra"
24
25 "k8s.io/apimachinery/pkg/api/errors"
26 "k8s.io/apimachinery/pkg/api/meta"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/types"
29 "k8s.io/cli-runtime/pkg/genericclioptions"
30 "k8s.io/cli-runtime/pkg/genericiooptions"
31 "k8s.io/cli-runtime/pkg/printers"
32 "k8s.io/cli-runtime/pkg/resource"
33 cmdutil "k8s.io/kubectl/pkg/cmd/util"
34 "k8s.io/kubectl/pkg/cmd/util/editor"
35 "k8s.io/kubectl/pkg/scheme"
36 "k8s.io/kubectl/pkg/util"
37 "k8s.io/kubectl/pkg/util/i18n"
38 "k8s.io/kubectl/pkg/util/templates"
39 )
40
41
42 type SetLastAppliedOptions struct {
43 CreateAnnotation bool
44
45 PrintFlags *genericclioptions.PrintFlags
46 PrintObj printers.ResourcePrinterFunc
47
48 FilenameOptions resource.FilenameOptions
49
50 infoList []*resource.Info
51 namespace string
52 enforceNamespace bool
53 dryRunStrategy cmdutil.DryRunStrategy
54 shortOutput bool
55 output string
56 patchBufferList []PatchBuffer
57 builder *resource.Builder
58 unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
59
60 genericiooptions.IOStreams
61 }
62
63
64 type PatchBuffer struct {
65 Patch []byte
66 PatchType types.PatchType
67 }
68
69 var (
70 applySetLastAppliedLong = templates.LongDesc(i18n.T(`
71 Set the latest last-applied-configuration annotations by setting it to match the contents of a file.
72 This results in the last-applied-configuration being updated as though 'kubectl apply -f <file>' was run,
73 without updating any other parts of the object.`))
74
75 applySetLastAppliedExample = templates.Examples(i18n.T(`
76 # Set the last-applied-configuration of a resource to match the contents of a file
77 kubectl apply set-last-applied -f deploy.yaml
78
79 # Execute set-last-applied against each configuration file in a directory
80 kubectl apply set-last-applied -f path/
81
82 # 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
83 kubectl apply set-last-applied -f deploy.yaml --create-annotation=true
84 `))
85 )
86
87
88 func NewSetLastAppliedOptions(ioStreams genericiooptions.IOStreams) *SetLastAppliedOptions {
89 return &SetLastAppliedOptions{
90 PrintFlags: genericclioptions.NewPrintFlags("configured").WithTypeSetter(scheme.Scheme),
91 IOStreams: ioStreams,
92 }
93 }
94
95
96 func NewCmdApplySetLastApplied(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
97 o := NewSetLastAppliedOptions(ioStreams)
98 cmd := &cobra.Command{
99 Use: "set-last-applied -f FILENAME",
100 DisableFlagsInUseLine: true,
101 Short: i18n.T("Set the last-applied-configuration annotation on a live object to match the contents of a file"),
102 Long: applySetLastAppliedLong,
103 Example: applySetLastAppliedExample,
104 Run: func(cmd *cobra.Command, args []string) {
105 cmdutil.CheckErr(o.Complete(f, cmd))
106 cmdutil.CheckErr(o.Validate())
107 cmdutil.CheckErr(o.RunSetLastApplied())
108 },
109 }
110
111 o.PrintFlags.AddFlags(cmd)
112
113 cmdutil.AddDryRunFlag(cmd)
114 cmd.Flags().BoolVar(&o.CreateAnnotation, "create-annotation", o.CreateAnnotation, "Will create 'last-applied-configuration' annotations if current objects doesn't have one")
115 cmdutil.AddJsonFilenameFlag(cmd.Flags(), &o.FilenameOptions.Filenames, "Filename, directory, or URL to files that contains the last-applied-configuration annotations")
116
117 return cmd
118 }
119
120
121 func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
122 var err error
123 o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
124 if err != nil {
125 return err
126 }
127 o.output = cmdutil.GetFlagString(cmd, "output")
128 o.shortOutput = o.output == "name"
129
130 o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
131 if err != nil {
132 return err
133 }
134 o.builder = f.NewBuilder()
135 o.unstructuredClientForMapping = f.UnstructuredClientForMapping
136
137 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
138 printer, err := o.PrintFlags.ToPrinter()
139 if err != nil {
140 return err
141 }
142 o.PrintObj = printer.PrintObj
143
144 return nil
145 }
146
147
148 func (o *SetLastAppliedOptions) Validate() error {
149 r := o.builder.
150 Unstructured().
151 NamespaceParam(o.namespace).DefaultNamespace().
152 FilenameParam(o.enforceNamespace, &o.FilenameOptions).
153 Flatten().
154 Do()
155
156 err := r.Visit(func(info *resource.Info, err error) error {
157 if err != nil {
158 return err
159 }
160 patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.Object.(runtime.Unstructured))
161 if err != nil {
162 return err
163 }
164
165
166 if err := info.Get(); err != nil {
167 if errors.IsNotFound(err) {
168 return err
169 }
170 return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
171 }
172 originalBuf, err := util.GetOriginalConfiguration(info.Object)
173 if err != nil {
174 return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
175 }
176 if originalBuf == nil && !o.CreateAnnotation {
177 return fmt.Errorf("no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name)
178 }
179
180
181 if !bytes.Equal(cmdutil.StripComments(originalBuf), cmdutil.StripComments(diffBuf)) {
182 p := PatchBuffer{Patch: patchBuf, PatchType: patchType}
183 o.patchBufferList = append(o.patchBufferList, p)
184 o.infoList = append(o.infoList, info)
185 } else {
186 fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name)
187 }
188
189 return nil
190 })
191 return err
192 }
193
194
195 func (o *SetLastAppliedOptions) RunSetLastApplied() error {
196 for i, patch := range o.patchBufferList {
197 info := o.infoList[i]
198 finalObj := info.Object
199
200 if o.dryRunStrategy != cmdutil.DryRunClient {
201 mapping := info.ResourceMapping()
202 client, err := o.unstructuredClientForMapping(mapping)
203 if err != nil {
204 return err
205 }
206 helper := resource.
207 NewHelper(client, mapping).
208 DryRun(o.dryRunStrategy == cmdutil.DryRunServer)
209 finalObj, err = helper.Patch(info.Namespace, info.Name, patch.PatchType, patch.Patch, nil)
210 if err != nil {
211 return err
212 }
213 }
214 if err := o.PrintObj(finalObj, o.Out); err != nil {
215 return err
216 }
217 }
218 return nil
219 }
220
View as plain text