builder.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  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 resource
  14. import (
  15. "fmt"
  16. "io"
  17. "net/url"
  18. "os"
  19. "strings"
  20. "k8s.io/kubernetes/pkg/api"
  21. "k8s.io/kubernetes/pkg/api/meta"
  22. "k8s.io/kubernetes/pkg/api/unversioned"
  23. "k8s.io/kubernetes/pkg/api/validation"
  24. "k8s.io/kubernetes/pkg/labels"
  25. "k8s.io/kubernetes/pkg/runtime"
  26. utilerrors "k8s.io/kubernetes/pkg/util/errors"
  27. "k8s.io/kubernetes/pkg/util/sets"
  28. )
  29. var FileExtensions = []string{".json", ".yaml", ".yml"}
  30. var InputExtensions = append(FileExtensions, "stdin")
  31. const defaultHttpGetAttempts int = 3
  32. // Builder provides convenience functions for taking arguments and parameters
  33. // from the command line and converting them to a list of resources to iterate
  34. // over using the Visitor interface.
  35. type Builder struct {
  36. mapper *Mapper
  37. errs []error
  38. paths []Visitor
  39. stream bool
  40. dir bool
  41. selector labels.Selector
  42. selectAll bool
  43. resources []string
  44. namespace string
  45. names []string
  46. resourceTuples []resourceTuple
  47. defaultNamespace bool
  48. requireNamespace bool
  49. flatten bool
  50. latest bool
  51. requireObject bool
  52. singleResourceType bool
  53. continueOnError bool
  54. singular bool
  55. export bool
  56. schema validation.Schema
  57. }
  58. type resourceTuple struct {
  59. Resource string
  60. Name string
  61. }
  62. // NewBuilder creates a builder that operates on generic objects.
  63. func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder {
  64. return &Builder{
  65. mapper: &Mapper{typer, mapper, clientMapper, decoder},
  66. requireObject: true,
  67. }
  68. }
  69. func (b *Builder) Schema(schema validation.Schema) *Builder {
  70. b.schema = schema
  71. return b
  72. }
  73. // FilenameParam groups input in two categories: URLs and files (files, directories, STDIN)
  74. // If enforceNamespace is false, namespaces in the specs will be allowed to
  75. // override the default namespace. If it is true, namespaces that don't match
  76. // will cause an error.
  77. // If ContinueOnError() is set prior to this method, objects on the path that are not
  78. // recognized will be ignored (but logged at V(2)).
  79. func (b *Builder) FilenameParam(enforceNamespace, recursive bool, paths ...string) *Builder {
  80. for _, s := range paths {
  81. switch {
  82. case s == "-":
  83. b.Stdin()
  84. case strings.Index(s, "http://") == 0 || strings.Index(s, "https://") == 0:
  85. url, err := url.Parse(s)
  86. if err != nil {
  87. b.errs = append(b.errs, fmt.Errorf("the URL passed to filename %q is not valid: %v", s, err))
  88. continue
  89. }
  90. b.URL(defaultHttpGetAttempts, url)
  91. default:
  92. if !recursive {
  93. b.singular = true
  94. }
  95. b.Path(recursive, s)
  96. }
  97. }
  98. if enforceNamespace {
  99. b.RequireNamespace()
  100. }
  101. return b
  102. }
  103. // URL accepts a number of URLs directly.
  104. func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
  105. for _, u := range urls {
  106. b.paths = append(b.paths, &URLVisitor{
  107. URL: u,
  108. StreamVisitor: NewStreamVisitor(nil, b.mapper, u.String(), b.schema),
  109. HttpAttemptCount: httpAttemptCount,
  110. })
  111. }
  112. return b
  113. }
  114. // Stdin will read objects from the standard input. If ContinueOnError() is set
  115. // prior to this method being called, objects in the stream that are unrecognized
  116. // will be ignored (but logged at V(2)).
  117. func (b *Builder) Stdin() *Builder {
  118. b.stream = true
  119. b.paths = append(b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
  120. return b
  121. }
  122. // Stream will read objects from the provided reader, and if an error occurs will
  123. // include the name string in the error message. If ContinueOnError() is set
  124. // prior to this method being called, objects in the stream that are unrecognized
  125. // will be ignored (but logged at V(2)).
  126. func (b *Builder) Stream(r io.Reader, name string) *Builder {
  127. b.stream = true
  128. b.paths = append(b.paths, NewStreamVisitor(r, b.mapper, name, b.schema))
  129. return b
  130. }
  131. // Path accepts a set of paths that may be files, directories (all can containing
  132. // one or more resources). Creates a FileVisitor for each file and then each
  133. // FileVisitor is streaming the content to a StreamVisitor. If ContinueOnError() is set
  134. // prior to this method being called, objects on the path that are unrecognized will be
  135. // ignored (but logged at V(2)).
  136. func (b *Builder) Path(recursive bool, paths ...string) *Builder {
  137. for _, p := range paths {
  138. _, err := os.Stat(p)
  139. if os.IsNotExist(err) {
  140. b.errs = append(b.errs, fmt.Errorf("the path %q does not exist", p))
  141. continue
  142. }
  143. if err != nil {
  144. b.errs = append(b.errs, fmt.Errorf("the path %q cannot be accessed: %v", p, err))
  145. continue
  146. }
  147. visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema)
  148. if err != nil {
  149. b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err))
  150. }
  151. if len(visitors) > 1 {
  152. b.dir = true
  153. }
  154. b.paths = append(b.paths, visitors...)
  155. }
  156. return b
  157. }
  158. // ResourceTypes is a list of types of resources to operate on, when listing objects on
  159. // the server or retrieving objects that match a selector.
  160. func (b *Builder) ResourceTypes(types ...string) *Builder {
  161. b.resources = append(b.resources, types...)
  162. return b
  163. }
  164. // ResourceNames accepts a default type and one or more names, and creates tuples of
  165. // resources
  166. func (b *Builder) ResourceNames(resource string, names ...string) *Builder {
  167. for _, name := range names {
  168. // See if this input string is of type/name format
  169. tuple, ok, err := splitResourceTypeName(name)
  170. if err != nil {
  171. b.errs = append(b.errs, err)
  172. return b
  173. }
  174. if ok {
  175. b.resourceTuples = append(b.resourceTuples, tuple)
  176. continue
  177. }
  178. if len(resource) == 0 {
  179. b.errs = append(b.errs, fmt.Errorf("the argument %q must be RESOURCE/NAME", name))
  180. continue
  181. }
  182. // Use the given default type to create a resource tuple
  183. b.resourceTuples = append(b.resourceTuples, resourceTuple{Resource: resource, Name: name})
  184. }
  185. return b
  186. }
  187. // SelectorParam defines a selector that should be applied to the object types to load.
  188. // This will not affect files loaded from disk or URL. If the parameter is empty it is
  189. // a no-op - to select all resources invoke `b.Selector(labels.Everything)`.
  190. func (b *Builder) SelectorParam(s string) *Builder {
  191. selector, err := labels.Parse(s)
  192. if err != nil {
  193. b.errs = append(b.errs, fmt.Errorf("the provided selector %q is not valid: %v", s, err))
  194. return b
  195. }
  196. if selector.Empty() {
  197. return b
  198. }
  199. if b.selectAll {
  200. b.errs = append(b.errs, fmt.Errorf("found non empty selector %q with previously set 'all' parameter. ", s))
  201. return b
  202. }
  203. return b.Selector(selector)
  204. }
  205. // Selector accepts a selector directly, and if non nil will trigger a list action.
  206. func (b *Builder) Selector(selector labels.Selector) *Builder {
  207. b.selector = selector
  208. return b
  209. }
  210. // ExportParam accepts the export boolean for these resources
  211. func (b *Builder) ExportParam(export bool) *Builder {
  212. b.export = export
  213. return b
  214. }
  215. // NamespaceParam accepts the namespace that these resources should be
  216. // considered under from - used by DefaultNamespace() and RequireNamespace()
  217. func (b *Builder) NamespaceParam(namespace string) *Builder {
  218. b.namespace = namespace
  219. return b
  220. }
  221. // DefaultNamespace instructs the builder to set the namespace value for any object found
  222. // to NamespaceParam() if empty.
  223. func (b *Builder) DefaultNamespace() *Builder {
  224. b.defaultNamespace = true
  225. return b
  226. }
  227. // AllNamespaces instructs the builder to use NamespaceAll as a namespace to request resources
  228. // acroll all namespace. This overrides the namespace set by NamespaceParam().
  229. func (b *Builder) AllNamespaces(allNamespace bool) *Builder {
  230. if allNamespace {
  231. b.namespace = api.NamespaceAll
  232. }
  233. return b
  234. }
  235. // RequireNamespace instructs the builder to set the namespace value for any object found
  236. // to NamespaceParam() if empty, and if the value on the resource does not match
  237. // NamespaceParam() an error will be returned.
  238. func (b *Builder) RequireNamespace() *Builder {
  239. b.requireNamespace = true
  240. return b
  241. }
  242. // SelectEverythingParam
  243. func (b *Builder) SelectAllParam(selectAll bool) *Builder {
  244. if selectAll && b.selector != nil {
  245. b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. "))
  246. return b
  247. }
  248. b.selectAll = selectAll
  249. return b
  250. }
  251. // ResourceTypeOrNameArgs indicates that the builder should accept arguments
  252. // of the form `(<type1>[,<type2>,...]|<type> <name1>[,<name2>,...])`. When one argument is
  253. // received, the types provided will be retrieved from the server (and be comma delimited).
  254. // When two or more arguments are received, they must be a single type and resource name(s).
  255. // The allowEmptySelector permits to select all the resources (via Everything func).
  256. func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string) *Builder {
  257. args = normalizeMultipleResourcesArgs(args)
  258. if ok, err := hasCombinedTypeArgs(args); ok {
  259. if err != nil {
  260. b.errs = append(b.errs, err)
  261. return b
  262. }
  263. for _, s := range args {
  264. tuple, ok, err := splitResourceTypeName(s)
  265. if err != nil {
  266. b.errs = append(b.errs, err)
  267. return b
  268. }
  269. if ok {
  270. b.resourceTuples = append(b.resourceTuples, tuple)
  271. }
  272. }
  273. return b
  274. }
  275. if len(args) > 0 {
  276. // Try replacing aliases only in types
  277. args[0] = b.replaceAliases(args[0])
  278. }
  279. switch {
  280. case len(args) > 2:
  281. b.names = append(b.names, args[1:]...)
  282. b.ResourceTypes(SplitResourceArgument(args[0])...)
  283. case len(args) == 2:
  284. b.names = append(b.names, args[1])
  285. b.ResourceTypes(SplitResourceArgument(args[0])...)
  286. case len(args) == 1:
  287. b.ResourceTypes(SplitResourceArgument(args[0])...)
  288. if b.selector == nil && allowEmptySelector {
  289. b.selector = labels.Everything()
  290. }
  291. case len(args) == 0:
  292. default:
  293. b.errs = append(b.errs, fmt.Errorf("arguments must consist of a resource or a resource and name"))
  294. }
  295. return b
  296. }
  297. // replaceAliases accepts an argument and tries to expand any existing
  298. // aliases found in it
  299. func (b *Builder) replaceAliases(input string) string {
  300. replaced := []string{}
  301. for _, arg := range strings.Split(input, ",") {
  302. if aliases, ok := b.mapper.AliasesForResource(arg); ok {
  303. arg = strings.Join(aliases, ",")
  304. }
  305. replaced = append(replaced, arg)
  306. }
  307. return strings.Join(replaced, ",")
  308. }
  309. func hasCombinedTypeArgs(args []string) (bool, error) {
  310. hasSlash := 0
  311. for _, s := range args {
  312. if strings.Contains(s, "/") {
  313. hasSlash++
  314. }
  315. }
  316. switch {
  317. case hasSlash > 0 && hasSlash == len(args):
  318. return true, nil
  319. case hasSlash > 0 && hasSlash != len(args):
  320. baseCmd := "cmd"
  321. if len(os.Args) > 0 {
  322. baseCmdSlice := strings.Split(os.Args[0], "/")
  323. baseCmd = baseCmdSlice[len(baseCmdSlice)-1]
  324. }
  325. return true, fmt.Errorf("there is no need to specify a resource type as a separate argument when passing arguments in resource/name form (e.g. '%s get resource/<resource_name>' instead of '%s get resource resource/<resource_name>'", baseCmd, baseCmd)
  326. default:
  327. return false, nil
  328. }
  329. }
  330. // Normalize args convert multiple resources to resource tuples, a,b,c d
  331. // as a transform to a/d b/d c/d
  332. func normalizeMultipleResourcesArgs(args []string) []string {
  333. if len(args) >= 2 {
  334. resources := []string{}
  335. resources = append(resources, SplitResourceArgument(args[0])...)
  336. if len(resources) > 1 {
  337. names := []string{}
  338. names = append(names, args[1:]...)
  339. newArgs := []string{}
  340. for _, resource := range resources {
  341. for _, name := range names {
  342. newArgs = append(newArgs, strings.Join([]string{resource, name}, "/"))
  343. }
  344. }
  345. return newArgs
  346. }
  347. }
  348. return args
  349. }
  350. // splitResourceTypeName handles type/name resource formats and returns a resource tuple
  351. // (empty or not), whether it successfully found one, and an error
  352. func splitResourceTypeName(s string) (resourceTuple, bool, error) {
  353. if !strings.Contains(s, "/") {
  354. return resourceTuple{}, false, nil
  355. }
  356. seg := strings.Split(s, "/")
  357. if len(seg) != 2 {
  358. return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form may not have more than one slash")
  359. }
  360. resource, name := seg[0], seg[1]
  361. if len(resource) == 0 || len(name) == 0 || len(SplitResourceArgument(resource)) != 1 {
  362. return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form must have a single resource and name")
  363. }
  364. return resourceTuple{Resource: resource, Name: name}, true, nil
  365. }
  366. // Flatten will convert any objects with a field named "Items" that is an array of runtime.Object
  367. // compatible types into individual entries and give them their own items. The original object
  368. // is not passed to any visitors.
  369. func (b *Builder) Flatten() *Builder {
  370. b.flatten = true
  371. return b
  372. }
  373. // Latest will fetch the latest copy of any objects loaded from URLs or files from the server.
  374. func (b *Builder) Latest() *Builder {
  375. b.latest = true
  376. return b
  377. }
  378. // RequireObject ensures that resulting infos have an object set. If false, resulting info may not have an object set.
  379. func (b *Builder) RequireObject(require bool) *Builder {
  380. b.requireObject = require
  381. return b
  382. }
  383. // ContinueOnError will attempt to load and visit as many objects as possible, even if some visits
  384. // return errors or some objects cannot be loaded. The default behavior is to terminate after
  385. // the first error is returned from a VisitorFunc.
  386. func (b *Builder) ContinueOnError() *Builder {
  387. b.continueOnError = true
  388. return b
  389. }
  390. // SingleResourceType will cause the builder to error if the user specifies more than a single type
  391. // of resource.
  392. func (b *Builder) SingleResourceType() *Builder {
  393. b.singleResourceType = true
  394. return b
  395. }
  396. // mappingFor returns the RESTMapping for the Kind referenced by the resource.
  397. // prefers a fully specified GroupVersionResource match. If we don't have one match on GroupResource
  398. func (b *Builder) mappingFor(resourceArg string) (*meta.RESTMapping, error) {
  399. fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(resourceArg)
  400. gvk := unversioned.GroupVersionKind{}
  401. if fullySpecifiedGVR != nil {
  402. gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR)
  403. }
  404. if gvk.Empty() {
  405. var err error
  406. gvk, err = b.mapper.KindFor(groupResource.WithVersion(""))
  407. if err != nil {
  408. return nil, err
  409. }
  410. }
  411. return b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
  412. }
  413. func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) {
  414. if len(b.resources) > 1 && b.singleResourceType {
  415. return nil, fmt.Errorf("you may only specify a single resource type")
  416. }
  417. mappings := []*meta.RESTMapping{}
  418. for _, r := range b.resources {
  419. mapping, err := b.mappingFor(r)
  420. if err != nil {
  421. return nil, err
  422. }
  423. mappings = append(mappings, mapping)
  424. }
  425. return mappings, nil
  426. }
  427. func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) {
  428. mappings := make(map[string]*meta.RESTMapping)
  429. canonical := make(map[string]struct{})
  430. for _, r := range b.resourceTuples {
  431. if _, ok := mappings[r.Resource]; ok {
  432. continue
  433. }
  434. mapping, err := b.mappingFor(r.Resource)
  435. if err != nil {
  436. return nil, err
  437. }
  438. mappings[mapping.Resource] = mapping
  439. mappings[r.Resource] = mapping
  440. canonical[mapping.Resource] = struct{}{}
  441. }
  442. if len(canonical) > 1 && b.singleResourceType {
  443. return nil, fmt.Errorf("you may only specify a single resource type")
  444. }
  445. return mappings, nil
  446. }
  447. func (b *Builder) visitorResult() *Result {
  448. if len(b.errs) > 0 {
  449. return &Result{err: utilerrors.NewAggregate(b.errs)}
  450. }
  451. if b.selectAll {
  452. b.selector = labels.Everything()
  453. }
  454. // visit selectors
  455. if b.selector != nil {
  456. if len(b.names) != 0 {
  457. return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")}
  458. }
  459. if len(b.resourceTuples) != 0 {
  460. return &Result{err: fmt.Errorf("selectors and the all flag cannot be used when passing resource/name arguments")}
  461. }
  462. if len(b.resources) == 0 {
  463. return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
  464. }
  465. // empty selector has different error message for paths being provided
  466. if len(b.paths) != 0 {
  467. if b.selector.Empty() {
  468. return &Result{err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
  469. } else {
  470. return &Result{err: fmt.Errorf("a selector may not be specified when path, URL, or stdin is provided as input")}
  471. }
  472. }
  473. mappings, err := b.resourceMappings()
  474. if err != nil {
  475. return &Result{err: err}
  476. }
  477. visitors := []Visitor{}
  478. for _, mapping := range mappings {
  479. client, err := b.mapper.ClientForMapping(mapping)
  480. if err != nil {
  481. return &Result{err: err}
  482. }
  483. selectorNamespace := b.namespace
  484. if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
  485. selectorNamespace = ""
  486. }
  487. visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, b.selector, b.export))
  488. }
  489. if b.continueOnError {
  490. return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
  491. }
  492. return &Result{visitor: VisitorList(visitors), sources: visitors}
  493. }
  494. // visit items specified by resource and name
  495. if len(b.resourceTuples) != 0 {
  496. // if b.singular is false, this could be by default, so double-check length
  497. // of resourceTuples to determine if in fact it is singular or not
  498. isSingular := b.singular
  499. if !isSingular {
  500. isSingular = len(b.resourceTuples) == 1
  501. }
  502. if len(b.paths) != 0 {
  503. return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
  504. }
  505. if len(b.resources) != 0 {
  506. return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")}
  507. }
  508. // retrieve one client for each resource
  509. mappings, err := b.resourceTupleMappings()
  510. if err != nil {
  511. return &Result{singular: isSingular, err: err}
  512. }
  513. clients := make(map[string]RESTClient)
  514. for _, mapping := range mappings {
  515. s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
  516. if _, ok := clients[s]; ok {
  517. continue
  518. }
  519. client, err := b.mapper.ClientForMapping(mapping)
  520. if err != nil {
  521. return &Result{err: err}
  522. }
  523. clients[s] = client
  524. }
  525. items := []Visitor{}
  526. for _, tuple := range b.resourceTuples {
  527. mapping, ok := mappings[tuple.Resource]
  528. if !ok {
  529. return &Result{singular: isSingular, err: fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings)}
  530. }
  531. s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
  532. client, ok := clients[s]
  533. if !ok {
  534. return &Result{singular: isSingular, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)}
  535. }
  536. selectorNamespace := b.namespace
  537. if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
  538. selectorNamespace = ""
  539. } else {
  540. if len(b.namespace) == 0 {
  541. return &Result{singular: isSingular, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
  542. }
  543. }
  544. info := NewInfo(client, mapping, selectorNamespace, tuple.Name, b.export)
  545. items = append(items, info)
  546. }
  547. var visitors Visitor
  548. if b.continueOnError {
  549. visitors = EagerVisitorList(items)
  550. } else {
  551. visitors = VisitorList(items)
  552. }
  553. return &Result{singular: isSingular, visitor: visitors, sources: items}
  554. }
  555. // visit items specified by name
  556. if len(b.names) != 0 {
  557. isSingular := len(b.names) == 1
  558. if len(b.paths) != 0 {
  559. return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
  560. }
  561. if len(b.resources) == 0 {
  562. return &Result{singular: isSingular, err: fmt.Errorf("you must provide a resource and a resource name together")}
  563. }
  564. if len(b.resources) > 1 {
  565. return &Result{singular: isSingular, err: fmt.Errorf("you must specify only one resource")}
  566. }
  567. mappings, err := b.resourceMappings()
  568. if err != nil {
  569. return &Result{singular: isSingular, err: err}
  570. }
  571. mapping := mappings[0]
  572. client, err := b.mapper.ClientForMapping(mapping)
  573. if err != nil {
  574. return &Result{err: err}
  575. }
  576. selectorNamespace := b.namespace
  577. if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
  578. selectorNamespace = ""
  579. } else {
  580. if len(b.namespace) == 0 {
  581. return &Result{singular: isSingular, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
  582. }
  583. }
  584. visitors := []Visitor{}
  585. for _, name := range b.names {
  586. info := NewInfo(client, mapping, selectorNamespace, name, b.export)
  587. visitors = append(visitors, info)
  588. }
  589. return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors}
  590. }
  591. // visit items specified by paths
  592. if len(b.paths) != 0 {
  593. singular := !b.dir && !b.stream && len(b.paths) == 1
  594. if len(b.resources) != 0 {
  595. return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")}
  596. }
  597. var visitors Visitor
  598. if b.continueOnError {
  599. visitors = EagerVisitorList(b.paths)
  600. } else {
  601. visitors = VisitorList(b.paths)
  602. }
  603. // only items from disk can be refetched
  604. if b.latest {
  605. // must flatten lists prior to fetching
  606. if b.flatten {
  607. visitors = NewFlattenListVisitor(visitors, b.mapper)
  608. }
  609. // must set namespace prior to fetching
  610. if b.defaultNamespace {
  611. visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
  612. }
  613. visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
  614. }
  615. return &Result{singular: singular, visitor: visitors, sources: b.paths}
  616. }
  617. if len(b.resources) != 0 {
  618. return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")}
  619. }
  620. return &Result{err: fmt.Errorf("you must provide one or more resources by argument or filename (%s)", strings.Join(InputExtensions, "|"))}
  621. }
  622. // Do returns a Result object with a Visitor for the resources identified by the Builder.
  623. // The visitor will respect the error behavior specified by ContinueOnError. Note that stream
  624. // inputs are consumed by the first execution - use Infos() or Object() on the Result to capture a list
  625. // for further iteration.
  626. func (b *Builder) Do() *Result {
  627. r := b.visitorResult()
  628. if r.err != nil {
  629. return r
  630. }
  631. if b.flatten {
  632. r.visitor = NewFlattenListVisitor(r.visitor, b.mapper)
  633. }
  634. helpers := []VisitorFunc{}
  635. if b.defaultNamespace {
  636. helpers = append(helpers, SetNamespace(b.namespace))
  637. }
  638. if b.requireNamespace {
  639. helpers = append(helpers, RequireNamespace(b.namespace))
  640. }
  641. helpers = append(helpers, FilterNamespace)
  642. if b.requireObject {
  643. helpers = append(helpers, RetrieveLazy)
  644. }
  645. r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
  646. if b.continueOnError {
  647. r.visitor = ContinueOnErrorVisitor{r.visitor}
  648. }
  649. return r
  650. }
  651. // SplitResourceArgument splits the argument with commas and returns unique
  652. // strings in the original order.
  653. func SplitResourceArgument(arg string) []string {
  654. out := []string{}
  655. set := sets.NewString()
  656. for _, s := range strings.Split(arg, ",") {
  657. if set.Has(s) {
  658. continue
  659. }
  660. set.Insert(s)
  661. out = append(out, s)
  662. }
  663. return out
  664. }
  665. // HasNames returns true if the provided args contain resource names
  666. func HasNames(args []string) (bool, error) {
  667. args = normalizeMultipleResourcesArgs(args)
  668. hasCombinedTypes, err := hasCombinedTypeArgs(args)
  669. if err != nil {
  670. return false, err
  671. }
  672. return hasCombinedTypes || len(args) > 1, nil
  673. }