pagination of vcenter queries
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-02-13 14:04:28 +11:00
parent 1b052b93b6
commit 5cd8f9c2a1
2 changed files with 293 additions and 326 deletions

View File

@@ -11,8 +11,10 @@ import (
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/event"
"github.com/vmware/govmomi/fault"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/view"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
@@ -40,6 +42,124 @@ type VmProperties struct {
var clientUserAgent = "vCTP"
const (
defaultPropertyPageSize int32 = 500
)
func normalizeKinds(kind []string) []string {
if len(kind) > 0 {
return kind
}
return []string{"ManagedEntity"}
}
func normalizePageSize(pageSize int32) int32 {
if pageSize <= 0 {
return defaultPropertyPageSize
}
return pageSize
}
func buildPropertySpecs(kind []string, props []string) []types.PropertySpec {
kinds := normalizeKinds(kind)
specs := make([]types.PropertySpec, 0, len(kinds))
for _, t := range kinds {
spec := types.PropertySpec{Type: t}
if len(props) == 0 {
spec.All = types.NewBool(true)
} else {
spec.PathSet = props
}
specs = append(specs, spec)
}
return specs
}
func refKey(ref types.ManagedObjectReference) string {
return ref.Type + ":" + ref.Value
}
func (v *Vcenter) retrieveContainerViewPaged(ctx context.Context, cv *view.ContainerView, kind []string, props []string, dst any, pageSize int32) error {
pc := property.DefaultCollector(v.client.Client)
req := types.RetrieveProperties{
SpecSet: []types.PropertyFilterSpec{
{
ObjectSet: []types.ObjectSpec{
{
Obj: cv.Reference(),
Skip: types.NewBool(true),
SelectSet: []types.BaseSelectionSpec{
&types.TraversalSpec{
Type: cv.Reference().Type,
Path: "view",
},
},
},
},
PropSet: buildPropertySpecs(kind, props),
},
},
}
res, err := pc.RetrieveProperties(ctx, req, normalizePageSize(pageSize))
if err != nil {
return err
}
if d, ok := dst.(*[]types.ObjectContent); ok {
*d = res.Returnval
return nil
}
return mo.LoadObjectContent(res.Returnval, dst)
}
func (v *Vcenter) retrieveByRefsPaged(ctx context.Context, refs []types.ManagedObjectReference, props []string, dst any, pageSize int32) error {
if len(refs) == 0 {
return nil
}
kindSet := make(map[string]struct{}, len(refs))
kinds := make([]string, 0, len(refs))
for _, ref := range refs {
if _, ok := kindSet[ref.Type]; ok {
continue
}
kindSet[ref.Type] = struct{}{}
kinds = append(kinds, ref.Type)
}
objectSet := make([]types.ObjectSpec, 0, len(refs))
for _, ref := range refs {
objectSet = append(objectSet, types.ObjectSpec{
Obj: ref,
Skip: types.NewBool(false),
})
}
pc := property.DefaultCollector(v.client.Client)
req := types.RetrieveProperties{
SpecSet: []types.PropertyFilterSpec{
{
ObjectSet: objectSet,
PropSet: buildPropertySpecs(kinds, props),
},
},
}
res, err := pc.RetrieveProperties(ctx, req, normalizePageSize(pageSize))
if err != nil {
return err
}
if d, ok := dst.(*[]types.ObjectContent); ok {
*d = res.Returnval
return nil
}
return mo.LoadObjectContent(res.Returnval, dst)
}
// SetUserAgent customizes the User-Agent used when talking to vCenter.
func SetUserAgent(ua string) {
if strings.TrimSpace(ua) != "" {
@@ -127,46 +247,25 @@ func (v *Vcenter) Logout(ctx context.Context) error {
}
func (v *Vcenter) GetAllVmReferences() ([]*object.VirtualMachine, error) {
var results []*object.VirtualMachine
finder := find.NewFinder(v.client.Client, true)
m := view.NewManager(v.client.Client)
vms, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
cv, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create VM container view: %w", err)
}
defer vms.Destroy(v.ctx)
defer cv.Destroy(v.ctx)
// List all datacenters
datacenters, err := finder.DatacenterList(v.ctx, "*")
if err != nil {
return nil, fmt.Errorf("failed to list datacenters: %w", err)
var content []types.ObjectContent
if err := v.retrieveContainerViewPaged(v.ctx, cv, []string{"VirtualMachine"}, []string{"name"}, &content, defaultPropertyPageSize); err != nil {
return nil, fmt.Errorf("failed to retrieve VM references: %w", err)
}
for _, dc := range datacenters {
v.Logger.Debug("Getting VMs in", "datacenter", dc.Name())
// Set the current datacenter
finder.SetDatacenter(dc)
// Get the list of all virtual machines in the current datacenter
vms, err := finder.VirtualMachineList(v.ctx, "*")
if err != nil {
v.Logger.Error("Failed to list VMs in", "datacenter", dc.Name(), "error", err)
continue
}
for _, vm := range vms {
//vmRef := vm.Reference()
//v.Logger.Debug("result", "vm", vm, "MoRef", vmRef, "path", vm.InventoryPath)
results = append(results, vm)
}
results := make([]*object.VirtualMachine, 0, len(content))
for _, c := range content {
results = append(results, object.NewVirtualMachine(v.client.Client, c.Obj))
}
v.Logger.Debug("Found VM references", "count", len(results))
return results, err
return results, nil
}
// GetAllVMsWithProps returns all VMs with the properties needed for snapshotting in a single property-collector call.
@@ -191,7 +290,7 @@ func (v *Vcenter) GetAllVMsWithProps() ([]mo.VirtualMachine, error) {
"runtime.host",
"resourcePool",
}
if err := cv.Retrieve(v.ctx, []string{"VirtualMachine"}, props, &vms); err != nil {
if err := v.retrieveContainerViewPaged(v.ctx, cv, []string{"VirtualMachine"}, props, &vms, defaultPropertyPageSize); err != nil {
return nil, fmt.Errorf("failed to retrieve VMs: %w", err)
}
return vms, nil
@@ -413,29 +512,55 @@ func (v *Vcenter) BuildHostLookup() (map[string]HostLookup, error) {
v.Logger.Warn("failed to list hosts for datacenter", "datacenter", dc.Name(), "error", err)
continue
}
if len(hosts) == 0 {
continue
}
hostRefs := make([]types.ManagedObjectReference, 0, len(hosts))
for _, host := range hosts {
ref := host.Reference()
var moHost mo.HostSystem
if err := v.client.RetrieveOne(v.ctx, ref, []string{"parent"}, &moHost); err != nil {
v.Logger.Warn("failed to retrieve host info", "host", host.Name(), "error", err)
hostRefs = append(hostRefs, host.Reference())
}
var moHosts []mo.HostSystem
if err := v.retrieveByRefsPaged(v.ctx, hostRefs, []string{"parent"}, &moHosts, defaultPropertyPageSize); err != nil {
v.Logger.Warn("failed to retrieve host parents for datacenter", "datacenter", dc.Name(), "error", err)
continue
}
parentRefs := make([]types.ManagedObjectReference, 0, len(moHosts))
parentSeen := make(map[string]struct{})
for _, host := range moHosts {
if host.Parent == nil {
continue
}
key := refKey(*host.Parent)
if _, cached := clusterCache[key]; cached {
continue
}
if _, seen := parentSeen[key]; seen {
continue
}
parentSeen[key] = struct{}{}
parentRefs = append(parentRefs, *host.Parent)
}
clusterName := ""
if moHost.Parent != nil {
if cached, ok := clusterCache[moHost.Parent.Value]; ok {
clusterName = cached
} else {
var moCompute mo.ComputeResource
if err := v.client.RetrieveOne(v.ctx, *moHost.Parent, []string{"name"}, &moCompute); err == nil {
clusterName = moCompute.Name
clusterCache[moHost.Parent.Value] = clusterName
}
if len(parentRefs) > 0 {
var computes []mo.ComputeResource
if err := v.retrieveByRefsPaged(v.ctx, parentRefs, []string{"name"}, &computes, defaultPropertyPageSize); err != nil {
v.Logger.Warn("failed to retrieve cluster names for datacenter", "datacenter", dc.Name(), "error", err)
} else {
for _, compute := range computes {
clusterCache[refKey(compute.Reference())] = compute.Name
}
}
}
lookup[ref.Value] = HostLookup{
for _, host := range moHosts {
clusterName := ""
if host.Parent != nil {
clusterName = clusterCache[refKey(*host.Parent)]
}
lookup[host.Reference().Value] = HostLookup{
Cluster: clusterName,
Datacenter: dc.Name(),
}
@@ -454,7 +579,7 @@ func (v *Vcenter) BuildFolderPathLookup() (FolderLookup, error) {
defer folders.Destroy(v.ctx)
var results []mo.Folder
if err := folders.Retrieve(v.ctx, []string{"Folder"}, []string{"name", "parent"}, &results); err != nil {
if err := v.retrieveContainerViewPaged(v.ctx, folders, []string{"Folder"}, []string{"name", "parent"}, &results, defaultPropertyPageSize); err != nil {
return nil, err
}
@@ -509,109 +634,43 @@ func (v *Vcenter) GetVMFolderPathFromLookup(vm mo.VirtualMachine, lookup FolderL
}
func (v *Vcenter) ConvertObjToMoVM(vmObj *object.VirtualMachine) (*mo.VirtualMachine, error) {
// Use the InventoryPath to extract the datacenter name and VM path
inventoryPath := vmObj.InventoryPath
parts := strings.SplitN(inventoryPath, "/", 3)
if len(parts) < 2 {
return nil, fmt.Errorf("invalid InventoryPath: %s", inventoryPath)
if vmObj == nil {
return nil, fmt.Errorf("vm object is nil")
}
// The first part of the path is the datacenter name
datacenterName := parts[1]
// Finder to search for datacenter and VM
finder := find.NewFinder(v.client.Client, true)
// Find the specific datacenter by name
datacenter, err := finder.Datacenter(v.ctx, fmt.Sprintf("/%s", datacenterName))
if err != nil {
return nil, fmt.Errorf("failed to find datacenter %s: %w", datacenterName, err)
}
// Set the found datacenter in the finder
finder.SetDatacenter(datacenter)
// Now retrieve the VM using its ManagedObjectReference
vmRef := vmObj.Reference()
// Retrieve the full mo.VirtualMachine object for the reference
var moVM mo.VirtualMachine
err = v.client.RetrieveOne(v.ctx, vmRef, nil, &moVM)
err := v.client.RetrieveOne(v.ctx, vmRef, []string{"name", "config"}, &moVM)
if err != nil {
return nil, fmt.Errorf("failed to retrieve VM %s in datacenter %s: %w", vmObj.Name(), datacenterName, err)
return nil, fmt.Errorf("failed to retrieve VM %s: %w", vmObj.Name(), err)
}
// Return the found mo.VirtualMachine object
//v.Logger.Debug("Found VM in datacenter", "vm_name", moVM.Name, "dc_name", datacenterName)
return &moVM, nil
}
func (v *Vcenter) ConvertObjToMoHost(hostObj *object.HostSystem) (*mo.HostSystem, error) {
// Use the InventoryPath to extract the datacenter name and Host path
inventoryPath := hostObj.InventoryPath
parts := strings.SplitN(inventoryPath, "/", 3)
v.Logger.Debug("inventory path", "parts", parts)
if len(parts) < 2 {
return nil, fmt.Errorf("invalid InventoryPath: %s", inventoryPath)
if hostObj == nil {
return nil, fmt.Errorf("host object is nil")
}
// The first part of the path is the datacenter name
datacenterName := parts[1]
// Finder to search for datacenter and VM
finder := find.NewFinder(v.client.Client, true)
// Find the specific datacenter by name
datacenter, err := finder.Datacenter(v.ctx, fmt.Sprintf("/%s", datacenterName))
if err != nil {
return nil, fmt.Errorf("failed to find datacenter %s: %w", datacenterName, err)
}
// Set the found datacenter in the finder
finder.SetDatacenter(datacenter)
// Now retrieve the VM using its ManagedObjectReference
hostRef := hostObj.Reference()
// Retrieve the full mo.HostSystem object for the reference
var moHost mo.HostSystem
err = v.client.RetrieveOne(v.ctx, hostRef, nil, &moHost)
err := v.client.RetrieveOne(v.ctx, hostRef, []string{"name", "parent"}, &moHost)
if err != nil {
return nil, fmt.Errorf("failed to retrieve Host %s in datacenter %s: %w", hostObj.Name(), datacenterName, err)
return nil, fmt.Errorf("failed to retrieve host %s: %w", hostObj.Name(), err)
}
// Return the found mo.HostSystem object
v.Logger.Debug("Found Host in datacenter", "host_name", moHost.Name, "dc_name", datacenterName)
v.Logger.Debug("found host", "host_name", moHost.Name)
return &moHost, nil
}
func (v *Vcenter) GetHostSystemObject(hostRef types.ManagedObjectReference) (*mo.HostSystem, error) {
finder := find.NewFinder(v.client.Client, true)
// List all datacenters
datacenters, err := finder.DatacenterList(v.ctx, "*")
if err != nil {
return nil, fmt.Errorf("failed to list datacenters: %w", err)
var hs mo.HostSystem
if err := v.client.RetrieveOne(v.ctx, hostRef, []string{"name", "parent"}, &hs); err != nil {
return nil, err
}
for _, dc := range datacenters {
v.Logger.Debug("Checking dc for host", "name", dc.Name(), "hostRef", hostRef.String())
// Set the current datacenter
finder.SetDatacenter(dc)
var hs mo.HostSystem
err := v.client.RetrieveOne(v.ctx, hostRef, nil, &hs)
if err != nil {
return nil, err
} else {
v.Logger.Debug("Found hostsystem", "name", hs.Name)
return &hs, nil
}
}
return nil, nil
v.Logger.Debug("found hostsystem", "name", hs.Name)
return &hs, nil
}
// Function to find the cluster or compute resource from a host reference
@@ -620,87 +679,43 @@ func (v *Vcenter) GetClusterFromHost(hostRef *types.ManagedObjectReference) (str
v.Logger.Warn("nil hostRef passed to GetClusterFromHost")
return "", nil
}
// Get the host object
host, err := v.GetHostSystemObject(*hostRef)
var host mo.HostSystem
err := v.client.RetrieveOne(v.ctx, *hostRef, []string{"parent"}, &host)
if err != nil {
v.Logger.Error("cant get host", "error", err)
return "", err
return "", fmt.Errorf("failed to retrieve host parent: %w", err)
}
if host == nil {
v.Logger.Warn("host lookup returned nil", "host_ref", hostRef)
if host.Parent == nil {
return "", nil
}
if host.Parent.Type != "ClusterComputeResource" && host.Parent.Type != "ComputeResource" {
return "", nil
}
v.Logger.Debug("host parent", "parent", host.Parent)
if host.Parent != nil && host.Parent.Type == "ClusterComputeResource" {
// Retrieve properties of the compute resource
var moCompute mo.ComputeResource
err = v.client.RetrieveOne(v.ctx, *host.Parent, nil, &moCompute)
if err != nil {
return "", fmt.Errorf("failed to retrieve compute resource: %w", err)
}
v.Logger.Debug("VM is on host in cluster/compute resource", "name", moCompute.Name)
return moCompute.Name, nil
var moCompute mo.ComputeResource
if err := v.client.RetrieveOne(v.ctx, *host.Parent, []string{"name"}, &moCompute); err != nil {
return "", fmt.Errorf("failed to retrieve compute resource: %w", err)
}
return "", nil
v.Logger.Debug("vm host parent compute resource resolved", "name", moCompute.Name, "host_ref", hostRef.Value)
return moCompute.Name, nil
}
// Function to determine the datacenter a VM belongs to
func (v *Vcenter) GetDatacenterForVM(vm mo.VirtualMachine) (string, error) {
// Start with the VM's parent reference
ref := vm.Reference()
// Traverse the inventory hierarchy upwards to find the datacenter
for {
// Get the parent reference of the current object
parentRef, err := v.getParent(ref)
if err != nil {
return "", fmt.Errorf("failed to get parent object: %w", err)
}
// If we get a nil parent reference, it means we've hit the root without finding the datacenter
if parentRef == nil {
return "", fmt.Errorf("failed to find datacenter for VM")
}
// Check if the parent is a Datacenter
switch parentRef.Type {
case "Datacenter":
// If we found a Datacenter, retrieve its properties
datacenter := object.NewDatacenter(v.client.Client, *parentRef)
var moDC mo.Datacenter
err = v.client.RetrieveOne(v.ctx, datacenter.Reference(), nil, &moDC)
if err != nil {
return "", fmt.Errorf("failed to retrieve datacenter: %w", err)
}
//log.Printf("VM is in datacenter: %s", moDC.Name)
v.Logger.Debug("VM datacenter found", "vm_name", vm.Name, "dc_name", moDC.Name)
return moDC.Name, nil
default:
// Continue traversing upwards if not a Datacenter
ref = *parentRef
}
}
}
// Helper function to get the parent ManagedObjectReference of a given object
func (v *Vcenter) getParent(ref types.ManagedObjectReference) (*types.ManagedObjectReference, error) {
// Retrieve the object's properties
var obj mo.ManagedEntity
err := v.client.RetrieveOne(v.ctx, ref, []string{"parent"}, &obj)
entities, err := mo.Ancestors(v.ctx, v.client.Client, v.client.ServiceContent.PropertyCollector, vm.Reference())
if err != nil {
return nil, fmt.Errorf("failed to retrieve parent of object: %w", err)
return "", fmt.Errorf("failed to retrieve VM ancestors: %w", err)
}
// Return the parent reference
if obj.Parent != nil {
return obj.Parent, nil
for _, entity := range entities {
if entity.Self.Type != "Datacenter" {
continue
}
if entity.Name == "" {
return "", fmt.Errorf("datacenter ancestor found without name for VM %s", vm.Reference().Value)
}
return entity.Name, nil
}
return nil, nil
return "", fmt.Errorf("failed to find datacenter for VM %s", vm.Reference().Value)
}
func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
@@ -714,7 +729,7 @@ func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
var matchingVMs []mo.VirtualMachine
err = vms.Retrieve(v.ctx, []string{"VirtualMachine"}, []string{"name"}, &matchingVMs)
err = v.retrieveContainerViewPaged(v.ctx, vms, []string{"VirtualMachine"}, []string{"name"}, &matchingVMs, defaultPropertyPageSize)
if err != nil {
return nil, err
}
@@ -734,93 +749,39 @@ func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
}
func (v *Vcenter) FindVMByID(vmID string) (*VmProperties, error) {
v.Logger.Debug("searching for vm id", "vm_id", vmID)
finder := find.NewFinder(v.client.Client, true)
// List all datacenters
datacenters, err := finder.DatacenterList(v.ctx, "*")
if err != nil {
return nil, fmt.Errorf("failed to list datacenters: %w", err)
}
for _, dc := range datacenters {
// Set the current datacenter
finder.SetDatacenter(dc)
// Create a ManagedObjectReference for the VM
vmRef := types.ManagedObjectReference{
Type: "VirtualMachine",
Value: vmID,
}
// Try to find the VM by ID in the current datacenter
//vm, err := finder.ObjectReference(v.ctx, vmRef)
var vm mo.VirtualMachine
err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm)
if err == nil {
return &VmProperties{
//Datacenter: dc.Name(),
Vm: vm,
}, nil
} else if _, ok := err.(*find.NotFoundError); !ok {
// If the error is not a NotFoundError, return it
//return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err)
v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_name", dc.Name())
} else {
return nil, fmt.Errorf("failed to retrieve VM: %w", err)
}
}
return nil, fmt.Errorf("VM with ID %s not found in any datacenter", vmID)
}
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*mo.VirtualMachine, error) {
var err error
//v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID)
finder := find.NewFinder(v.client.Client, true)
// Create a ManagedObjectReference for the datacenter
dcRef := types.ManagedObjectReference{
Type: "Datacenter",
Value: dcID,
}
// Convert the reference to a Datacenter object
datacenter := object.NewDatacenter(v.client.Client, dcRef)
if datacenter == nil {
return nil, fmt.Errorf("Datacenter with id %s not found", dcID)
}
// Use finder.SetDatacenter to set the datacenter
finder.SetDatacenter(datacenter)
// Create a ManagedObjectReference for the VM
vmRef := types.ManagedObjectReference{
Type: "VirtualMachine",
Value: vmID,
}
var vm mo.VirtualMachine
//err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm)
err = v.client.RetrieveOne(v.ctx, vmRef, nil, &vm)
if err == nil {
//v.Logger.Debug("Found VM")
return &vm, nil
} else if _, ok := err.(*find.NotFoundError); !ok {
// If the error is not a NotFoundError, return it
//return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err)
//v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
return nil, nil
} else {
return nil, fmt.Errorf("failed to retrieve VM: %w", err)
if err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm); err != nil {
return nil, fmt.Errorf("failed to retrieve VM %s: %w", vmID, err)
}
return &VmProperties{Vm: vm}, nil
}
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*mo.VirtualMachine, error) {
if dcID != "" {
v.Logger.Debug("searching for vm id in datacenter context", "vm_id", vmID, "datacenter_id", dcID)
}
vmRef := types.ManagedObjectReference{
Type: "VirtualMachine",
Value: vmID,
}
var vm mo.VirtualMachine
props := []string{"name", "config", "runtime.powerState", "runtime.host", "resourcePool", "parent"}
if err := v.client.RetrieveOne(v.ctx, vmRef, props, &vm); err != nil {
if fault.Is(err, &types.ManagedObjectNotFound{}) {
return nil, nil
}
return nil, fmt.Errorf("failed to retrieve VM %s: %w", vmID, err)
}
return &vm, nil
}
// Helper function to retrieve the resource pool for the VM
@@ -850,7 +811,7 @@ func (v *Vcenter) BuildResourcePoolLookup() (map[string]string, error) {
defer cv.Destroy(v.ctx)
var pools []mo.ResourcePool
if err := cv.Retrieve(v.ctx, []string{"ResourcePool"}, []string{"name"}, &pools); err != nil {
if err := v.retrieveContainerViewPaged(v.ctx, cv, []string{"ResourcePool"}, []string{"name"}, &pools, defaultPropertyPageSize); err != nil {
return nil, fmt.Errorf("failed to retrieve resource pools: %w", err)
}
@@ -863,55 +824,39 @@ func (v *Vcenter) BuildResourcePoolLookup() (map[string]string, error) {
// Helper function to retrieve the full folder path for the VM
func (v *Vcenter) GetVMFolderPath(vm mo.VirtualMachine) (string, error) {
//finder := find.NewFinder(v.client.Client, true)
v.Logger.Debug("Commencing vm folder path search")
// Start from the VM's parent
parentRef := vm.Parent
if parentRef == nil {
return "", fmt.Errorf("no parent found for the VM")
entities, err := mo.Ancestors(v.ctx, v.client.Client, v.client.ServiceContent.PropertyCollector, vm.Reference())
if err != nil {
return "", fmt.Errorf("failed to retrieve VM ancestors: %w", err)
}
// Traverse the folder hierarchy to build the full folder path
folderPath := ""
//v.Logger.Debug("parent is", "parent", parentRef)
maxHops := 128
for parentRef != nil && parentRef.Type != "Datacenter" && maxHops > 0 {
// Retrieve the parent object
//parentObj, err := finder.ObjectReference(v.ctx, *parentRef)
//if err != nil {
// return "", fmt.Errorf("failed to find parent object in inventory: %w", err)
//}
// Retrieve the folder name
var parentObj mo.Folder
err := v.client.RetrieveOne(v.ctx, *parentRef, nil, &parentObj)
if err != nil {
v.Logger.Error("Failed to get object for parent reference", "ref", parentRef)
break
var folderParts []string
seenDatacenter := false
vmRef := vm.Reference()
for _, entity := range entities {
switch entity.Self.Type {
case "Datacenter":
seenDatacenter = true
folderParts = folderParts[:0]
case "VirtualMachine":
if entity.Self == vmRef {
goto done
}
case "Folder":
if seenDatacenter {
folderParts = append(folderParts, entity.Name)
}
}
}
done:
// Prepend the folder name to the path
folderPath = path.Join("/", parentObj.Name, folderPath)
// Move up to the next parent
//if folder, ok := parentObj.(*object.Folder); ok {
if parentObj.Parent != nil {
parentRef = parentObj.Parent
//v.Logger.Debug("Parent uplevel is", "ref", parentRef)
} else {
return "", fmt.Errorf("unexpected parent type: %s", parentObj.Reference().Type)
}
//break
maxHops--
if !seenDatacenter {
return "", fmt.Errorf("failed to find datacenter ancestor for VM %s", vm.Reference().Value)
}
if len(folderParts) == 0 {
return "", fmt.Errorf("failed to find folder path for VM %s", vm.Reference().Value)
}
if parentRef == nil || maxHops == 0 {
return "", fmt.Errorf("folder traversal terminated early for VM %s", vm.Name)
}
return folderPath, nil
return path.Join("/", path.Join(folderParts...)), nil
}