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