github.com/vmware/govmomi@v0.37.2/govc/object/find.go (about)

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