esxlib/esx/inventory_vms.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)
}