1
16
17 package set
18
19 import (
20 "fmt"
21
22 "github.com/spf13/cobra"
23 "k8s.io/klog/v2"
24
25 v1 "k8s.io/api/core/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/types"
28 utilerrors "k8s.io/apimachinery/pkg/util/errors"
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 "k8s.io/client-go/tools/clientcmd"
34 cmdutil "k8s.io/kubectl/pkg/cmd/util"
35 "k8s.io/kubectl/pkg/polymorphichelpers"
36 "k8s.io/kubectl/pkg/scheme"
37 "k8s.io/kubectl/pkg/util/i18n"
38 "k8s.io/kubectl/pkg/util/templates"
39 )
40
41
42
43 type SetImageOptions struct {
44 resource.FilenameOptions
45
46 PrintFlags *genericclioptions.PrintFlags
47 RecordFlags *genericclioptions.RecordFlags
48
49 Infos []*resource.Info
50 Selector string
51 DryRunStrategy cmdutil.DryRunStrategy
52 All bool
53 Output string
54 Local bool
55 ResolveImage ImageResolverFunc
56 fieldManager string
57
58 PrintObj printers.ResourcePrinterFunc
59 Recorder genericclioptions.Recorder
60
61 UpdatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
62 Resources []string
63 ContainerImages map[string]string
64
65 genericiooptions.IOStreams
66 }
67
68
69
70
71 type ImageResolverFunc func(in string) (string, error)
72
73
74 var ImageResolver = resolveImageFunc
75
76 var (
77 imageResources = i18n.T(`
78 pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), statefulset (sts), cronjob (cj), replicaset (rs)`)
79
80 imageLong = templates.LongDesc(i18n.T(`
81 Update existing container image(s) of resources.
82
83 Possible resources include (case insensitive):
84 `) + imageResources)
85
86 imageExample = templates.Examples(`
87 # Set a deployment's nginx container image to 'nginx:1.9.1', and its busybox container image to 'busybox'
88 kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
89
90 # Update all deployments' and rc's nginx container's image to 'nginx:1.9.1'
91 kubectl set image deployments,rc nginx=nginx:1.9.1 --all
92
93 # Update image of all containers of daemonset abc to 'nginx:1.9.1'
94 kubectl set image daemonset abc *=nginx:1.9.1
95
96 # Print result (in yaml format) of updating nginx container image from local file, without hitting the server
97 kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml`)
98 )
99
100
101 func NewImageOptions(streams genericiooptions.IOStreams) *SetImageOptions {
102 return &SetImageOptions{
103 PrintFlags: genericclioptions.NewPrintFlags("image updated").WithTypeSetter(scheme.Scheme),
104 RecordFlags: genericclioptions.NewRecordFlags(),
105
106 Recorder: genericclioptions.NoopRecorder{},
107
108 IOStreams: streams,
109 }
110 }
111
112
113 func NewCmdImage(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
114 o := NewImageOptions(streams)
115
116 cmd := &cobra.Command{
117 Use: "image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N",
118 DisableFlagsInUseLine: true,
119 Short: i18n.T("Update the image of a pod template"),
120 Long: imageLong,
121 Example: imageExample,
122 Run: func(cmd *cobra.Command, args []string) {
123 cmdutil.CheckErr(o.Complete(f, cmd, args))
124 cmdutil.CheckErr(o.Validate())
125 cmdutil.CheckErr(o.Run())
126 },
127 }
128
129 o.PrintFlags.AddFlags(cmd)
130 o.RecordFlags.AddFlags(cmd)
131
132 usage := "identifying the resource to get from a server."
133 cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
134 cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources, in the namespace of the specified resource types")
135 cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set image will NOT contact api-server but run locally.")
136 cmdutil.AddDryRunFlag(cmd)
137 cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-set")
138 cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
139
140 return cmd
141 }
142
143
144 func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
145 var err error
146
147 o.RecordFlags.Complete(cmd)
148 o.Recorder, err = o.RecordFlags.ToRecorder()
149 if err != nil {
150 return err
151 }
152
153 o.UpdatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
154 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
155 if err != nil {
156 return err
157 }
158
159 o.Output = cmdutil.GetFlagString(cmd, "output")
160 o.ResolveImage = ImageResolver
161
162 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
163 printer, err := o.PrintFlags.ToPrinter()
164 if err != nil {
165 return err
166 }
167
168 o.PrintObj = printer.PrintObj
169
170 cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
171 if err != nil && !(o.Local && clientcmd.IsEmptyConfig(err)) {
172 return err
173 }
174
175 o.Resources, o.ContainerImages, err = getResourcesAndImages(args)
176 if err != nil {
177 return err
178 }
179
180 builder := f.NewBuilder().
181 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
182 LocalParam(o.Local).
183 ContinueOnError().
184 NamespaceParam(cmdNamespace).DefaultNamespace().
185 FilenameParam(enforceNamespace, &o.FilenameOptions).
186 Flatten()
187
188 if !o.Local {
189 builder.LabelSelectorParam(o.Selector).
190 ResourceTypeOrNameArgs(o.All, o.Resources...).
191 Latest()
192 } else {
193
194
195
196 if len(o.Resources) > 0 {
197 return resource.LocalResourceError
198 }
199 }
200
201 o.Infos, err = builder.Do().Infos()
202 if err != nil {
203 return err
204 }
205
206 return nil
207 }
208
209
210 func (o *SetImageOptions) Validate() error {
211 errors := []error{}
212 if o.All && len(o.Selector) > 0 {
213 errors = append(errors, fmt.Errorf("cannot set --all and --selector at the same time"))
214 }
215 if len(o.Resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
216 errors = append(errors, fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>"))
217 }
218 if len(o.ContainerImages) < 1 {
219 errors = append(errors, fmt.Errorf("at least one image update is required"))
220 } else if len(o.ContainerImages) > 1 && hasWildcardKey(o.ContainerImages) {
221 errors = append(errors, fmt.Errorf("all containers are already specified by *, but saw more than one container_name=container_image pairs"))
222 }
223 if o.Local && o.DryRunStrategy == cmdutil.DryRunServer {
224 errors = append(errors, fmt.Errorf("cannot specify --local and --dry-run=server - did you mean --dry-run=client?"))
225 }
226 return utilerrors.NewAggregate(errors)
227 }
228
229
230 func (o *SetImageOptions) Run() error {
231 allErrs := []error{}
232
233 patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
234 _, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
235 for name, image := range o.ContainerImages {
236 resolvedImageName, err := o.ResolveImage(image)
237 if err != nil {
238 allErrs = append(allErrs, fmt.Errorf("error: unable to resolve image %q for container %q: %v", image, name, err))
239 if name == "*" {
240 break
241 }
242 continue
243 }
244
245 initContainerFound := setImage(spec.InitContainers, name, resolvedImageName)
246 containerFound := setImage(spec.Containers, name, resolvedImageName)
247 if !containerFound && !initContainerFound {
248 allErrs = append(allErrs, fmt.Errorf("error: unable to find container named %q", name))
249 }
250 }
251 return nil
252 })
253 if err != nil {
254 return nil, err
255 }
256
257 if err := o.Recorder.Record(obj); err != nil {
258 klog.V(4).Infof("error recording current command: %v", err)
259 }
260
261 return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
262 })
263
264 for _, patch := range patches {
265 info := patch.Info
266 if patch.Err != nil {
267 name := info.ObjectName()
268 allErrs = append(allErrs, fmt.Errorf("error: %s %v\n", name, patch.Err))
269 continue
270 }
271
272
273 if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
274 continue
275 }
276
277 if o.Local || o.DryRunStrategy == cmdutil.DryRunClient {
278 if err := o.PrintObj(info.Object, o.Out); err != nil {
279 allErrs = append(allErrs, err)
280 }
281 continue
282 }
283
284
285 actual, err := resource.
286 NewHelper(info.Client, info.Mapping).
287 DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
288 WithFieldManager(o.fieldManager).
289 Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
290 if err != nil {
291 allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v", err))
292 continue
293 }
294
295 if err := o.PrintObj(actual, o.Out); err != nil {
296 allErrs = append(allErrs, err)
297 }
298 }
299 return utilerrors.NewAggregate(allErrs)
300 }
301
302 func setImage(containers []v1.Container, containerName string, image string) bool {
303 containerFound := false
304
305 for i, c := range containers {
306 if c.Name == containerName || containerName == "*" {
307 containerFound = true
308 containers[i].Image = image
309 }
310 }
311 return containerFound
312 }
313
314
315 func getResourcesAndImages(args []string) (resources []string, containerImages map[string]string, err error) {
316 pairType := "image"
317 resources, imageArgs, err := cmdutil.GetResourcesAndPairs(args, pairType)
318 if err != nil {
319 return
320 }
321 containerImages, _, err = cmdutil.ParsePairs(imageArgs, pairType, false)
322 return
323 }
324
325 func hasWildcardKey(containerImages map[string]string) bool {
326 _, ok := containerImages["*"]
327 return ok
328 }
329
330
331 func resolveImageFunc(in string) (string, error) {
332 return in, nil
333 }
334
View as plain text