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 generateversioned "k8s.io/kubectl/pkg/generate/versioned"
36 "k8s.io/kubectl/pkg/polymorphichelpers"
37 "k8s.io/kubectl/pkg/scheme"
38 "k8s.io/kubectl/pkg/util/i18n"
39 "k8s.io/kubectl/pkg/util/templates"
40 )
41
42 var (
43 resourcesLong = templates.LongDesc(i18n.T(`
44 Specify compute resource requirements (CPU, memory) for any resource that defines a pod template. If a pod is successfully scheduled, it is guaranteed the amount of resource requested, but may burst up to its specified limits.
45
46 For each compute resource, if a limit is specified and a request is omitted, the request will default to the limit.
47
48 Possible resources include (case insensitive): %s.`))
49
50 resourcesExample = templates.Examples(`
51 # Set a deployments nginx container cpu limits to "200m" and memory to "512Mi"
52 kubectl set resources deployment nginx -c=nginx --limits=cpu=200m,memory=512Mi
53
54 # Set the resource request and limits for all containers in nginx
55 kubectl set resources deployment nginx --limits=cpu=200m,memory=512Mi --requests=cpu=100m,memory=256Mi
56
57 # Remove the resource requests for resources on containers in nginx
58 kubectl set resources deployment nginx --limits=cpu=0,memory=0 --requests=cpu=0,memory=0
59
60 # Print the result (in yaml format) of updating nginx container limits from a local, without hitting the server
61 kubectl set resources -f path/to/file.yaml --limits=cpu=200m,memory=512Mi --local -o yaml`)
62 )
63
64
65
66 type SetResourcesOptions struct {
67 resource.FilenameOptions
68
69 PrintFlags *genericclioptions.PrintFlags
70 RecordFlags *genericclioptions.RecordFlags
71
72 Infos []*resource.Info
73 Selector string
74 ContainerSelector string
75 Output string
76 All bool
77 Local bool
78 fieldManager string
79
80 DryRunStrategy cmdutil.DryRunStrategy
81
82 PrintObj printers.ResourcePrinterFunc
83 Recorder genericclioptions.Recorder
84
85 Limits string
86 Requests string
87 ResourceRequirements v1.ResourceRequirements
88
89 UpdatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
90 Resources []string
91
92 genericiooptions.IOStreams
93 }
94
95
96
97 func NewResourcesOptions(streams genericiooptions.IOStreams) *SetResourcesOptions {
98 return &SetResourcesOptions{
99 PrintFlags: genericclioptions.NewPrintFlags("resource requirements updated").WithTypeSetter(scheme.Scheme),
100 RecordFlags: genericclioptions.NewRecordFlags(),
101
102 Recorder: genericclioptions.NoopRecorder{},
103
104 ContainerSelector: "*",
105
106 IOStreams: streams,
107 }
108 }
109
110
111 func NewCmdResources(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
112 o := NewResourcesOptions(streams)
113
114 cmd := &cobra.Command{
115 Use: "resources (-f FILENAME | TYPE NAME) ([--limits=LIMITS & --requests=REQUESTS]",
116 DisableFlagsInUseLine: true,
117 Short: i18n.T("Update resource requests/limits on objects with pod templates"),
118 Long: fmt.Sprintf(resourcesLong, cmdutil.SuggestAPIResources("kubectl")),
119 Example: resourcesExample,
120 Run: func(cmd *cobra.Command, args []string) {
121 cmdutil.CheckErr(o.Complete(f, cmd, args))
122 cmdutil.CheckErr(o.Validate())
123 cmdutil.CheckErr(o.Run())
124 },
125 }
126
127 o.PrintFlags.AddFlags(cmd)
128 o.RecordFlags.AddFlags(cmd)
129
130
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 cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
136 cmd.Flags().StringVarP(&o.ContainerSelector, "containers", "c", o.ContainerSelector, "The names of containers in the selected pod templates to change, all containers are selected by default - may use wildcards")
137 cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set resources will NOT contact api-server but run locally.")
138 cmdutil.AddDryRunFlag(cmd)
139 cmd.Flags().StringVar(&o.Limits, "limits", o.Limits, "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.")
140 cmd.Flags().StringVar(&o.Requests, "requests", o.Requests, "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges.")
141 cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-set")
142 return cmd
143 }
144
145
146 func (o *SetResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
147 var err error
148
149 o.RecordFlags.Complete(cmd)
150 o.Recorder, err = o.RecordFlags.ToRecorder()
151 if err != nil {
152 return err
153 }
154
155 o.UpdatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
156 o.Output = cmdutil.GetFlagString(cmd, "output")
157 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
158 if err != nil {
159 return err
160 }
161
162 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
163 printer, err := o.PrintFlags.ToPrinter()
164 if err != nil {
165 return err
166 }
167 o.PrintObj = printer.PrintObj
168
169 cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
170 if err != nil && !(o.Local && clientcmd.IsEmptyConfig(err)) {
171 return err
172 }
173
174 builder := f.NewBuilder().
175 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
176 LocalParam(o.Local).
177 ContinueOnError().
178 NamespaceParam(cmdNamespace).DefaultNamespace().
179 FilenameParam(enforceNamespace, &o.FilenameOptions).
180 Flatten()
181
182 if !o.Local {
183 builder.LabelSelectorParam(o.Selector).
184 ResourceTypeOrNameArgs(o.All, args...).
185 Latest()
186 } else {
187
188
189
190
191
192 if len(args) > 0 {
193 return resource.LocalResourceError
194 }
195 }
196
197 o.Infos, err = builder.Do().Infos()
198 return err
199 }
200
201
202 func (o *SetResourcesOptions) Validate() error {
203 var err error
204 if o.Local && o.DryRunStrategy == cmdutil.DryRunServer {
205 return fmt.Errorf("cannot specify --local and --dry-run=server - did you mean --dry-run=client?")
206 }
207 if o.All && len(o.Selector) > 0 {
208 return fmt.Errorf("cannot set --all and --selector at the same time")
209 }
210 if len(o.Limits) == 0 && len(o.Requests) == 0 {
211 return fmt.Errorf("you must specify an update to requests or limits (in the form of --requests/--limits)")
212 }
213
214 o.ResourceRequirements, err = generateversioned.HandleResourceRequirementsV1(map[string]string{"limits": o.Limits, "requests": o.Requests})
215 if err != nil {
216 return err
217 }
218
219 return nil
220 }
221
222
223 func (o *SetResourcesOptions) Run() error {
224 allErrs := []error{}
225 patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
226 transformed := false
227 _, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
228 initContainers, _ := selectContainers(spec.InitContainers, o.ContainerSelector)
229 containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
230 containers = append(containers, initContainers...)
231 if len(containers) != 0 {
232 for i := range containers {
233 if len(o.Limits) != 0 && len(containers[i].Resources.Limits) == 0 {
234 containers[i].Resources.Limits = make(v1.ResourceList)
235 }
236 for key, value := range o.ResourceRequirements.Limits {
237 containers[i].Resources.Limits[key] = value
238 }
239
240 if len(o.Requests) != 0 && len(containers[i].Resources.Requests) == 0 {
241 containers[i].Resources.Requests = make(v1.ResourceList)
242 }
243 for key, value := range o.ResourceRequirements.Requests {
244 containers[i].Resources.Requests[key] = value
245 }
246 transformed = true
247 }
248 } else {
249 allErrs = append(allErrs, fmt.Errorf("error: unable to find container named %s", o.ContainerSelector))
250 }
251 return nil
252 })
253 if err != nil {
254 return nil, err
255 }
256 if !transformed {
257 return nil, nil
258 }
259
260 if err := o.Recorder.Record(obj); err != nil {
261 klog.V(4).Infof("error recording current command: %v", err)
262 }
263
264 return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
265 })
266
267 for _, patch := range patches {
268 info := patch.Info
269 name := info.ObjectName()
270 if patch.Err != nil {
271 allErrs = append(allErrs, fmt.Errorf("error: %s %v\n", name, patch.Err))
272 continue
273 }
274
275
276 if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
277 continue
278 }
279
280 if o.Local || o.DryRunStrategy == cmdutil.DryRunClient {
281 if err := o.PrintObj(info.Object, o.Out); err != nil {
282 allErrs = append(allErrs, err)
283 }
284 continue
285 }
286
287 actual, err := resource.
288 NewHelper(info.Client, info.Mapping).
289 DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
290 WithFieldManager(o.fieldManager).
291 Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
292 if err != nil {
293 allErrs = append(allErrs, fmt.Errorf("failed to patch resources update to pod template %v", err))
294 continue
295 }
296
297 if err := o.PrintObj(actual, o.Out); err != nil {
298 allErrs = append(allErrs, err)
299 }
300 }
301 return utilerrors.NewAggregate(allErrs)
302 }
303
View as plain text