package vcenter import ( "context" "fmt" "log" "log/slog" "net/url" "os" "path" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" ) type Vcenter struct { Logger *slog.Logger ctx context.Context client *govmomi.Client } type VmProperties struct { Vm mo.VirtualMachine ResourcePool string } // New creates a new Vcenter with the given logger func New(logger *slog.Logger) *Vcenter { //ctx, cancel := context.WithCancel(context.Background()) //defer cancel() return &Vcenter{ Logger: logger, ctx: context.Background(), } } func (v *Vcenter) Login(vUrl string) error { var insecure bool insecureString := os.Getenv("VCENTER_INSECURE") username := os.Getenv("VCENTER_USERNAME") password := os.Getenv("VCENTER_PASSWORD") // Connect to vCenter u, err := soap.ParseURL(vUrl) if err != nil { log.Fatalf("Error parsing vCenter URL: %s", err) } u.User = url.UserPassword(username, password) /* c, err := govmomi.NewClient(ctx, u, insecure) if err != nil { log.Fatalf("Error connecting to vCenter: %s", err) } */ if insecureString == "true" { insecure = true } c, err := govmomi.NewClient(v.ctx, u, insecure) if err != nil { v.Logger.Error("Unable to connect to vCenter", "error", err) return fmt.Errorf("unable to connect to vCenter : %s", err) } //defer c.Logout(v.ctx) v.client = c v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", username) return nil } func (v *Vcenter) Logout() error { //v.Logger.Debug("vcenter logging out") if v.ctx == nil { v.Logger.Warn("Nil context, unable to logout") return nil } if v.client.Valid() { //v.Logger.Debug("vcenter client is valid. Logging out") return v.client.Logout(v.ctx) } else { v.Logger.Debug("vcenter client is not valid") return nil } } func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) { m := view.NewManager(v.client.Client) vms, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true) if err != nil { return nil, err } defer vms.Destroy(v.ctx) var matchingVMs []mo.VirtualMachine err = vms.Retrieve(v.ctx, []string{"VirtualMachine"}, []string{"name"}, &matchingVMs) if err != nil { return nil, err } // Temporarily just return all VMs //return matchingVMs, nil var result []mo.VirtualMachine for _, vm := range matchingVMs { if vm.Name == vmName { result = append(result, vm) } } return result, nil } 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) (*VmProperties, error) { //var dcName string var err error resourcePool := "" 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") // Retrieve the resource pool the VM is in if vm.ResourcePool != nil { rp := object.NewResourcePool(v.client.Client, *vm.ResourcePool) rpName, err := rp.ObjectName(v.ctx) if err != nil { v.Logger.Error("failed to get resource pool name", "error", err) } else { v.Logger.Debug("Found resource pool name", "rp_name", rpName) resourcePool = rpName } } return &VmProperties{ //Datacenter: dcName, Vm: vm, ResourcePool: resourcePool, }, 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) } //v.Logger.Info("Unable to find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID) //return nil, nil } // 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") } // Traverse the folder hierarchy to build the full folder path folderPath := "" v.Logger.Debug("parent is", "parent", parentRef) for parentRef.Type != "Datacenter" { // 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 } // 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 } return folderPath, nil }