package client import ( "context" "errors" "fmt" "path" "regexp" "strings" "esxlib/pkg/sshutil" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) type PowerState string const ( PowerStateOff = PowerState("off") PowerStateOn = PowerState("on") PowerStateSuspended = PowerState("suspended") PowerStateUndefined = PowerState("") ) type VirtualMachine struct { c *vim25.Client sshAuth *sshutil.Auth mo *mo.VirtualMachine ref types.ManagedObjectReference } func (v *VirtualMachine) refresh() error { pc := property.DefaultCollector(v.c) refs := []types.ManagedObjectReference{v.ref} var vms []mo.VirtualMachine err := pc.Retrieve(context.Background(), refs, nil, &vms) if err != nil { return err } if len(vms) != 1 || vms[0].Name != v.mo.Name { return errors.New("internal error (vms don't) match") } v.mo = &vms[0] return nil } func (v *VirtualMachine) Update() error { return v.refresh() } func (v *VirtualMachine) Name() string { return v.mo.Name } func (v *VirtualMachine) UUID() string { return v.mo.Config.Uuid } func (v *VirtualMachine) Id() string { return v.mo.Summary.Vm.Value } func (v *VirtualMachine) InternalPath() (string, string, error) { layout, err := v.GetFiles() if err != nil { panic(err) } for _, l := range layout.File { if strings.HasSuffix(l.Name, ".vmx") { re := regexp.MustCompile(`\[(.*?)\] (.*)`) matches := re.FindStringSubmatch(l.Name) if len(matches) == 3 { return matches[1], path.Dir(matches[2]), nil } } } return "", "", fmt.Errorf("not found") } func (v *VirtualMachine) GetNetworkAddressV4() []string { addresses := make([]string, 0) for _, x := range v.mo.Config.ExtraConfig { kv := x.GetOptionValue() switch kv.Key { case "guestinfo.local-ipv4": addresses = append(addresses, kv.Value.(string)) } } return addresses } func (v *VirtualMachine) GetFiles() (*types.VirtualMachineFileLayoutEx, error) { if err := v.refresh(); err != nil { return nil, err } return v.mo.LayoutEx, nil } func (v *VirtualMachine) State() PowerState { switch v.mo.Runtime.PowerState { case types.VirtualMachinePowerStatePoweredOff: return PowerStateOff case types.VirtualMachinePowerStatePoweredOn: return PowerStateOn case types.VirtualMachinePowerStateSuspended: return PowerStateSuspended default: return PowerStateUndefined } } func (v *VirtualMachine) PowerOn(ctx context.Context) error { if v.sshAuth != nil { cmd := fmt.Sprintf("vim-cmd vmsvc/power.on %s", v.Id()) _, err := sshutil.CombinedOutput(sshutil.Auth{ User: v.sshAuth.User, Password: v.sshAuth.Password, Host: v.sshAuth.Host, }, cmd) return err } return fmt.Errorf("unsupported (requires esx ssh privs)") } func (v *VirtualMachine) PowerOff(ctx context.Context) error { if v.sshAuth != nil { cmd := fmt.Sprintf("vim-cmd vmsvc/power.off %s", v.Id()) _, err := sshutil.CombinedOutput(sshutil.Auth{ User: v.sshAuth.User, Password: v.sshAuth.Password, Host: v.sshAuth.Host, }, cmd) return err } return fmt.Errorf("unsupported (requires esx ssh privs)") } func (v *VirtualMachine) Suspend(ctx context.Context) error { // Workaround: if you are running the FREE license of ESX, the power management API is not available to you // IE: ServerFaultCode: Current license or ESXi version prohibits execution of the requested operation // so as an alternative you can provide the SSH auth to the server and we do it the gross way if v.sshAuth != nil { cmd := fmt.Sprintf("vim-cmd vmsvc/power.suspend %s", v.Id()) _, err := sshutil.CombinedOutput(sshutil.Auth{ User: v.sshAuth.User, Password: v.sshAuth.Password, Host: v.sshAuth.Host, }, cmd) return err } // standard API method req := types.SuspendVM_Task{ This: v.mo.Reference(), } res, err := methods.SuspendVM_Task(ctx, v.c, &req) if err != nil { return err } task := object.NewTask(v.c, res.Returnval) return task.Wait(ctx) } func (v *VirtualMachine) Resume(ctx context.Context) error { // Workaround: if you are running the FREE license of ESX, the power management API is not available to you // IE: ServerFaultCode: Current license or ESXi version prohibits execution of the requested operation // so as an alternative you can provide the SSH auth to the server and we do it the gross way if v.sshAuth != nil { cmd := fmt.Sprintf("vim-cmd vmsvc/power.on %s", v.Id()) _, err := sshutil.CombinedOutput(sshutil.Auth{ User: v.sshAuth.User, Password: v.sshAuth.Password, Host: v.sshAuth.Host, }, cmd) return err } // standard API method req := types.PowerOnVM_Task{ This: v.mo.Reference(), } res, err := methods.PowerOnVM_Task(ctx, v.c, &req) if err != nil { return err } task := object.NewTask(v.c, res.Returnval) return task.Wait(ctx) }