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  }