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

     1  /*
     2  Copyright (c) 2017-2023 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  	"context"
    21  	"encoding/json"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"reflect"
    27  	"strings"
    28  	"text/tabwriter"
    29  	"time"
    30  
    31  	"github.com/vmware/govmomi/govc/cli"
    32  	"github.com/vmware/govmomi/govc/flags"
    33  	"github.com/vmware/govmomi/property"
    34  	"github.com/vmware/govmomi/view"
    35  	"github.com/vmware/govmomi/vim25"
    36  	"github.com/vmware/govmomi/vim25/methods"
    37  	"github.com/vmware/govmomi/vim25/mo"
    38  	"github.com/vmware/govmomi/vim25/soap"
    39  	"github.com/vmware/govmomi/vim25/types"
    40  	"github.com/vmware/govmomi/vim25/xml"
    41  )
    42  
    43  type collect struct {
    44  	*flags.DatacenterFlag
    45  
    46  	object bool
    47  	single bool
    48  	simple bool
    49  	raw    string
    50  	delim  string
    51  	dump   bool
    52  	n      int
    53  	kind   kinds
    54  	wait   time.Duration
    55  
    56  	filter property.Match
    57  	obj    string
    58  }
    59  
    60  func init() {
    61  	cli.Register("object.collect", &collect{})
    62  }
    63  
    64  func (cmd *collect) Register(ctx context.Context, f *flag.FlagSet) {
    65  	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
    66  	cmd.DatacenterFlag.Register(ctx, f)
    67  
    68  	f.BoolVar(&cmd.simple, "s", false, "Output property value only")
    69  	f.StringVar(&cmd.delim, "d", ",", "Delimiter for array values")
    70  	f.BoolVar(&cmd.object, "o", false, "Output the structure of a single Managed Object")
    71  	f.BoolVar(&cmd.dump, "O", false, "Output the CreateFilter request itself")
    72  	f.StringVar(&cmd.raw, "R", "", "Raw XML encoded CreateFilter request")
    73  	f.IntVar(&cmd.n, "n", 0, "Wait for N property updates")
    74  	f.Var(&cmd.kind, "type", "Resource type.  If specified, MOID is used for a container view root")
    75  	f.DurationVar(&cmd.wait, "wait", 0, "Max wait time for updates")
    76  }
    77  
    78  func (cmd *collect) Usage() string {
    79  	return "[MOID] [PROPERTY]..."
    80  }
    81  
    82  func (cmd *collect) Description() string {
    83  	atable := aliasHelp()
    84  
    85  	return fmt.Sprintf(`Collect managed object properties.
    86  
    87  MOID can be an inventory path or ManagedObjectReference.
    88  MOID defaults to '-', an alias for 'ServiceInstance:ServiceInstance' or the root folder if a '-type' flag is given.
    89  
    90  If a '-type' flag is given, properties are collected using a ContainerView object where MOID is the root of the view.
    91  
    92  By default only the current property value(s) are collected.  To wait for updates, use the '-n' flag or
    93  specify a property filter.  A property filter can be specified by prefixing the property name with a '-',
    94  followed by the value to match.
    95  
    96  The '-R' flag sets the Filter using the given XML encoded request, which can be captured by 'vcsim -trace' for example.
    97  It can be useful for replaying property filters created by other clients and converting filters to Go code via '-O -dump'.
    98  
    99  The '-type' flag value can be a managed entity type or one of the following aliases:
   100  
   101  %s
   102  Examples:
   103    govc object.collect - content
   104    govc object.collect -s HostSystem:ha-host hardware.systemInfo.uuid
   105    govc object.collect -s /ha-datacenter/vm/foo overallStatus
   106    govc object.collect -s /ha-datacenter/vm/foo -guest.guestOperationsReady true # property filter
   107    govc object.collect -type m / name runtime.powerState # collect properties for multiple objects
   108    govc object.collect -json -n=-1 EventManager:ha-eventmgr latestEvent | jq .
   109    govc object.collect -json -s $(govc object.collect -s - content.perfManager) description.counterType | jq .
   110    govc object.collect -R create-filter-request.xml # replay filter
   111    govc object.collect -R create-filter-request.xml -O # convert filter to Go code
   112    govc object.collect -s vm/my-vm summary.runtime.host | xargs govc ls -L # inventory path of VM's host
   113    govc object.collect -dump -o "network/VM Network" # output Managed Object structure as Go code
   114    govc object.collect -json $vm config | \ # use -json + jq to search array elements
   115      jq -r '.[] | select(.val.hardware.device[].macAddress == "00:0c:29:0c:73:c0") | .val.name'`, atable)
   116  }
   117  
   118  var stringer = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
   119  
   120  type change struct {
   121  	cmd    *collect
   122  	Update types.ObjectUpdate
   123  }
   124  
   125  func (pc *change) MarshalJSON() ([]byte, error) {
   126  	if len(pc.cmd.kind) == 0 {
   127  		return json.Marshal(pc.Update.ChangeSet)
   128  	}
   129  
   130  	return json.Marshal(pc.Update)
   131  }
   132  
   133  func (pc *change) output(name string, rval reflect.Value, rtype reflect.Type) {
   134  	s := "..."
   135  
   136  	kind := rval.Kind()
   137  
   138  	if kind == reflect.Ptr || kind == reflect.Interface {
   139  		if rval.IsNil() {
   140  			s = ""
   141  		} else {
   142  			rval = rval.Elem()
   143  			kind = rval.Kind()
   144  		}
   145  	}
   146  
   147  	switch kind {
   148  	case reflect.Ptr, reflect.Interface:
   149  	case reflect.Slice:
   150  		if rval.Len() == 0 {
   151  			s = ""
   152  			break
   153  		}
   154  
   155  		etype := rtype.Elem()
   156  
   157  		if etype.Kind() != reflect.Interface && etype.Kind() != reflect.Struct || etype.Implements(stringer) {
   158  			var val []string
   159  
   160  			for i := 0; i < rval.Len(); i++ {
   161  				v := rval.Index(i).Interface()
   162  
   163  				if fstr, ok := v.(fmt.Stringer); ok {
   164  					s = fstr.String()
   165  				} else {
   166  					s = fmt.Sprintf("%v", v)
   167  				}
   168  
   169  				val = append(val, s)
   170  			}
   171  
   172  			s = strings.Join(val, pc.cmd.delim)
   173  		}
   174  	case reflect.Struct:
   175  		if rtype.Implements(stringer) {
   176  			s = rval.Interface().(fmt.Stringer).String()
   177  		}
   178  	default:
   179  		s = fmt.Sprintf("%v", rval.Interface())
   180  	}
   181  
   182  	if pc.cmd.simple {
   183  		fmt.Fprintln(pc.cmd.Out, s)
   184  		return
   185  	}
   186  
   187  	if pc.cmd.obj != "" {
   188  		fmt.Fprintf(pc.cmd.Out, "%s\t", pc.cmd.obj)
   189  	}
   190  
   191  	fmt.Fprintf(pc.cmd.Out, "%s\t%s\t%s\n", name, rtype, s)
   192  }
   193  
   194  func (pc *change) writeStruct(name string, rval reflect.Value, rtype reflect.Type) {
   195  	for i := 0; i < rval.NumField(); i++ {
   196  		fval := rval.Field(i)
   197  		field := rtype.Field(i)
   198  
   199  		if field.Anonymous {
   200  			pc.writeStruct(name, fval, fval.Type())
   201  			continue
   202  		}
   203  
   204  		fname := fmt.Sprintf("%s.%s%s", name, strings.ToLower(field.Name[:1]), field.Name[1:])
   205  		pc.output(fname, fval, field.Type)
   206  	}
   207  }
   208  
   209  func (pc *change) Write(w io.Writer) error {
   210  	tw := tabwriter.NewWriter(pc.cmd.Out, 4, 0, 2, ' ', 0)
   211  	pc.cmd.Out = tw
   212  
   213  	for _, c := range pc.Update.ChangeSet {
   214  		if c.Val == nil {
   215  			// type is unknown in this case, as xsi:type was not provided - just skip for now
   216  			continue
   217  		}
   218  
   219  		rval := reflect.ValueOf(c.Val)
   220  		rtype := rval.Type()
   221  
   222  		if strings.HasPrefix(rtype.Name(), "ArrayOf") {
   223  			rval = rval.Field(0)
   224  			rtype = rval.Type()
   225  		}
   226  
   227  		if len(pc.cmd.kind) != 0 {
   228  			pc.cmd.obj = pc.Update.Obj.String()
   229  		}
   230  
   231  		if pc.cmd.single && rtype.Kind() == reflect.Struct && !rtype.Implements(stringer) {
   232  			pc.writeStruct(c.Name, rval, rtype)
   233  			continue
   234  		}
   235  
   236  		pc.output(c.Name, rval, rtype)
   237  	}
   238  
   239  	return tw.Flush()
   240  }
   241  
   242  func (pc *change) Dump() interface{} {
   243  	if pc.cmd.simple && len(pc.Update.ChangeSet) == 1 {
   244  		val := pc.Update.ChangeSet[0].Val
   245  		if val != nil {
   246  			rval := reflect.ValueOf(val)
   247  			rtype := rval.Type()
   248  
   249  			if strings.HasPrefix(rtype.Name(), "ArrayOf") {
   250  				return rval.Field(0).Interface()
   251  			}
   252  		}
   253  
   254  		return val
   255  	}
   256  
   257  	return pc.Update
   258  }
   259  
   260  func (cmd *collect) match(update types.ObjectUpdate) bool {
   261  	if len(cmd.filter) == 0 {
   262  		return false
   263  	}
   264  
   265  	for _, c := range update.ChangeSet {
   266  		if cmd.filter.Property(types.DynamicProperty{Name: c.Name, Val: c.Val}) {
   267  			return true
   268  		}
   269  	}
   270  
   271  	return false
   272  }
   273  
   274  func (cmd *collect) toFilter(f *flag.FlagSet, props []string) ([]string, error) {
   275  	// TODO: Only supporting 1 filter prop for now.  More than one would require some
   276  	// accounting / accumulating of multiple updates.  And need to consider objects
   277  	// then enter/leave a container view.
   278  	if len(props) != 2 || !strings.HasPrefix(props[0], "-") {
   279  		return props, nil
   280  	}
   281  
   282  	cmd.filter = property.Match{props[0][1:]: props[1]}
   283  
   284  	return cmd.filter.Keys(), nil
   285  }
   286  
   287  type dumpFilter struct {
   288  	types.CreateFilter
   289  }
   290  
   291  func (f *dumpFilter) Dump() interface{} {
   292  	return f.CreateFilter
   293  }
   294  
   295  // Write satisfies the flags.OutputWriter interface, but is not used with dumpFilter.
   296  func (f *dumpFilter) Write(w io.Writer) error {
   297  	return nil
   298  }
   299  
   300  type dumpEntity struct {
   301  	entity interface{}
   302  }
   303  
   304  func (e *dumpEntity) Dump() interface{} {
   305  	return e.entity
   306  }
   307  
   308  func (e *dumpEntity) MarshalJSON() ([]byte, error) {
   309  	return json.Marshal(e.entity)
   310  }
   311  
   312  // Write satisfies the flags.OutputWriter interface, but is not used with dumpEntity.
   313  func (e *dumpEntity) Write(w io.Writer) error {
   314  	return nil
   315  }
   316  
   317  func (cmd *collect) decodeFilter(filter *property.WaitFilter) error {
   318  	var r io.Reader
   319  
   320  	if cmd.raw == "-" {
   321  		r = os.Stdin
   322  	} else {
   323  		f, err := os.Open(cmd.raw)
   324  		if err != nil {
   325  			return err
   326  		}
   327  		defer f.Close()
   328  		r = f
   329  	}
   330  
   331  	env := soap.Envelope{
   332  		Body: &methods.CreateFilterBody{Req: &filter.CreateFilter},
   333  	}
   334  
   335  	dec := xml.NewDecoder(r)
   336  	dec.TypeFunc = types.TypeFunc()
   337  	return dec.Decode(&env)
   338  }
   339  
   340  func (cmd *collect) Run(ctx context.Context, f *flag.FlagSet) error {
   341  	client, err := cmd.Client()
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	p := property.DefaultCollector(client)
   347  	filter := new(property.WaitFilter)
   348  
   349  	if cmd.raw == "" {
   350  		ref := vim25.ServiceInstance
   351  		arg := f.Arg(0)
   352  
   353  		if len(cmd.kind) != 0 {
   354  			ref = client.ServiceContent.RootFolder
   355  		}
   356  
   357  		switch arg {
   358  		case "", "-":
   359  		default:
   360  			ref, err = cmd.ManagedObject(ctx, arg)
   361  			if err != nil {
   362  				if !ref.FromString(arg) {
   363  					return err
   364  				}
   365  			}
   366  		}
   367  
   368  		var props []string
   369  		if f.NArg() > 1 {
   370  			props = f.Args()[1:]
   371  			cmd.single = len(props) == 1
   372  		}
   373  
   374  		props, err = cmd.toFilter(f, props)
   375  		if err != nil {
   376  			return err
   377  		}
   378  
   379  		if len(cmd.kind) == 0 {
   380  			filter.Add(ref, ref.Type, props)
   381  		} else {
   382  			m := view.NewManager(client)
   383  
   384  			v, cerr := m.CreateContainerView(ctx, ref, cmd.kind, true)
   385  			if cerr != nil {
   386  				return cerr
   387  			}
   388  
   389  			defer func() {
   390  				_ = v.Destroy(ctx)
   391  			}()
   392  
   393  			for _, kind := range cmd.kind {
   394  				filter.Add(v.Reference(), kind, props, v.TraversalSpec())
   395  			}
   396  		}
   397  	} else {
   398  		if err = cmd.decodeFilter(filter); err != nil {
   399  			return err
   400  		}
   401  	}
   402  
   403  	if cmd.dump {
   404  		if !cmd.All() {
   405  			cmd.Dump = true
   406  		}
   407  		return cmd.WriteResult(&dumpFilter{filter.CreateFilter})
   408  	}
   409  
   410  	if cmd.object {
   411  		if !cmd.All() {
   412  			cmd.Dump = true
   413  		}
   414  		req := types.RetrieveProperties{
   415  			SpecSet: []types.PropertyFilterSpec{filter.Spec},
   416  		}
   417  		res, err := p.RetrieveProperties(ctx, req)
   418  		if err != nil {
   419  			return nil
   420  		}
   421  		content := res.Returnval
   422  		if len(content) != 1 {
   423  			return fmt.Errorf("%d objects match", len(content))
   424  		}
   425  		obj, err := mo.ObjectContentToType(content[0])
   426  		if err != nil {
   427  			return err
   428  		}
   429  		return cmd.WriteResult(&dumpEntity{obj})
   430  	}
   431  
   432  	hasFilter := len(cmd.filter) != 0
   433  
   434  	if cmd.wait != 0 {
   435  		filter.Options = &types.WaitOptions{
   436  			MaxWaitSeconds: types.NewInt32(int32(cmd.wait.Seconds())),
   437  		}
   438  	}
   439  
   440  	return cmd.WithCancel(ctx, func(wctx context.Context) error {
   441  		matches := 0
   442  		return property.WaitForUpdates(wctx, p, filter, func(updates []types.ObjectUpdate) bool {
   443  			for _, update := range updates {
   444  				c := &change{cmd, update}
   445  
   446  				if hasFilter {
   447  					if cmd.match(update) {
   448  						matches++
   449  					} else {
   450  						continue
   451  					}
   452  				}
   453  
   454  				_ = cmd.WriteResult(c)
   455  			}
   456  
   457  			if filter.Truncated {
   458  				return false // vCenter truncates updates if > 100
   459  			}
   460  
   461  			if hasFilter {
   462  				if matches > 0 {
   463  					matches = 0 // reset
   464  					return true
   465  				}
   466  				return false
   467  			}
   468  
   469  			cmd.n--
   470  
   471  			return cmd.n == -1 && cmd.wait == 0
   472  		})
   473  	})
   474  }