1
16
17 package resource
18
19 import (
20 "errors"
21 "fmt"
22 "io"
23 "net/url"
24 "os"
25 "path/filepath"
26 "strings"
27 "sync"
28
29 "k8s.io/apimachinery/pkg/api/meta"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
33 "k8s.io/apimachinery/pkg/labels"
34 "k8s.io/apimachinery/pkg/runtime"
35 "k8s.io/apimachinery/pkg/runtime/schema"
36 "k8s.io/apimachinery/pkg/runtime/serializer"
37 utilerrors "k8s.io/apimachinery/pkg/util/errors"
38 "k8s.io/apimachinery/pkg/util/sets"
39 "k8s.io/client-go/discovery"
40 "k8s.io/client-go/rest"
41 "k8s.io/client-go/restmapper"
42 "sigs.k8s.io/kustomize/kyaml/filesys"
43 )
44
45 var FileExtensions = []string{".json", ".yaml", ".yml"}
46 var InputExtensions = append(FileExtensions, "stdin")
47
48 const defaultHttpGetAttempts = 3
49 const pathNotExistError = "the path %q does not exist"
50
51
52
53
54 type Builder struct {
55 categoryExpanderFn CategoryExpanderFunc
56
57
58 mapper *mapper
59
60
61 clientConfigFn ClientConfigFunc
62
63 restMapperFn RESTMapperFunc
64
65
66
67 objectTyper runtime.ObjectTyper
68
69
70 negotiatedSerializer runtime.NegotiatedSerializer
71
72
73 local bool
74
75 errs []error
76
77 paths []Visitor
78 stream bool
79 stdinInUse bool
80 dir bool
81
82 visitorConcurrency int
83
84 labelSelector *string
85 fieldSelector *string
86 selectAll bool
87 limitChunks int64
88 requestTransforms []RequestTransform
89
90 resources []string
91 subresource string
92
93 namespace string
94 allNamespace bool
95 names []string
96
97 resourceTuples []resourceTuple
98
99 defaultNamespace bool
100 requireNamespace bool
101
102 flatten bool
103 latest bool
104
105 requireObject bool
106
107 singleResourceType bool
108 continueOnError bool
109
110 singleItemImplied bool
111
112 schema ContentValidator
113
114
115 fakeClientFn FakeClientFunc
116 }
117
118 var missingResourceError = fmt.Errorf(`You must provide one or more resources by argument or filename.
119 Example resource specifications include:
120 '-f rsrc.yaml'
121 '--filename=rsrc.json'
122 '<resource> <name>'
123 '<resource>'`)
124
125 var LocalResourceError = errors.New(`error: you must specify resources by --filename when --local is set.
126 Example resource specifications include:
127 '-f rsrc.yaml'
128 '--filename=rsrc.json'`)
129
130 var StdinMultiUseError = errors.New("standard input cannot be used for multiple arguments")
131
132
133 func IsUsageError(err error) bool {
134 if err == nil {
135 return false
136 }
137 return err == missingResourceError
138 }
139
140 type FilenameOptions struct {
141 Filenames []string
142 Kustomize string
143 Recursive bool
144 }
145
146 func (o *FilenameOptions) validate() []error {
147 var errs []error
148 if len(o.Filenames) > 0 && len(o.Kustomize) > 0 {
149 errs = append(errs, fmt.Errorf("only one of -f or -k can be specified"))
150 }
151 if len(o.Kustomize) > 0 && o.Recursive {
152 errs = append(errs, fmt.Errorf("the -k flag can't be used with -f or -R"))
153 }
154 return errs
155 }
156
157 func (o *FilenameOptions) RequireFilenameOrKustomize() error {
158 if len(o.Filenames) == 0 && len(o.Kustomize) == 0 {
159 return fmt.Errorf("must specify one of -f and -k")
160 }
161 return nil
162 }
163
164 type resourceTuple struct {
165 Resource string
166 Name string
167 }
168
169 type FakeClientFunc func(version schema.GroupVersion) (RESTClient, error)
170
171 func NewFakeBuilder(fakeClientFn FakeClientFunc, restMapper RESTMapperFunc, categoryExpander CategoryExpanderFunc) *Builder {
172 ret := newBuilder(nil, restMapper, categoryExpander)
173 ret.fakeClientFn = fakeClientFn
174 return ret
175 }
176
177
178
179
180
181 func newBuilder(clientConfigFn ClientConfigFunc, restMapper RESTMapperFunc, categoryExpander CategoryExpanderFunc) *Builder {
182 return &Builder{
183 clientConfigFn: clientConfigFn,
184 restMapperFn: restMapper,
185 categoryExpanderFn: categoryExpander,
186 requireObject: true,
187 }
188 }
189
190
191
192 type noopClientGetter struct{}
193
194 func (noopClientGetter) ToRESTConfig() (*rest.Config, error) {
195 return nil, fmt.Errorf("local operation only")
196 }
197 func (noopClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
198 return nil, fmt.Errorf("local operation only")
199 }
200 func (noopClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
201 return nil, fmt.Errorf("local operation only")
202 }
203
204
205 func NewLocalBuilder() *Builder {
206 return NewBuilder(noopClientGetter{}).Local()
207 }
208
209 func NewBuilder(restClientGetter RESTClientGetter) *Builder {
210 categoryExpanderFn := func() (restmapper.CategoryExpander, error) {
211 discoveryClient, err := restClientGetter.ToDiscoveryClient()
212 if err != nil {
213 return nil, err
214 }
215 return restmapper.NewDiscoveryCategoryExpander(discoveryClient), err
216 }
217
218 return newBuilder(
219 restClientGetter.ToRESTConfig,
220 restClientGetter.ToRESTMapper,
221 (&cachingCategoryExpanderFunc{delegate: categoryExpanderFn}).ToCategoryExpander,
222 )
223 }
224
225 func (b *Builder) Schema(schema ContentValidator) *Builder {
226 b.schema = schema
227 return b
228 }
229
230 func (b *Builder) AddError(err error) *Builder {
231 if err == nil {
232 return b
233 }
234 b.errs = append(b.errs, err)
235 return b
236 }
237
238
239
240 func (b *Builder) VisitorConcurrency(concurrency int) *Builder {
241 b.visitorConcurrency = concurrency
242 return b
243 }
244
245
246
247
248
249
250
251 func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *FilenameOptions) *Builder {
252 if errs := filenameOptions.validate(); len(errs) > 0 {
253 b.errs = append(b.errs, errs...)
254 return b
255 }
256 recursive := filenameOptions.Recursive
257 paths := filenameOptions.Filenames
258 for _, s := range paths {
259 switch {
260 case s == "-":
261 b.Stdin()
262 case strings.Index(s, "http://") == 0 || strings.Index(s, "https://") == 0:
263 url, err := url.Parse(s)
264 if err != nil {
265 b.errs = append(b.errs, fmt.Errorf("the URL passed to filename %q is not valid: %v", s, err))
266 continue
267 }
268 b.URL(defaultHttpGetAttempts, url)
269 default:
270 matches, err := expandIfFilePattern(s)
271 if err != nil {
272 b.errs = append(b.errs, err)
273 continue
274 }
275 if !recursive && len(matches) == 1 {
276 b.singleItemImplied = true
277 }
278 b.Path(recursive, matches...)
279 }
280 }
281 if filenameOptions.Kustomize != "" {
282 b.paths = append(
283 b.paths,
284 &KustomizeVisitor{
285 mapper: b.mapper,
286 dirPath: filenameOptions.Kustomize,
287 schema: b.schema,
288 fSys: filesys.MakeFsOnDisk(),
289 })
290 }
291
292 if enforceNamespace {
293 b.RequireNamespace()
294 }
295
296 return b
297 }
298
299
300
301
302
303
304 func (b *Builder) Unstructured() *Builder {
305 if b.mapper != nil {
306 b.errs = append(b.errs, fmt.Errorf("another mapper was already selected, cannot use unstructured types"))
307 return b
308 }
309 b.objectTyper = unstructuredscheme.NewUnstructuredObjectTyper()
310 b.mapper = &mapper{
311 localFn: b.isLocal,
312 restMapperFn: b.restMapperFn,
313 clientFn: b.getClient,
314 decoder: &metadataValidatingDecoder{unstructured.UnstructuredJSONScheme},
315 }
316
317 return b
318 }
319
320
321
322 func (b *Builder) WithScheme(scheme *runtime.Scheme, decodingVersions ...schema.GroupVersion) *Builder {
323 if b.mapper != nil {
324 b.errs = append(b.errs, fmt.Errorf("another mapper was already selected, cannot use internal types"))
325 return b
326 }
327 b.objectTyper = scheme
328 codecFactory := serializer.NewCodecFactory(scheme)
329 negotiatedSerializer := runtime.NegotiatedSerializer(codecFactory)
330
331
332 if len(decodingVersions) > 0 {
333 negotiatedSerializer = codecFactory.WithoutConversion()
334 }
335 b.negotiatedSerializer = negotiatedSerializer
336
337 b.mapper = &mapper{
338 localFn: b.isLocal,
339 restMapperFn: b.restMapperFn,
340 clientFn: b.getClient,
341 decoder: codecFactory.UniversalDecoder(decodingVersions...),
342 }
343
344 return b
345 }
346
347
348 func (b *Builder) LocalParam(local bool) *Builder {
349 if local {
350 b.Local()
351 }
352 return b
353 }
354
355
356 func (b *Builder) Local() *Builder {
357 b.local = true
358 return b
359 }
360
361 func (b *Builder) isLocal() bool {
362 return b.local
363 }
364
365
366 func (b *Builder) Mapper() *mapper {
367 mapper := *b.mapper
368 return &mapper
369 }
370
371
372 func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
373 for _, u := range urls {
374 b.paths = append(b.paths, &URLVisitor{
375 URL: u,
376 StreamVisitor: NewStreamVisitor(nil, b.mapper, u.String(), b.schema),
377 HttpAttemptCount: httpAttemptCount,
378 })
379 }
380 return b
381 }
382
383
384
385
386
387
388 func (b *Builder) Stdin() *Builder {
389 b.stream = true
390 if b.stdinInUse {
391 b.errs = append(b.errs, StdinMultiUseError)
392 }
393 b.stdinInUse = true
394 b.paths = append(b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
395 return b
396 }
397
398
399
400
401
402 func (b *Builder) StdinInUse() *Builder {
403 if b.stdinInUse {
404 b.errs = append(b.errs, StdinMultiUseError)
405 }
406 b.stdinInUse = true
407 return b
408 }
409
410
411
412
413
414 func (b *Builder) Stream(r io.Reader, name string) *Builder {
415 b.stream = true
416 b.paths = append(b.paths, NewStreamVisitor(r, b.mapper, name, b.schema))
417 return b
418 }
419
420
421
422
423
424
425 func (b *Builder) Path(recursive bool, paths ...string) *Builder {
426 for _, p := range paths {
427 _, err := os.Stat(p)
428 if os.IsNotExist(err) {
429 b.errs = append(b.errs, fmt.Errorf(pathNotExistError, p))
430 continue
431 }
432 if err != nil {
433 b.errs = append(b.errs, fmt.Errorf("the path %q cannot be accessed: %v", p, err))
434 continue
435 }
436
437 visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema)
438 if err != nil {
439 b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err))
440 }
441 if len(visitors) > 1 {
442 b.dir = true
443 }
444
445 b.paths = append(b.paths, visitors...)
446 }
447 if len(b.paths) == 0 && len(b.errs) == 0 {
448 b.errs = append(b.errs, fmt.Errorf("error reading %v: recognized file extensions are %v", paths, FileExtensions))
449 }
450 return b
451 }
452
453
454
455 func (b *Builder) ResourceTypes(types ...string) *Builder {
456 b.resources = append(b.resources, types...)
457 return b
458 }
459
460
461
462 func (b *Builder) ResourceNames(resource string, names ...string) *Builder {
463 for _, name := range names {
464
465 tuple, ok, err := splitResourceTypeName(name)
466 if err != nil {
467 b.errs = append(b.errs, err)
468 return b
469 }
470
471 if ok {
472 b.resourceTuples = append(b.resourceTuples, tuple)
473 continue
474 }
475 if len(resource) == 0 {
476 b.errs = append(b.errs, fmt.Errorf("the argument %q must be RESOURCE/NAME", name))
477 continue
478 }
479
480
481 b.resourceTuples = append(b.resourceTuples, resourceTuple{Resource: resource, Name: name})
482 }
483 return b
484 }
485
486
487
488
489 func (b *Builder) LabelSelectorParam(s string) *Builder {
490 selector := strings.TrimSpace(s)
491 if len(selector) == 0 {
492 return b
493 }
494 if b.selectAll {
495 b.errs = append(b.errs, fmt.Errorf("found non-empty label selector %q with previously set 'all' parameter. ", s))
496 return b
497 }
498 return b.LabelSelector(selector)
499 }
500
501
502
503 func (b *Builder) LabelSelector(selector string) *Builder {
504 if len(selector) == 0 {
505 return b
506 }
507
508 b.labelSelector = &selector
509 return b
510 }
511
512
513
514
515 func (b *Builder) FieldSelectorParam(s string) *Builder {
516 s = strings.TrimSpace(s)
517 if len(s) == 0 {
518 return b
519 }
520 if b.selectAll {
521 b.errs = append(b.errs, fmt.Errorf("found non-empty field selector %q with previously set 'all' parameter. ", s))
522 return b
523 }
524 b.fieldSelector = &s
525 return b
526 }
527
528
529
530 func (b *Builder) NamespaceParam(namespace string) *Builder {
531 b.namespace = namespace
532 return b
533 }
534
535
536
537 func (b *Builder) DefaultNamespace() *Builder {
538 b.defaultNamespace = true
539 return b
540 }
541
542
543
544 func (b *Builder) AllNamespaces(allNamespace bool) *Builder {
545 if allNamespace {
546 b.namespace = metav1.NamespaceAll
547 }
548 b.allNamespace = allNamespace
549 return b
550 }
551
552
553
554
555 func (b *Builder) RequireNamespace() *Builder {
556 b.requireNamespace = true
557 return b
558 }
559
560
561
562
563 func (b *Builder) RequestChunksOf(chunkSize int64) *Builder {
564 b.limitChunks = chunkSize
565 return b
566 }
567
568
569
570 func (b *Builder) TransformRequests(opts ...RequestTransform) *Builder {
571 b.requestTransforms = opts
572 return b
573 }
574
575
576
577 func (b *Builder) Subresource(subresource string) *Builder {
578 b.subresource = subresource
579 return b
580 }
581
582
583 func (b *Builder) SelectAllParam(selectAll bool) *Builder {
584 if selectAll && (b.labelSelector != nil || b.fieldSelector != nil) {
585 b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. "))
586 return b
587 }
588 b.selectAll = selectAll
589 return b
590 }
591
592
593
594
595
596
597 func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string) *Builder {
598 args = normalizeMultipleResourcesArgs(args)
599 if ok, err := hasCombinedTypeArgs(args); ok {
600 if err != nil {
601 b.errs = append(b.errs, err)
602 return b
603 }
604 for _, s := range args {
605 tuple, ok, err := splitResourceTypeName(s)
606 if err != nil {
607 b.errs = append(b.errs, err)
608 return b
609 }
610 if ok {
611 b.resourceTuples = append(b.resourceTuples, tuple)
612 }
613 }
614 return b
615 }
616 if len(args) > 0 {
617
618 args[0] = b.ReplaceAliases(args[0])
619 }
620 switch {
621 case len(args) > 2:
622 b.names = append(b.names, args[1:]...)
623 b.ResourceTypes(SplitResourceArgument(args[0])...)
624 case len(args) == 2:
625 b.names = append(b.names, args[1])
626 b.ResourceTypes(SplitResourceArgument(args[0])...)
627 case len(args) == 1:
628 b.ResourceTypes(SplitResourceArgument(args[0])...)
629 if b.labelSelector == nil && allowEmptySelector {
630 selector := labels.Everything().String()
631 b.labelSelector = &selector
632 }
633 case len(args) == 0:
634 default:
635 b.errs = append(b.errs, fmt.Errorf("arguments must consist of a resource or a resource and name"))
636 }
637 return b
638 }
639
640
641
642 func (b *Builder) ReplaceAliases(input string) string {
643 replaced := []string{}
644 for _, arg := range strings.Split(input, ",") {
645 if b.categoryExpanderFn == nil {
646 continue
647 }
648 categoryExpander, err := b.categoryExpanderFn()
649 if err != nil {
650 b.AddError(err)
651 continue
652 }
653
654 if resources, ok := categoryExpander.Expand(arg); ok {
655 asStrings := []string{}
656 for _, resource := range resources {
657 if len(resource.Group) == 0 {
658 asStrings = append(asStrings, resource.Resource)
659 continue
660 }
661 asStrings = append(asStrings, resource.Resource+"."+resource.Group)
662 }
663 arg = strings.Join(asStrings, ",")
664 }
665 replaced = append(replaced, arg)
666 }
667 return strings.Join(replaced, ",")
668 }
669
670 func hasCombinedTypeArgs(args []string) (bool, error) {
671 hasSlash := 0
672 for _, s := range args {
673 if strings.Contains(s, "/") {
674 hasSlash++
675 }
676 }
677 switch {
678 case hasSlash > 0 && hasSlash == len(args):
679 return true, nil
680 case hasSlash > 0 && hasSlash != len(args):
681 baseCmd := "cmd"
682 if len(os.Args) > 0 {
683 baseCmdSlice := strings.Split(os.Args[0], "/")
684 baseCmd = baseCmdSlice[len(baseCmdSlice)-1]
685 }
686 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)
687 default:
688 return false, nil
689 }
690 }
691
692
693
694 func normalizeMultipleResourcesArgs(args []string) []string {
695 if len(args) >= 2 {
696 resources := []string{}
697 resources = append(resources, SplitResourceArgument(args[0])...)
698 if len(resources) > 1 {
699 names := []string{}
700 names = append(names, args[1:]...)
701 newArgs := []string{}
702 for _, resource := range resources {
703 for _, name := range names {
704 newArgs = append(newArgs, strings.Join([]string{resource, name}, "/"))
705 }
706 }
707 return newArgs
708 }
709 }
710 return args
711 }
712
713
714
715 func splitResourceTypeName(s string) (resourceTuple, bool, error) {
716 if !strings.Contains(s, "/") {
717 return resourceTuple{}, false, nil
718 }
719 seg := strings.Split(s, "/")
720 if len(seg) != 2 {
721 return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form may not have more than one slash")
722 }
723 resource, name := seg[0], seg[1]
724 if len(resource) == 0 || len(name) == 0 || len(SplitResourceArgument(resource)) != 1 {
725 return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form must have a single resource and name")
726 }
727 return resourceTuple{Resource: resource, Name: name}, true, nil
728 }
729
730
731
732
733 func (b *Builder) Flatten() *Builder {
734 b.flatten = true
735 return b
736 }
737
738
739 func (b *Builder) Latest() *Builder {
740 b.latest = true
741 return b
742 }
743
744
745 func (b *Builder) RequireObject(require bool) *Builder {
746 b.requireObject = require
747 return b
748 }
749
750
751
752
753 func (b *Builder) ContinueOnError() *Builder {
754 b.continueOnError = true
755 return b
756 }
757
758
759
760 func (b *Builder) SingleResourceType() *Builder {
761 b.singleResourceType = true
762 return b
763 }
764
765
766
767
768 func (b *Builder) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error) {
769 fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg)
770 gvk := schema.GroupVersionKind{}
771 restMapper, err := b.restMapperFn()
772 if err != nil {
773 return nil, err
774 }
775
776 if fullySpecifiedGVR != nil {
777 gvk, _ = restMapper.KindFor(*fullySpecifiedGVR)
778 }
779 if gvk.Empty() {
780 gvk, _ = restMapper.KindFor(groupResource.WithVersion(""))
781 }
782 if !gvk.Empty() {
783 return restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
784 }
785
786 fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg)
787 if fullySpecifiedGVK == nil {
788 gvk := groupKind.WithVersion("")
789 fullySpecifiedGVK = &gvk
790 }
791
792 if !fullySpecifiedGVK.Empty() {
793 if mapping, err := restMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil {
794 return mapping, nil
795 }
796 }
797
798 mapping, err := restMapper.RESTMapping(groupKind, gvk.Version)
799 if err != nil {
800
801
802
803
804
805 if meta.IsNoMatchError(err) {
806 return nil, fmt.Errorf("the server doesn't have a resource type %q", groupResource.Resource)
807 }
808 return nil, err
809 }
810
811 return mapping, nil
812 }
813
814 func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) {
815 if len(b.resources) > 1 && b.singleResourceType {
816 return nil, fmt.Errorf("you may only specify a single resource type")
817 }
818 mappings := []*meta.RESTMapping{}
819 seen := map[schema.GroupVersionKind]bool{}
820 for _, r := range b.resources {
821 mapping, err := b.mappingFor(r)
822 if err != nil {
823 return nil, err
824 }
825
826 if seen[mapping.GroupVersionKind] {
827 continue
828 }
829 seen[mapping.GroupVersionKind] = true
830
831 mappings = append(mappings, mapping)
832 }
833 return mappings, nil
834 }
835
836 func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) {
837 mappings := make(map[string]*meta.RESTMapping)
838 canonical := make(map[schema.GroupVersionResource]struct{})
839 for _, r := range b.resourceTuples {
840 if _, ok := mappings[r.Resource]; ok {
841 continue
842 }
843 mapping, err := b.mappingFor(r.Resource)
844 if err != nil {
845 return nil, err
846 }
847
848 mappings[r.Resource] = mapping
849 canonical[mapping.Resource] = struct{}{}
850 }
851 if len(canonical) > 1 && b.singleResourceType {
852 return nil, fmt.Errorf("you may only specify a single resource type")
853 }
854 return mappings, nil
855 }
856
857 func (b *Builder) visitorResult() *Result {
858 if len(b.errs) > 0 {
859 return &Result{err: utilerrors.NewAggregate(b.errs)}
860 }
861
862 if b.selectAll {
863 selector := labels.Everything().String()
864 b.labelSelector = &selector
865 }
866
867
868 if len(b.paths) != 0 {
869 return b.visitByPaths()
870 }
871
872
873 if b.labelSelector != nil || b.fieldSelector != nil {
874 return b.visitBySelector()
875 }
876
877
878 if len(b.resourceTuples) != 0 {
879 return b.visitByResource()
880 }
881
882
883 if len(b.names) != 0 {
884 return b.visitByName()
885 }
886
887 if len(b.resources) != 0 {
888 for _, r := range b.resources {
889 _, err := b.mappingFor(r)
890 if err != nil {
891 return &Result{err: err}
892 }
893 }
894 return &Result{err: fmt.Errorf("resource(s) were provided, but no name was specified")}
895 }
896 return &Result{err: missingResourceError}
897 }
898
899 func (b *Builder) visitBySelector() *Result {
900 result := &Result{
901 targetsSingleItems: false,
902 }
903
904 if len(b.names) != 0 {
905 return result.withError(fmt.Errorf("name cannot be provided when a selector is specified"))
906 }
907 if len(b.resourceTuples) != 0 {
908 return result.withError(fmt.Errorf("selectors and the all flag cannot be used when passing resource/name arguments"))
909 }
910 if len(b.resources) == 0 {
911 return result.withError(fmt.Errorf("at least one resource must be specified to use a selector"))
912 }
913 if len(b.subresource) != 0 {
914 return result.withError(fmt.Errorf("subresource cannot be used when bulk resources are specified"))
915 }
916
917 mappings, err := b.resourceMappings()
918 if err != nil {
919 result.err = err
920 return result
921 }
922
923 var labelSelector, fieldSelector string
924 if b.labelSelector != nil {
925 labelSelector = *b.labelSelector
926 }
927 if b.fieldSelector != nil {
928 fieldSelector = *b.fieldSelector
929 }
930
931 visitors := []Visitor{}
932 for _, mapping := range mappings {
933 client, err := b.getClient(mapping.GroupVersionKind.GroupVersion())
934 if err != nil {
935 result.err = err
936 return result
937 }
938 selectorNamespace := b.namespace
939 if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
940 selectorNamespace = ""
941 }
942 visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.limitChunks))
943 }
944 if b.continueOnError {
945 result.visitor = EagerVisitorList(visitors)
946 } else {
947 result.visitor = VisitorList(visitors)
948 }
949 result.sources = visitors
950 return result
951 }
952
953 func (b *Builder) getClient(gv schema.GroupVersion) (RESTClient, error) {
954 var (
955 client RESTClient
956 err error
957 )
958
959 switch {
960 case b.fakeClientFn != nil:
961 client, err = b.fakeClientFn(gv)
962 case b.negotiatedSerializer != nil:
963 client, err = b.clientConfigFn.withStdinUnavailable(b.stdinInUse).clientForGroupVersion(gv, b.negotiatedSerializer)
964 default:
965 client, err = b.clientConfigFn.withStdinUnavailable(b.stdinInUse).unstructuredClientForGroupVersion(gv)
966 }
967
968 if err != nil {
969 return nil, err
970 }
971
972 return NewClientWithOptions(client, b.requestTransforms...), nil
973 }
974
975 func (b *Builder) visitByResource() *Result {
976
977
978 isSingleItemImplied := b.singleItemImplied
979 if !isSingleItemImplied {
980 isSingleItemImplied = len(b.resourceTuples) == 1
981 }
982
983 result := &Result{
984 singleItemImplied: isSingleItemImplied,
985 targetsSingleItems: true,
986 }
987
988 if len(b.resources) != 0 {
989 return result.withError(fmt.Errorf("you may not specify individual resources and bulk resources in the same call"))
990 }
991
992
993 mappings, err := b.resourceTupleMappings()
994 if err != nil {
995 result.err = err
996 return result
997 }
998 clients := make(map[string]RESTClient)
999 for _, mapping := range mappings {
1000 s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource.Resource)
1001 if _, ok := clients[s]; ok {
1002 continue
1003 }
1004 client, err := b.getClient(mapping.GroupVersionKind.GroupVersion())
1005 if err != nil {
1006 result.err = err
1007 return result
1008 }
1009 clients[s] = client
1010 }
1011
1012 items := []Visitor{}
1013 for _, tuple := range b.resourceTuples {
1014 mapping, ok := mappings[tuple.Resource]
1015 if !ok {
1016 return result.withError(fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings))
1017 }
1018 s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource.Resource)
1019 client, ok := clients[s]
1020 if !ok {
1021 return result.withError(fmt.Errorf("could not find a client for resource %q", tuple.Resource))
1022 }
1023
1024 selectorNamespace := b.namespace
1025 if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
1026 selectorNamespace = ""
1027 } else {
1028 if len(b.namespace) == 0 {
1029 errMsg := "namespace may not be empty when retrieving a resource by name"
1030 if b.allNamespace {
1031 errMsg = "a resource cannot be retrieved by name across all namespaces"
1032 }
1033 return result.withError(fmt.Errorf(errMsg))
1034 }
1035 }
1036
1037 info := &Info{
1038 Client: client,
1039 Mapping: mapping,
1040 Namespace: selectorNamespace,
1041 Name: tuple.Name,
1042 Subresource: b.subresource,
1043 }
1044 items = append(items, info)
1045 }
1046
1047 var visitors Visitor
1048 if b.continueOnError {
1049 visitors = EagerVisitorList(items)
1050 } else {
1051 visitors = VisitorList(items)
1052 }
1053 result.visitor = visitors
1054 result.sources = items
1055 return result
1056 }
1057
1058 func (b *Builder) visitByName() *Result {
1059 result := &Result{
1060 singleItemImplied: len(b.names) == 1,
1061 targetsSingleItems: true,
1062 }
1063
1064 if len(b.paths) != 0 {
1065 return result.withError(fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well"))
1066 }
1067 if len(b.resources) == 0 {
1068 return result.withError(fmt.Errorf("you must provide a resource and a resource name together"))
1069 }
1070 if len(b.resources) > 1 {
1071 return result.withError(fmt.Errorf("you must specify only one resource"))
1072 }
1073
1074 mappings, err := b.resourceMappings()
1075 if err != nil {
1076 result.err = err
1077 return result
1078 }
1079 mapping := mappings[0]
1080
1081 client, err := b.getClient(mapping.GroupVersionKind.GroupVersion())
1082 if err != nil {
1083 result.err = err
1084 return result
1085 }
1086
1087 selectorNamespace := b.namespace
1088 if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
1089 selectorNamespace = ""
1090 } else {
1091 if len(b.namespace) == 0 {
1092 errMsg := "namespace may not be empty when retrieving a resource by name"
1093 if b.allNamespace {
1094 errMsg = "a resource cannot be retrieved by name across all namespaces"
1095 }
1096 return result.withError(fmt.Errorf(errMsg))
1097 }
1098 }
1099
1100 visitors := []Visitor{}
1101 for _, name := range b.names {
1102 info := &Info{
1103 Client: client,
1104 Mapping: mapping,
1105 Namespace: selectorNamespace,
1106 Name: name,
1107 Subresource: b.subresource,
1108 }
1109 visitors = append(visitors, info)
1110 }
1111 result.visitor = VisitorList(visitors)
1112 result.sources = visitors
1113 return result
1114 }
1115
1116 func (b *Builder) visitByPaths() *Result {
1117 result := &Result{
1118 singleItemImplied: !b.dir && !b.stream && len(b.paths) == 1,
1119 targetsSingleItems: true,
1120 }
1121
1122 if len(b.resources) != 0 {
1123 return result.withError(fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well"))
1124 }
1125 if len(b.names) != 0 {
1126 return result.withError(fmt.Errorf("name cannot be provided when a path is specified"))
1127 }
1128 if len(b.resourceTuples) != 0 {
1129 return result.withError(fmt.Errorf("resource/name arguments cannot be provided when a path is specified"))
1130 }
1131
1132 var visitors Visitor
1133 if b.continueOnError {
1134 visitors = EagerVisitorList(b.paths)
1135 } else {
1136 visitors = ConcurrentVisitorList{
1137 visitors: b.paths,
1138 concurrency: b.visitorConcurrency,
1139 }
1140 }
1141
1142 if b.flatten {
1143 visitors = NewFlattenListVisitor(visitors, b.objectTyper, b.mapper)
1144 }
1145
1146
1147 if b.latest {
1148
1149 if b.defaultNamespace {
1150 visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
1151 }
1152 visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
1153 }
1154 if b.labelSelector != nil {
1155 selector, err := labels.Parse(*b.labelSelector)
1156 if err != nil {
1157 return result.withError(fmt.Errorf("the provided selector %q is not valid: %v", *b.labelSelector, err))
1158 }
1159 visitors = NewFilteredVisitor(visitors, FilterByLabelSelector(selector))
1160 }
1161 result.visitor = visitors
1162 result.sources = b.paths
1163 return result
1164 }
1165
1166
1167
1168
1169
1170 func (b *Builder) Do() *Result {
1171 r := b.visitorResult()
1172 r.mapper = b.Mapper()
1173 if r.err != nil {
1174 return r
1175 }
1176 if b.flatten {
1177 r.visitor = NewFlattenListVisitor(r.visitor, b.objectTyper, b.mapper)
1178 }
1179 helpers := []VisitorFunc{}
1180 if b.defaultNamespace {
1181 helpers = append(helpers, SetNamespace(b.namespace))
1182 }
1183 if b.requireNamespace {
1184 helpers = append(helpers, RequireNamespace(b.namespace))
1185 }
1186 helpers = append(helpers, FilterNamespace)
1187 if b.requireObject {
1188 helpers = append(helpers, RetrieveLazy)
1189 }
1190 if b.continueOnError {
1191 r.visitor = ContinueOnErrorVisitor{Visitor: r.visitor}
1192 }
1193 r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
1194 return r
1195 }
1196
1197
1198
1199 func SplitResourceArgument(arg string) []string {
1200 out := []string{}
1201 set := sets.NewString()
1202 for _, s := range strings.Split(arg, ",") {
1203 if set.Has(s) {
1204 continue
1205 }
1206 set.Insert(s)
1207 out = append(out, s)
1208 }
1209 return out
1210 }
1211
1212
1213 func HasNames(args []string) (bool, error) {
1214 args = normalizeMultipleResourcesArgs(args)
1215 hasCombinedTypes, err := hasCombinedTypeArgs(args)
1216 if err != nil {
1217 return false, err
1218 }
1219 return hasCombinedTypes || len(args) > 1, nil
1220 }
1221
1222
1223
1224
1225 func expandIfFilePattern(pattern string) ([]string, error) {
1226 if _, err := os.Stat(pattern); os.IsNotExist(err) {
1227 matches, err := filepath.Glob(pattern)
1228 if err == nil && len(matches) == 0 {
1229 return nil, fmt.Errorf(pathNotExistError, pattern)
1230 }
1231 if err == filepath.ErrBadPattern {
1232 return nil, fmt.Errorf("pattern %q is not valid: %v", pattern, err)
1233 }
1234 return matches, err
1235 }
1236 return []string{pattern}, nil
1237 }
1238
1239 type cachingCategoryExpanderFunc struct {
1240 delegate CategoryExpanderFunc
1241
1242 lock sync.Mutex
1243 cached restmapper.CategoryExpander
1244 }
1245
1246 func (c *cachingCategoryExpanderFunc) ToCategoryExpander() (restmapper.CategoryExpander, error) {
1247 c.lock.Lock()
1248 defer c.lock.Unlock()
1249 if c.cached != nil {
1250 return c.cached, nil
1251 }
1252
1253 ret, err := c.delegate()
1254 if err != nil {
1255 return nil, err
1256 }
1257 c.cached = ret
1258 return c.cached, nil
1259 }
1260
View as plain text