github.com/vmware/govmomi@v0.51.0/find/recurser.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package find
     6  
     7  import (
     8  	"context"
     9  	"os"
    10  	"path"
    11  	"strings"
    12  
    13  	"github.com/vmware/govmomi/list"
    14  	"github.com/vmware/govmomi/object"
    15  	"github.com/vmware/govmomi/property"
    16  	"github.com/vmware/govmomi/vim25/mo"
    17  )
    18  
    19  // spec is used to specify per-search configuration, independent of the Finder instance.
    20  type spec struct {
    21  	// Relative returns the root object to resolve Relative paths (starts with ".")
    22  	Relative func(ctx context.Context) (object.Reference, error)
    23  
    24  	// ListMode can be used to optionally force "ls" behavior, rather than "find" behavior
    25  	ListMode *bool
    26  
    27  	// Contents configures the Recurser to list the Contents of traversable leaf nodes.
    28  	// This is typically set to true when used from the ls command, where listing
    29  	// a folder means listing its Contents. This is typically set to false for
    30  	// commands that take managed entities that are not folders as input.
    31  	Contents bool
    32  
    33  	// Parents specifies the types which can contain the child types being searched for.
    34  	// for example, when searching for a HostSystem, parent types can be
    35  	// "ComputeResource" or "ClusterComputeResource".
    36  	Parents []string
    37  
    38  	// Include specifies which types to be included in the results, used only in "find" mode.
    39  	Include []string
    40  
    41  	// Nested should be set to types that can be Nested, used only in "find" mode.
    42  	Nested []string
    43  
    44  	// ChildType avoids traversing into folders that can't contain the Include types, used only in "find" mode.
    45  	ChildType []string
    46  }
    47  
    48  func (s *spec) traversable(o mo.Reference) bool {
    49  	ref := o.Reference()
    50  
    51  	switch ref.Type {
    52  	case "Datacenter":
    53  		if len(s.Include) == 1 && s.Include[0] == "Datacenter" {
    54  			// No point in traversing deeper as Datacenters cannot be nested
    55  			return false
    56  		}
    57  		return true
    58  	case "Folder":
    59  		if f, ok := o.(mo.Folder); ok {
    60  			// TODO: Not making use of this yet, but here we can optimize when searching the entire
    61  			// inventory across Datacenters for specific types, for example: 'govc ls -t VirtualMachine /**'
    62  			// should not traverse into a Datacenter's host, network or datatore folders.
    63  			if !s.traversableChildType(f.ChildType) {
    64  				return false
    65  			}
    66  		}
    67  
    68  		return true
    69  	}
    70  
    71  	for _, kind := range s.Parents {
    72  		if kind == ref.Type {
    73  			return true
    74  		}
    75  	}
    76  
    77  	return false
    78  }
    79  
    80  func (s *spec) traversableChildType(ctypes []string) bool {
    81  	if len(s.ChildType) == 0 {
    82  		return true
    83  	}
    84  
    85  	for _, t := range ctypes {
    86  		for _, c := range s.ChildType {
    87  			if t == c {
    88  				return true
    89  			}
    90  		}
    91  	}
    92  
    93  	return false
    94  }
    95  
    96  func (s *spec) wanted(e list.Element) bool {
    97  	if len(s.Include) == 0 {
    98  		return true
    99  	}
   100  
   101  	w := e.Object.Reference().Type
   102  
   103  	for _, kind := range s.Include {
   104  		if w == kind {
   105  			return true
   106  		}
   107  	}
   108  
   109  	return false
   110  }
   111  
   112  // listMode is a global option to revert to the original Finder behavior,
   113  // disabling the newer "find" mode.
   114  var listMode = os.Getenv("GOVMOMI_FINDER_LIST_MODE") == "true"
   115  
   116  func (s *spec) listMode(isPath bool) bool {
   117  	if listMode {
   118  		return true
   119  	}
   120  
   121  	if s.ListMode != nil {
   122  		return *s.ListMode
   123  	}
   124  
   125  	return isPath
   126  }
   127  
   128  type recurser struct {
   129  	Collector *property.Collector
   130  
   131  	// All configures the recurses to fetch complete objects for leaf nodes.
   132  	All bool
   133  }
   134  
   135  func (r recurser) List(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) {
   136  	if len(parts) == 0 {
   137  		// Include non-traversable leaf elements in result. For example, consider
   138  		// the pattern "./vm/my-vm-*", where the pattern should match the VMs and
   139  		// not try to traverse them.
   140  		//
   141  		// Include traversable leaf elements in result, if the contents
   142  		// field is set to false.
   143  		//
   144  		if !s.Contents || !s.traversable(root.Object.Reference()) {
   145  			return []list.Element{root}, nil
   146  		}
   147  	}
   148  
   149  	k := list.Lister{
   150  		Collector: r.Collector,
   151  		Reference: root.Object.Reference(),
   152  		Prefix:    root.Path,
   153  	}
   154  
   155  	if r.All && len(parts) < 2 {
   156  		k.All = true
   157  	}
   158  
   159  	in, err := k.List(ctx)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	// This folder is a leaf as far as the glob goes.
   165  	if len(parts) == 0 {
   166  		return in, nil
   167  	}
   168  
   169  	all := parts
   170  	pattern := parts[0]
   171  	parts = parts[1:]
   172  
   173  	var out []list.Element
   174  	for _, e := range in {
   175  		matched, err := path.Match(pattern, path.Base(e.Path))
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		if !matched {
   181  			matched = strings.HasSuffix(e.Path, "/"+path.Join(all...))
   182  			if matched {
   183  				// name contains a '/'
   184  				out = append(out, e)
   185  			}
   186  
   187  			continue
   188  		}
   189  
   190  		nres, err := r.List(ctx, s, e, parts)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  
   195  		out = append(out, nres...)
   196  	}
   197  
   198  	return out, nil
   199  }
   200  
   201  func (r recurser) Find(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) {
   202  	var out []list.Element
   203  
   204  	if len(parts) > 0 {
   205  		pattern := parts[0]
   206  		matched, err := path.Match(pattern, path.Base(root.Path))
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  
   211  		if matched && s.wanted(root) {
   212  			out = append(out, root)
   213  		}
   214  	}
   215  
   216  	if !s.traversable(root.Object) {
   217  		return out, nil
   218  	}
   219  
   220  	k := list.Lister{
   221  		Collector: r.Collector,
   222  		Reference: root.Object.Reference(),
   223  		Prefix:    root.Path,
   224  	}
   225  
   226  	in, err := k.List(ctx)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	for _, e := range in {
   232  		nres, err := r.Find(ctx, s, e, parts)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  
   237  		out = append(out, nres...)
   238  	}
   239  
   240  	return out, nil
   241  }