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