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

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