github.com/vmware/govmomi@v0.37.1/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 }