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