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) }