1
16
17 package util
18
19 import (
20 "bytes"
21 "errors"
22 "fmt"
23 "io"
24 "net/url"
25 "os"
26 "strconv"
27 "strings"
28 "time"
29
30 jsonpatch "github.com/evanphx/json-patch"
31 "github.com/spf13/cobra"
32 "github.com/spf13/pflag"
33
34 apierrors "k8s.io/apimachinery/pkg/api/errors"
35 "k8s.io/apimachinery/pkg/api/meta"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/runtime"
38 utilerrors "k8s.io/apimachinery/pkg/util/errors"
39 "k8s.io/apimachinery/pkg/util/sets"
40 "k8s.io/apimachinery/pkg/util/strategicpatch"
41 "k8s.io/apimachinery/pkg/util/yaml"
42 "k8s.io/cli-runtime/pkg/genericclioptions"
43 "k8s.io/cli-runtime/pkg/resource"
44 "k8s.io/client-go/dynamic"
45 "k8s.io/client-go/rest"
46 "k8s.io/client-go/scale"
47 "k8s.io/client-go/tools/clientcmd"
48 "k8s.io/klog/v2"
49 utilexec "k8s.io/utils/exec"
50 )
51
52 const (
53 ApplyAnnotationsFlag = "save-config"
54 DefaultErrorExitCode = 1
55 DefaultChunkSize = 500
56 )
57
58 type debugError interface {
59 DebugError() (msg string, args []interface{})
60 }
61
62
63
64
65 func AddSourceToErr(verb string, source string, err error) error {
66 if source != "" {
67 if statusError, ok := err.(apierrors.APIStatus); ok {
68 status := statusError.Status()
69 status.Message = fmt.Sprintf("error when %s %q: %v", verb, source, status.Message)
70 return &apierrors.StatusError{ErrStatus: status}
71 }
72 return fmt.Errorf("error when %s %q: %v", verb, source, err)
73 }
74 return err
75 }
76
77 var fatalErrHandler = fatal
78
79
80
81
82 func BehaviorOnFatal(f func(string, int)) {
83 fatalErrHandler = f
84 }
85
86
87
88 func DefaultBehaviorOnFatal() {
89 fatalErrHandler = fatal
90 }
91
92
93
94
95 func fatal(msg string, code int) {
96
97
98 if klog.V(99).Enabled() {
99 klog.FatalDepth(2, msg)
100 }
101 if len(msg) > 0 {
102
103 if !strings.HasSuffix(msg, "\n") {
104 msg += "\n"
105 }
106 fmt.Fprint(os.Stderr, msg)
107 }
108 os.Exit(code)
109 }
110
111
112
113 var ErrExit = fmt.Errorf("exit")
114
115
116
117
118
119
120 func CheckErr(err error) {
121 checkErr(err, fatalErrHandler)
122 }
123
124
125
126
127
128
129
130 func CheckDiffErr(err error) {
131 checkErr(err, func(msg string, code int) {
132 fatalErrHandler(msg, code+1)
133 })
134 }
135
136
137
138 func isInvalidReasonStatusError(err error) bool {
139 if !apierrors.IsInvalid(err) {
140 return false
141 }
142 statusError, isStatusError := err.(*apierrors.StatusError)
143 if !isStatusError {
144 return false
145 }
146 status := statusError.Status()
147 return status.Reason == metav1.StatusReasonInvalid
148 }
149
150
151
152 func checkErr(err error, handleErr func(string, int)) {
153
154 if agg, ok := err.(utilerrors.Aggregate); ok && len(agg.Errors()) == 1 {
155 err = agg.Errors()[0]
156 }
157
158 if err == nil {
159 return
160 }
161
162 switch {
163 case err == ErrExit:
164 handleErr("", DefaultErrorExitCode)
165 case isInvalidReasonStatusError(err):
166 status := err.(*apierrors.StatusError).Status()
167 details := status.Details
168 s := "The request is invalid"
169 if details == nil {
170
171 if len(status.Message) > 0 {
172 s += ": " + status.Message
173 }
174 handleErr(s, DefaultErrorExitCode)
175 return
176 }
177 if len(details.Kind) != 0 || len(details.Name) != 0 {
178 s = fmt.Sprintf("The %s %q is invalid", details.Kind, details.Name)
179 } else if len(status.Message) > 0 && len(details.Causes) == 0 {
180
181
182 s += ": " + status.Message
183 }
184
185 if len(details.Causes) > 0 {
186 errs := statusCausesToAggrError(details.Causes)
187 handleErr(MultilineError(s+": ", errs), DefaultErrorExitCode)
188 } else {
189 handleErr(s, DefaultErrorExitCode)
190 }
191 case clientcmd.IsConfigurationInvalid(err):
192 handleErr(MultilineError("Error in configuration: ", err), DefaultErrorExitCode)
193 default:
194 switch err := err.(type) {
195 case *meta.NoResourceMatchError:
196 switch {
197 case len(err.PartialResource.Group) > 0 && len(err.PartialResource.Version) > 0:
198 handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q and version %q", err.PartialResource.Resource, err.PartialResource.Group, err.PartialResource.Version), DefaultErrorExitCode)
199 case len(err.PartialResource.Group) > 0:
200 handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q", err.PartialResource.Resource, err.PartialResource.Group), DefaultErrorExitCode)
201 case len(err.PartialResource.Version) > 0:
202 handleErr(fmt.Sprintf("the server doesn't have a resource type %q in version %q", err.PartialResource.Resource, err.PartialResource.Version), DefaultErrorExitCode)
203 default:
204 handleErr(fmt.Sprintf("the server doesn't have a resource type %q", err.PartialResource.Resource), DefaultErrorExitCode)
205 }
206 case utilerrors.Aggregate:
207 handleErr(MultipleErrors(``, err.Errors()), DefaultErrorExitCode)
208 case utilexec.ExitError:
209 handleErr(err.Error(), err.ExitStatus())
210 default:
211 msg, ok := StandardErrorMessage(err)
212 if !ok {
213 msg = err.Error()
214 if !strings.HasPrefix(msg, "error: ") {
215 msg = fmt.Sprintf("error: %s", msg)
216 }
217 }
218 handleErr(msg, DefaultErrorExitCode)
219 }
220 }
221 }
222
223 func statusCausesToAggrError(scs []metav1.StatusCause) utilerrors.Aggregate {
224 errs := make([]error, 0, len(scs))
225 errorMsgs := sets.NewString()
226 for _, sc := range scs {
227
228 msg := fmt.Sprintf("%s: %s", sc.Field, sc.Message)
229 if errorMsgs.Has(msg) {
230 continue
231 }
232 errorMsgs.Insert(msg)
233 errs = append(errs, errors.New(msg))
234 }
235 return utilerrors.NewAggregate(errs)
236 }
237
238
239
240
241
242
243
244 func StandardErrorMessage(err error) (string, bool) {
245 if debugErr, ok := err.(debugError); ok {
246 klog.V(4).Infof(debugErr.DebugError())
247 }
248 status, isStatus := err.(apierrors.APIStatus)
249 switch {
250 case isStatus:
251 switch s := status.Status(); {
252 case s.Reason == metav1.StatusReasonUnauthorized:
253 return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true
254 case len(s.Reason) > 0:
255 return fmt.Sprintf("Error from server (%s): %s", s.Reason, err.Error()), true
256 default:
257 return fmt.Sprintf("Error from server: %s", err.Error()), true
258 }
259 case apierrors.IsUnexpectedObjectError(err):
260 return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true
261 }
262 switch t := err.(type) {
263 case *url.Error:
264 klog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
265 switch {
266 case strings.Contains(t.Err.Error(), "connection refused"):
267 host := t.URL
268 if server, err := url.Parse(t.URL); err == nil {
269 host = server.Host
270 }
271 return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
272 }
273 return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
274 }
275 return "", false
276 }
277
278
279
280 func MultilineError(prefix string, err error) string {
281 if agg, ok := err.(utilerrors.Aggregate); ok {
282 errs := utilerrors.Flatten(agg).Errors()
283 buf := &bytes.Buffer{}
284 switch len(errs) {
285 case 0:
286 return fmt.Sprintf("%s%v\n", prefix, err)
287 case 1:
288 return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0]))
289 default:
290 fmt.Fprintln(buf, prefix)
291 for _, err := range errs {
292 fmt.Fprintf(buf, "* %v\n", messageForError(err))
293 }
294 return buf.String()
295 }
296 }
297 return fmt.Sprintf("%s%s\n", prefix, err)
298 }
299
300
301
302
303 func PrintErrorWithCauses(err error, errOut io.Writer) bool {
304 switch t := err.(type) {
305 case *apierrors.StatusError:
306 errorDetails := t.Status().Details
307 if errorDetails != nil {
308 fmt.Fprintf(errOut, "error: %s %q is invalid\n\n", errorDetails.Kind, errorDetails.Name)
309 for _, cause := range errorDetails.Causes {
310 fmt.Fprintf(errOut, "* %s: %s\n", cause.Field, cause.Message)
311 }
312 return true
313 }
314 }
315
316 fmt.Fprintf(errOut, "error: %v\n", err)
317 return false
318 }
319
320
321
322 func MultipleErrors(prefix string, errs []error) string {
323 buf := &bytes.Buffer{}
324 for _, err := range errs {
325 fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err))
326 }
327 return buf.String()
328 }
329
330
331 func messageForError(err error) string {
332 msg, ok := StandardErrorMessage(err)
333 if !ok {
334 msg = err.Error()
335 }
336 return msg
337 }
338
339 func UsageErrorf(cmd *cobra.Command, format string, args ...interface{}) error {
340 msg := fmt.Sprintf(format, args...)
341 return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
342 }
343
344 func IsFilenameSliceEmpty(filenames []string, directory string) bool {
345 return len(filenames) == 0 && directory == ""
346 }
347
348 func GetFlagString(cmd *cobra.Command, flag string) string {
349 s, err := cmd.Flags().GetString(flag)
350 if err != nil {
351 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
352 }
353 return s
354 }
355
356
357 func GetFlagStringSlice(cmd *cobra.Command, flag string) []string {
358 s, err := cmd.Flags().GetStringSlice(flag)
359 if err != nil {
360 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
361 }
362 return s
363 }
364
365
366 func GetFlagStringArray(cmd *cobra.Command, flag string) []string {
367 s, err := cmd.Flags().GetStringArray(flag)
368 if err != nil {
369 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
370 }
371 return s
372 }
373
374 func GetFlagBool(cmd *cobra.Command, flag string) bool {
375 b, err := cmd.Flags().GetBool(flag)
376 if err != nil {
377 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
378 }
379 return b
380 }
381
382
383 func GetFlagInt(cmd *cobra.Command, flag string) int {
384 i, err := cmd.Flags().GetInt(flag)
385 if err != nil {
386 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
387 }
388 return i
389 }
390
391
392 func GetFlagInt32(cmd *cobra.Command, flag string) int32 {
393 i, err := cmd.Flags().GetInt32(flag)
394 if err != nil {
395 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
396 }
397 return i
398 }
399
400
401 func GetFlagInt64(cmd *cobra.Command, flag string) int64 {
402 i, err := cmd.Flags().GetInt64(flag)
403 if err != nil {
404 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
405 }
406 return i
407 }
408
409 func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
410 d, err := cmd.Flags().GetDuration(flag)
411 if err != nil {
412 klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
413 }
414 return d
415 }
416
417 func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
418 timeout := GetFlagDuration(cmd, "pod-running-timeout")
419 if timeout <= 0 {
420 return timeout, fmt.Errorf("--pod-running-timeout must be higher than zero")
421 }
422 return timeout, nil
423 }
424
425 type FeatureGate string
426
427 const (
428 ApplySet FeatureGate = "KUBECTL_APPLYSET"
429 CmdPluginAsSubcommand FeatureGate = "KUBECTL_ENABLE_CMD_SHADOW"
430 OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
431 RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
432 PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
433 DebugCustomProfile FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
434 )
435
436
437
438 func (f FeatureGate) IsEnabled() bool {
439 return strings.ToLower(os.Getenv(string(f))) == "true"
440 }
441
442
443
444
445
446 func (f FeatureGate) IsDisabled() bool {
447 return strings.ToLower(os.Getenv(string(f))) == "false"
448 }
449
450 func AddValidateFlags(cmd *cobra.Command) {
451 cmd.Flags().String(
452 "validate",
453 "strict",
454 `Must be one of: strict (or true), warn, ignore (or false).
455 "true" or "strict" will use a schema to validate the input and fail the request if invalid. It will perform server side validation if ServerSideFieldValidation is enabled on the api-server, but will fall back to less reliable client-side validation if not.
456 "warn" will warn about unknown or duplicate fields without blocking the request if server-side field validation is enabled on the API server, and behave as "ignore" otherwise.
457 "false" or "ignore" will not perform any schema validation, silently dropping any unknown or duplicate fields.`,
458 )
459
460 cmd.Flags().Lookup("validate").NoOptDefVal = "strict"
461 }
462
463 func AddFilenameOptionFlags(cmd *cobra.Command, options *resource.FilenameOptions, usage string) {
464 AddJsonFilenameFlag(cmd.Flags(), &options.Filenames, "Filename, directory, or URL to files "+usage)
465 AddKustomizeFlag(cmd.Flags(), &options.Kustomize)
466 cmd.Flags().BoolVarP(&options.Recursive, "recursive", "R", options.Recursive, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
467 }
468
469 func AddJsonFilenameFlag(flags *pflag.FlagSet, value *[]string, usage string) {
470 flags.StringSliceVarP(value, "filename", "f", *value, usage)
471 annotations := make([]string, 0, len(resource.FileExtensions))
472 for _, ext := range resource.FileExtensions {
473 annotations = append(annotations, strings.TrimLeft(ext, "."))
474 }
475 flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
476 }
477
478
479 func AddKustomizeFlag(flags *pflag.FlagSet, value *string) {
480 flags.StringVarP(value, "kustomize", "k", *value, "Process the kustomization directory. This flag can't be used together with -f or -R.")
481 }
482
483
484 func AddDryRunFlag(cmd *cobra.Command) {
485 cmd.Flags().String(
486 "dry-run",
487 "none",
488 `Must be "none", "server", or "client". If client strategy, only print the object that would be sent, without sending it. If server strategy, submit server-side request without persisting the resource.`,
489 )
490 cmd.Flags().Lookup("dry-run").NoOptDefVal = "unchanged"
491 }
492
493 func AddFieldManagerFlagVar(cmd *cobra.Command, p *string, defaultFieldManager string) {
494 cmd.Flags().StringVar(p, "field-manager", defaultFieldManager, "Name of the manager used to track field ownership.")
495 }
496
497 func AddContainerVarFlags(cmd *cobra.Command, p *string, containerName string) {
498 cmd.Flags().StringVarP(p, "container", "c", containerName, "Container name. If omitted, use the kubectl.kubernetes.io/default-container annotation for selecting the container to be attached or the first container in the pod will be chosen")
499 }
500
501 func AddServerSideApplyFlags(cmd *cobra.Command) {
502 cmd.Flags().Bool("server-side", false, "If true, apply runs in the server instead of the client.")
503 cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts.")
504 }
505
506 func AddPodRunningTimeoutFlag(cmd *cobra.Command, defaultTimeout time.Duration) {
507 cmd.Flags().Duration("pod-running-timeout", defaultTimeout, "The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running")
508 }
509
510 func AddApplyAnnotationFlags(cmd *cobra.Command) {
511 cmd.Flags().Bool(ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
512 }
513
514 func AddApplyAnnotationVarFlags(cmd *cobra.Command, applyAnnotation *bool) {
515 cmd.Flags().BoolVar(applyAnnotation, ApplyAnnotationsFlag, *applyAnnotation, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
516 }
517
518 func AddChunkSizeFlag(cmd *cobra.Command, value *int64) {
519 cmd.Flags().Int64Var(value, "chunk-size", *value,
520 "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.")
521 }
522
523 func AddLabelSelectorFlagVar(cmd *cobra.Command, p *string) {
524 cmd.Flags().StringVarP(p, "selector", "l", *p, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.")
525 }
526
527 func AddPruningFlags(cmd *cobra.Command, prune *bool, pruneAllowlist *[]string, all *bool, applySetRef *string) {
528
529 cmd.Flags().StringArrayVar(pruneAllowlist, "prune-allowlist", *pruneAllowlist, "Overwrite the default allowlist with <group/version/kind> for --prune")
530 cmd.Flags().BoolVar(all, "all", *all, "Select all resources in the namespace of the specified resource types.")
531
532
533 if ApplySet.IsEnabled() {
534 cmd.Flags().StringVar(applySetRef, "applyset", *applySetRef, "[alpha] The name of the ApplySet that tracks which resources are being managed, for the purposes of determining what to prune. Live resources that are part of the ApplySet but have been removed from the provided configs will be deleted. Format: [RESOURCE][.GROUP]/NAME. A Secret will be used if no resource or group is specified.")
535 cmd.Flags().BoolVar(prune, "prune", *prune, "Automatically delete previously applied resource objects that do not appear in the provided configs. For alpha1, use with either -l or --all. For alpha2, use with --applyset.")
536 } else {
537
538 cmd.Flags().BoolVar(prune, "prune", *prune, "Automatically delete resource objects, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
539 }
540 }
541
542 func AddSubresourceFlags(cmd *cobra.Command, subresource *string, usage string, allowedSubresources ...string) {
543 cmd.Flags().StringVar(subresource, "subresource", "", fmt.Sprintf("%s Must be one of %v. This flag is beta and may change in the future.", usage, allowedSubresources))
544 CheckErr(cmd.RegisterFlagCompletionFunc("subresource", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
545 return allowedSubresources, cobra.ShellCompDirectiveNoFileComp
546 }))
547 }
548
549 type ValidateOptions struct {
550 ValidationDirective string
551 }
552
553
554
555
556 func Merge(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) {
557
558 target, err := runtime.Encode(codec, dst)
559 if err != nil {
560 return nil, err
561 }
562 patched, err := jsonpatch.MergePatch(target, []byte(fragment))
563 if err != nil {
564 return nil, err
565 }
566 out, err := runtime.Decode(codec, patched)
567 if err != nil {
568 return nil, err
569 }
570 return out, nil
571 }
572
573
574
575 func StrategicMerge(codec runtime.Codec, dst runtime.Object, fragment string, dataStruct runtime.Object) (runtime.Object, error) {
576 target, err := runtime.Encode(codec, dst)
577 if err != nil {
578 return nil, err
579 }
580 patched, err := strategicpatch.StrategicMergePatch(target, []byte(fragment), dataStruct)
581 if err != nil {
582 return nil, err
583 }
584 out, err := runtime.Decode(codec, patched)
585 if err != nil {
586 return nil, err
587 }
588 return out, nil
589 }
590
591
592
593 func JSONPatch(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) {
594 target, err := runtime.Encode(codec, dst)
595 if err != nil {
596 return nil, err
597 }
598 patch, err := jsonpatch.DecodePatch([]byte(fragment))
599 if err != nil {
600 return nil, err
601 }
602 patched, err := patch.Apply(target)
603 if err != nil {
604 return nil, err
605 }
606 out, err := runtime.Decode(codec, patched)
607 if err != nil {
608 return nil, err
609 }
610 return out, nil
611 }
612
613
614
615 func DumpReaderToFile(reader io.Reader, filename string) error {
616 f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
617 if err != nil {
618 return err
619 }
620 defer f.Close()
621
622 buffer := make([]byte, 1024)
623 for {
624 count, err := reader.Read(buffer)
625 if err == io.EOF {
626 break
627 }
628 if err != nil {
629 return err
630 }
631 _, err = f.Write(buffer[:count])
632 if err != nil {
633 return err
634 }
635 }
636 return nil
637 }
638
639 func GetServerSideApplyFlag(cmd *cobra.Command) bool {
640 return GetFlagBool(cmd, "server-side")
641 }
642
643 func GetForceConflictsFlag(cmd *cobra.Command) bool {
644 return GetFlagBool(cmd, "force-conflicts")
645 }
646
647 func GetFieldManagerFlag(cmd *cobra.Command) string {
648 return GetFlagString(cmd, "field-manager")
649 }
650
651 func GetValidationDirective(cmd *cobra.Command) (string, error) {
652 var validateFlag = GetFlagString(cmd, "validate")
653 b, err := strconv.ParseBool(validateFlag)
654 if err != nil {
655 switch validateFlag {
656 case "strict":
657 return metav1.FieldValidationStrict, nil
658 case "warn":
659 return metav1.FieldValidationWarn, nil
660 case "ignore":
661 return metav1.FieldValidationIgnore, nil
662 default:
663 return metav1.FieldValidationStrict, fmt.Errorf(`invalid - validate option %q; must be one of: strict (or true), warn, ignore (or false)`, validateFlag)
664 }
665 }
666
667 if b {
668 return metav1.FieldValidationStrict, nil
669 }
670 return metav1.FieldValidationIgnore, nil
671 }
672
673 type DryRunStrategy int
674
675 const (
676
677 DryRunNone DryRunStrategy = iota
678
679
680
681 DryRunClient
682
683
684
685
686
687
688
689
690
691
692
693 DryRunServer
694 )
695
696 func GetDryRunStrategy(cmd *cobra.Command) (DryRunStrategy, error) {
697 var dryRunFlag = GetFlagString(cmd, "dry-run")
698 b, err := strconv.ParseBool(dryRunFlag)
699
700 if err != nil {
701 switch dryRunFlag {
702 case cmd.Flag("dry-run").NoOptDefVal:
703 klog.Warning(`--dry-run is deprecated and can be replaced with --dry-run=client.`)
704 return DryRunClient, nil
705 case "client":
706 return DryRunClient, nil
707 case "server":
708 return DryRunServer, nil
709 case "none":
710 return DryRunNone, nil
711 default:
712 return DryRunNone, fmt.Errorf(`Invalid dry-run value (%v). Must be "none", "server", or "client".`, dryRunFlag)
713 }
714 }
715
716 if b {
717 klog.Warningf(`--dry-run=%v is deprecated (boolean value) and can be replaced with --dry-run=%s.`, dryRunFlag, "client")
718 return DryRunClient, nil
719 }
720 klog.Warningf(`--dry-run=%v is deprecated (boolean value) and can be replaced with --dry-run=%s.`, dryRunFlag, "none")
721 return DryRunNone, nil
722 }
723
724
725
726
727
728
729
730 func PrintFlagsWithDryRunStrategy(printFlags *genericclioptions.PrintFlags, dryRunStrategy DryRunStrategy) *genericclioptions.PrintFlags {
731 switch dryRunStrategy {
732 case DryRunClient:
733 printFlags.Complete("%s (dry run)")
734 case DryRunServer:
735 printFlags.Complete("%s (server dry run)")
736 }
737 return printFlags
738 }
739
740
741 func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
742 foundPair := false
743 for _, s := range args {
744 nonResource := (strings.Contains(s, "=") && s[0] != '=') || (strings.HasSuffix(s, "-") && s != "-")
745 switch {
746 case !foundPair && nonResource:
747 foundPair = true
748 fallthrough
749 case foundPair && nonResource:
750 pairArgs = append(pairArgs, s)
751 case !foundPair && !nonResource:
752 resources = append(resources, s)
753 case foundPair && !nonResource:
754 err = fmt.Errorf("all resources must be specified before %s changes: %s", pairType, s)
755 return
756 }
757 }
758 return
759 }
760
761
762 func ParsePairs(pairArgs []string, pairType string, supportRemove bool) (newPairs map[string]string, removePairs []string, err error) {
763 newPairs = map[string]string{}
764 if supportRemove {
765 removePairs = []string{}
766 }
767 var invalidBuf bytes.Buffer
768 var invalidBufNonEmpty bool
769 for _, pairArg := range pairArgs {
770 if strings.Contains(pairArg, "=") && pairArg[0] != '=' {
771 parts := strings.SplitN(pairArg, "=", 2)
772 if len(parts) != 2 {
773 if invalidBufNonEmpty {
774 invalidBuf.WriteString(", ")
775 }
776 invalidBuf.WriteString(pairArg)
777 invalidBufNonEmpty = true
778 } else {
779 newPairs[parts[0]] = parts[1]
780 }
781 } else if supportRemove && strings.HasSuffix(pairArg, "-") && pairArg != "-" {
782 removePairs = append(removePairs, pairArg[:len(pairArg)-1])
783 } else {
784 if invalidBufNonEmpty {
785 invalidBuf.WriteString(", ")
786 }
787 invalidBuf.WriteString(pairArg)
788 invalidBufNonEmpty = true
789 }
790 }
791 if invalidBufNonEmpty {
792 err = fmt.Errorf("invalid %s format: %s", pairType, invalidBuf.String())
793 return
794 }
795
796 return
797 }
798
799
800
801 func IsSiblingCommandExists(cmd *cobra.Command, targetCmdName string) bool {
802 for _, c := range cmd.Parent().Commands() {
803 if c.Name() == targetCmdName {
804 return true
805 }
806 }
807
808 return false
809 }
810
811
812
813 func DefaultSubCommandRun(out io.Writer) func(c *cobra.Command, args []string) {
814 return func(c *cobra.Command, args []string) {
815 c.SetOut(out)
816 c.SetErr(out)
817 RequireNoArguments(c, args)
818 c.Help()
819 CheckErr(ErrExit)
820 }
821 }
822
823
824 func RequireNoArguments(c *cobra.Command, args []string) {
825 if len(args) > 0 {
826 CheckErr(UsageErrorf(c, "unknown command %q", strings.Join(args, " ")))
827 }
828 }
829
830
831
832
833 func StripComments(file []byte) []byte {
834 stripped := file
835 stripped, err := yaml.ToJSON(stripped)
836 if err != nil {
837 stripped = ManualStrip(file)
838 }
839 return stripped
840 }
841
842
843 func ManualStrip(file []byte) []byte {
844 stripped := []byte{}
845 lines := bytes.Split(file, []byte("\n"))
846 for i, line := range lines {
847 trimline := bytes.TrimSpace(line)
848
849 if bytes.HasPrefix(trimline, []byte("#")) && !bytes.HasPrefix(trimline, []byte("#!")) {
850 continue
851 }
852 stripped = append(stripped, line...)
853 if i < len(lines)-1 {
854 stripped = append(stripped, '\n')
855 }
856 }
857 return stripped
858 }
859
860
861 type ScaleClientFunc func(genericclioptions.RESTClientGetter) (scale.ScalesGetter, error)
862
863
864 var ScaleClientFn ScaleClientFunc = scaleClient
865
866
867 func scaleClient(restClientGetter genericclioptions.RESTClientGetter) (scale.ScalesGetter, error) {
868 discoveryClient, err := restClientGetter.ToDiscoveryClient()
869 if err != nil {
870 return nil, err
871 }
872
873 clientConfig, err := restClientGetter.ToRESTConfig()
874 if err != nil {
875 return nil, err
876 }
877
878 setKubernetesDefaults(clientConfig)
879 restClient, err := rest.RESTClientFor(clientConfig)
880 if err != nil {
881 return nil, err
882 }
883 resolver := scale.NewDiscoveryScaleKindResolver(discoveryClient)
884 mapper, err := restClientGetter.ToRESTMapper()
885 if err != nil {
886 return nil, err
887 }
888
889 return scale.New(restClient, mapper, dynamic.LegacyAPIPathResolverFunc, resolver), nil
890 }
891
892 func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) {
893 fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+
894 "but it isn't available. "+
895 "Falling back to %q.\n",
896 newGeneratorName,
897 oldGeneratorName,
898 )
899 }
900
901
902 func Difference(fullArray []string, subArray []string) []string {
903 exclude := make(map[string]bool, len(subArray))
904 for _, elem := range subArray {
905 exclude[elem] = true
906 }
907 var result []string
908 for _, elem := range fullArray {
909 if _, found := exclude[elem]; !found {
910 result = append(result, elem)
911 }
912 }
913 return result
914 }
915
View as plain text