helpers.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  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. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "net/url"
  22. "os"
  23. "strings"
  24. "time"
  25. kerrors "k8s.io/kubernetes/pkg/api/errors"
  26. "k8s.io/kubernetes/pkg/api/meta"
  27. "k8s.io/kubernetes/pkg/api/unversioned"
  28. "k8s.io/kubernetes/pkg/apimachinery/registered"
  29. "k8s.io/kubernetes/pkg/apis/extensions"
  30. "k8s.io/kubernetes/pkg/client/typed/discovery"
  31. "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
  32. "k8s.io/kubernetes/pkg/kubectl"
  33. "k8s.io/kubernetes/pkg/kubectl/resource"
  34. "k8s.io/kubernetes/pkg/runtime"
  35. utilerrors "k8s.io/kubernetes/pkg/util/errors"
  36. utilexec "k8s.io/kubernetes/pkg/util/exec"
  37. "k8s.io/kubernetes/pkg/util/sets"
  38. "k8s.io/kubernetes/pkg/util/strategicpatch"
  39. "github.com/evanphx/json-patch"
  40. "github.com/golang/glog"
  41. "github.com/spf13/cobra"
  42. "github.com/spf13/pflag"
  43. )
  44. const (
  45. ApplyAnnotationsFlag = "save-config"
  46. DefaultErrorExitCode = 1
  47. )
  48. type debugError interface {
  49. DebugError() (msg string, args []interface{})
  50. }
  51. // AddSourceToErr adds handleResourcePrefix and source string to error message.
  52. // verb is the string like "creating", "deleting" etc.
  53. // souce is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
  54. func AddSourceToErr(verb string, source string, err error) error {
  55. if source != "" {
  56. if statusError, ok := err.(kerrors.APIStatus); ok {
  57. status := statusError.Status()
  58. status.Message = fmt.Sprintf("error when %s %q: %v", verb, source, status.Message)
  59. return &kerrors.StatusError{ErrStatus: status}
  60. }
  61. return fmt.Errorf("error when %s %q: %v", verb, source, err)
  62. }
  63. return err
  64. }
  65. var fatalErrHandler = fatal
  66. // BehaviorOnFatal allows you to override the default behavior when a fatal
  67. // error occurs, which is to call os.Exit(code). You can pass 'panic' as a function
  68. // here if you prefer the panic() over os.Exit(1).
  69. func BehaviorOnFatal(f func(string, int)) {
  70. fatalErrHandler = f
  71. }
  72. // DefaultBehaviorOnFatal allows you to undo any previous override. Useful in
  73. // tests.
  74. func DefaultBehaviorOnFatal() {
  75. fatalErrHandler = fatal
  76. }
  77. // fatal prints the message if set and then exits. If V(2) or greater, glog.Fatal
  78. // is invoked for extended information.
  79. func fatal(msg string, code int) {
  80. if len(msg) > 0 {
  81. // add newline if needed
  82. if !strings.HasSuffix(msg, "\n") {
  83. msg += "\n"
  84. }
  85. if glog.V(2) {
  86. glog.FatalDepth(2, msg)
  87. }
  88. fmt.Fprint(os.Stderr, msg)
  89. }
  90. os.Exit(code)
  91. }
  92. // CheckErr prints a user friendly error to STDERR and exits with a non-zero
  93. // exit code. Unrecognized errors will be printed with an "error: " prefix.
  94. //
  95. // This method is generic to the command in use and may be used by non-Kubectl
  96. // commands.
  97. func CheckErr(err error) {
  98. checkErr("", err, fatalErrHandler)
  99. }
  100. // checkErrWithPrefix works like CheckErr, but adds a caller-defined prefix to non-nil errors
  101. func checkErrWithPrefix(prefix string, err error) {
  102. checkErr(prefix, err, fatalErrHandler)
  103. }
  104. // checkErr formats a given error as a string and calls the passed handleErr
  105. // func with that string and an kubectl exit code.
  106. func checkErr(prefix string, err error, handleErr func(string, int)) {
  107. switch {
  108. case err == nil:
  109. return
  110. case kerrors.IsInvalid(err):
  111. details := err.(*kerrors.StatusError).Status().Details
  112. s := fmt.Sprintf("%sThe %s %q is invalid", prefix, details.Kind, details.Name)
  113. if len(details.Causes) > 0 {
  114. errs := statusCausesToAggrError(details.Causes)
  115. handleErr(MultilineError(s+": ", errs), DefaultErrorExitCode)
  116. } else {
  117. handleErr(s, DefaultErrorExitCode)
  118. }
  119. case clientcmd.IsConfigurationInvalid(err):
  120. handleErr(MultilineError(fmt.Sprintf("%sError in configuration: ", prefix), err), DefaultErrorExitCode)
  121. default:
  122. switch err := err.(type) {
  123. case *meta.NoResourceMatchError:
  124. switch {
  125. case len(err.PartialResource.Group) > 0 && len(err.PartialResource.Version) > 0:
  126. handleErr(fmt.Sprintf("%sthe server doesn't have a resource type %q in group %q and version %q", prefix, err.PartialResource.Resource, err.PartialResource.Group, err.PartialResource.Version), DefaultErrorExitCode)
  127. case len(err.PartialResource.Group) > 0:
  128. handleErr(fmt.Sprintf("%sthe server doesn't have a resource type %q in group %q", prefix, err.PartialResource.Resource, err.PartialResource.Group), DefaultErrorExitCode)
  129. case len(err.PartialResource.Version) > 0:
  130. handleErr(fmt.Sprintf("%sthe server doesn't have a resource type %q in version %q", prefix, err.PartialResource.Resource, err.PartialResource.Version), DefaultErrorExitCode)
  131. default:
  132. handleErr(fmt.Sprintf("%sthe server doesn't have a resource type %q", prefix, err.PartialResource.Resource), DefaultErrorExitCode)
  133. }
  134. case utilerrors.Aggregate:
  135. handleErr(MultipleErrors(prefix, err.Errors()), DefaultErrorExitCode)
  136. case utilexec.ExitError:
  137. // do not print anything, only terminate with given error
  138. handleErr("", err.ExitStatus())
  139. default: // for any other error type
  140. msg, ok := StandardErrorMessage(err)
  141. if !ok {
  142. msg = err.Error()
  143. if !strings.HasPrefix(msg, "error: ") {
  144. msg = fmt.Sprintf("error: %s", msg)
  145. }
  146. }
  147. handleErr(msg, DefaultErrorExitCode)
  148. }
  149. }
  150. }
  151. func statusCausesToAggrError(scs []unversioned.StatusCause) utilerrors.Aggregate {
  152. errs := make([]error, 0, len(scs))
  153. errorMsgs := sets.NewString()
  154. for _, sc := range scs {
  155. // check for duplicate error messages and skip them
  156. msg := fmt.Sprintf("%s: %s", sc.Field, sc.Message)
  157. if errorMsgs.Has(msg) {
  158. continue
  159. }
  160. errorMsgs.Insert(msg)
  161. errs = append(errs, errors.New(msg))
  162. }
  163. return utilerrors.NewAggregate(errs)
  164. }
  165. // StandardErrorMessage translates common errors into a human readable message, or returns
  166. // false if the error is not one of the recognized types. It may also log extended
  167. // information to glog.
  168. //
  169. // This method is generic to the command in use and may be used by non-Kubectl
  170. // commands.
  171. func StandardErrorMessage(err error) (string, bool) {
  172. if debugErr, ok := err.(debugError); ok {
  173. glog.V(4).Infof(debugErr.DebugError())
  174. }
  175. status, isStatus := err.(kerrors.APIStatus)
  176. switch {
  177. case isStatus:
  178. switch s := status.Status(); {
  179. case s.Reason == "Unauthorized":
  180. return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true
  181. default:
  182. return fmt.Sprintf("Error from server: %s", err.Error()), true
  183. }
  184. case kerrors.IsUnexpectedObjectError(err):
  185. return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true
  186. }
  187. switch t := err.(type) {
  188. case *url.Error:
  189. glog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
  190. switch {
  191. case strings.Contains(t.Err.Error(), "connection refused"):
  192. host := t.URL
  193. if server, err := url.Parse(t.URL); err == nil {
  194. host = server.Host
  195. }
  196. return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
  197. }
  198. return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
  199. }
  200. return "", false
  201. }
  202. // MultilineError returns a string representing an error that splits sub errors into their own
  203. // lines. The returned string will end with a newline.
  204. func MultilineError(prefix string, err error) string {
  205. if agg, ok := err.(utilerrors.Aggregate); ok {
  206. errs := utilerrors.Flatten(agg).Errors()
  207. buf := &bytes.Buffer{}
  208. switch len(errs) {
  209. case 0:
  210. return fmt.Sprintf("%s%v\n", prefix, err)
  211. case 1:
  212. return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0]))
  213. default:
  214. fmt.Fprintln(buf, prefix)
  215. for _, err := range errs {
  216. fmt.Fprintf(buf, "* %v\n", messageForError(err))
  217. }
  218. return buf.String()
  219. }
  220. }
  221. return fmt.Sprintf("%s%s\n", prefix, err)
  222. }
  223. // MultipleErrors returns a newline delimited string containing
  224. // the prefix and referenced errors in standard form.
  225. func MultipleErrors(prefix string, errs []error) string {
  226. buf := &bytes.Buffer{}
  227. for _, err := range errs {
  228. fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err))
  229. }
  230. return buf.String()
  231. }
  232. // messageForError returns the string representing the error.
  233. func messageForError(err error) string {
  234. msg, ok := StandardErrorMessage(err)
  235. if !ok {
  236. msg = err.Error()
  237. }
  238. return msg
  239. }
  240. func UsageError(cmd *cobra.Command, format string, args ...interface{}) error {
  241. msg := fmt.Sprintf(format, args...)
  242. return fmt.Errorf("%s\nSee '%s -h' for help and examples.", msg, cmd.CommandPath())
  243. }
  244. // Whether this cmd need watching objects.
  245. func isWatch(cmd *cobra.Command) bool {
  246. if w, err := cmd.Flags().GetBool("watch"); w && err == nil {
  247. return true
  248. }
  249. if wo, err := cmd.Flags().GetBool("watch-only"); wo && err == nil {
  250. return true
  251. }
  252. return false
  253. }
  254. func getFlag(cmd *cobra.Command, flag string) *pflag.Flag {
  255. f := cmd.Flags().Lookup(flag)
  256. if f == nil {
  257. glog.Fatalf("flag accessed but not defined for command %s: %s", cmd.Name(), flag)
  258. }
  259. return f
  260. }
  261. func GetFlagString(cmd *cobra.Command, flag string) string {
  262. s, err := cmd.Flags().GetString(flag)
  263. if err != nil {
  264. glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  265. }
  266. return s
  267. }
  268. // GetFlagStringList can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
  269. func GetFlagStringSlice(cmd *cobra.Command, flag string) []string {
  270. s, err := cmd.Flags().GetStringSlice(flag)
  271. if err != nil {
  272. glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  273. }
  274. return s
  275. }
  276. // GetWideFlag is used to determine if "-o wide" is used
  277. func GetWideFlag(cmd *cobra.Command) bool {
  278. f := cmd.Flags().Lookup("output")
  279. if f.Value.String() == "wide" {
  280. return true
  281. }
  282. return false
  283. }
  284. func GetFlagBool(cmd *cobra.Command, flag string) bool {
  285. b, err := cmd.Flags().GetBool(flag)
  286. if err != nil {
  287. glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  288. }
  289. return b
  290. }
  291. // Assumes the flag has a default value.
  292. func GetFlagInt(cmd *cobra.Command, flag string) int {
  293. i, err := cmd.Flags().GetInt(flag)
  294. if err != nil {
  295. glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  296. }
  297. return i
  298. }
  299. // Assumes the flag has a default value.
  300. func GetFlagInt64(cmd *cobra.Command, flag string) int64 {
  301. i, err := cmd.Flags().GetInt64(flag)
  302. if err != nil {
  303. glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  304. }
  305. return i
  306. }
  307. func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
  308. d, err := cmd.Flags().GetDuration(flag)
  309. if err != nil {
  310. glog.Fatalf("err accessing flag %s for command %s: %v", flag, cmd.Name(), err)
  311. }
  312. return d
  313. }
  314. func AddValidateFlags(cmd *cobra.Command) {
  315. cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it")
  316. cmd.Flags().String("schema-cache-dir", fmt.Sprintf("~/%s/%s", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName), fmt.Sprintf("If non-empty, load/store cached API schemas in this directory, default is '$HOME/%s/%s'", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName))
  317. cmd.MarkFlagFilename("schema-cache-dir")
  318. }
  319. func AddRecursiveFlag(cmd *cobra.Command, value *bool) {
  320. cmd.Flags().BoolVarP(value, "recursive", "R", *value, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
  321. }
  322. // AddDryRunFlag adds dry-run flag to a command. Usually used by mutations.
  323. func AddDryRunFlag(cmd *cobra.Command) {
  324. cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
  325. }
  326. func AddApplyAnnotationFlags(cmd *cobra.Command) {
  327. cmd.Flags().Bool(ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future.")
  328. }
  329. // AddGeneratorFlags adds flags common to resource generation commands
  330. // TODO: need to take a pass at other generator commands to use this set of flags
  331. func AddGeneratorFlags(cmd *cobra.Command, defaultGenerator string) {
  332. cmd.Flags().String("generator", defaultGenerator, "The name of the API generator to use.")
  333. AddDryRunFlag(cmd)
  334. }
  335. func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) {
  336. data, err := ioutil.ReadAll(reader)
  337. if err != nil {
  338. return nil, err
  339. }
  340. if len(data) == 0 {
  341. return nil, fmt.Errorf("Read from %s but no data found", source)
  342. }
  343. return data, nil
  344. }
  345. // Merge requires JSON serialization
  346. // TODO: merge assumes JSON serialization, and does not properly abstract API retrieval
  347. func Merge(codec runtime.Codec, dst runtime.Object, fragment, kind string) (runtime.Object, error) {
  348. // encode dst into versioned json and apply fragment directly too it
  349. target, err := runtime.Encode(codec, dst)
  350. if err != nil {
  351. return nil, err
  352. }
  353. patched, err := jsonpatch.MergePatch(target, []byte(fragment))
  354. if err != nil {
  355. return nil, err
  356. }
  357. out, err := runtime.Decode(codec, patched)
  358. if err != nil {
  359. return nil, err
  360. }
  361. return out, nil
  362. }
  363. // DumpReaderToFile writes all data from the given io.Reader to the specified file
  364. // (usually for temporary use).
  365. func DumpReaderToFile(reader io.Reader, filename string) error {
  366. f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
  367. defer f.Close()
  368. if err != nil {
  369. return err
  370. }
  371. buffer := make([]byte, 1024)
  372. for {
  373. count, err := reader.Read(buffer)
  374. if err == io.EOF {
  375. break
  376. }
  377. if err != nil {
  378. return err
  379. }
  380. _, err = f.Write(buffer[:count])
  381. if err != nil {
  382. return err
  383. }
  384. }
  385. return nil
  386. }
  387. // UpdateObject updates resource object with updateFn
  388. func UpdateObject(info *resource.Info, codec runtime.Codec, updateFn func(runtime.Object) error) (runtime.Object, error) {
  389. helper := resource.NewHelper(info.Client, info.Mapping)
  390. if err := updateFn(info.Object); err != nil {
  391. return nil, err
  392. }
  393. // Update the annotation used by kubectl apply
  394. if err := kubectl.UpdateApplyAnnotation(info, codec); err != nil {
  395. return nil, err
  396. }
  397. if _, err := helper.Replace(info.Namespace, info.Name, true, info.Object); err != nil {
  398. return nil, err
  399. }
  400. return info.Object, nil
  401. }
  402. // AddCmdRecordFlag adds --record flag to command
  403. func AddRecordFlag(cmd *cobra.Command) {
  404. cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.")
  405. }
  406. func GetRecordFlag(cmd *cobra.Command) bool {
  407. return GetFlagBool(cmd, "record")
  408. }
  409. func GetDryRunFlag(cmd *cobra.Command) bool {
  410. return GetFlagBool(cmd, "dry-run")
  411. }
  412. // RecordChangeCause annotate change-cause to input runtime object.
  413. func RecordChangeCause(obj runtime.Object, changeCause string) error {
  414. accessor, err := meta.Accessor(obj)
  415. if err != nil {
  416. return err
  417. }
  418. annotations := accessor.GetAnnotations()
  419. if annotations == nil {
  420. annotations = make(map[string]string)
  421. }
  422. annotations[kubectl.ChangeCauseAnnotation] = changeCause
  423. accessor.SetAnnotations(annotations)
  424. return nil
  425. }
  426. // ChangeResourcePatch creates a strategic merge patch between the origin input resource info
  427. // and the annotated with change-cause input resource info.
  428. func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) {
  429. oldData, err := json.Marshal(info.Object)
  430. if err != nil {
  431. return nil, err
  432. }
  433. if err := RecordChangeCause(info.Object, changeCause); err != nil {
  434. return nil, err
  435. }
  436. newData, err := json.Marshal(info.Object)
  437. if err != nil {
  438. return nil, err
  439. }
  440. return strategicpatch.CreateTwoWayMergePatch(oldData, newData, info.Object)
  441. }
  442. // containsChangeCause checks if input resource info contains change-cause annotation.
  443. func ContainsChangeCause(info *resource.Info) bool {
  444. annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
  445. if err != nil {
  446. return false
  447. }
  448. return len(annotations[kubectl.ChangeCauseAnnotation]) > 0
  449. }
  450. // ShouldRecord checks if we should record current change cause
  451. func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
  452. return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record"))
  453. }
  454. // GetThirdPartyGroupVersions returns the thirdparty "group/versions"s and
  455. // resources supported by the server. A user may delete a thirdparty resource
  456. // when this function is running, so this function may return a "NotFound" error
  457. // due to the race.
  458. func GetThirdPartyGroupVersions(discovery discovery.DiscoveryInterface) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) {
  459. result := []unversioned.GroupVersion{}
  460. gvks := []unversioned.GroupVersionKind{}
  461. groupList, err := discovery.ServerGroups()
  462. if err != nil {
  463. // On forbidden or not found, just return empty lists.
  464. if kerrors.IsForbidden(err) || kerrors.IsNotFound(err) {
  465. return result, gvks, nil
  466. }
  467. return nil, nil, err
  468. }
  469. for ix := range groupList.Groups {
  470. group := &groupList.Groups[ix]
  471. for jx := range group.Versions {
  472. gv, err2 := unversioned.ParseGroupVersion(group.Versions[jx].GroupVersion)
  473. if err2 != nil {
  474. return nil, nil, err
  475. }
  476. // Skip GroupVersionKinds that have been statically registered.
  477. if registered.IsRegisteredVersion(gv) {
  478. continue
  479. }
  480. result = append(result, gv)
  481. resourceList, err := discovery.ServerResourcesForGroupVersion(group.Versions[jx].GroupVersion)
  482. if err != nil {
  483. return nil, nil, err
  484. }
  485. for kx := range resourceList.APIResources {
  486. gvks = append(gvks, gv.WithKind(resourceList.APIResources[kx].Kind))
  487. }
  488. }
  489. }
  490. return result, gvks, nil
  491. }
  492. func GetIncludeThirdPartyAPIs(cmd *cobra.Command) bool {
  493. if cmd.Flags().Lookup("include-extended-apis") == nil {
  494. return false
  495. }
  496. return GetFlagBool(cmd, "include-extended-apis")
  497. }
  498. func AddInclude3rdPartyFlags(cmd *cobra.Command) {
  499. cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
  500. }
  501. // GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args
  502. func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
  503. foundPair := false
  504. for _, s := range args {
  505. nonResource := strings.Contains(s, "=") || strings.HasSuffix(s, "-")
  506. switch {
  507. case !foundPair && nonResource:
  508. foundPair = true
  509. fallthrough
  510. case foundPair && nonResource:
  511. pairArgs = append(pairArgs, s)
  512. case !foundPair && !nonResource:
  513. resources = append(resources, s)
  514. case foundPair && !nonResource:
  515. err = fmt.Errorf("all resources must be specified before %s changes: %s", pairType, s)
  516. return
  517. }
  518. }
  519. return
  520. }
  521. // ParsePairs retrieves new and remove pairs (if supportRemove is true) from "KEY=VALUE or KEY-" pair args
  522. func ParsePairs(pairArgs []string, pairType string, supportRemove bool) (newPairs map[string]string, removePairs []string, err error) {
  523. newPairs = map[string]string{}
  524. if supportRemove {
  525. removePairs = []string{}
  526. }
  527. var invalidBuf bytes.Buffer
  528. for _, pairArg := range pairArgs {
  529. if strings.Index(pairArg, "=") != -1 {
  530. parts := strings.SplitN(pairArg, "=", 2)
  531. if len(parts) != 2 || len(parts[1]) == 0 {
  532. if invalidBuf.Len() > 0 {
  533. invalidBuf.WriteString(", ")
  534. }
  535. invalidBuf.WriteString(fmt.Sprintf(pairArg))
  536. } else {
  537. newPairs[parts[0]] = parts[1]
  538. }
  539. } else if supportRemove && strings.HasSuffix(pairArg, "-") {
  540. removePairs = append(removePairs, pairArg[:len(pairArg)-1])
  541. } else {
  542. if invalidBuf.Len() > 0 {
  543. invalidBuf.WriteString(", ")
  544. }
  545. invalidBuf.WriteString(fmt.Sprintf(pairArg))
  546. }
  547. }
  548. if invalidBuf.Len() > 0 {
  549. err = fmt.Errorf("invalid %s format: %s", pairType, invalidBuf.String())
  550. return
  551. }
  552. return
  553. }
  554. // MaybeConvertObject attempts to convert an object to a specific group/version. If the object is
  555. // a third party resource it is simply passed through.
  556. func MaybeConvertObject(obj runtime.Object, gv unversioned.GroupVersion, converter runtime.ObjectConvertor) (runtime.Object, error) {
  557. switch obj.(type) {
  558. case *extensions.ThirdPartyResourceData:
  559. // conversion is not supported for 3rd party objects
  560. return obj, nil
  561. default:
  562. return converter.ConvertToVersion(obj, gv)
  563. }
  564. }