Initial ESX management libraries
This commit is contained in:
parent
41ab945e2a
commit
c706dc5126
|
@ -0,0 +1,74 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"esxlib/pkg/sshutil"
|
||||||
|
|
||||||
|
"github.com/vmware/govmomi/session/cache"
|
||||||
|
"github.com/vmware/govmomi/vim25"
|
||||||
|
"github.com/vmware/govmomi/vim25/soap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var diskRegexp = regexp.MustCompile("\\[(.*?)\\] (.*)")
|
||||||
|
|
||||||
|
type Option func(c *Client)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
sshAuth *sshutil.Auth
|
||||||
|
vim *vim25.Client
|
||||||
|
|
||||||
|
VirtualMachines VirtualMachines
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientRef interface {
|
||||||
|
VIM() *vim25.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) VIM() *vim25.Client {
|
||||||
|
return c.vim
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSSH(host, user, password string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.sshAuth = &sshutil.Auth{
|
||||||
|
Host: host,
|
||||||
|
User: user,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx context.Context, url string, insecure bool, opts ...Option) (*Client, error) {
|
||||||
|
// Parse URL from string
|
||||||
|
u, err := soap.ParseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Share govc's session cache
|
||||||
|
s := &cache.Session{
|
||||||
|
URL: u,
|
||||||
|
Insecure: insecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
c := new(vim25.Client)
|
||||||
|
err = s.Login(ctx, c, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
vim: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.VirtualMachines.ClientRef = client
|
||||||
|
client.VirtualMachines.sshAuth = client.sshAuth
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
|
@ -0,0 +1,284 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"esxlib/pkg/sshutil"
|
||||||
|
|
||||||
|
"github.com/vmware/govmomi/find"
|
||||||
|
"github.com/vmware/govmomi/view"
|
||||||
|
"github.com/vmware/govmomi/vim25/mo"
|
||||||
|
"github.com/vmware/govmomi/vim25/soap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VirtualMachines struct {
|
||||||
|
ClientRef
|
||||||
|
sshAuth *sshutil.Auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) List(ctx context.Context) ([]*VirtualMachine, error) {
|
||||||
|
m := view.NewManager(vms.VIM())
|
||||||
|
v, err := m.CreateContainerView(ctx, vms.VIM().ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer v.Destroy(ctx)
|
||||||
|
|
||||||
|
// Retrieve summary property for all machines
|
||||||
|
// Reference: http://pubs.vmware.com/vsphere-60/topic/com.vmware.wssdk.apiref.doc/vim.VirtualMachine.html
|
||||||
|
var moList []mo.VirtualMachine
|
||||||
|
err = v.Retrieve(ctx, []string{"VirtualMachine"}, nil, &moList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vmList := make([]*VirtualMachine, 0)
|
||||||
|
|
||||||
|
for idx := range moList {
|
||||||
|
vmList = append(vmList, &VirtualMachine{
|
||||||
|
c: vms.VIM(),
|
||||||
|
sshAuth: vms.sshAuth,
|
||||||
|
ref: moList[idx].Reference(),
|
||||||
|
mo: &moList[idx],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return vmList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) Get(ctx context.Context, name string) (*VirtualMachine, error) {
|
||||||
|
vmList, err := vms.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, vm := range vmList {
|
||||||
|
if vm.Name() == name {
|
||||||
|
return vmList[idx], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("vm does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) Create(ctx context.Context, name string, memSize uint, diskSize uint, initScript []byte) (*VirtualMachine, error) {
|
||||||
|
// we need some into about this ESX server from someplace
|
||||||
|
// DestinationPath: /vmfs/volumes/datastore1
|
||||||
|
// SourcePath
|
||||||
|
container := "datastore1"
|
||||||
|
path := fmt.Sprintf("%s/%s.vmx", name, name)
|
||||||
|
|
||||||
|
initScriptEncoded := ""
|
||||||
|
if len(initScript) != 0 {
|
||||||
|
initScriptEncoded = base64.StdEncoding.EncodeToString(initScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("ubuntu-lunar.cloudinit.tmpl").ParseFiles("configs/ubuntu-lunar.cloudinit.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := struct {
|
||||||
|
Name string
|
||||||
|
Hostname string
|
||||||
|
MemSizeMB uint
|
||||||
|
CloudInitBase64 string
|
||||||
|
InitScriptBase64 string
|
||||||
|
}{
|
||||||
|
Name: name,
|
||||||
|
Hostname: name,
|
||||||
|
MemSizeMB: memSize,
|
||||||
|
InitScriptBase64: initScriptEncoded,
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := bufio.NewWriter(&b)
|
||||||
|
|
||||||
|
err = tmpl.Execute(w, config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = w.Flush()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudInitEncoded := base64.StdEncoding.EncodeToString(b.Bytes())
|
||||||
|
config.CloudInitBase64 = cloudInitEncoded
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", string(b.Bytes()))
|
||||||
|
|
||||||
|
// Now encode the vm config
|
||||||
|
tmpl, err = template.New("ubuntu-lunar.esx.tmpl").ParseFiles("configs/ubuntu-lunar.esx.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var upload bytes.Buffer
|
||||||
|
w = bufio.NewWriter(&upload)
|
||||||
|
|
||||||
|
err = tmpl.Execute(w, config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = w.Flush()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := soap.DefaultUpload
|
||||||
|
|
||||||
|
err = vms.Mkdir(ctx, container, name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
finder := find.NewFinder(vms.VIM())
|
||||||
|
ds, err := finder.Datastore(ctx, container)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.Upload(ctx, &upload, path, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vms.cloneImage(ctx, "/vmfs/volumes/nas011/iso/lunar-server-cloudimg-amd64.vmdk", fmt.Sprintf("/vmfs/volumes/%s/%s/%s.vmdk", container, name, name), diskSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vms.registerVM(ctx, fmt.Sprintf("/vmfs/volumes/%s/%s/%s.vmx", container, name, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vm, err := vms.Get(ctx, name)
|
||||||
|
return vm, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) Destroy(ctx context.Context, name string) error {
|
||||||
|
vm, err := vms.Get(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vm.PowerOff(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the content
|
||||||
|
datastore, dir, err := vm.InternalPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vms.unregisterVM(ctx, vm.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vms.wipe(ctx, datastore, dir)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) unregisterVM(ctx context.Context, id string) error {
|
||||||
|
cmd := fmt.Sprintf("vim-cmd vmsvc/unregister %s", id)
|
||||||
|
_, err := sshutil.CombinedOutput(sshutil.Auth{
|
||||||
|
User: vms.sshAuth.User,
|
||||||
|
Password: vms.sshAuth.Password,
|
||||||
|
Host: vms.sshAuth.Host,
|
||||||
|
}, cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cmd: %s\n", cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) registerVM(ctx context.Context, vmxPath string) error {
|
||||||
|
cmd := fmt.Sprintf("vim-cmd solo/registervm %s", vmxPath)
|
||||||
|
_, err := sshutil.CombinedOutput(sshutil.Auth{
|
||||||
|
User: vms.sshAuth.User,
|
||||||
|
Password: vms.sshAuth.Password,
|
||||||
|
Host: vms.sshAuth.Host,
|
||||||
|
}, cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cmd: %s\n", cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) cloneImage(ctx context.Context, src, dest string, size uint) error {
|
||||||
|
cmd := fmt.Sprintf("vmkfstools --clonevirtualdisk %s --diskformat thin %s", src, dest)
|
||||||
|
_, err := sshutil.CombinedOutput(sshutil.Auth{
|
||||||
|
User: vms.sshAuth.User,
|
||||||
|
Password: vms.sshAuth.Password,
|
||||||
|
Host: vms.sshAuth.Host,
|
||||||
|
}, cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cmd: %s\n", cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = fmt.Sprintf("vmkfstools -X %s %s", "20G", dest)
|
||||||
|
_, err = sshutil.CombinedOutput(sshutil.Auth{
|
||||||
|
User: vms.sshAuth.User,
|
||||||
|
Password: vms.sshAuth.Password,
|
||||||
|
Host: vms.sshAuth.Host,
|
||||||
|
}, cmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cmd: %s\n", cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) wipe(ctx context.Context, datastore, dir string) error {
|
||||||
|
if vms.sshAuth != nil {
|
||||||
|
cmd := fmt.Sprintf("rm -rf /vmfs/volumes/%s/%s", datastore, dir)
|
||||||
|
_, err := sshutil.CombinedOutput(sshutil.Auth{
|
||||||
|
User: vms.sshAuth.User,
|
||||||
|
Password: vms.sshAuth.Password,
|
||||||
|
Host: vms.sshAuth.Host,
|
||||||
|
}, cmd)
|
||||||
|
|
||||||
|
fmt.Printf("cmd: %s\n", cmd)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
panic("no ssh config")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vms *VirtualMachines) Mkdir(ctx context.Context, datastore, dir string) error {
|
||||||
|
// you need a ESX payed license to create directories ... thats some bullshit */
|
||||||
|
if vms.sshAuth != nil {
|
||||||
|
cmd := fmt.Sprintf("mkdir -p /vmfs/volumes/%s/%s", datastore, dir)
|
||||||
|
_, err := sshutil.CombinedOutput(sshutil.Auth{
|
||||||
|
User: vms.sshAuth.User,
|
||||||
|
Password: vms.sshAuth.Password,
|
||||||
|
Host: vms.sshAuth.Host,
|
||||||
|
}, cmd)
|
||||||
|
|
||||||
|
fmt.Printf("cmd: %s\n", cmd)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
panic("no ssh config")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"esxlib/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Host string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalFromFile(path string, v interface{}) error {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
|
err := UnmarshalFromFile("config.json", config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://%s:%s@%s", config.User, config.Password, config.Host)
|
||||||
|
c, err := client.NewClient(
|
||||||
|
context.TODO(),
|
||||||
|
url,
|
||||||
|
true,
|
||||||
|
client.WithSSH(fmt.Sprintf("%s:22", config.Host), config.User, config.Password))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls, err := c.VirtualMachines.List(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vm := range ls {
|
||||||
|
fmt.Printf("%s\n", vm.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
vm, err := c.VirtualMachines.Create(context.TODO(), "test2", 1024, 20000, []byte("#!/bin/bash"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%+v\n", vm)
|
||||||
|
|
||||||
|
err = vm.PowerOn(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
err = vm.Update()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs := vm.GetNetworkAddressV4()
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range addrs {
|
||||||
|
fmt.Printf("%s\n", a)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Destroy the vm now\n")
|
||||||
|
err = c.VirtualMachines.Destroy(context.TODO(), "test2")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
https://mac-blog.org.ua/esxi-automate-vm-creation/
|
||||||
|
https://mac-blog.org.ua/esxi-cloud-init-ubuntu/
|
|
@ -0,0 +1,22 @@
|
||||||
|
#cloud-config
|
||||||
|
users:
|
||||||
|
- name: test
|
||||||
|
plain_text_passwd: test
|
||||||
|
groups: sudo
|
||||||
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||||
|
shell: /bin/bash
|
||||||
|
lock_passwd: false
|
||||||
|
|
||||||
|
{{ if ne .InitScriptBase64 "" -}}
|
||||||
|
write_files:
|
||||||
|
- path: /root/.cloud-init-user.sh
|
||||||
|
encoding: b64
|
||||||
|
content: {{ .InitScriptBase64 }}
|
||||||
|
owner: root:root
|
||||||
|
permissions: '0755'
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if ne .InitScriptBase64 "" -}}
|
||||||
|
runcmd:
|
||||||
|
- /root/.cloud-init-user.sh
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,72 @@
|
||||||
|
.encoding = "UTF-8"
|
||||||
|
config.version = "8"
|
||||||
|
virtualHW.version = "19"
|
||||||
|
svga.present = "TRUE"
|
||||||
|
pciBridge0.present = "TRUE"
|
||||||
|
pciBridge4.present = "TRUE"
|
||||||
|
pciBridge4.virtualDev = "pcieRootPort"
|
||||||
|
pciBridge4.functions = "8"
|
||||||
|
pciBridge5.present = "TRUE"
|
||||||
|
pciBridge5.virtualDev = "pcieRootPort"
|
||||||
|
pciBridge5.functions = "8"
|
||||||
|
pciBridge6.present = "TRUE"
|
||||||
|
pciBridge6.virtualDev = "pcieRootPort"
|
||||||
|
pciBridge6.functions = "8"
|
||||||
|
pciBridge7.present = "TRUE"
|
||||||
|
pciBridge7.virtualDev = "pcieRootPort"
|
||||||
|
pciBridge7.functions = "8"
|
||||||
|
vmci0.present = "TRUE"
|
||||||
|
hpet0.present = "TRUE"
|
||||||
|
floppy0.present = "FALSE"
|
||||||
|
RemoteDisplay.maxConnections = "-1"
|
||||||
|
numvcpus = "4"
|
||||||
|
memSize = "{{ .MemSizeMB }}"
|
||||||
|
bios.bootRetry.delay = "10"
|
||||||
|
powerType.powerOff = "default"
|
||||||
|
powerType.suspend = "soft"
|
||||||
|
powerType.reset = "default"
|
||||||
|
tools.upgrade.policy = "manual"
|
||||||
|
sched.cpu.units = "mhz"
|
||||||
|
sched.cpu.affinity = "all"
|
||||||
|
sched.cpu.latencySensitivity = "normal"
|
||||||
|
scsi0.virtualDev = "lsilogic"
|
||||||
|
scsi0.present = "TRUE"
|
||||||
|
sata0.present = "TRUE"
|
||||||
|
ehci.present = "TRUE"
|
||||||
|
svga.autodetect = "TRUE"
|
||||||
|
|
||||||
|
ethernet0.virtualDev = "vmxnet3"
|
||||||
|
ethernet0.networkName = "LAN"
|
||||||
|
ethernet0.addressType = "generated"
|
||||||
|
ethernet0.wakeOnPcktRcv = "FALSE"
|
||||||
|
ethernet0.uptCompatibility = "TRUE"
|
||||||
|
ethernet0.present = "TRUE"
|
||||||
|
|
||||||
|
sata0:0.startConnected = "FALSE"
|
||||||
|
sata0:0.autodetect = "TRUE"
|
||||||
|
sata0:0.deviceType = "atapi-cdrom"
|
||||||
|
sata0:0.fileName = "auto detect"
|
||||||
|
sata0:0.present = "TRUE"
|
||||||
|
displayName = "{{ .Name }}"
|
||||||
|
guestOS = "ubuntu-64"
|
||||||
|
toolScripts.afterPowerOn = "TRUE"
|
||||||
|
toolScripts.afterResume = "TRUE"
|
||||||
|
toolScripts.beforeSuspend = "TRUE"
|
||||||
|
toolScripts.beforePowerOff = "TRUE"
|
||||||
|
tools.syncTime = "FALSE"
|
||||||
|
sched.cpu.min = "0"
|
||||||
|
sched.cpu.shares = "normal"
|
||||||
|
sched.mem.min = "0"
|
||||||
|
sched.mem.minSize = "0"
|
||||||
|
sched.mem.shares = "normal"
|
||||||
|
|
||||||
|
|
||||||
|
sched.scsi0:0.shares = "normal"
|
||||||
|
sched.scsi0:0.throughputCap = "off"
|
||||||
|
scsi0:0.deviceType = "scsi-hardDisk"
|
||||||
|
scsi0:0.fileName = "{{ .Name }}.vmdk"
|
||||||
|
scsi0:0.present = "TRUE"
|
||||||
|
|
||||||
|
|
||||||
|
guestinfo.userdata.encoding = "base64"
|
||||||
|
guestinfo.userdata = {{ .CloudInitBase64 }}
|
|
@ -0,0 +1,10 @@
|
||||||
|
module esxlib
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/vmware/govmomi v0.30.4
|
||||||
|
golang.org/x/crypto v0.10.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.9.0 // indirect
|
|
@ -0,0 +1,8 @@
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/vmware/govmomi v0.30.4 h1:BCKLoTmiBYRuplv3GxKEMBLtBaJm8PA56vo9bddIpYQ=
|
||||||
|
github.com/vmware/govmomi v0.30.4/go.mod h1:F7adsVewLNHsW/IIm7ziFURaXDaHEwcc+ym4r3INMdY=
|
||||||
|
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||||
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
|
@ -0,0 +1,54 @@
|
||||||
|
package sshutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
Host string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func SshInteractive(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
||||||
|
answers = make([]string, len(questions))
|
||||||
|
// The second parameter is unused
|
||||||
|
for n, _ := range questions {
|
||||||
|
answers[n] = "your_password"
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CombinedOutput(auth Auth, cmd string) ([]byte, error) {
|
||||||
|
sshConfig := &ssh.ClientConfig{
|
||||||
|
User: auth.User,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.KeyboardInteractive(
|
||||||
|
func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
||||||
|
answers = make([]string, len(questions))
|
||||||
|
// The second parameter is unused
|
||||||
|
for n, _ := range questions {
|
||||||
|
answers[n] = auth.Password
|
||||||
|
}
|
||||||
|
return answers, nil
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey()
|
||||||
|
|
||||||
|
client, err := ssh.Dial("tcp", auth.Host, sshConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
out, err := session.CombinedOutput(cmd)
|
||||||
|
return out, err
|
||||||
|
}
|
Loading…
Reference in New Issue