diff --git a/find/doc.go b/find/doc.go new file mode 100644 index 000000000..75f05d81e --- /dev/null +++ b/find/doc.go @@ -0,0 +1,34 @@ +/* +Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package find implements inventory listing and searching. + +The Finder is an alternative to the object.SearchIndex FindByInventoryPath() and FindChild() methods. +SearchIndex.FindByInventoryPath requires an absolute path, whereas the Finder also supports relative paths +and patterns via filepath.Match. +SearchIndex.FindChild requires a parent to find the child, whereas the Finder also supports an ancestor via +recursive object traversal. + +The various Finder methods accept a "path" argument, which can absolute or relative to the Folder for the object type. +The Finder supports two modes, "list" and "find". The "list" mode behaves like the "ls" command, only searching within +the immediate path. The "find" mode behaves like the "find" command, with the search starting at the immediate path but +also recursing into sub Folders relative to the Datacenter. The default mode is "list" if the given path contains a "/", +otherwise "find" mode is used. + +See also: https://github.com/vmware/govmomi/blob/master/govc/README.md#usage +*/ +package find diff --git a/find/finder.go b/find/finder.go index 9f638a52a..5eb6aa1bb 100644 --- a/find/finder.go +++ b/find/finder.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import ( "context" "errors" "path" + "strings" "github.com/vmware/govmomi/list" "github.com/vmware/govmomi/object" @@ -30,9 +31,8 @@ import ( ) type Finder struct { - client *vim25.Client - recurser list.Recurser - + client *vim25.Client + r recurser dc *object.Datacenter folders *object.DatacenterFolders } @@ -40,7 +40,7 @@ type Finder struct { func NewFinder(client *vim25.Client, all bool) *Finder { f := &Finder{ client: client, - recurser: list.Recurser{ + r: recurser{ Collector: property.DefaultCollector(client), All: all, }, @@ -55,9 +55,9 @@ func (f *Finder) SetDatacenter(dc *object.Datacenter) *Finder { return f } -type findRelativeFunc func(ctx context.Context) (object.Reference, error) +func (f *Finder) find(ctx context.Context, arg string, s *spec) ([]list.Element, error) { + isPath := strings.Contains(arg, "/") -func (f *Finder) find(ctx context.Context, fn findRelativeFunc, tl bool, arg string) ([]list.Element, error) { root := list.Element{ Path: "/", Object: object.NewRootFolder(f.client), @@ -70,7 +70,7 @@ func (f *Finder) find(ctx context.Context, fn findRelativeFunc, tl bool, arg str case "..": // Not supported; many edge case, little value return nil, errors.New("cannot traverse up a tree") case ".": // Relative to whatever - pivot, err := fn(ctx) + pivot, err := s.Relative(ctx) if err != nil { return nil, err } @@ -93,13 +93,13 @@ func (f *Finder) find(ctx context.Context, fn findRelativeFunc, tl bool, arg str } } - f.recurser.TraverseLeafs = tl - es, err := f.recurser.Recurse(ctx, root, parts) - if err != nil { - return nil, err + if s.listMode(isPath) { + return f.r.List(ctx, s, root, parts) } - return es, nil + s.Parents = append(s.Parents, s.Nested...) + + return f.r.Find(ctx, s, root, parts) } func (f *Finder) datacenter() (*object.Datacenter, error) { @@ -208,7 +208,7 @@ func (f *Finder) rootFolder(_ context.Context) (object.Reference, error) { return object.NewRootFolder(f.client), nil } -func (f *Finder) managedObjectList(ctx context.Context, path string, tl bool) ([]list.Element, error) { +func (f *Finder) managedObjectList(ctx context.Context, path string, tl bool, include []string) ([]list.Element, error) { fn := f.rootFolder if f.dc != nil { @@ -219,7 +219,23 @@ func (f *Finder) managedObjectList(ctx context.Context, path string, tl bool) ([ path = "." } - return f.find(ctx, fn, tl, path) + s := &spec{ + Relative: fn, + Parents: []string{"ComputeResource", "ClusterComputeResource", "HostSystem", "VirtualApp", "StoragePod"}, + Include: include, + } + + if tl { + if path == "/**" { + // TODO: support switching to find mode for any path, not just relative to f.rootFolder or f.dcReference + path = "*" + } else { + s.Contents = true + s.ListMode = types.NewBool(true) + } + } + + return f.find(ctx, path, s) } // Element returns an Element for the given ManagedObjectReference @@ -229,7 +245,11 @@ func (f *Finder) Element(ctx context.Context, ref types.ManagedObjectReference) return ref, nil } - e, err := f.find(ctx, rl, false, ".") + s := &spec{ + Relative: rl, + } + + e, err := f.find(ctx, "./", s) if err != nil { return nil, err } @@ -270,16 +290,21 @@ func (f *Finder) ObjectReference(ctx context.Context, ref types.ManagedObjectRef return r, nil } -func (f *Finder) ManagedObjectList(ctx context.Context, path string) ([]list.Element, error) { - return f.managedObjectList(ctx, path, false) +func (f *Finder) ManagedObjectList(ctx context.Context, path string, include ...string) ([]list.Element, error) { + return f.managedObjectList(ctx, path, false, include) } -func (f *Finder) ManagedObjectListChildren(ctx context.Context, path string) ([]list.Element, error) { - return f.managedObjectList(ctx, path, true) +func (f *Finder) ManagedObjectListChildren(ctx context.Context, path string, include ...string) ([]list.Element, error) { + return f.managedObjectList(ctx, path, true, include) } func (f *Finder) DatacenterList(ctx context.Context, path string) ([]*object.Datacenter, error) { - es, err := f.find(ctx, f.rootFolder, false, path) + s := &spec{ + Relative: f.rootFolder, + Include: []string{"Datacenter"}, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -336,7 +361,12 @@ func (f *Finder) DatacenterOrDefault(ctx context.Context, path string) (*object. } func (f *Finder) DatastoreList(ctx context.Context, path string) ([]*object.Datastore, error) { - es, err := f.find(ctx, f.datastoreFolder, false, path) + s := &spec{ + Relative: f.datastoreFolder, + Parents: []string{"StoragePod"}, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -404,7 +434,11 @@ func (f *Finder) DatastoreOrDefault(ctx context.Context, path string) (*object.D } func (f *Finder) DatastoreClusterList(ctx context.Context, path string) ([]*object.StoragePod, error) { - es, err := f.find(ctx, f.datastoreFolder, false, path) + s := &spec{ + Relative: f.datastoreFolder, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -461,7 +495,11 @@ func (f *Finder) DatastoreClusterOrDefault(ctx context.Context, path string) (*o } func (f *Finder) ComputeResourceList(ctx context.Context, path string) ([]*object.ComputeResource, error) { - es, err := f.find(ctx, f.hostFolder, false, path) + s := &spec{ + Relative: f.hostFolder, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -523,7 +561,11 @@ func (f *Finder) ComputeResourceOrDefault(ctx context.Context, path string) (*ob } func (f *Finder) ClusterComputeResourceList(ctx context.Context, path string) ([]*object.ClusterComputeResource, error) { - es, err := f.find(ctx, f.hostFolder, false, path) + s := &spec{ + Relative: f.hostFolder, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -564,7 +606,13 @@ func (f *Finder) ClusterComputeResource(ctx context.Context, path string) (*obje } func (f *Finder) HostSystemList(ctx context.Context, path string) ([]*object.HostSystem, error) { - es, err := f.find(ctx, f.hostFolder, false, path) + s := &spec{ + Relative: f.hostFolder, + Parents: []string{"ComputeResource", "ClusterComputeResource"}, + Include: []string{"HostSystem"}, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -635,7 +683,11 @@ func (f *Finder) HostSystemOrDefault(ctx context.Context, path string) (*object. } func (f *Finder) NetworkList(ctx context.Context, path string) ([]object.NetworkReference, error) { - es, err := f.find(ctx, f.networkFolder, false, path) + s := &spec{ + Relative: f.networkFolder, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -701,7 +753,14 @@ func (f *Finder) NetworkOrDefault(ctx context.Context, path string) (object.Netw } func (f *Finder) ResourcePoolList(ctx context.Context, path string) ([]*object.ResourcePool, error) { - es, err := f.find(ctx, f.hostFolder, true, path) + s := &spec{ + Relative: f.hostFolder, + Parents: []string{"ComputeResource", "ClusterComputeResource", "VirtualApp"}, + Nested: []string{"ResourcePool"}, + Contents: true, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -804,7 +863,12 @@ func (f *Finder) FolderOrDefault(ctx context.Context, path string) (*object.Fold } func (f *Finder) VirtualMachineList(ctx context.Context, path string) ([]*object.VirtualMachine, error) { - es, err := f.find(ctx, f.vmFolder, false, path) + s := &spec{ + Relative: f.vmFolder, + Parents: []string{"VirtualApp"}, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } @@ -840,7 +904,11 @@ func (f *Finder) VirtualMachine(ctx context.Context, path string) (*object.Virtu } func (f *Finder) VirtualAppList(ctx context.Context, path string) ([]*object.VirtualApp, error) { - es, err := f.find(ctx, f.vmFolder, false, path) + s := &spec{ + Relative: f.vmFolder, + } + + es, err := f.find(ctx, path, s) if err != nil { return nil, err } diff --git a/find/recurser.go b/find/recurser.go new file mode 100644 index 000000000..533fa9e38 --- /dev/null +++ b/find/recurser.go @@ -0,0 +1,246 @@ +/* +Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package find + +import ( + "context" + "os" + "path" + "path/filepath" + + "github.com/vmware/govmomi/list" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25/mo" +) + +// spec is used to specify per-search configuration, independent of the Finder instance. +type spec struct { + // Relative returns the root object to resolve Relative paths (starts with ".") + Relative func(ctx context.Context) (object.Reference, error) + + // ListMode can be used to optionally force "ls" behavior, rather than "find" behavior + ListMode *bool + + // Contents configures the Recurser to list the Contents of traversable leaf nodes. + // This is typically set to true when used from the ls command, where listing + // a folder means listing its Contents. This is typically set to false for + // commands that take managed entities that are not folders as input. + Contents bool + + // Parents specifies the types which can contain the child types being searched for. + // for example, when searching for a HostSystem, parent types can be + // "ComputeResource" or "ClusterComputeResource". + Parents []string + + // Include specifies which types to be included in the results, used only in "find" mode. + Include []string + + // Nested should be set to types that can be Nested, used only in "find" mode. + Nested []string + + // ChildType avoids traversing into folders that can't contain the Include types, used only in "find" mode. + ChildType []string +} + +func (s *spec) traversable(o mo.Reference) bool { + ref := o.Reference() + + switch ref.Type { + case "Datacenter": + if len(s.Include) == 1 && s.Include[0] == "Datacenter" { + // No point in traversing deeper as Datacenters cannot be nested + return false + } + return true + case "Folder": + if f, ok := o.(mo.Folder); ok { + // TODO: Not making use of this yet, but here we can optimize when searching the entire + // inventory across Datacenters for specific types, for example: 'govc ls -t VirtualMachine /**' + // should not traverse into a Datacenter's host, network or datatore folders. + if !s.traversableChildType(f.ChildType) { + return false + } + } + + return true + } + + for _, kind := range s.Parents { + if kind == ref.Type { + return true + } + } + + return false +} + +func (s *spec) traversableChildType(ctypes []string) bool { + if len(s.ChildType) == 0 { + return true + } + + for _, t := range ctypes { + for _, c := range s.ChildType { + if t == c { + return true + } + } + } + + return false +} + +func (s *spec) wanted(e list.Element) bool { + if len(s.Include) == 0 { + return true + } + + w := e.Object.Reference().Type + + for _, kind := range s.Include { + if w == kind { + return true + } + } + + return false +} + +// listMode is a global option to revert to the original Finder behavior, +// disabling the newer "find" mode. +var listMode = os.Getenv("GOVMOMI_FINDER_LIST_MODE") == "true" + +func (s *spec) listMode(isPath bool) bool { + if listMode { + return true + } + + if s.ListMode != nil { + return *s.ListMode + } + + return isPath +} + +type recurser struct { + Collector *property.Collector + + // All configures the recurses to fetch complete objects for leaf nodes. + All bool +} + +func (r recurser) List(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) { + if len(parts) == 0 { + // Include non-traversable leaf elements in result. For example, consider + // the pattern "./vm/my-vm-*", where the pattern should match the VMs and + // not try to traverse them. + // + // Include traversable leaf elements in result, if the contents + // field is set to false. + // + if !s.Contents || !s.traversable(root.Object.Reference()) { + return []list.Element{root}, nil + } + } + + k := list.Lister{ + Collector: r.Collector, + Reference: root.Object.Reference(), + Prefix: root.Path, + } + + if r.All && len(parts) < 2 { + k.All = true + } + + in, err := k.List(ctx) + if err != nil { + return nil, err + } + + // This folder is a leaf as far as the glob goes. + if len(parts) == 0 { + return in, nil + } + + pattern := parts[0] + parts = parts[1:] + + var out []list.Element + for _, e := range in { + matched, err := filepath.Match(pattern, path.Base(e.Path)) + if err != nil { + return nil, err + } + + if !matched { + continue + } + + nres, err := r.List(ctx, s, e, parts) + if err != nil { + return nil, err + } + + out = append(out, nres...) + } + + return out, nil +} + +func (r recurser) Find(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) { + var out []list.Element + + if len(parts) > 0 { + pattern := parts[0] + matched, err := filepath.Match(pattern, path.Base(root.Path)) + if err != nil { + return nil, err + } + + if matched && s.wanted(root) { + out = append(out, root) + } + } + + if !s.traversable(root.Object) { + return out, nil + } + + k := list.Lister{ + Collector: r.Collector, + Reference: root.Object.Reference(), + Prefix: root.Path, + } + + in, err := k.List(ctx) + if err != nil { + return nil, err + } + + for _, e := range in { + nres, err := r.Find(ctx, s, e, parts) + if err != nil { + return nil, err + } + + out = append(out, nres...) + } + + return out, nil +} diff --git a/govc/datacenter/info.go b/govc/datacenter/info.go index 1850965a0..e0fd0d0ce 100644 --- a/govc/datacenter/info.go +++ b/govc/datacenter/info.go @@ -148,6 +148,7 @@ func (r *infoResult) Write(w io.Writer) error { for _, o := range r.objects { dc := objects[o.Reference()] fmt.Fprintf(tw, "Name:\t%s\n", dc.Name) + fmt.Fprintf(tw, " Path:\t%s\n", o.InventoryPath) folders, err := o.Folders(r.ctx) if err != nil { diff --git a/govc/emacs/govc.el b/govc/emacs/govc.el index 22be8aa2b..0c9cb388a 100644 --- a/govc/emacs/govc.el +++ b/govc/emacs/govc.el @@ -394,7 +394,7 @@ Return value is `json-read'." (defun govc-ls-datacenter () "List datacenters." - (govc "ls" "-t" "Datacenter" "/" "/*")) + (govc "ls" "-t" "Datacenter" "/**")) (defun govc-object-prompt (prompt ls) "PROMPT for object name via LS function. Return object without PROMPT if there is just one instance." @@ -815,7 +815,7 @@ Inherit SESSION if given." (buffer (get-buffer-create "*govc-esxcli*"))) (pop-to-buffer buffer) (tabulated-list-mode) - (setq govc-args (list "-host.ipath" host)) + (setq govc-args (list "-host" host)) (govc-session-clone session) (setq tabulated-list-format [("CCAlgo" 10 t) ("ForeignAddress" 20 t) @@ -838,7 +838,7 @@ Inherit SESSION if given." (defun govc-host-info () "Wrapper for govc host.info." - (govc-table-info "host.info" (or govc-filter "*/*"))) + (govc-table-info "host.info" (or govc-filter "*"))) (defun govc-host-json-info () "JSON via govc host.info -json on current selection." @@ -897,20 +897,9 @@ Optionally filter by FILTER and inherit SESSION." ;;; govc pool mode -(defun govc-ls-pool (&optional pools) - "List resource POOLS recursively." - (let ((subpools (govc "ls" "-t" "ResourcePool" (--map (concat it "/*") (or pools '("host")))))) - (append pools - (if subpools - (govc-ls-pool subpools))))) - -(defun govc-ls-vapp () - "List virtual apps." - (govc "ls" "-t" "VirtualApp" "vm")) - (defun govc-pool-destroy (name) "Destroy pool with given NAME." - (interactive (list (completing-read "Destroy pool: " (govc-ls-pool)))) + (interactive (list (completing-read "Destroy pool: " (govc "ls" "-t" "ResourcePool" "host/*")))) (govc "pool.destroy" name)) (defun govc-pool-destroy-selection () @@ -921,7 +910,7 @@ Optionally filter by FILTER and inherit SESSION." (defun govc-pool-info () "Wrapper for govc pool.info." - (govc-table-info "pool.info" (or govc-filter (append (govc-ls-pool) (govc-ls-vapp))))) + (govc-table-info "pool.info" (list "-a" (or govc-filter (setq govc-filter "*"))))) (defun govc-pool-json-info () "JSON via govc pool.info -json on current selection." @@ -1295,7 +1284,7 @@ Open via `eww' by default, via `browse-url' if ARG is non-nil." (defun govc-vm-info () "Wrapper for govc vm.info." - (govc-table-info "vm.info" (list "-r" (or govc-filter (setq govc-filter (govc-vm-filter)))))) + (govc-table-info "vm.info" (list "-r" (or govc-filter (setq govc-filter "*"))))) (defun govc-vm-host () "Host info via `govc-host' with host(s) of current selection." @@ -1381,19 +1370,6 @@ Open via `eww' by default, via `browse-url' if ARG is non-nil." map) "Keymap for `govc-vm-mode'.") -(defun govc-vm-filter () - "Default `govc-filter' for `vm-info'." - (--map (concat it "/*") - (append (govc-ls-folder (list (concat govc-session-datacenter "/vm"))) - (govc "ls" "-t" "VirtualApp" "vm")))) - -(defun govc-ls-folder (folders) - "List FOLDERS recursively." - (let ((subfolders (govc "ls" "-t" "Folder" folders))) - (append folders - (if subfolders - (govc-ls-folder subfolders))))) - (defun govc-vm (&optional filter session) "VM info via govc. Optionally filter by FILTER and inherit SESSION." diff --git a/govc/flags/version.go b/govc/flags/version.go index 65b9f39a2..3f3e13103 100644 --- a/govc/flags/version.go +++ b/govc/flags/version.go @@ -21,7 +21,7 @@ import ( "strings" ) -const Version = "0.12.1" +const Version = "0.13.0" type version []int diff --git a/govc/ls/command.go b/govc/ls/command.go index bbd201963..06a9b409f 100644 --- a/govc/ls/command.go +++ b/govc/ls/command.go @@ -99,6 +99,12 @@ func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error { var ref = new(types.ManagedObjectReference) + var types []string + if cmd.Type != "" { + // TODO: support multiple -t flags + types = []string{cmd.Type} + } + for _, arg := range args { if cmd.DeRef && ref.FromString(arg) { e, err := finder.Element(ctx, *ref) @@ -115,7 +121,7 @@ func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error { } } - es, err := finder.ManagedObjectListChildren(ctx, arg) + es, err := finder.ManagedObjectListChildren(ctx, arg, types...) if err != nil { return err } diff --git a/list/lister.go b/list/lister.go index ae162b7fc..2ee32e6bc 100644 --- a/list/lister.go +++ b/list/lister.go @@ -33,6 +33,10 @@ type Element struct { Object mo.Reference } +func (e Element) String() string { + return fmt.Sprintf("%s @ %s", e.Object.Reference(), e.Path) +} + func ToElement(r mo.Reference, prefix string) Element { var name string @@ -112,23 +116,6 @@ type Lister struct { All bool } -func traversable(ref types.ManagedObjectReference) bool { - switch ref.Type { - case "Folder": - case "Datacenter": - case "ComputeResource", "ClusterComputeResource": - // Treat ComputeResource and ClusterComputeResource as one and the same. - // It doesn't matter from the perspective of the lister. - case "HostSystem": - case "VirtualApp": - case "StoragePod": - default: - return false - } - - return true -} - func (l Lister) retrieveProperties(ctx context.Context, req types.RetrieveProperties, dst *[]interface{}) error { res, err := l.Collector.RetrieveProperties(ctx, req) if err != nil { @@ -225,6 +212,8 @@ func (l Lister) ListFolder(ctx context.Context) ([]Element, error) { // Additional basic properties. switch t { + case "Folder": + pspec.PathSet = append(pspec.PathSet, "childType") case "ComputeResource", "ClusterComputeResource": // The ComputeResource and ClusterComputeResource are dereferenced in // the ResourcePoolFlag. Make sure they always have their resourcePool @@ -286,7 +275,7 @@ func (l Lister) ListDatacenter(ctx context.Context) ([]Element, error) { if l.All { pspec.All = types.NewBool(true) } else { - pspec.PathSet = []string{"name"} + pspec.PathSet = []string{"name", "childType"} } req := types.RetrieveProperties{ diff --git a/list/recurser.go b/list/recurser.go deleted file mode 100644 index 32a67829f..000000000 --- a/list/recurser.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package list - -import ( - "context" - "path" - "path/filepath" - - "github.com/vmware/govmomi/property" -) - -type Recurser struct { - Collector *property.Collector - - // All configures the recurses to fetch complete objects for leaf nodes. - All bool - - // TraverseLeafs configures the Recurser to traverse traversable leaf nodes. - // This is typically set to true when used from the ls command, where listing - // a folder means listing its contents. This is typically set to false for - // commands that take managed entities that are not folders as input. - TraverseLeafs bool -} - -func (r Recurser) Recurse(ctx context.Context, root Element, parts []string) ([]Element, error) { - if len(parts) == 0 { - // Include non-traversable leaf elements in result. For example, consider - // the pattern "./vm/my-vm-*", where the pattern should match the VMs and - // not try to traverse them. - // - // Include traversable leaf elements in result, if the TraverseLeafs - // field is set to false. - // - if !traversable(root.Object.Reference()) || !r.TraverseLeafs { - return []Element{root}, nil - } - } - - k := Lister{ - Collector: r.Collector, - Reference: root.Object.Reference(), - Prefix: root.Path, - } - - if r.All && len(parts) < 2 { - k.All = true - } - - in, err := k.List(ctx) - if err != nil { - return nil, err - } - - // This folder is a leaf as far as the glob goes. - if len(parts) == 0 { - return in, nil - } - - pattern := parts[0] - parts = parts[1:] - - var out []Element - for _, e := range in { - matched, err := filepath.Match(pattern, path.Base(e.Path)) - if err != nil { - return nil, err - } - - if !matched { - continue - } - - nres, err := r.Recurse(ctx, e, parts) - if err != nil { - return nil, err - } - - out = append(out, nres...) - } - - return out, nil -}