github.com/vmware/govmomi@v0.51.0/list/lister.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 list 6 7 import ( 8 "context" 9 "fmt" 10 "path" 11 "reflect" 12 13 "github.com/vmware/govmomi/fault" 14 "github.com/vmware/govmomi/property" 15 "github.com/vmware/govmomi/vim25/mo" 16 "github.com/vmware/govmomi/vim25/types" 17 ) 18 19 type Element struct { 20 Path string 21 Object mo.Reference 22 } 23 24 func (e Element) String() string { 25 return fmt.Sprintf("%s @ %s", e.Object.Reference(), e.Path) 26 } 27 28 func ToElement(r mo.Reference, prefix string) Element { 29 var name string 30 31 // Comments about types to be expected in folders copied from the 32 // documentation of the Folder managed object: 33 // https://developer.broadcom.com/xapis/vsphere-web-services-api/latest/vim.Folder.html 34 switch m := r.(type) { 35 case mo.Folder: 36 name = m.Name 37 case mo.StoragePod: 38 name = m.Name 39 40 // { "vim.Datacenter" } - Identifies the root folder and its descendant 41 // folders. Data center folders can contain child data center folders and 42 // Datacenter managed objects. Datacenter objects contain virtual machine, 43 // compute resource, network entity, and datastore folders. 44 case mo.Datacenter: 45 name = m.Name 46 47 // { "vim.Virtualmachine", "vim.VirtualApp" } - Identifies a virtual machine 48 // folder. A virtual machine folder may contain child virtual machine 49 // folders. It also can contain VirtualMachine managed objects, templates, 50 // and VirtualApp managed objects. 51 case mo.VirtualMachine: 52 name = m.Name 53 case mo.VirtualApp: 54 name = m.Name 55 56 // { "vim.ComputeResource" } - Identifies a compute resource 57 // folder, which contains child compute resource folders and ComputeResource 58 // hierarchies. 59 case mo.ComputeResource: 60 name = m.Name 61 case mo.ClusterComputeResource: 62 name = m.Name 63 case mo.HostSystem: 64 name = m.Name 65 case mo.ResourcePool: 66 name = m.Name 67 68 // { "vim.Network" } - Identifies a network entity folder. 69 // Network entity folders on a vCenter Server can contain Network, 70 // DistributedVirtualSwitch, and DistributedVirtualPortgroup managed objects. 71 // Network entity folders on an ESXi host can contain only Network objects. 72 case mo.Network: 73 name = m.Name 74 case mo.OpaqueNetwork: 75 name = m.Name 76 case mo.DistributedVirtualSwitch: 77 name = m.Name 78 case mo.DistributedVirtualPortgroup: 79 name = m.Name 80 case mo.VmwareDistributedVirtualSwitch: 81 name = m.Name 82 83 // { "vim.Datastore" } - Identifies a datastore folder. Datastore folders can 84 // contain child datastore folders and Datastore managed objects. 85 case mo.Datastore: 86 name = m.Name 87 88 default: 89 panic("not implemented for type " + reflect.TypeOf(r).String()) 90 } 91 92 e := Element{ 93 Path: path.Join(prefix, name), 94 Object: r, 95 } 96 97 return e 98 } 99 100 type Lister struct { 101 Collector *property.Collector 102 Reference types.ManagedObjectReference 103 Prefix string 104 All bool 105 } 106 107 func (l Lister) retrieveProperties(ctx context.Context, req types.RetrieveProperties, dst *[]any) error { 108 res, err := l.Collector.RetrieveProperties(ctx, req) 109 if err != nil { 110 return err 111 } 112 113 // Instead of using mo.LoadRetrievePropertiesResponse, use a custom loop to 114 // iterate over the results and ignore entries that have properties that 115 // could not be retrieved (a non-empty `missingSet` property). Since the 116 // returned objects are enumerated by vSphere in the first place, any object 117 // that has a non-empty `missingSet` property is indicative of a race 118 // condition in vSphere where the object was enumerated initially, but was 119 // removed before its properties could be collected. 120 for _, p := range res.Returnval { 121 v, err := mo.ObjectContentToType(p) 122 if err != nil { 123 if fault.Is(err, &types.ManagedObjectNotFound{}) { 124 continue 125 } 126 127 return err 128 } 129 130 *dst = append(*dst, v) 131 } 132 133 return nil 134 } 135 136 func (l Lister) List(ctx context.Context) ([]Element, error) { 137 switch l.Reference.Type { 138 case "Folder", "StoragePod": 139 return l.ListFolder(ctx) 140 case "Datacenter": 141 return l.ListDatacenter(ctx) 142 case "ComputeResource", "ClusterComputeResource": 143 // Treat ComputeResource and ClusterComputeResource as one and the same. 144 // It doesn't matter from the perspective of the lister. 145 return l.ListComputeResource(ctx) 146 case "ResourcePool": 147 return l.ListResourcePool(ctx) 148 case "HostSystem": 149 return l.ListHostSystem(ctx) 150 case "VirtualApp": 151 return l.ListVirtualApp(ctx) 152 case "VmwareDistributedVirtualSwitch", "DistributedVirtualSwitch": 153 return l.ListDistributedVirtualSwitch(ctx) 154 default: 155 return nil, fmt.Errorf("cannot traverse type " + l.Reference.Type) 156 } 157 } 158 159 func (l Lister) ListFolder(ctx context.Context) ([]Element, error) { 160 spec := types.PropertyFilterSpec{ 161 ObjectSet: []types.ObjectSpec{ 162 { 163 Obj: l.Reference, 164 SelectSet: []types.BaseSelectionSpec{ 165 &types.TraversalSpec{ 166 Path: "childEntity", 167 Skip: types.NewBool(false), 168 Type: "Folder", 169 }, 170 }, 171 Skip: types.NewBool(true), 172 }, 173 }, 174 } 175 176 // Retrieve all objects that we can deal with 177 childTypes := []string{ 178 "Folder", 179 "Datacenter", 180 "VirtualApp", 181 "VirtualMachine", 182 "Network", 183 "ComputeResource", 184 "ClusterComputeResource", 185 "Datastore", 186 "DistributedVirtualSwitch", 187 } 188 189 for _, t := range childTypes { 190 pspec := types.PropertySpec{ 191 Type: t, 192 } 193 194 if l.All { 195 pspec.All = types.NewBool(true) 196 } else { 197 pspec.PathSet = []string{"name"} 198 199 // Additional basic properties. 200 switch t { 201 case "Folder": 202 pspec.PathSet = append(pspec.PathSet, "childType") 203 case "ComputeResource", "ClusterComputeResource": 204 // The ComputeResource and ClusterComputeResource are dereferenced in 205 // the ResourcePoolFlag. Make sure they always have their resourcePool 206 // field populated. 207 pspec.PathSet = append(pspec.PathSet, "resourcePool") 208 } 209 } 210 211 spec.PropSet = append(spec.PropSet, pspec) 212 } 213 214 req := types.RetrieveProperties{ 215 SpecSet: []types.PropertyFilterSpec{spec}, 216 } 217 218 var dst []any 219 220 err := l.retrieveProperties(ctx, req, &dst) 221 if err != nil { 222 return nil, err 223 } 224 225 es := []Element{} 226 for _, v := range dst { 227 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 228 } 229 230 return es, nil 231 } 232 233 func (l Lister) ListDatacenter(ctx context.Context) ([]Element, error) { 234 ospec := types.ObjectSpec{ 235 Obj: l.Reference, 236 Skip: types.NewBool(true), 237 } 238 239 // Include every datastore folder in the select set 240 fields := []string{ 241 "vmFolder", 242 "hostFolder", 243 "datastoreFolder", 244 "networkFolder", 245 } 246 247 for _, f := range fields { 248 tspec := types.TraversalSpec{ 249 Path: f, 250 Skip: types.NewBool(false), 251 Type: "Datacenter", 252 } 253 254 ospec.SelectSet = append(ospec.SelectSet, &tspec) 255 } 256 257 pspec := types.PropertySpec{ 258 Type: "Folder", 259 } 260 261 if l.All { 262 pspec.All = types.NewBool(true) 263 } else { 264 pspec.PathSet = []string{"name", "childType"} 265 } 266 267 req := types.RetrieveProperties{ 268 SpecSet: []types.PropertyFilterSpec{ 269 { 270 ObjectSet: []types.ObjectSpec{ospec}, 271 PropSet: []types.PropertySpec{pspec}, 272 }, 273 }, 274 } 275 276 var dst []any 277 278 err := l.retrieveProperties(ctx, req, &dst) 279 if err != nil { 280 return nil, err 281 } 282 283 es := []Element{} 284 for _, v := range dst { 285 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 286 } 287 288 return es, nil 289 } 290 291 func (l Lister) ListComputeResource(ctx context.Context) ([]Element, error) { 292 ospec := types.ObjectSpec{ 293 Obj: l.Reference, 294 Skip: types.NewBool(true), 295 } 296 297 fields := []string{ 298 "host", 299 "network", 300 "resourcePool", 301 } 302 303 for _, f := range fields { 304 tspec := types.TraversalSpec{ 305 Path: f, 306 Skip: types.NewBool(false), 307 Type: "ComputeResource", 308 } 309 310 ospec.SelectSet = append(ospec.SelectSet, &tspec) 311 } 312 313 childTypes := []string{ 314 "HostSystem", 315 "Network", 316 "ResourcePool", 317 } 318 319 var pspecs []types.PropertySpec 320 for _, t := range childTypes { 321 pspec := types.PropertySpec{ 322 Type: t, 323 } 324 325 if l.All { 326 pspec.All = types.NewBool(true) 327 } else { 328 pspec.PathSet = []string{"name"} 329 } 330 331 pspecs = append(pspecs, pspec) 332 } 333 334 req := types.RetrieveProperties{ 335 SpecSet: []types.PropertyFilterSpec{ 336 { 337 ObjectSet: []types.ObjectSpec{ospec}, 338 PropSet: pspecs, 339 }, 340 }, 341 } 342 343 var dst []any 344 345 err := l.retrieveProperties(ctx, req, &dst) 346 if err != nil { 347 return nil, err 348 } 349 350 es := []Element{} 351 for _, v := range dst { 352 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 353 } 354 355 return es, nil 356 } 357 358 func (l Lister) ListResourcePool(ctx context.Context) ([]Element, error) { 359 ospec := types.ObjectSpec{ 360 Obj: l.Reference, 361 Skip: types.NewBool(true), 362 } 363 364 fields := []string{ 365 "resourcePool", 366 } 367 368 for _, f := range fields { 369 tspec := types.TraversalSpec{ 370 Path: f, 371 Skip: types.NewBool(false), 372 Type: "ResourcePool", 373 } 374 375 ospec.SelectSet = append(ospec.SelectSet, &tspec) 376 } 377 378 childTypes := []string{ 379 "ResourcePool", 380 } 381 382 var pspecs []types.PropertySpec 383 for _, t := range childTypes { 384 pspec := types.PropertySpec{ 385 Type: t, 386 } 387 388 if l.All { 389 pspec.All = types.NewBool(true) 390 } else { 391 pspec.PathSet = []string{"name"} 392 } 393 394 pspecs = append(pspecs, pspec) 395 } 396 397 req := types.RetrieveProperties{ 398 SpecSet: []types.PropertyFilterSpec{ 399 { 400 ObjectSet: []types.ObjectSpec{ospec}, 401 PropSet: pspecs, 402 }, 403 }, 404 } 405 406 var dst []any 407 408 err := l.retrieveProperties(ctx, req, &dst) 409 if err != nil { 410 return nil, err 411 } 412 413 es := []Element{} 414 for _, v := range dst { 415 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 416 } 417 418 return es, nil 419 } 420 421 func (l Lister) ListHostSystem(ctx context.Context) ([]Element, error) { 422 ospec := types.ObjectSpec{ 423 Obj: l.Reference, 424 Skip: types.NewBool(true), 425 } 426 427 fields := []string{ 428 "datastore", 429 "network", 430 "vm", 431 } 432 433 for _, f := range fields { 434 tspec := types.TraversalSpec{ 435 Path: f, 436 Skip: types.NewBool(false), 437 Type: "HostSystem", 438 } 439 440 ospec.SelectSet = append(ospec.SelectSet, &tspec) 441 } 442 443 childTypes := []string{ 444 "Datastore", 445 "Network", 446 "VirtualMachine", 447 } 448 449 var pspecs []types.PropertySpec 450 for _, t := range childTypes { 451 pspec := types.PropertySpec{ 452 Type: t, 453 } 454 455 if l.All { 456 pspec.All = types.NewBool(true) 457 } else { 458 pspec.PathSet = []string{"name"} 459 } 460 461 pspecs = append(pspecs, pspec) 462 } 463 464 req := types.RetrieveProperties{ 465 SpecSet: []types.PropertyFilterSpec{ 466 { 467 ObjectSet: []types.ObjectSpec{ospec}, 468 PropSet: pspecs, 469 }, 470 }, 471 } 472 473 var dst []any 474 475 err := l.retrieveProperties(ctx, req, &dst) 476 if err != nil { 477 return nil, err 478 } 479 480 es := []Element{} 481 for _, v := range dst { 482 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 483 } 484 485 return es, nil 486 } 487 488 func (l Lister) ListDistributedVirtualSwitch(ctx context.Context) ([]Element, error) { 489 ospec := types.ObjectSpec{ 490 Obj: l.Reference, 491 Skip: types.NewBool(true), 492 } 493 494 fields := []string{ 495 "portgroup", 496 } 497 498 for _, f := range fields { 499 tspec := types.TraversalSpec{ 500 Path: f, 501 Skip: types.NewBool(false), 502 Type: "DistributedVirtualSwitch", 503 } 504 505 ospec.SelectSet = append(ospec.SelectSet, &tspec) 506 } 507 508 childTypes := []string{ 509 "DistributedVirtualPortgroup", 510 } 511 512 var pspecs []types.PropertySpec 513 for _, t := range childTypes { 514 pspec := types.PropertySpec{ 515 Type: t, 516 } 517 518 if l.All { 519 pspec.All = types.NewBool(true) 520 } else { 521 pspec.PathSet = []string{"name"} 522 } 523 524 pspecs = append(pspecs, pspec) 525 } 526 527 req := types.RetrieveProperties{ 528 SpecSet: []types.PropertyFilterSpec{ 529 { 530 ObjectSet: []types.ObjectSpec{ospec}, 531 PropSet: pspecs, 532 }, 533 }, 534 } 535 536 var dst []any 537 538 err := l.retrieveProperties(ctx, req, &dst) 539 if err != nil { 540 return nil, err 541 } 542 543 es := []Element{} 544 for _, v := range dst { 545 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 546 } 547 548 return es, nil 549 } 550 551 func (l Lister) ListVirtualApp(ctx context.Context) ([]Element, error) { 552 ospec := types.ObjectSpec{ 553 Obj: l.Reference, 554 Skip: types.NewBool(true), 555 } 556 557 fields := []string{ 558 "resourcePool", 559 "vm", 560 } 561 562 for _, f := range fields { 563 tspec := types.TraversalSpec{ 564 Path: f, 565 Skip: types.NewBool(false), 566 Type: "VirtualApp", 567 } 568 569 ospec.SelectSet = append(ospec.SelectSet, &tspec) 570 } 571 572 childTypes := []string{ 573 "ResourcePool", 574 "VirtualMachine", 575 } 576 577 var pspecs []types.PropertySpec 578 for _, t := range childTypes { 579 pspec := types.PropertySpec{ 580 Type: t, 581 } 582 583 if l.All { 584 pspec.All = types.NewBool(true) 585 } else { 586 pspec.PathSet = []string{"name"} 587 } 588 589 pspecs = append(pspecs, pspec) 590 } 591 592 req := types.RetrieveProperties{ 593 SpecSet: []types.PropertyFilterSpec{ 594 { 595 ObjectSet: []types.ObjectSpec{ospec}, 596 PropSet: pspecs, 597 }, 598 }, 599 } 600 601 var dst []any 602 603 err := l.retrieveProperties(ctx, req, &dst) 604 if err != nil { 605 return nil, err 606 } 607 608 es := []Element{} 609 for _, v := range dst { 610 es = append(es, ToElement(v.(mo.Reference), l.Prefix)) 611 } 612 613 return es, nil 614 }