From 5cd8f9c2a1dbee76339704e40e2e070ebd792227 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Fri, 13 Feb 2026 14:04:28 +1100 Subject: [PATCH] pagination of vcenter queries --- internal/vcenter/vcenter.go | 585 ++++++++++++++++-------------------- src/preinstall.sh | 34 ++- 2 files changed, 293 insertions(+), 326 deletions(-) diff --git a/internal/vcenter/vcenter.go b/internal/vcenter/vcenter.go index 650b345..76c699e 100644 --- a/internal/vcenter/vcenter.go +++ b/internal/vcenter/vcenter.go @@ -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 } diff --git a/src/preinstall.sh b/src/preinstall.sh index 01fd55e..a87c395 100644 --- a/src/preinstall.sh +++ b/src/preinstall.sh @@ -1,6 +1,8 @@ #!/bin/bash USER="vctp" GROUP="dtms" +CONFIG_FILE="/etc/dtms/vctp.yml" +DEFAULT_PORT=9443 # Path to the custom sudoers file SUDOERS_FILE="/etc/sudoers.d/${USER}" @@ -28,15 +30,35 @@ getent passwd "$USER" >/dev/null || useradd -r -g "$GROUP" -m -s /bin/bash -c "v # set group ownership on vctp data directory if not already done [ "$(stat -c "%G" /var/lib/vctp)" = "$GROUP" ] || chgrp -R "$GROUP" /var/lib/vctp +# Resolve effective application port from existing config (if present). +# Falls back to 9443 when the config file is missing/empty or bind_port is invalid. +APP_PORT="$DEFAULT_PORT" +if [ -s "$CONFIG_FILE" ]; then + CONFIGURED_PORT="$(awk ' + /^[[:space:]]*#/ { next } + /^[[:space:]]*bind_port[[:space:]]*:/ { + line=$0 + sub(/^[[:space:]]*bind_port[[:space:]]*:[[:space:]]*/, "", line) + sub(/[[:space:]]*#.*/, "", line) + gsub(/["'\''[:space:]]/, "", line) + if (line ~ /^[0-9]+$/) { + print line + exit + } + } + ' "$CONFIG_FILE")" + + if [ -n "$CONFIGURED_PORT" ] && [ "$CONFIGURED_PORT" -ge 1 ] && [ "$CONFIGURED_PORT" -le 65535 ]; then + APP_PORT="$CONFIGURED_PORT" + fi +fi + # Check if firewalld is installed and active if command -v systemctl >/dev/null 2>&1 && systemctl is-enabled firewalld >/dev/null 2>&1 && systemctl is-active firewalld >/dev/null 2>&1; then - echo "Firewalld is enabled and running. Adding necessary ports..." + echo "Firewalld is enabled and running. Opening vCTP port ${APP_PORT}/tcp..." - # Open HTTPS port (443/tcp) - firewall-cmd --permanent --add-service=https >/dev/null 2>&1 - - # Open custom application port (9443/tcp) - firewall-cmd --permanent --add-port=9443/tcp >/dev/null 2>&1 + # Open effective application port. + firewall-cmd --permanent --add-port="${APP_PORT}/tcp" >/dev/null 2>&1 # Reload firewalld to apply changes firewall-cmd --reload >/dev/null 2>&1