289 lines
7.0 KiB
Go
289 lines
7.0 KiB
Go
|
package esx
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"math/rand"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
"time"
|
||
|
|
||
|
crypt "github.com/ncw/pwhash/sha512_crypt"
|
||
|
"github.com/vmware/govmomi/find"
|
||
|
"github.com/vmware/govmomi/view"
|
||
|
"github.com/vmware/govmomi/vim25/mo"
|
||
|
"github.com/vmware/govmomi/vim25/soap"
|
||
|
)
|
||
|
|
||
|
// VirtualMachines provides an interface fo retrieving virtual machines
|
||
|
type VirtualMachines struct {
|
||
|
ClientRef
|
||
|
}
|
||
|
|
||
|
func encryptPassword(userPassword string) string {
|
||
|
// Generate a random string for use in the salt
|
||
|
const charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
|
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
|
s := make([]byte, 8)
|
||
|
for i := range s {
|
||
|
s[i] = charset[seededRand.Intn(len(charset))]
|
||
|
}
|
||
|
salt := fmt.Sprintf("$6$%s", s)
|
||
|
// use salt to hash user-supplied password
|
||
|
hash := crypt.Crypt(userPassword, salt)
|
||
|
return string(hash)
|
||
|
}
|
||
|
|
||
|
// List returns a list of registered Virtual Machines
|
||
|
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{
|
||
|
ClientRef: vms.ClientRef,
|
||
|
ref: moList[idx].Reference(),
|
||
|
mo: &moList[idx],
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return vmList, nil
|
||
|
}
|
||
|
|
||
|
// GetByName returns a virtual machine by name
|
||
|
func (vms *VirtualMachines) GetByName(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) GetById(ctx context.Context, id string) (*VirtualMachine, error) {
|
||
|
vmList, err := vms.List(ctx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for idx, vm := range vmList {
|
||
|
if vm.Id() == id {
|
||
|
return vmList[idx], nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil, errors.New("vm does not exist")
|
||
|
}
|
||
|
|
||
|
func (vms *VirtualMachines) CreateFromSlug(ctx context.Context, slug *Slug, name, password, sshKeys string, initScript []byte) (*VirtualMachine, error) {
|
||
|
path := fmt.Sprintf("%s/%s.vmx", name, name)
|
||
|
|
||
|
initScriptEncoded := ""
|
||
|
if len(initScript) != 0 {
|
||
|
initScriptEncoded = base64.StdEncoding.EncodeToString(initScript)
|
||
|
}
|
||
|
sshKeysEncoded := ""
|
||
|
if sshKeys != "" {
|
||
|
sshKeysEncoded = base64.StdEncoding.EncodeToString([]byte(sshKeys))
|
||
|
}
|
||
|
|
||
|
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
|
||
|
DiskSizeGB uint
|
||
|
MemSizeMB uint
|
||
|
CpuCount uint
|
||
|
CloudInitBase64 string
|
||
|
InitScriptBase64 string
|
||
|
RootPassword string
|
||
|
AuthorizedKeys string
|
||
|
Network string
|
||
|
}{
|
||
|
Name: name,
|
||
|
Hostname: name,
|
||
|
CpuCount: slug.CpuCount,
|
||
|
MemSizeMB: slug.MemoryMB,
|
||
|
DiskSizeGB: slug.DiskSizeGB,
|
||
|
InitScriptBase64: initScriptEncoded,
|
||
|
RootPassword: encryptPassword(password),
|
||
|
AuthorizedKeys: sshKeysEncoded,
|
||
|
Network: vms.hostProperties().DefaultNetwork,
|
||
|
}
|
||
|
|
||
|
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
|
||
|
|
||
|
finder := find.NewFinder(vms.VIM())
|
||
|
ds, err := finder.Datastore(ctx, vms.hostProperties().DefaultDatastore)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
err = vms.createVMDirectory(ctx, vms.hostProperties().GetPathToVM(name))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// if we fail at this point we need to do some cleanup
|
||
|
err = ds.Upload(ctx, &upload, path, &p)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
err = vms.cloneImage(ctx, vms.hostProperties().GetPathToSlug(*slug), vms.hostProperties().GetPathToVMDisk(name), config.DiskSizeGB)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
err = vms.registerVM(ctx, vms.hostProperties().GetPathToVMConfig(name))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
vm, err := vms.GetByName(ctx, name)
|
||
|
return vm, err
|
||
|
|
||
|
}
|
||
|
|
||
|
// Create a new Virtual machine from a slug
|
||
|
func (vms *VirtualMachines) Create(ctx context.Context, slugName string, name, sshKeys string, initScript []byte) (*VirtualMachine, error) {
|
||
|
slug, err := vms.hostProperties().GetSlug(slugName)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return vms.CreateFromSlug(ctx, slug, name, "", sshKeys, initScript)
|
||
|
}
|
||
|
|
||
|
// Destroy the named virtual machine
|
||
|
func (vms *VirtualMachines) Destroy(ctx context.Context, id string) error {
|
||
|
vm, err := vms.GetById(ctx, id)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if vm.State() != PowerStateOff {
|
||
|
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.destroyVMDirectory(ctx, datastore, dir)
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (vms *VirtualMachines) unregisterVM(ctx context.Context, id string) error {
|
||
|
cmd := fmt.Sprintf("vim-cmd vmsvc/unregister %s", id)
|
||
|
return vms.hostExec(ctx, cmd)
|
||
|
}
|
||
|
|
||
|
func (vms *VirtualMachines) registerVM(ctx context.Context, vmxPath string) error {
|
||
|
cmd := fmt.Sprintf("vim-cmd solo/registervm %s", vmxPath)
|
||
|
return vms.hostExec(ctx, cmd)
|
||
|
}
|
||
|
|
||
|
// cloneImage will copy a base image slug over to a new vm location
|
||
|
func (vms *VirtualMachines) cloneImage(ctx context.Context, src, dest string, sizeGB uint) error {
|
||
|
cmd := fmt.Sprintf("vmkfstools --clonevirtualdisk %s --diskformat thin %s", src, dest)
|
||
|
if err := vms.hostExec(ctx, cmd); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
cmd = fmt.Sprintf("vmkfstools -X %dG %s", sizeGB, dest)
|
||
|
if err := vms.hostExec(ctx, cmd); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (vms *VirtualMachines) destroyVMDirectory(ctx context.Context, datastore, dir string) error {
|
||
|
cmd := fmt.Sprintf("rm -rf /vmfs/volumes/%s/%s", datastore, dir)
|
||
|
return vms.hostExec(ctx, cmd)
|
||
|
}
|
||
|
|
||
|
func (vms *VirtualMachines) createVMDirectory(ctx context.Context, dir string) error {
|
||
|
if !strings.HasPrefix(dir, "/vmfs/volumes/") {
|
||
|
return fmt.Errorf("mkdir got a bad path: %s", dir)
|
||
|
}
|
||
|
|
||
|
cmd := fmt.Sprintf("mkdir %s", dir)
|
||
|
return vms.hostExec(ctx, cmd)
|
||
|
}
|