github.com/vmware/govmomi@v0.51.0/cli/object/find.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 object
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"strings"
    14  	"text/tabwriter"
    15  
    16  	"github.com/vmware/govmomi/cli"
    17  	"github.com/vmware/govmomi/cli/flags"
    18  	"github.com/vmware/govmomi/fault"
    19  	"github.com/vmware/govmomi/internal"
    20  	"github.com/vmware/govmomi/object"
    21  	"github.com/vmware/govmomi/property"
    22  	"github.com/vmware/govmomi/view"
    23  	"github.com/vmware/govmomi/vim25"
    24  	"github.com/vmware/govmomi/vim25/mo"
    25  	"github.com/vmware/govmomi/vim25/types"
    26  )
    27  
    28  type find struct {
    29  	*flags.DatacenterFlag
    30  
    31  	ref      bool
    32  	id       bool
    33  	long     bool
    34  	parent   bool
    35  	kind     kinds
    36  	name     string
    37  	maxdepth int
    38  }
    39  
    40  var alias = []struct {
    41  	name string
    42  	kind string
    43  }{
    44  	{"a", "VirtualApp"},
    45  	{"c", "ClusterComputeResource"},
    46  	{"d", "Datacenter"},
    47  	{"f", "Folder"},
    48  	{"g", "DistributedVirtualPortgroup"},
    49  	{"h", "HostSystem"},
    50  	{"m", "VirtualMachine"},
    51  	{"n", "Network"},
    52  	{"o", "OpaqueNetwork"},
    53  	{"p", "ResourcePool"},
    54  	{"r", "ComputeResource"},
    55  	{"s", "Datastore"},
    56  	{"w", "DistributedVirtualSwitch"},
    57  }
    58  
    59  func aliasHelp() string {
    60  	var help bytes.Buffer
    61  
    62  	for _, a := range alias {
    63  		fmt.Fprintf(&help, "  %s    %s\n", a.name, a.kind)
    64  	}
    65  
    66  	return help.String()
    67  }
    68  
    69  type kinds []string
    70  
    71  func (e *kinds) String() string {
    72  	return fmt.Sprint(*e)
    73  }
    74  
    75  func (e *kinds) Set(value string) error {
    76  	*e = append(*e, e.alias(value))
    77  	return nil
    78  }
    79  
    80  func (e *kinds) alias(value string) string {
    81  	if len(value) != 1 {
    82  		return value
    83  	}
    84  
    85  	for _, a := range alias {
    86  		if a.name == value {
    87  			return a.kind
    88  		}
    89  	}
    90  
    91  	return value
    92  }
    93  
    94  func (e *kinds) wanted(kind string) bool {
    95  	if len(*e) == 0 {
    96  		return true
    97  	}
    98  
    99  	for _, k := range *e {
   100  		if kind == k {
   101  			return true
   102  		}
   103  	}
   104  
   105  	return false
   106  }
   107  
   108  func init() {
   109  	cli.Register("find", &find{})
   110  }
   111  
   112  func (cmd *find) Register(ctx context.Context, f *flag.FlagSet) {
   113  	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
   114  	cmd.DatacenterFlag.Register(ctx, f)
   115  
   116  	f.Var(&cmd.kind, "type", "Resource type")
   117  	f.StringVar(&cmd.name, "name", "*", "Resource name")
   118  	f.IntVar(&cmd.maxdepth, "maxdepth", -1, "Max depth")
   119  	f.BoolVar(&cmd.ref, "i", false, "Print the managed object reference")
   120  	f.BoolVar(&cmd.id, "I", false, "Print the managed object ID")
   121  	f.BoolVar(&cmd.long, "l", false, "Long listing format")
   122  	f.BoolVar(&cmd.parent, "p", false, "Find parent objects")
   123  }
   124  
   125  func (cmd *find) Usage() string {
   126  	return "[ROOT] [KEY VAL]..."
   127  }
   128  
   129  func (cmd *find) Description() string {
   130  	atable := aliasHelp()
   131  
   132  	return fmt.Sprintf(`Find managed objects.
   133  
   134  ROOT can be an inventory path or ManagedObjectReference.
   135  ROOT defaults to '.', an alias for the root folder or DC if set.
   136  
   137  Optional KEY VAL pairs can be used to filter results against object instance properties.
   138  Use the govc 'collect' command to view possible object property keys.
   139  
   140  The '-type' flag value can be a managed entity type or one of the following aliases:
   141  
   142  %s
   143  Examples:
   144    govc find
   145    govc find -l / # include object type in output
   146    govc find -l -I / # include MOID in output
   147    govc find /dc1 -type c
   148    govc find vm -name my-vm-*
   149    govc find . -type n
   150    govc find -p /folder-a/dc-1/host/folder-b/cluster-a -type Datacenter # prints /folder-a/dc-1
   151    govc find . -type m -runtime.powerState poweredOn
   152    govc find . -type m -datastore $(govc find -i datastore -name vsanDatastore)
   153    govc find . -type s -summary.type vsan
   154    govc find . -type s -customValue *:prod # Key:Value
   155    govc find . -type h -hardware.cpuInfo.numCpuCores 16`, atable)
   156  }
   157  
   158  // rootMatch returns true if the root object path should be printed
   159  func (cmd *find) rootMatch(ctx context.Context, root object.Reference, client *vim25.Client, filter property.Match) bool {
   160  	ref := root.Reference()
   161  
   162  	if !cmd.kind.wanted(ref.Type) {
   163  		return false
   164  	}
   165  
   166  	if len(filter) == 1 && filter["name"] == "*" {
   167  		return true
   168  	}
   169  
   170  	var content []types.ObjectContent
   171  
   172  	pc := property.DefaultCollector(client)
   173  	_ = pc.RetrieveWithFilter(ctx, []types.ManagedObjectReference{ref}, filter.Keys(), &content, filter)
   174  
   175  	return content != nil
   176  }
   177  
   178  type findResult []string
   179  
   180  func (r findResult) Write(w io.Writer) error {
   181  	for i := range r {
   182  		fmt.Fprintln(w, r[i])
   183  	}
   184  	return nil
   185  }
   186  
   187  func (r findResult) Dump() any {
   188  	return []string(r)
   189  }
   190  
   191  type findResultLong []string
   192  
   193  func (r findResultLong) Write(w io.Writer) error {
   194  	tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
   195  	for i := range r {
   196  		fmt.Fprintln(tw, r[i])
   197  	}
   198  	return tw.Flush()
   199  }
   200  
   201  func (cmd *find) writeResult(paths []string) error {
   202  	if cmd.long {
   203  		return cmd.WriteResult(findResultLong(paths))
   204  	}
   205  	return cmd.WriteResult(findResult(paths))
   206  }
   207  
   208  func (cmd *find) mo(o types.ManagedObjectReference) string {
   209  	if cmd.id {
   210  		return o.Value
   211  	}
   212  	return o.String()
   213  }
   214  
   215  func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error {
   216  	client, err := cmd.Client()
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	finder, err := cmd.Finder()
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	if cmd.id {
   227  		cmd.ref = true
   228  	}
   229  
   230  	root := client.ServiceContent.RootFolder
   231  	rootPath := "/"
   232  
   233  	arg := f.Arg(0)
   234  	props := f.Args()
   235  
   236  	if len(props) > 0 {
   237  		if strings.HasPrefix(arg, "-") {
   238  			arg = "."
   239  		} else {
   240  			props = props[1:]
   241  		}
   242  	}
   243  
   244  	if len(props)%2 != 0 {
   245  		return flag.ErrHelp
   246  	}
   247  
   248  	dc, err := cmd.DatacenterIfSpecified()
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	switch arg {
   254  	case rootPath:
   255  	case "", ".":
   256  		if dc == nil {
   257  			arg = rootPath
   258  		} else {
   259  			arg = "."
   260  			root = dc.Reference()
   261  			rootPath = dc.InventoryPath
   262  		}
   263  	default:
   264  		path := arg
   265  		if !strings.Contains(arg, "/") {
   266  			// Force list mode
   267  			p := "."
   268  			if dc != nil {
   269  				p = dc.InventoryPath
   270  			}
   271  			path = strings.Join([]string{p, arg}, "/")
   272  		}
   273  
   274  		l, ferr := finder.ManagedObjectList(ctx, path)
   275  		if ferr != nil {
   276  			return err
   277  		}
   278  
   279  		switch len(l) {
   280  		case 0:
   281  			return fmt.Errorf("%s not found", arg)
   282  		case 1:
   283  			root = l[0].Object.Reference()
   284  			rootPath = l[0].Path
   285  		default:
   286  			return fmt.Errorf("%q matches %d objects", arg, len(l))
   287  		}
   288  	}
   289  
   290  	filter := property.Match{}
   291  
   292  	if len(props)%2 != 0 {
   293  		return flag.ErrHelp
   294  	}
   295  
   296  	for i := 0; i < len(props); i++ {
   297  		key := props[i]
   298  		if !strings.HasPrefix(key, "-") {
   299  			return flag.ErrHelp
   300  		}
   301  
   302  		key = key[1:]
   303  		i++
   304  		val := props[i]
   305  
   306  		if xf := f.Lookup(key); xf != nil {
   307  			// Support use of -flag following the ROOT arg (flag package does not do this)
   308  			if err = xf.Value.Set(val); err != nil {
   309  				return err
   310  			}
   311  		} else {
   312  			filter[key] = val
   313  		}
   314  	}
   315  
   316  	filter["name"] = cmd.name
   317  	var paths []string
   318  
   319  	printPath := func(o types.ManagedObjectReference, p string) {
   320  		if cmd.ref && !cmd.long {
   321  			paths = append(paths, cmd.mo(o))
   322  			return
   323  		}
   324  
   325  		path := strings.Replace(p, rootPath, arg, 1)
   326  		if cmd.long {
   327  			id := strings.TrimPrefix(o.Type, "Vmware")
   328  			if cmd.ref {
   329  				id = cmd.mo(o)
   330  			}
   331  
   332  			path = id + "\t" + path
   333  		}
   334  		paths = append(paths, path)
   335  	}
   336  
   337  	recurse := false
   338  
   339  	switch cmd.maxdepth {
   340  	case -1:
   341  		recurse = true
   342  	case 0:
   343  	case 1:
   344  	default:
   345  		return flag.ErrHelp // TODO: ?
   346  	}
   347  
   348  	if cmd.parent {
   349  		entities, err := mo.Ancestors(ctx, client, client.ServiceContent.PropertyCollector, root)
   350  		if err != nil {
   351  			return err
   352  		}
   353  
   354  		for i := len(entities) - 1; i >= 0; i-- {
   355  			if cmd.rootMatch(ctx, entities[i], client, filter) {
   356  				printPath(entities[i].Reference(), internal.InventoryPath(entities[:i+1]))
   357  			}
   358  		}
   359  
   360  		return cmd.writeResult(paths)
   361  	}
   362  
   363  	if cmd.rootMatch(ctx, root, client, filter) {
   364  		printPath(root, arg)
   365  	}
   366  
   367  	if cmd.maxdepth == 0 {
   368  		return cmd.writeResult(paths)
   369  	}
   370  
   371  	m := view.NewManager(client)
   372  
   373  	v, err := m.CreateContainerView(ctx, root, cmd.kind, recurse)
   374  	if err != nil {
   375  		return err
   376  	}
   377  
   378  	defer func() {
   379  		_ = v.Destroy(ctx)
   380  	}()
   381  
   382  	objs, err := v.Find(ctx, cmd.kind, filter)
   383  	if err != nil {
   384  		return err
   385  	}
   386  
   387  	for _, o := range objs {
   388  		var path string
   389  
   390  		if cmd.long || !cmd.ref {
   391  			e, err := finder.Element(ctx, o)
   392  			if err != nil {
   393  				if fault.Is(err, &types.ManagedObjectNotFound{}) {
   394  					continue // object was deleted after v.Find() returned
   395  				}
   396  				return err
   397  			}
   398  			path = e.Path
   399  		}
   400  
   401  		printPath(o, path)
   402  	}
   403  
   404  	return cmd.writeResult(paths)
   405  }