helpers.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. /*
  2. Copyright 2014 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 util
  14. import (
  15. "bytes"
  16. "errors"
  17. "fmt"
  18. "io"
  19. "net/url"
  20. "os"
  21. "strings"
  22. "time"
  23. "github.com/evanphx/json-patch"
  24. "github.com/spf13/cobra"
  25. "github.com/spf13/pflag"
  26. kerrors "k8s.io/apimachinery/pkg/api/errors"
  27. "k8s.io/apimachinery/pkg/api/meta"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/runtime"
  30. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  31. "k8s.io/apimachinery/pkg/util/sets"
  32. "k8s.io/apimachinery/pkg/util/yaml"
  33. "k8s.io/cli-runtime/pkg/genericclioptions"
  34. "k8s.io/cli-runtime/pkg/resource"
  35. "k8s.io/client-go/dynamic"
  36. "k8s.io/client-go/rest"
  37. "k8s.io/client-go/scale"
  38. "k8s.io/client-go/tools/clientcmd"
  39. "k8s.io/klog"
  40. utilexec "k8s.io/utils/exec"
  41. )
  42. const (
  43. ApplyAnnotationsFlag = "save-config"
  44. DefaultErrorExitCode = 1
  45. )
  46. type debugError interface {
  47. DebugError() (msg string, args []interface{})
  48. }
  49. // AddSourceToErr adds handleResourcePrefix and source string to error message.
  50. // verb is the string like "creating", "deleting" etc.
  51. // source is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
  52. func AddSourceToErr(verb string, source string, err error) error {
  53. if source != "" {
  54. if statusError, ok := err.(kerrors.APIStatus); ok {
  55. status := statusError.Status()
  56. status.Message = fmt.Sprintf("error when %s %q: %v", verb, source, status.Message)
  57. return &kerrors.StatusError{ErrStatus: status}
  58. }
  59. return fmt.Errorf("error when %s %q: %v", verb, source, err)
  60. }
  61. return err
  62. }
  63. var fatalErrHandler = fatal
  64. // BehaviorOnFatal allows you to override the default behavior when a fatal
  65. // error occurs, which is to call os.Exit(code). You can pass 'panic' as a function
  66. // here if you prefer the panic() over os.Exit(1).
  67. func BehaviorOnFatal(f func(string, int)) {
  68. fatalErrHandler = f
  69. }
  70. // DefaultBehaviorOnFatal allows you to undo any previous override. Useful in
  71. // tests.
  72. func DefaultBehaviorOnFatal() {
  73. fatalErrHandler = fatal
  74. }
  75. // fatal prints the message (if provided) and then exits. If V(2) or greater,
  76. // klog.Fatal is invoked for extended information.
  77. func fatal(msg string, code int) {
  78. if klog.V(2) {
  79. klog.FatalDepth(2, msg)
  80. }
  81. if len(msg) > 0 {
  82. // add newline if needed
  83. if !strings.HasSuffix(msg, "\n") {
  84. msg += "\n"
  85. }
  86. fmt.Fprint(os.Stderr, msg)
  87. }
  88. os.Exit(code)
  89. }
  90. // ErrExit may be passed to CheckError to instruct it to output nothing but exit with
  91. // status code 1.
  92. var ErrExit = fmt.Errorf("exit")
  93. // CheckErr prints a user friendly error to STDERR and exits with a non-zero
  94. // exit code. Unrecognized errors will be printed with an "error: " prefix.
  95. //
  96. // This method is generic to the command in use and may be used by non-Kubectl
  97. // commands.
  98. func CheckErr(err error) {
  99. checkErr(err, fatalErrHandler)
  100. }
  101. // checkErr formats a given error as a string and calls the passed handleErr
  102. // func with that string and an kubectl exit code.
  103. func checkErr(err error, handleErr func(string, int)) {
  104. // unwrap aggregates of 1
  105. if agg, ok := err.(utilerrors.Aggregate); ok && len(agg.Errors()) == 1 {
  106. err = agg.Errors()[0]
  107. }
  108. if err == nil {
  109. return
  110. }
  111. switch {
  112. case err == ErrExit:
  113. handleErr("", DefaultErrorExitCode)
  114. case kerrors.IsInvalid(err):
  115. details := err.(*kerrors.StatusError).Status().Details
  116. s := fmt.Sprintf("The %s %q is invalid", details.Kind, details.Name)
  117. if len(details.Kind) == 0 && len(details.Name) == 0 {
  118. s = "The request is invalid"
  119. }
  120. if len(details.Causes) > 0 {
  121. errs := statusCausesToAggrError(details.Causes)
  122. handleErr(MultilineError(s+": ", errs), DefaultErrorExitCode)
  123. } else {
  124. handleErr(s, DefaultErrorExitCode)
  125. }
  126. case clientcmd.IsConfigurationInvalid(err):
  127. handleErr(MultilineError("Error in configuration: ", err), DefaultErrorExitCode)
  128. default:
  129. switch err := err.(type) {
  130. case *meta.NoResourceMatchError:
  131. switch {
  132. case len(err.PartialResource.Group) > 0 && len(err.PartialResource.Version) > 0:
  133. 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)
  134. case len(err.PartialResource.Group) > 0:
  135. handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q", err.PartialResource.Resource, err.PartialResource.Group), DefaultErrorExitCode)
  136. case len(err.PartialResource.Version) > 0:
  137. handleErr(fmt.Sprintf("the server doesn't have a resource type %q in version %q", err.PartialResource.Resource, err.PartialResource.Version), DefaultErrorExitCode)
  138. default:
  139. handleErr(fmt.Sprintf("the server doesn't have a resource type %q", err.PartialResource.Resource), DefaultErrorExitCode)
  140. }
  141. case utilerrors.Aggregate:
  142. handleErr(MultipleErrors(``, err.Errors()), DefaultErrorExitCode)
  143. case utilexec.ExitError:
  144. handleErr(err.Error(), err.ExitStatus())
  145. default: // for any other error type
  146. msg, ok := StandardErrorMessage(err)
  147. if !ok {
  148. msg = err.Error()
  149. if !strings.HasPrefix(msg, "error: ") {
  150. msg = fmt.Sprintf("error: %s", msg)
  151. }
  152. }
  153. handleErr(msg, DefaultErrorExitCode)
  154. }
  155. }
  156. }
  157. func statusCausesToAggrError(scs []metav1.StatusCause) utilerrors.Aggregate {
  158. errs := make([]error, 0, len(scs))
  159. errorMsgs := sets.NewString()
  160. for _, sc := range scs {
  161. // check for duplicate error messages and skip them
  162. msg := fmt.Sprintf("%s: %s", sc.Field, sc.Message)
  163. if errorMsgs.Has(msg) {
  164. continue
  165. }
  166. errorMsgs.Insert(msg)
  167. errs = append(errs, errors.New(msg))
  168. }
  169. return utilerrors.NewAggregate(errs)
  170. }
  171. // StandardErrorMessage translates common errors into a human readable message, or returns
  172. // false if the error is not one of the recognized types. It may also log extended
  173. // information to klog.
  174. //
  175. // This method is generic to the command in use and may be used by non-Kubectl
  176. // commands.
  177. func StandardErrorMessage(err error) (string, bool) {
  178. if debugErr, ok := err.(debugError); ok {
  179. klog.V(4).Infof(debugErr.DebugError())
  180. }
  181. status, isStatus := err.(kerrors.APIStatus)
  182. switch {
  183. case isStatus:
  184. switch s := status.Status(); {
  185. case s.Reason == metav1.StatusReasonUnauthorized:
  186. return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true
  187. case len(s.Reason) > 0:
  188. return fmt.Sprintf("Error from server (%s): %s", s.Reason, err.Error()), true
  189. default:
  190. return fmt.Sprintf("Error from server: %s", err.Error()), true
  191. }
  192. case kerrors.IsUnexpectedObjectError(err):
  193. return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true
  194. }
  195. switch t := err.(type) {
  196. case *url.Error:
  197. klog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
  198. switch {
  199. case strings.Contains(t.Err.Error(), "connection refused"):
  200. host := t.URL
  201. if server, err := url.Parse(t.URL); err == nil {
  202. host = server.Host
  203. }
  204. return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
  205. }
  206. return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
  207. }
  208. return "", false
  209. }
  210. // MultilineError returns a string representing an error that splits sub errors into their own
  211. // lines. The returned string will end with a newline.
  212. func MultilineError(prefix string, err error) string {
  213. if agg, ok := err.(utilerrors.Aggregate); ok {
  214. errs := utilerrors.Flatten(agg).Errors()
  215. buf := &bytes.Buffer{}
  216. switch len(errs) {
  217. case 0:
  218. return fmt.Sprintf("%s%v\n", prefix, err)
  219. case 1:
  220. return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0]))
  221. default:
  222. fmt.Fprintln(buf, prefix)
  223. for _, err := range errs {
  224. fmt.Fprintf(buf, "* %v\n", messageForError(err))
  225. }
  226. return buf.String()
  227. }
  228. }
  229. return fmt.Sprintf("%s%s\n", prefix, err)
  230. }
  231. // PrintErrorWithCauses prints an error's kind, name, and each of the error's causes in a new line.
  232. // The returned string will end with a newline.
  233. // Returns true if a case exists to handle the error type, or false otherwise.
  234. func PrintErrorWithCauses(err error, errOut io.Writer) bool {
  235. switch t := err.(type) {
  236. case *kerrors.StatusError:
  237. errorDetails := t.Status().Details
  238. if errorDetails != nil {
  239. fmt.Fprintf(errOut, "error: %s %q is invalid\n\n", errorDetails.Kind, errorDetails.Name)
  240. for _, cause := range errorDetails.Causes {
  241. fmt.Fprintf(errOut, "* %s: %s\n", cause.Field, cause.Message)
  242. }
  243. return true
  244. }
  245. }
  246. fmt.Fprintf(errOut, "error: %v\n", err)
  247. return false
  248. }
  249. // MultipleErrors returns a newline delimited string containing
  250. // the prefix and referenced errors in standard form.
  251. func MultipleErrors(prefix string, errs []error) string {
  252. buf := &bytes.Buffer{}
  253. for _, err := range errs {
  254. fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err))
  255. }
  256. return buf.String()
  257. }
  258. // messageForError returns the string representing the error.
  259. func messageForError(err error) string {
  260. msg, ok := StandardErrorMessage(err)
  261. if !ok {
  262. msg = err.Error()
  263. }
  264. return msg
  265. }
  266. func UsageErrorf(cmd *cobra.Command, format string, args ...interface{}) error {
  267. msg := fmt.Sprintf(format, args...)
  268. return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
  269. }
  270. func IsFilenameSliceEmpty(filenames []string, directory string) bool {
  271. return len(filenames) == 0 && directory == ""
  272. }
  273. func GetFlagString(cmd *cobra.Command, flag string) string {
  274. s, err := cmd.Flags().GetString(flag)
  275. if err != nil {
  276. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  277. }
  278. return s
  279. }
  280. // GetFlagStringSlice can be used to accept multiple argument with flag repetition (e.g. -f arg1,arg2 -f arg3 ...)
  281. func GetFlagStringSlice(cmd *cobra.Command, flag string) []string {
  282. s, err := cmd.Flags().GetStringSlice(flag)
  283. if err != nil {
  284. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  285. }
  286. return s
  287. }
  288. // GetFlagStringArray can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
  289. func GetFlagStringArray(cmd *cobra.Command, flag string) []string {
  290. s, err := cmd.Flags().GetStringArray(flag)
  291. if err != nil {
  292. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  293. }
  294. return s
  295. }
  296. func GetFlagBool(cmd *cobra.Command, flag string) bool {
  297. b, err := cmd.Flags().GetBool(flag)
  298. if err != nil {
  299. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  300. }
  301. return b
  302. }
  303. // Assumes the flag has a default value.
  304. func GetFlagInt(cmd *cobra.Command, flag string) int {
  305. i, err := cmd.Flags().GetInt(flag)
  306. if err != nil {
  307. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  308. }
  309. return i
  310. }
  311. // Assumes the flag has a default value.
  312. func GetFlagInt32(cmd *cobra.Command, flag string) int32 {
  313. i, err := cmd.Flags().GetInt32(flag)
  314. if err != nil {
  315. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  316. }
  317. return i
  318. }
  319. // Assumes the flag has a default value.
  320. func GetFlagInt64(cmd *cobra.Command, flag string) int64 {
  321. i, err := cmd.Flags().GetInt64(flag)
  322. if err != nil {
  323. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  324. }
  325. return i
  326. }
  327. func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
  328. d, err := cmd.Flags().GetDuration(flag)
  329. if err != nil {
  330. klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  331. }
  332. return d
  333. }
  334. func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
  335. timeout := GetFlagDuration(cmd, "pod-running-timeout")
  336. if timeout <= 0 {
  337. return timeout, fmt.Errorf("--pod-running-timeout must be higher than zero")
  338. }
  339. return timeout, nil
  340. }
  341. func AddValidateFlags(cmd *cobra.Command) {
  342. cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it")
  343. }
  344. func AddValidateOptionFlags(cmd *cobra.Command, options *ValidateOptions) {
  345. cmd.Flags().BoolVar(&options.EnableValidation, "validate", options.EnableValidation, "If true, use a schema to validate the input before sending it")
  346. }
  347. func AddFilenameOptionFlags(cmd *cobra.Command, options *resource.FilenameOptions, usage string) {
  348. AddJsonFilenameFlag(cmd.Flags(), &options.Filenames, "Filename, directory, or URL to files "+usage)
  349. AddKustomizeFlag(cmd.Flags(), &options.Kustomize)
  350. 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.")
  351. }
  352. func AddJsonFilenameFlag(flags *pflag.FlagSet, value *[]string, usage string) {
  353. flags.StringSliceVarP(value, "filename", "f", *value, usage)
  354. annotations := make([]string, 0, len(resource.FileExtensions))
  355. for _, ext := range resource.FileExtensions {
  356. annotations = append(annotations, strings.TrimLeft(ext, "."))
  357. }
  358. flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
  359. }
  360. // AddKustomizeFlag adds kustomize flag to a command
  361. func AddKustomizeFlag(flags *pflag.FlagSet, value *string) {
  362. flags.StringVarP(value, "kustomize", "k", *value, "Process the kustomization directory. This flag can't be used together with -f or -R.")
  363. }
  364. // AddDryRunFlag adds dry-run flag to a command. Usually used by mutations.
  365. func AddDryRunFlag(cmd *cobra.Command) {
  366. cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
  367. }
  368. func AddServerSideApplyFlags(cmd *cobra.Command) {
  369. cmd.Flags().Bool("experimental-server-side", false, "If true, apply runs in the server instead of the client. This is an alpha feature and flag.")
  370. cmd.Flags().Bool("experimental-force-conflicts", false, "If true, server-side apply will force the changes against conflicts. This is an alpha feature and flag.")
  371. cmd.Flags().String("experimental-field-manager", "kubectl", "Name of the manager used to track field ownership. This is an alpha feature and flag.")
  372. }
  373. func AddIncludeUninitializedFlag(cmd *cobra.Command) {
  374. cmd.Flags().Bool("include-uninitialized", false, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`)
  375. cmd.Flags().MarkDeprecated("include-uninitialized", "The Initializers feature has been removed. This flag is now a no-op, and will be removed in v1.15")
  376. }
  377. func AddPodRunningTimeoutFlag(cmd *cobra.Command, defaultTimeout time.Duration) {
  378. 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")
  379. }
  380. func AddApplyAnnotationFlags(cmd *cobra.Command) {
  381. 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.")
  382. }
  383. func AddApplyAnnotationVarFlags(cmd *cobra.Command, applyAnnotation *bool) {
  384. 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.")
  385. }
  386. // AddGeneratorFlags adds flags common to resource generation commands
  387. // TODO: need to take a pass at other generator commands to use this set of flags
  388. func AddGeneratorFlags(cmd *cobra.Command, defaultGenerator string) {
  389. cmd.Flags().String("generator", defaultGenerator, "The name of the API generator to use.")
  390. AddDryRunFlag(cmd)
  391. }
  392. type ValidateOptions struct {
  393. EnableValidation bool
  394. }
  395. // Merge requires JSON serialization
  396. // TODO: merge assumes JSON serialization, and does not properly abstract API retrieval
  397. func Merge(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) {
  398. // encode dst into versioned json and apply fragment directly too it
  399. target, err := runtime.Encode(codec, dst)
  400. if err != nil {
  401. return nil, err
  402. }
  403. patched, err := jsonpatch.MergePatch(target, []byte(fragment))
  404. if err != nil {
  405. return nil, err
  406. }
  407. out, err := runtime.Decode(codec, patched)
  408. if err != nil {
  409. return nil, err
  410. }
  411. return out, nil
  412. }
  413. // DumpReaderToFile writes all data from the given io.Reader to the specified file
  414. // (usually for temporary use).
  415. func DumpReaderToFile(reader io.Reader, filename string) error {
  416. f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
  417. if err != nil {
  418. return err
  419. }
  420. defer f.Close()
  421. buffer := make([]byte, 1024)
  422. for {
  423. count, err := reader.Read(buffer)
  424. if err == io.EOF {
  425. break
  426. }
  427. if err != nil {
  428. return err
  429. }
  430. _, err = f.Write(buffer[:count])
  431. if err != nil {
  432. return err
  433. }
  434. }
  435. return nil
  436. }
  437. func GetServerSideApplyFlag(cmd *cobra.Command) bool {
  438. return GetFlagBool(cmd, "experimental-server-side")
  439. }
  440. func GetForceConflictsFlag(cmd *cobra.Command) bool {
  441. return GetFlagBool(cmd, "experimental-force-conflicts")
  442. }
  443. func GetFieldManagerFlag(cmd *cobra.Command) string {
  444. return GetFlagString(cmd, "experimental-field-manager")
  445. }
  446. func GetDryRunFlag(cmd *cobra.Command) bool {
  447. return GetFlagBool(cmd, "dry-run")
  448. }
  449. // GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args
  450. func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
  451. foundPair := false
  452. for _, s := range args {
  453. nonResource := (strings.Contains(s, "=") && s[0] != '=') || (strings.HasSuffix(s, "-") && s != "-")
  454. switch {
  455. case !foundPair && nonResource:
  456. foundPair = true
  457. fallthrough
  458. case foundPair && nonResource:
  459. pairArgs = append(pairArgs, s)
  460. case !foundPair && !nonResource:
  461. resources = append(resources, s)
  462. case foundPair && !nonResource:
  463. err = fmt.Errorf("all resources must be specified before %s changes: %s", pairType, s)
  464. return
  465. }
  466. }
  467. return
  468. }
  469. // ParsePairs retrieves new and remove pairs (if supportRemove is true) from "KEY=VALUE or KEY-" pair args
  470. func ParsePairs(pairArgs []string, pairType string, supportRemove bool) (newPairs map[string]string, removePairs []string, err error) {
  471. newPairs = map[string]string{}
  472. if supportRemove {
  473. removePairs = []string{}
  474. }
  475. var invalidBuf bytes.Buffer
  476. var invalidBufNonEmpty bool
  477. for _, pairArg := range pairArgs {
  478. if strings.Contains(pairArg, "=") && pairArg[0] != '=' {
  479. parts := strings.SplitN(pairArg, "=", 2)
  480. if len(parts) != 2 {
  481. if invalidBufNonEmpty {
  482. invalidBuf.WriteString(", ")
  483. }
  484. invalidBuf.WriteString(pairArg)
  485. invalidBufNonEmpty = true
  486. } else {
  487. newPairs[parts[0]] = parts[1]
  488. }
  489. } else if supportRemove && strings.HasSuffix(pairArg, "-") && pairArg != "-" {
  490. removePairs = append(removePairs, pairArg[:len(pairArg)-1])
  491. } else {
  492. if invalidBufNonEmpty {
  493. invalidBuf.WriteString(", ")
  494. }
  495. invalidBuf.WriteString(pairArg)
  496. invalidBufNonEmpty = true
  497. }
  498. }
  499. if invalidBufNonEmpty {
  500. err = fmt.Errorf("invalid %s format: %s", pairType, invalidBuf.String())
  501. return
  502. }
  503. return
  504. }
  505. // IsSiblingCommandExists receives a pointer to a cobra command and a target string.
  506. // Returns true if the target string is found in the list of sibling commands.
  507. func IsSiblingCommandExists(cmd *cobra.Command, targetCmdName string) bool {
  508. for _, c := range cmd.Parent().Commands() {
  509. if c.Name() == targetCmdName {
  510. return true
  511. }
  512. }
  513. return false
  514. }
  515. // DefaultSubCommandRun prints a command's help string to the specified output if no
  516. // arguments (sub-commands) are provided, or a usage error otherwise.
  517. func DefaultSubCommandRun(out io.Writer) func(c *cobra.Command, args []string) {
  518. return func(c *cobra.Command, args []string) {
  519. c.SetOutput(out)
  520. RequireNoArguments(c, args)
  521. c.Help()
  522. CheckErr(ErrExit)
  523. }
  524. }
  525. // RequireNoArguments exits with a usage error if extra arguments are provided.
  526. func RequireNoArguments(c *cobra.Command, args []string) {
  527. if len(args) > 0 {
  528. CheckErr(UsageErrorf(c, "unknown command %q", strings.Join(args, " ")))
  529. }
  530. }
  531. // StripComments will transform a YAML file into JSON, thus dropping any comments
  532. // in it. Note that if the given file has a syntax error, the transformation will
  533. // fail and we will manually drop all comments from the file.
  534. func StripComments(file []byte) []byte {
  535. stripped := file
  536. stripped, err := yaml.ToJSON(stripped)
  537. if err != nil {
  538. stripped = ManualStrip(file)
  539. }
  540. return stripped
  541. }
  542. // ManualStrip is used for dropping comments from a YAML file
  543. func ManualStrip(file []byte) []byte {
  544. stripped := []byte{}
  545. lines := bytes.Split(file, []byte("\n"))
  546. for i, line := range lines {
  547. if bytes.HasPrefix(bytes.TrimSpace(line), []byte("#")) {
  548. continue
  549. }
  550. stripped = append(stripped, line...)
  551. if i < len(lines)-1 {
  552. stripped = append(stripped, '\n')
  553. }
  554. }
  555. return stripped
  556. }
  557. // ScaleClientFunc provides a ScalesGetter
  558. type ScaleClientFunc func(genericclioptions.RESTClientGetter) (scale.ScalesGetter, error)
  559. // ScaleClientFn gives a way to easily override the function for unit testing if needed.
  560. var ScaleClientFn ScaleClientFunc = scaleClient
  561. // scaleClient gives you back scale getter
  562. func scaleClient(restClientGetter genericclioptions.RESTClientGetter) (scale.ScalesGetter, error) {
  563. discoveryClient, err := restClientGetter.ToDiscoveryClient()
  564. if err != nil {
  565. return nil, err
  566. }
  567. clientConfig, err := restClientGetter.ToRESTConfig()
  568. if err != nil {
  569. return nil, err
  570. }
  571. setKubernetesDefaults(clientConfig)
  572. restClient, err := rest.RESTClientFor(clientConfig)
  573. if err != nil {
  574. return nil, err
  575. }
  576. resolver := scale.NewDiscoveryScaleKindResolver(discoveryClient)
  577. mapper, err := restClientGetter.ToRESTMapper()
  578. if err != nil {
  579. return nil, err
  580. }
  581. return scale.New(restClient, mapper, dynamic.LegacyAPIPathResolverFunc, resolver), nil
  582. }
  583. func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) {
  584. fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+
  585. "but it isn't available. "+
  586. "Falling back to %q.\n",
  587. newGeneratorName,
  588. oldGeneratorName,
  589. )
  590. }