Initial commit
This commit is contained in:
58
builder/xenserver/common/artifact.go
Normal file
58
builder/xenserver/common/artifact.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// This is the common builder ID to all of these artifacts.
|
||||
const BuilderId = "packer.xenserver"
|
||||
|
||||
type LocalArtifact struct {
|
||||
dir string
|
||||
f []string
|
||||
}
|
||||
|
||||
func NewArtifact(dir string) (packer.Artifact, error) {
|
||||
files := make([]string, 0, 1)
|
||||
visit := func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := filepath.Walk(dir, visit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LocalArtifact{
|
||||
dir: dir,
|
||||
f: files,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*LocalArtifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) Files() []string {
|
||||
return a.f
|
||||
}
|
||||
|
||||
func (*LocalArtifact) Id() string {
|
||||
return "VM"
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) String() string {
|
||||
return fmt.Sprintf("VM files in directory: %s", a.dir)
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *LocalArtifact) Destroy() error {
|
||||
return os.RemoveAll(a.dir)
|
||||
}
|
974
builder/xenserver/common/client.go
Normal file
974
builder/xenserver/common/client.go
Normal file
@@ -0,0 +1,974 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
xmlrpc "github.com/amfranz/go-xmlrpc-client"
|
||||
xenapi "github.com/terra-farm/go-xen-api-client"
|
||||
)
|
||||
|
||||
type XenAPIClient struct {
|
||||
Session interface{}
|
||||
Host string
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
RPC *xmlrpc.Client
|
||||
}
|
||||
|
||||
type APIResult struct {
|
||||
Status string
|
||||
Value interface{}
|
||||
ErrorDescription string
|
||||
}
|
||||
|
||||
type XenAPIObject struct {
|
||||
Ref string
|
||||
Client *XenAPIClient
|
||||
}
|
||||
|
||||
type Host XenAPIObject
|
||||
type VM XenAPIObject
|
||||
type SR XenAPIObject
|
||||
type VDI XenAPIObject
|
||||
type Network XenAPIObject
|
||||
type VBD XenAPIObject
|
||||
type VIF XenAPIObject
|
||||
type PIF XenAPIObject
|
||||
type Pool XenAPIObject
|
||||
type Task XenAPIObject
|
||||
|
||||
type VDIType int
|
||||
|
||||
const (
|
||||
_ VDIType = iota
|
||||
Disk
|
||||
CD
|
||||
Floppy
|
||||
)
|
||||
|
||||
type TaskStatusType int
|
||||
|
||||
const (
|
||||
_ TaskStatusType = iota
|
||||
Pending
|
||||
Success
|
||||
Failure
|
||||
Cancelling
|
||||
Cancelled
|
||||
)
|
||||
|
||||
func (c *XenAPIClient) RPCCall(result interface{}, method string, params []interface{}) (err error) {
|
||||
fmt.Println(params)
|
||||
p := xmlrpc.Params{Params: params}
|
||||
err = c.RPC.Call(method, p, result)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) Login() (err error) {
|
||||
//Do loging call
|
||||
result := xmlrpc.Struct{}
|
||||
|
||||
params := make([]interface{}, 2)
|
||||
params[0] = client.Username
|
||||
params[1] = client.Password
|
||||
|
||||
err = client.RPCCall(&result, "session.login_with_password", params)
|
||||
client.Session = result["Value"]
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) APICall(result *APIResult, method string, params ...interface{}) (err error) {
|
||||
if client.Session == nil {
|
||||
fmt.Println("Error: no session")
|
||||
return fmt.Errorf("No session. Unable to make call")
|
||||
}
|
||||
|
||||
//Make a params slice which will include the session
|
||||
p := make([]interface{}, len(params)+1)
|
||||
p[0] = client.Session
|
||||
|
||||
if params != nil {
|
||||
for idx, element := range params {
|
||||
p[idx+1] = element
|
||||
}
|
||||
}
|
||||
|
||||
res := xmlrpc.Struct{}
|
||||
|
||||
err = client.RPCCall(&res, method, p)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.Status = res["Status"].(string)
|
||||
|
||||
if result.Status != "Success" {
|
||||
fmt.Println("Encountered an API error: ", result.Status)
|
||||
fmt.Println(res["ErrorDescription"])
|
||||
return fmt.Errorf("API Error: %s", res["ErrorDescription"])
|
||||
} else {
|
||||
result.Value = res["Value"]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetHosts() (hosts []*Host, err error) {
|
||||
hosts = make([]*Host, 0)
|
||||
result := APIResult{}
|
||||
_ = client.APICall(&result, "host.get_all")
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
host := new(Host)
|
||||
host.Ref = elem.(string)
|
||||
host.Client = client
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetPools() (pools []*Pool, err error) {
|
||||
pools = make([]*Pool, 0)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "pool.get_all")
|
||||
if err != nil {
|
||||
return pools, err
|
||||
}
|
||||
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
pool := new(Pool)
|
||||
pool.Ref = elem.(string)
|
||||
pool.Client = client
|
||||
pools = append(pools, pool)
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetDefaultSR() (sr *SR, err error) {
|
||||
pools, err := client.GetPools()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool_rec, err := pools[0].GetRecord()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pool_rec["default_SR"] == "" {
|
||||
return nil, errors.New("No default_SR specified for the pool.")
|
||||
}
|
||||
|
||||
sr = new(SR)
|
||||
sr.Ref = pool_rec["default_SR"].(string)
|
||||
sr.Client = client
|
||||
|
||||
return sr, nil
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetVMByUuid(vm_uuid string) (vm *VM, err error) {
|
||||
vm = new(VM)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "VM.get_by_uuid", vm_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm.Ref = result.Value.(string)
|
||||
vm.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetVMByNameLabel(name_label string) (vms []*VM, err error) {
|
||||
vms = make([]*VM, 0)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "VM.get_by_name_label", name_label)
|
||||
if err != nil {
|
||||
return vms, err
|
||||
}
|
||||
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
vm := new(VM)
|
||||
vm.Ref = elem.(string)
|
||||
vm.Client = client
|
||||
vms = append(vms, vm)
|
||||
}
|
||||
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetNetworkByUuid(network_uuid string) (network *Network, err error) {
|
||||
network = new(Network)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "network.get_by_uuid", network_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
network.Ref = result.Value.(string)
|
||||
network.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetNetworkByNameLabel(name_label string) (networks []*Network, err error) {
|
||||
networks = make([]*Network, 0)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "network.get_by_name_label", name_label)
|
||||
if err != nil {
|
||||
return networks, err
|
||||
}
|
||||
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
network := new(Network)
|
||||
network.Ref = elem.(string)
|
||||
network.Client = client
|
||||
networks = append(networks, network)
|
||||
}
|
||||
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetVdiByNameLabel(name_label string) (vdis []*VDI, err error) {
|
||||
vdis = make([]*VDI, 0)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "VDI.get_by_name_label", name_label)
|
||||
if err != nil {
|
||||
return vdis, err
|
||||
}
|
||||
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
vdi := new(VDI)
|
||||
vdi.Ref = elem.(string)
|
||||
vdi.Client = client
|
||||
vdis = append(vdis, vdi)
|
||||
}
|
||||
|
||||
return vdis, nil
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetVdiByUuid(vdi_uuid string) (vdi *VDI, err error) {
|
||||
vdi = new(VDI)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "VDI.get_by_uuid", vdi_uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vdi.Ref = result.Value.(string)
|
||||
vdi.Client = client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *XenAPIClient) GetPIFs() (pifs []*PIF, err error) {
|
||||
pifs = make([]*PIF, 0)
|
||||
result := APIResult{}
|
||||
err = client.APICall(&result, "PIF.get_all")
|
||||
if err != nil {
|
||||
return pifs, err
|
||||
}
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
pif := new(PIF)
|
||||
pif.Ref = elem.(string)
|
||||
pif.Client = client
|
||||
pifs = append(pifs, pif)
|
||||
}
|
||||
|
||||
return pifs, nil
|
||||
}
|
||||
|
||||
// Host associated functions
|
||||
|
||||
func (self *Host) GetSoftwareVersion() (versions map[string]interface{}, err error) {
|
||||
versions = make(map[string]interface{})
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "host.get_software_version", self.Ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
versions[k] = v.(string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Host) CallPlugin(plugin, function string, args map[string]string) (res string, err error) {
|
||||
|
||||
args_rec := make(xmlrpc.Struct)
|
||||
for key, value := range args {
|
||||
args_rec[key] = value
|
||||
}
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "host.call_plugin", self.Ref, plugin, function, args_rec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The plugin should return a string value
|
||||
res = result.Value.(string)
|
||||
return
|
||||
}
|
||||
|
||||
// VM associated functions
|
||||
|
||||
func (self *VM) Clone(label string) (new_instance *VM, err error) {
|
||||
new_instance = new(VM)
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.clone", self.Ref, label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
new_instance.Ref = result.Value.(string)
|
||||
new_instance.Client = self.Client
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) Destroy() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.destroy", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) Start(paused, force bool) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.start", self.Ref, paused, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) CleanShutdown() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.clean_shutdown", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Unpause(c *Connection, vmRef xenapi.VMRef) (err error) {
|
||||
err = c.client.VM.Unpause(c.session, vmRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) SetHVMBoot(policy, bootOrder string) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_HVM_boot_policy", self.Ref, policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = APIResult{}
|
||||
params := make(xmlrpc.Struct)
|
||||
params["order"] = bootOrder
|
||||
err = self.Client.APICall(&result, "VM.set_HVM_boot_params", self.Ref, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) SetPVBootloader(pv_bootloader, pv_args string) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_PV_bootloader", self.Ref, pv_bootloader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_PV_bootloader_args", self.Ref, pv_args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *VM) GetDomainId() (domid string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_domid", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
domid = result.Value.(string)
|
||||
return domid, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetPowerState() (state string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_power_state", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
state = result.Value.(string)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetUuid() (uuid string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_uuid", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uuid = result.Value.(string)
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetVBDs() (vbds []VBD, err error) {
|
||||
vbds = make([]VBD, 0)
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_VBDs", self.Ref)
|
||||
if err != nil {
|
||||
return vbds, err
|
||||
}
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
vbd := VBD{}
|
||||
vbd.Ref = elem.(string)
|
||||
vbd.Client = self.Client
|
||||
vbds = append(vbds, vbd)
|
||||
}
|
||||
|
||||
return vbds, nil
|
||||
}
|
||||
|
||||
func GetDisks(c *Connection, vmRef xenapi.VMRef) (vdis []xenapi.VDIRef, err error) {
|
||||
// Return just data disks (non-isos)
|
||||
vdis = make([]xenapi.VDIRef, 0)
|
||||
vbds, err := c.client.VM.GetVBDs(c.session, vmRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, vbd := range vbds {
|
||||
rec, err := c.client.VBD.GetRecord(c.session, vbd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rec.Type == "Disk" {
|
||||
|
||||
vdi, err := c.client.VBD.GetVDI(c.session, vbd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vdis = append(vdis, vdi)
|
||||
|
||||
}
|
||||
}
|
||||
return vdis, nil
|
||||
}
|
||||
|
||||
func (self *VM) GetGuestMetricsRef() (ref string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.get_guest_metrics", self.Ref)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
ref = result.Value.(string)
|
||||
return ref, err
|
||||
}
|
||||
|
||||
func (self *VM) GetGuestMetrics() (metrics map[string]interface{}, err error) {
|
||||
metrics_ref, err := self.GetGuestMetricsRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if metrics_ref == "OpaqueRef:NULL" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM_guest_metrics.get_record", metrics_ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Value.(xmlrpc.Struct), nil
|
||||
}
|
||||
|
||||
func (self *VM) SetStaticMemoryRange(min, max uint) (err error) {
|
||||
result := APIResult{}
|
||||
strMin := fmt.Sprintf("%d", min)
|
||||
strMax := fmt.Sprintf("%d", max)
|
||||
err = self.Client.APICall(&result, "VM.set_memory_limits", self.Ref, strMin, strMax, strMin, strMax)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectVdi(c *Connection, vmRef xenapi.VMRef, vdiRef xenapi.VDIRef, vbdType xenapi.VbdType) (err error) {
|
||||
|
||||
var mode xenapi.VbdMode
|
||||
var unpluggable bool
|
||||
var bootable bool
|
||||
var t xenapi.VbdType
|
||||
switch vbdType {
|
||||
case xenapi.VbdTypeCD:
|
||||
mode = xenapi.VbdModeRO
|
||||
bootable = true
|
||||
unpluggable = false
|
||||
t = xenapi.VbdTypeCD
|
||||
case xenapi.VbdTypeDisk:
|
||||
mode = xenapi.VbdModeRW
|
||||
bootable = false
|
||||
unpluggable = false
|
||||
t = xenapi.VbdTypeDisk
|
||||
case xenapi.VbdTypeFloppy:
|
||||
mode = xenapi.VbdModeRW
|
||||
bootable = false
|
||||
unpluggable = true
|
||||
t = xenapi.VbdTypeFloppy
|
||||
}
|
||||
|
||||
vbd_ref, err := c.client.VBD.Create(c.session, xenapi.VBDRecord{
|
||||
VM: xenapi.VMRef(vmRef),
|
||||
VDI: xenapi.VDIRef(vdiRef),
|
||||
Userdevice: "autodetect",
|
||||
Empty: false,
|
||||
// OtherConfig: map[string]interface{{}},
|
||||
QosAlgorithmType: "",
|
||||
// QosAlgorithmParams: map[string]interface{{}},
|
||||
Mode: mode,
|
||||
Unpluggable: unpluggable,
|
||||
Bootable: bootable,
|
||||
Type: t,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("VBD Ref:", vbd_ref)
|
||||
|
||||
uuid, err := c.client.VBD.GetUUID(c.session, vbd_ref)
|
||||
|
||||
fmt.Println("VBD UUID: ", uuid)
|
||||
/*
|
||||
// 2. Plug VBD (Non need - the VM hasn't booted.
|
||||
// @todo - check VM state
|
||||
result = APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.plug", vbd_ref)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
return
|
||||
}
|
||||
|
||||
func DisconnectVdi(c *Connection, vmRef xenapi.VMRef, vdi xenapi.VDIRef) error {
|
||||
vbds, err := c.client.VM.GetVBDs(c.session, vmRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to get VM VBDs: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, vbd := range vbds {
|
||||
rec, err := c.client.VBD.GetRecord(c.session, vbd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not get record for VBD '%s': %s", vbd, err.Error())
|
||||
}
|
||||
recVdi := rec.VDI
|
||||
if recVdi == vdi {
|
||||
_ = c.client.VBD.Unplug(c.session, vbd)
|
||||
err = c.client.VBD.Destroy(c.session, vbd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not destroy VBD '%s': %s", vbd, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
log.Printf("Could not find VDI record in VBD '%s'", vbd)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Could not find VBD for VDI '%s'", vdi)
|
||||
}
|
||||
|
||||
func (self *VM) SetPlatform(params map[string]string) (err error) {
|
||||
result := APIResult{}
|
||||
platform_rec := make(xmlrpc.Struct)
|
||||
for key, value := range params {
|
||||
platform_rec[key] = value
|
||||
}
|
||||
|
||||
err = self.Client.APICall(&result, "VM.set_platform", self.Ref, platform_rec)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectNetwork(c *Connection, networkRef xenapi.NetworkRef, vmRef xenapi.VMRef, device string) (*xenapi.VIFRef, error) {
|
||||
vif, err := c.client.VIF.Create(c.session, xenapi.VIFRecord{
|
||||
Network: networkRef,
|
||||
VM: vmRef,
|
||||
Device: device,
|
||||
LockingMode: xenapi.VifLockingModeNetworkDefault,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Created the following VIF: %s", vif)
|
||||
|
||||
return &vif, nil
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
func (self *VM) SetIsATemplate(is_a_template bool) (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VM.set_is_a_template", self.Ref, is_a_template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SR associated functions
|
||||
|
||||
func (self *SR) CreateVdi(name_label string, size int64) (vdi *VDI, err error) {
|
||||
vdi = new(VDI)
|
||||
|
||||
vdi_rec := make(xmlrpc.Struct)
|
||||
vdi_rec["name_label"] = name_label
|
||||
vdi_rec["SR"] = self.Ref
|
||||
vdi_rec["virtual_size"] = fmt.Sprintf("%d", size)
|
||||
vdi_rec["type"] = "user"
|
||||
vdi_rec["sharable"] = false
|
||||
vdi_rec["read_only"] = false
|
||||
|
||||
oc := make(xmlrpc.Struct)
|
||||
oc["temp"] = "temp"
|
||||
vdi_rec["other_config"] = oc
|
||||
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VDI.create", vdi_rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vdi.Ref = result.Value.(string)
|
||||
vdi.Client = self.Client
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Network associated functions
|
||||
|
||||
func (self *Network) GetAssignedIPs() (ip_map map[string]string, err error) {
|
||||
ip_map = make(map[string]string, 0)
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "network.get_assigned_ips", self.Ref)
|
||||
if err != nil {
|
||||
return ip_map, err
|
||||
}
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
ip_map[k] = v.(string)
|
||||
}
|
||||
return ip_map, nil
|
||||
}
|
||||
|
||||
// PIF associated functions
|
||||
|
||||
func (self *PIF) GetRecord() (record map[string]interface{}, err error) {
|
||||
record = make(map[string]interface{})
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "PIF.get_record", self.Ref)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
record[k] = v
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// Pool associated functions
|
||||
|
||||
func (self *Pool) GetRecord() (record map[string]interface{}, err error) {
|
||||
record = make(map[string]interface{})
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "pool.get_record", self.Ref)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
record[k] = v
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// VBD associated functions
|
||||
func (self *VBD) GetRecord() (record map[string]interface{}, err error) {
|
||||
record = make(map[string]interface{})
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.get_record", self.Ref)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
for k, v := range result.Value.(xmlrpc.Struct) {
|
||||
record[k] = v
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (self *VBD) GetVDI() (vdi *VDI, err error) {
|
||||
vbd_rec, err := self.GetRecord()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vdi = new(VDI)
|
||||
vdi.Ref = vbd_rec["VDI"].(string)
|
||||
vdi.Client = self.Client
|
||||
|
||||
return vdi, nil
|
||||
}
|
||||
|
||||
func (self *VBD) Eject() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.eject", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *VBD) Unplug() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.unplug", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *VBD) Destroy() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VBD.destroy", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VIF associated functions
|
||||
|
||||
func (self *VIF) Destroy() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VIF.destroy", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VDI associated functions
|
||||
|
||||
func (self *VDI) GetUuid() (vdi_uuid string, err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VDI.get_uuid", self.Ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
vdi_uuid = result.Value.(string)
|
||||
return vdi_uuid, nil
|
||||
}
|
||||
|
||||
func (self *VDI) GetVBDs() (vbds []VBD, err error) {
|
||||
vbds = make([]VBD, 0)
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VDI.get_VBDs", self.Ref)
|
||||
if err != nil {
|
||||
return vbds, err
|
||||
}
|
||||
for _, elem := range result.Value.([]interface{}) {
|
||||
vbd := VBD{}
|
||||
vbd.Ref = elem.(string)
|
||||
vbd.Client = self.Client
|
||||
vbds = append(vbds, vbd)
|
||||
}
|
||||
|
||||
return vbds, nil
|
||||
}
|
||||
|
||||
func (self *VDI) Destroy() (err error) {
|
||||
result := APIResult{}
|
||||
err = self.Client.APICall(&result, "VDI.destroy", self.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Expose a VDI using the Transfer VM
|
||||
// (Legacy VHD export)
|
||||
|
||||
type TransferRecord struct {
|
||||
UrlFull string `xml:"url_full,attr"`
|
||||
}
|
||||
|
||||
func Expose(c *Connection, vdiRef xenapi.VDIRef, format string) (url string, err error) {
|
||||
|
||||
hosts, err := c.client.Host.GetAll(c.session)
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error()))
|
||||
return "", err
|
||||
}
|
||||
host := hosts[0]
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Failed to get VDI uuid for %s: %s", vdiRef, err.Error()))
|
||||
return "", err
|
||||
}
|
||||
|
||||
args := make(map[string]string)
|
||||
args["transfer_mode"] = "http"
|
||||
args["vdi_uuid"] = string(vdiRef)
|
||||
args["expose_vhd"] = "true"
|
||||
args["network_uuid"] = "management"
|
||||
args["timeout_minutes"] = "5"
|
||||
|
||||
handle, err := c.client.Host.CallPlugin(c.session, host, "transfer", "expose", args)
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Error whilst exposing VDI %s: %s", vdiRef, err.Error()))
|
||||
return "", err
|
||||
}
|
||||
|
||||
args = make(map[string]string)
|
||||
args["record_handle"] = handle
|
||||
record_xml, err := c.client.Host.CallPlugin(c.session, host, "transfer", "get_record", args)
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Unable to retrieve transfer record for VDI %s: %s", vdiRef, err.Error()))
|
||||
return "", err
|
||||
}
|
||||
|
||||
var record TransferRecord
|
||||
xml.Unmarshal([]byte(record_xml), &record)
|
||||
|
||||
if record.UrlFull == "" {
|
||||
return "", errors.New(fmt.Sprintf("Error: did not parse XML properly: '%s'", record_xml))
|
||||
}
|
||||
|
||||
// Handles either raw or VHD formats
|
||||
|
||||
switch format {
|
||||
case "vhd":
|
||||
url = fmt.Sprintf("%s.vhd", record.UrlFull)
|
||||
|
||||
case "raw":
|
||||
url = record.UrlFull
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Unexpose(c *Connection, vdiRef xenapi.VDIRef) (err error) {
|
||||
|
||||
disk_uuid, err := c.client.VDI.GetUUID(c.session, vdiRef)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hosts, err := c.client.Host.GetAll(c.session)
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
host := hosts[0]
|
||||
|
||||
args := make(map[string]string)
|
||||
args["vdi_uuid"] = disk_uuid
|
||||
|
||||
result, err := c.client.Host.CallPlugin(c.session, host, "transfer", "unexpose", args)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println(fmt.Sprintf("Unexpose result: %s", result))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Task associated functions
|
||||
|
||||
// func (self *Task) GetResult() (object *XenAPIObject, err error) {
|
||||
// result := APIResult{}
|
||||
// err = self.Client.APICall(&result, "task.get_result", self.Ref)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// switch ref := result.Value.(type) {
|
||||
// case string:
|
||||
// // @fixme: xapi currently sends us an xmlrpc-encoded string via xmlrpc.
|
||||
// // This seems to be a bug in xapi. Remove this workaround when it's fixed
|
||||
// re := regexp.MustCompile("^<value><array><data><value>([^<]*)</value>.*</data></array></value>$")
|
||||
// match := re.FindStringSubmatch(ref)
|
||||
// if match == nil {
|
||||
// object = nil
|
||||
// } else {
|
||||
// object = &XenAPIObject{
|
||||
// Ref: match[1],
|
||||
// Client: self.Client,
|
||||
// }
|
||||
// }
|
||||
// case nil:
|
||||
// object = nil
|
||||
// default:
|
||||
// err = fmt.Errorf("task.get_result: unknown value type %T (expected string or nil)", ref)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// Client Initiator
|
||||
type Connection struct {
|
||||
client *xenapi.Client
|
||||
session xenapi.SessionRef
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (c Connection) GetSession() string {
|
||||
return string(c.session)
|
||||
}
|
||||
|
||||
func NewXenAPIClient(host, username, password string) (*Connection, error) {
|
||||
client, err := xenapi.NewClient("https://"+host, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := client.Session.LoginWithPassword(username, password, "1.0", "packer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Connection{client, session, host, username, password}, nil
|
||||
}
|
||||
|
||||
func (c *Connection) GetClient() *xenapi.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func (c *Connection) GetSessionRef() xenapi.SessionRef {
|
||||
return c.session
|
||||
}
|
289
builder/xenserver/common/common_config.go
Normal file
289
builder/xenserver/common/common_config.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
xenapi "github.com/terra-farm/go-xen-api-client"
|
||||
)
|
||||
|
||||
type CommonConfig struct {
|
||||
Username string `mapstructure:"remote_username"`
|
||||
Password string `mapstructure:"remote_password"`
|
||||
HostIp string `mapstructure:"remote_host"`
|
||||
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
VMDescription string `mapstructure:"vm_description"`
|
||||
SrName string `mapstructure:"sr_name"`
|
||||
SrISOName string `mapstructure:"sr_iso_name"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||
NetworkNames []string `mapstructure:"network_names"`
|
||||
ExportNetworkNames []string `mapstructure:"export_network_names"`
|
||||
|
||||
HostPortMin uint `mapstructure:"host_port_min"`
|
||||
HostPortMax uint `mapstructure:"host_port_max"`
|
||||
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
BootWait time.Duration
|
||||
|
||||
ToolsIsoName string `mapstructure:"tools_iso_name"`
|
||||
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
|
||||
// SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
// SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHConfig `mapstructure:",squash"`
|
||||
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
SSHWaitTimeout time.Duration
|
||||
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
Format string `mapstructure:"format"`
|
||||
KeepVM string `mapstructure:"keep_vm"`
|
||||
IPGetter string `mapstructure:"ip_getter"`
|
||||
}
|
||||
|
||||
func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error {
|
||||
var err error
|
||||
var errs []error
|
||||
|
||||
// Set default values
|
||||
|
||||
if c.HostPortMin == 0 {
|
||||
c.HostPortMin = 5900
|
||||
}
|
||||
|
||||
if c.HostPortMax == 0 {
|
||||
c.HostPortMax = 6000
|
||||
}
|
||||
|
||||
if c.RawBootWait == "" {
|
||||
c.RawBootWait = "5s"
|
||||
}
|
||||
|
||||
if c.ToolsIsoName == "" {
|
||||
c.ToolsIsoName = "xs-tools.iso"
|
||||
}
|
||||
|
||||
if c.HTTPPortMin == 0 {
|
||||
c.HTTPPortMin = 8000
|
||||
}
|
||||
|
||||
if c.HTTPPortMax == 0 {
|
||||
c.HTTPPortMax = 9000
|
||||
}
|
||||
|
||||
if c.RawSSHWaitTimeout == "" {
|
||||
c.RawSSHWaitTimeout = "200m"
|
||||
}
|
||||
|
||||
if c.FloppyFiles == nil {
|
||||
c.FloppyFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
/*
|
||||
if c.SSHHostPortMin == 0 {
|
||||
c.SSHHostPortMin = 2222
|
||||
}
|
||||
|
||||
if c.SSHHostPortMax == 0 {
|
||||
c.SSHHostPortMax = 4444
|
||||
}
|
||||
*/
|
||||
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHWaitTimeout == "" {
|
||||
c.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
if c.OutputDir == "" {
|
||||
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
|
||||
}
|
||||
|
||||
if c.VMName == "" {
|
||||
c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", pc.PackerBuildName)
|
||||
}
|
||||
|
||||
if c.Format == "" {
|
||||
c.Format = "xva"
|
||||
}
|
||||
|
||||
if c.KeepVM == "" {
|
||||
c.KeepVM = "never"
|
||||
}
|
||||
|
||||
if c.IPGetter == "" {
|
||||
c.IPGetter = "auto"
|
||||
}
|
||||
|
||||
// Validation
|
||||
|
||||
if c.Username == "" {
|
||||
errs = append(errs, errors.New("remote_username must be specified."))
|
||||
}
|
||||
|
||||
if c.Password == "" {
|
||||
errs = append(errs, errors.New("remote_password must be specified."))
|
||||
}
|
||||
|
||||
if c.HostIp == "" {
|
||||
errs = append(errs, errors.New("remote_host must be specified."))
|
||||
}
|
||||
|
||||
if c.HostPortMin > c.HostPortMax {
|
||||
errs = append(errs, errors.New("the host min port must be less than the max"))
|
||||
}
|
||||
|
||||
if c.HTTPPortMin > c.HTTPPortMax {
|
||||
errs = append(errs, errors.New("the HTTP min port must be less than the max"))
|
||||
}
|
||||
|
||||
c.BootWait, err = time.ParseDuration(c.RawBootWait)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed to parse boot_wait: %s", err))
|
||||
}
|
||||
|
||||
if c.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(c.SSHKeyPath); err != nil {
|
||||
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
} else if _, err := FileSigner(c.SSHKeyPath); err != nil {
|
||||
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if c.SSHHostPortMin > c.SSHHostPortMax {
|
||||
errs = append(errs,
|
||||
errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
||||
}
|
||||
*/
|
||||
|
||||
if c.SSHUser == "" {
|
||||
errs = append(errs, errors.New("An ssh_username must be specified."))
|
||||
}
|
||||
|
||||
c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed to parse ssh_wait_timeout: %s", err))
|
||||
}
|
||||
|
||||
switch c.Format {
|
||||
case "xva", "xva_compressed", "vdi_raw", "vdi_vhd", "none":
|
||||
default:
|
||||
errs = append(errs, errors.New("format must be one of 'xva', 'vdi_raw', 'vdi_vhd', 'none'"))
|
||||
}
|
||||
|
||||
switch c.KeepVM {
|
||||
case "always", "never", "on_success":
|
||||
default:
|
||||
errs = append(errs, errors.New("keep_vm must be one of 'always', 'never', 'on_success'"))
|
||||
}
|
||||
|
||||
switch c.IPGetter {
|
||||
case "auto", "tools", "http":
|
||||
default:
|
||||
errs = append(errs, errors.New("ip_getter must be one of 'auto', 'tools', 'http'"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// steps should check config.ShouldKeepVM first before cleaning up the VM
|
||||
func (c CommonConfig) ShouldKeepVM(state multistep.StateBag) bool {
|
||||
switch c.KeepVM {
|
||||
case "always":
|
||||
return true
|
||||
case "never":
|
||||
return false
|
||||
case "on_success":
|
||||
// only keep instance if build was successful
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
return !(cancelled || halted)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown keep_vm value '%s'", c.KeepVM))
|
||||
}
|
||||
}
|
||||
|
||||
func (config CommonConfig) GetSR(c *Connection) (xenapi.SRRef, error) {
|
||||
var srRef xenapi.SRRef
|
||||
if config.SrName == "" {
|
||||
hostRef, err := c.GetClient().Session.GetThisHost(c.session, c.session)
|
||||
|
||||
if err != nil {
|
||||
return srRef, err
|
||||
}
|
||||
|
||||
pools, err := c.GetClient().Pool.GetAllRecords(c.session)
|
||||
|
||||
if err != nil {
|
||||
return srRef, err
|
||||
}
|
||||
|
||||
for _, pool := range pools {
|
||||
if pool.Master == hostRef {
|
||||
return pool.DefaultSR, nil
|
||||
}
|
||||
}
|
||||
|
||||
return srRef, errors.New(fmt.Sprintf("failed to find default SR on host '%s'", hostRef))
|
||||
|
||||
} else {
|
||||
// Use the provided name label to find the SR to use
|
||||
srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrName)
|
||||
|
||||
if err != nil {
|
||||
return srRef, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(srs) == 0:
|
||||
return srRef, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName)
|
||||
case len(srs) > 1:
|
||||
return srRef, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName)
|
||||
}
|
||||
|
||||
return srs[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func (config CommonConfig) GetISOSR(c *Connection) (xenapi.SRRef, error) {
|
||||
var srRef xenapi.SRRef
|
||||
if config.SrISOName == "" {
|
||||
return srRef, errors.New("sr_iso_name must be specified in the packer configuration")
|
||||
|
||||
} else {
|
||||
// Use the provided name label to find the SR to use
|
||||
srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrName)
|
||||
|
||||
if err != nil {
|
||||
return srRef, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(srs) == 0:
|
||||
return srRef, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName)
|
||||
case len(srs) > 1:
|
||||
return srRef, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName)
|
||||
}
|
||||
|
||||
return srs[0], nil
|
||||
}
|
||||
}
|
43
builder/xenserver/common/config.go
Normal file
43
builder/xenserver/common/config.go
Normal file
@@ -0,0 +1,43 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
CommonConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
VCPUsMax uint `mapstructure:"vcpus_max"`
|
||||
VCPUsAtStartup uint `mapstructure:"vcpus_atstartup"`
|
||||
VMMemory uint `mapstructure:"vm_memory"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
CloneTemplate string `mapstructure:"clone_template"`
|
||||
VMOtherConfig map[string]string `mapstructure:"vm_other_config"`
|
||||
|
||||
ISOChecksum string `mapstructure:"iso_checksum"`
|
||||
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
||||
ISOUrls []string `mapstructure:"iso_urls"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
ISOName string `mapstructure:"iso_name"`
|
||||
|
||||
PlatformArgs map[string]string `mapstructure:"platform_args"`
|
||||
|
||||
RawInstallTimeout string `mapstructure:"install_timeout"`
|
||||
InstallTimeout time.Duration ``
|
||||
SourcePath string `mapstructure:"source_path"`
|
||||
|
||||
Firmware string `mapstructure:"firmware"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c Config) GetInterpContext() *interpolate.Context {
|
||||
return &c.ctx
|
||||
}
|
225
builder/xenserver/common/config.hcl2spec.go
Normal file
225
builder/xenserver/common/config.hcl2spec.go
Normal file
@@ -0,0 +1,225 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||
Username *string `mapstructure:"remote_username" cty:"remote_username" hcl:"remote_username"`
|
||||
Password *string `mapstructure:"remote_password" cty:"remote_password" hcl:"remote_password"`
|
||||
HostIp *string `mapstructure:"remote_host" cty:"remote_host" hcl:"remote_host"`
|
||||
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
||||
VMDescription *string `mapstructure:"vm_description" cty:"vm_description" hcl:"vm_description"`
|
||||
SrName *string `mapstructure:"sr_name" cty:"sr_name" hcl:"sr_name"`
|
||||
SrISOName *string `mapstructure:"sr_iso_name" cty:"sr_iso_name" hcl:"sr_iso_name"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
|
||||
NetworkNames []string `mapstructure:"network_names" cty:"network_names" hcl:"network_names"`
|
||||
ExportNetworkNames []string `mapstructure:"export_network_names" cty:"export_network_names" hcl:"export_network_names"`
|
||||
HostPortMin *uint `mapstructure:"host_port_min" cty:"host_port_min" hcl:"host_port_min"`
|
||||
HostPortMax *uint `mapstructure:"host_port_max" cty:"host_port_max" hcl:"host_port_max"`
|
||||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
|
||||
ShutdownCommand *string `mapstructure:"shutdown_command" cty:"shutdown_command" hcl:"shutdown_command"`
|
||||
RawBootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
|
||||
ToolsIsoName *string `mapstructure:"tools_iso_name" cty:"tools_iso_name" hcl:"tools_iso_name"`
|
||||
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
|
||||
HTTPPortMin *uint `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
|
||||
HTTPPortMax *uint `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
|
||||
SSHKeyPath *string `mapstructure:"ssh_key_path" cty:"ssh_key_path" hcl:"ssh_key_path"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
|
||||
SSHPort *uint `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
|
||||
SSHUser *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
|
||||
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
|
||||
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
|
||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
|
||||
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
|
||||
SSHHostPortMin *uint `mapstructure:"ssh_host_port_min" cty:"ssh_host_port_min" hcl:"ssh_host_port_min"`
|
||||
SSHHostPortMax *uint `mapstructure:"ssh_host_port_max" cty:"ssh_host_port_max" hcl:"ssh_host_port_max"`
|
||||
SSHSkipNatMapping *bool `mapstructure:"ssh_skip_nat_mapping" cty:"ssh_skip_nat_mapping" hcl:"ssh_skip_nat_mapping"`
|
||||
OutputDir *string `mapstructure:"output_directory" cty:"output_directory" hcl:"output_directory"`
|
||||
Format *string `mapstructure:"format" cty:"format" hcl:"format"`
|
||||
KeepVM *string `mapstructure:"keep_vm" cty:"keep_vm" hcl:"keep_vm"`
|
||||
IPGetter *string `mapstructure:"ip_getter" cty:"ip_getter" hcl:"ip_getter"`
|
||||
VCPUsMax *uint `mapstructure:"vcpus_max" cty:"vcpus_max" hcl:"vcpus_max"`
|
||||
VCPUsAtStartup *uint `mapstructure:"vcpus_atstartup" cty:"vcpus_atstartup" hcl:"vcpus_atstartup"`
|
||||
VMMemory *uint `mapstructure:"vm_memory" cty:"vm_memory" hcl:"vm_memory"`
|
||||
DiskSize *uint `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
||||
CloneTemplate *string `mapstructure:"clone_template" cty:"clone_template" hcl:"clone_template"`
|
||||
VMOtherConfig map[string]string `mapstructure:"vm_other_config" cty:"vm_other_config" hcl:"vm_other_config"`
|
||||
ISOChecksum *string `mapstructure:"iso_checksum" cty:"iso_checksum" hcl:"iso_checksum"`
|
||||
ISOChecksumType *string `mapstructure:"iso_checksum_type" cty:"iso_checksum_type" hcl:"iso_checksum_type"`
|
||||
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
|
||||
ISOUrl *string `mapstructure:"iso_url" cty:"iso_url" hcl:"iso_url"`
|
||||
ISOName *string `mapstructure:"iso_name" cty:"iso_name" hcl:"iso_name"`
|
||||
PlatformArgs map[string]string `mapstructure:"platform_args" cty:"platform_args" hcl:"platform_args"`
|
||||
RawInstallTimeout *string `mapstructure:"install_timeout" cty:"install_timeout" hcl:"install_timeout"`
|
||||
SourcePath *string `mapstructure:"source_path" cty:"source_path" hcl:"source_path"`
|
||||
Firmware *string `mapstructure:"firmware" cty:"firmware" hcl:"firmware"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"remote_username": &hcldec.AttrSpec{Name: "remote_username", Type: cty.String, Required: false},
|
||||
"remote_password": &hcldec.AttrSpec{Name: "remote_password", Type: cty.String, Required: false},
|
||||
"remote_host": &hcldec.AttrSpec{Name: "remote_host", Type: cty.String, Required: false},
|
||||
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
|
||||
"vm_description": &hcldec.AttrSpec{Name: "vm_description", Type: cty.String, Required: false},
|
||||
"sr_name": &hcldec.AttrSpec{Name: "sr_name", Type: cty.String, Required: false},
|
||||
"sr_iso_name": &hcldec.AttrSpec{Name: "sr_iso_name", Type: cty.String, Required: false},
|
||||
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
|
||||
"network_names": &hcldec.AttrSpec{Name: "network_names", Type: cty.List(cty.String), Required: false},
|
||||
"export_network_names": &hcldec.AttrSpec{Name: "export_network_names", Type: cty.List(cty.String), Required: false},
|
||||
"host_port_min": &hcldec.AttrSpec{Name: "host_port_min", Type: cty.Number, Required: false},
|
||||
"host_port_max": &hcldec.AttrSpec{Name: "host_port_max", Type: cty.Number, Required: false},
|
||||
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||
"shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
|
||||
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
|
||||
"tools_iso_name": &hcldec.AttrSpec{Name: "tools_iso_name", Type: cty.String, Required: false},
|
||||
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
|
||||
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
|
||||
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
|
||||
"ssh_key_path": &hcldec.AttrSpec{Name: "ssh_key_path", Type: cty.String, Required: false},
|
||||
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
|
||||
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
|
||||
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
|
||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
|
||||
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
|
||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
|
||||
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
|
||||
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
|
||||
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
|
||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
|
||||
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
|
||||
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
|
||||
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
|
||||
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
|
||||
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
|
||||
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
|
||||
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
|
||||
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
|
||||
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
|
||||
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
|
||||
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
|
||||
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
|
||||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"ssh_host_port_min": &hcldec.AttrSpec{Name: "ssh_host_port_min", Type: cty.Number, Required: false},
|
||||
"ssh_host_port_max": &hcldec.AttrSpec{Name: "ssh_host_port_max", Type: cty.Number, Required: false},
|
||||
"ssh_skip_nat_mapping": &hcldec.AttrSpec{Name: "ssh_skip_nat_mapping", Type: cty.Bool, Required: false},
|
||||
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
||||
"format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false},
|
||||
"keep_vm": &hcldec.AttrSpec{Name: "keep_vm", Type: cty.String, Required: false},
|
||||
"ip_getter": &hcldec.AttrSpec{Name: "ip_getter", Type: cty.String, Required: false},
|
||||
"vcpus_max": &hcldec.AttrSpec{Name: "vcpus_max", Type: cty.Number, Required: false},
|
||||
"vcpus_atstartup": &hcldec.AttrSpec{Name: "vcpus_atstartup", Type: cty.Number, Required: false},
|
||||
"vm_memory": &hcldec.AttrSpec{Name: "vm_memory", Type: cty.Number, Required: false},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
"clone_template": &hcldec.AttrSpec{Name: "clone_template", Type: cty.String, Required: false},
|
||||
"vm_other_config": &hcldec.AttrSpec{Name: "vm_other_config", Type: cty.Map(cty.String), Required: false},
|
||||
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
|
||||
"iso_checksum_type": &hcldec.AttrSpec{Name: "iso_checksum_type", Type: cty.String, Required: false},
|
||||
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
|
||||
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
|
||||
"iso_name": &hcldec.AttrSpec{Name: "iso_name", Type: cty.String, Required: false},
|
||||
"platform_args": &hcldec.AttrSpec{Name: "platform_args", Type: cty.Map(cty.String), Required: false},
|
||||
"install_timeout": &hcldec.AttrSpec{Name: "install_timeout", Type: cty.String, Required: false},
|
||||
"source_path": &hcldec.AttrSpec{Name: "source_path", Type: cty.String, Required: false},
|
||||
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
25
builder/xenserver/common/find_port.go
Normal file
25
builder/xenserver/common/find_port.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
// FindPort finds and starts listening on a port in the range [portMin, portMax]
|
||||
// returns the listener and the port number on success, or nil, 0 on failure
|
||||
func FindPort(portMin uint, portMax uint) (net.Listener, uint) {
|
||||
log.Printf("Looking for an available port between %d and %d", portMin, portMax)
|
||||
|
||||
for port := portMin; port <= portMax; port++ {
|
||||
log.Printf("Trying port: %d", port)
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err == nil {
|
||||
return l, port
|
||||
} else {
|
||||
log.Printf("Port %d unavailable: %s", port, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
127
builder/xenserver/common/http_upload.go
Normal file
127
builder/xenserver/common/http_upload.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
xsclient "github.com/terra-farm/go-xen-api-client"
|
||||
)
|
||||
|
||||
func appendQuery(urlstring, k, v string) (string, error) {
|
||||
u, err := url.Parse(urlstring)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to parse URL '%s': %s", urlstring, err.Error())
|
||||
}
|
||||
m := u.Query()
|
||||
m.Add(k, v)
|
||||
u.RawQuery = m.Encode()
|
||||
return u.String(), err
|
||||
}
|
||||
|
||||
func HTTPUpload(import_url string, fh *os.File, state multistep.StateBag) (result string, err error) {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
task, err := c.client.Task.Create(c.session, "packer-task", "Packer task")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to create task: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer c.client.Task.Destroy(c.session, task)
|
||||
|
||||
import_task_url, err := appendQuery(import_url, "task_id", string(task))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get file length
|
||||
fstat, err := fh.Stat()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to stat '%s': %s", fh.Name(), err.Error())
|
||||
return
|
||||
}
|
||||
fileLength := fstat.Size()
|
||||
|
||||
// Define a new transport which allows self-signed certs
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
// Create a client
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
// Create request and download file
|
||||
request, err := http.NewRequest("PUT", import_task_url, fh)
|
||||
request.ContentLength = fileLength
|
||||
|
||||
ui.Say(fmt.Sprintf("PUT '%s'", import_task_url))
|
||||
|
||||
resp, err := httpClient.Do(request) // Do closes fh for us, according to docs
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
err = fmt.Errorf("PUT request got non-200 status code: %s", resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
logIteration := 0
|
||||
err = InterruptibleWait{
|
||||
Predicate: func() (bool, error) {
|
||||
status, err := c.client.Task.GetStatus(c.session, task)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to get task status: %s", err.Error())
|
||||
}
|
||||
switch status {
|
||||
case xsclient.TaskStatusTypePending:
|
||||
progress, err := c.client.Task.GetProgress(c.session, task)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to get progress: %s", err.Error())
|
||||
}
|
||||
logIteration = logIteration + 1
|
||||
if logIteration%5 == 0 {
|
||||
log.Printf("Upload %.0f%% complete", progress*100)
|
||||
}
|
||||
return false, nil
|
||||
case xsclient.TaskStatusTypeSuccess:
|
||||
return true, nil
|
||||
case xsclient.TaskStatusTypeFailure:
|
||||
errorInfo, err := c.client.Task.GetErrorInfo(c.session, task)
|
||||
if err != nil {
|
||||
errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())}
|
||||
}
|
||||
return false, fmt.Errorf("Task failed: %s", errorInfo)
|
||||
case xsclient.TaskStatusTypeCancelling, xsclient.TaskStatusTypeCancelled:
|
||||
return false, fmt.Errorf("Task cancelled")
|
||||
default:
|
||||
return false, fmt.Errorf("Unknown task status %v", status)
|
||||
}
|
||||
},
|
||||
PredicateInterval: 1 * time.Second,
|
||||
Timeout: 24 * time.Hour,
|
||||
}.Wait(state)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error uploading: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err = c.client.Task.GetResult(c.session, task)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error getting result: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Upload complete")
|
||||
return
|
||||
}
|
86
builder/xenserver/common/interruptible_wait.go
Normal file
86
builder/xenserver/common/interruptible_wait.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InterruptibleWait struct {
|
||||
Timeout time.Duration
|
||||
|
||||
// optional:
|
||||
Predicate func() (result bool, err error)
|
||||
PredicateInterval time.Duration
|
||||
}
|
||||
|
||||
type TimeoutError struct{}
|
||||
|
||||
func (err TimeoutError) Error() string {
|
||||
return "Timed out"
|
||||
}
|
||||
|
||||
type InterruptedError struct{}
|
||||
|
||||
func (err InterruptedError) Error() string {
|
||||
return "Interrupted"
|
||||
}
|
||||
|
||||
type PredicateResult struct {
|
||||
complete bool
|
||||
err error
|
||||
}
|
||||
|
||||
/* Wait waits for up to Timeout duration, checking an optional Predicate every PredicateInterval duration.
|
||||
The first run of Predicate is immediately after Wait is called.
|
||||
If the command is interrupted by the user, then an InterruptedError is returned.
|
||||
If Predicate is not nil, a timeout leads to TimeoutError being returned, and a successful Predicate run leads to nil being returned.
|
||||
If Predicate is nil, a timeout is not an error, and nil is returned.
|
||||
*/
|
||||
func (wait InterruptibleWait) Wait(state multistep.StateBag) error {
|
||||
predicateResult := make(chan PredicateResult, 1)
|
||||
stopWaiting := make(chan struct{})
|
||||
defer close(stopWaiting)
|
||||
|
||||
if wait.Predicate != nil {
|
||||
go func() {
|
||||
for {
|
||||
if complete, err := wait.Predicate(); err != nil || complete {
|
||||
predicateResult <- PredicateResult{complete, err}
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(wait.PredicateInterval):
|
||||
continue
|
||||
case <-stopWaiting:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
timeout := time.After(wait.Timeout)
|
||||
for {
|
||||
// wait for either install to complete/error,
|
||||
// an interrupt to come through, or a timeout to occur
|
||||
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return InterruptedError{}
|
||||
}
|
||||
|
||||
select {
|
||||
case result := <-predicateResult:
|
||||
return result.err
|
||||
|
||||
case <-time.After(1 * time.Second):
|
||||
continue
|
||||
|
||||
case <-timeout:
|
||||
if wait.Predicate != nil {
|
||||
return TimeoutError{}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
217
builder/xenserver/common/ssh.go
Normal file
217
builder/xenserver/common/ssh.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func SSHAddress(state multistep.StateBag) (string, error) {
|
||||
sshIP := state.Get("ssh_address").(string)
|
||||
sshHostPort := 22
|
||||
return fmt.Sprintf("%s:%d", sshIP, sshHostPort), nil
|
||||
}
|
||||
|
||||
func SSHLocalAddress(state multistep.StateBag) (string, error) {
|
||||
sshLocalPort, ok := state.Get("local_ssh_port").(uint)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("SSH port forwarding hasn't been set up yet")
|
||||
}
|
||||
conn_str := fmt.Sprintf("%s:%d", "127.0.0.1", sshLocalPort)
|
||||
return conn_str, nil
|
||||
}
|
||||
|
||||
func SSHPort(state multistep.StateBag) (int, error) {
|
||||
sshHostPort := state.Get("local_ssh_port").(uint)
|
||||
return int(sshHostPort), nil
|
||||
}
|
||||
|
||||
func CommHost(state multistep.StateBag) (string, error) {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
|
||||
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
auth := []gossh.AuthMethod{
|
||||
gossh.Password(config.SSHPassword),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
signer, err := FileSigner(config.SSHKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth = append(auth, gossh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: auth,
|
||||
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func doExecuteSSHCmd(cmd, target string, config *gossh.ClientConfig) (stdout string, err error) {
|
||||
client, err := gossh.Dial("tcp", target, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//Create session
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer session.Close()
|
||||
|
||||
var b bytes.Buffer
|
||||
session.Stdout = &b
|
||||
if err := session.Run(cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.Trim(b.String(), "\n"), nil
|
||||
}
|
||||
|
||||
func ExecuteHostSSHCmd(state multistep.StateBag, cmd string) (stdout string, err error) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
sshAddress, _ := SSHAddress(state)
|
||||
// Setup connection config
|
||||
sshConfig := &gossh.ClientConfig{
|
||||
User: config.Username,
|
||||
Auth: []gossh.AuthMethod{
|
||||
gossh.Password(config.Password),
|
||||
},
|
||||
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
return doExecuteSSHCmd(cmd, sshAddress, sshConfig)
|
||||
}
|
||||
|
||||
func ExecuteGuestSSHCmd(state multistep.StateBag, cmd string) (stdout string, err error) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
localAddress, err := SSHLocalAddress(state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sshConfig, err := SSHConfigFunc(config.SSHConfig)(state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return doExecuteSSHCmd(cmd, localAddress, sshConfig)
|
||||
}
|
||||
|
||||
func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_dest string, remote_port uint) error {
|
||||
defer local_conn.Close()
|
||||
|
||||
ssh_client_conn, err := gossh.Dial("tcp", server+":22", config)
|
||||
if err != nil {
|
||||
log.Printf("local ssh.Dial error: %s", err)
|
||||
return err
|
||||
}
|
||||
defer ssh_client_conn.Close()
|
||||
|
||||
remote_loc := fmt.Sprintf("%s:%d", remote_dest, remote_port)
|
||||
ssh_conn, err := ssh_client_conn.Dial("tcp", remote_loc)
|
||||
if err != nil {
|
||||
log.Printf("ssh.Dial error: %s", err)
|
||||
return err
|
||||
}
|
||||
defer ssh_conn.Close()
|
||||
|
||||
txDone := make(chan struct{})
|
||||
rxDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
_, err = io.Copy(ssh_conn, local_conn)
|
||||
if err != nil {
|
||||
log.Printf("io.copy failed: %v", err)
|
||||
}
|
||||
close(txDone)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err = io.Copy(local_conn, ssh_conn)
|
||||
if err != nil {
|
||||
log.Printf("io.copy failed: %v", err)
|
||||
}
|
||||
close(rxDone)
|
||||
}()
|
||||
|
||||
<-txDone
|
||||
<-rxDone
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ssh_port_forward(local_listener net.Listener, remote_port int, remote_dest, host, username, password string) error {
|
||||
|
||||
config := &gossh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []gossh.AuthMethod{
|
||||
gossh.Password(password),
|
||||
},
|
||||
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
for {
|
||||
local_connection, err := local_listener.Accept()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Local accept failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Forward to a remote port
|
||||
go forward(local_connection, config, host, remote_dest, uint(remote_port))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileSigner returns an gossh.Signer for a key file.
|
||||
func FileSigner(path string) (gossh.Signer, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
keyBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We parse the private key on our own first so that we can
|
||||
// show a nicer error if the private key has a password.
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to read key '%s': no key found", path)
|
||||
}
|
||||
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to read key '%s': password protected keys are\n"+
|
||||
"not supported. Please decrypt the key prior to use.", path)
|
||||
}
|
||||
|
||||
signer, err := gossh.ParsePrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
48
builder/xenserver/common/ssh_config.go
Normal file
48
builder/xenserver/common/ssh_config.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
)
|
||||
|
||||
type SSHConfig struct {
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHSkipNatMapping bool `mapstructure:"ssh_skip_nat_mapping"`
|
||||
|
||||
// These are deprecated, but we keep them around for BC
|
||||
// TODO(@mitchellh): remove
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"`
|
||||
}
|
||||
|
||||
func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
if c.SSHHostPortMin == 0 {
|
||||
c.SSHHostPortMin = 2222
|
||||
}
|
||||
|
||||
if c.SSHHostPortMax == 0 {
|
||||
c.SSHHostPortMax = 4444
|
||||
}
|
||||
|
||||
// TODO: backwards compatibility, write fixer instead
|
||||
if c.SSHKeyPath != "" {
|
||||
c.Comm.SSHPrivateKeyFile = c.SSHKeyPath
|
||||
}
|
||||
if c.SSHWaitTimeout != 0 {
|
||||
c.Comm.SSHTimeout = c.SSHWaitTimeout
|
||||
}
|
||||
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.SSHHostPortMin > c.SSHHostPortMax {
|
||||
errs = append(errs,
|
||||
errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
84
builder/xenserver/common/step_attach_vdi.go
Normal file
84
builder/xenserver/common/step_attach_vdi.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
xsclient "github.com/terra-farm/go-xen-api-client"
|
||||
)
|
||||
|
||||
type StepAttachVdi struct {
|
||||
VdiUuidKey string
|
||||
VdiType xsclient.VbdType
|
||||
|
||||
vdi xsclient.VDIRef
|
||||
}
|
||||
|
||||
func (self *StepAttachVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
log.Printf("Running attach vdi for key %s\n", self.VdiUuidKey)
|
||||
var vdiUuid string
|
||||
if vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey); ok {
|
||||
vdiUuid = vdiUuidRaw.(string)
|
||||
} else {
|
||||
log.Printf("Skipping attach of '%s'", self.VdiUuidKey)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
var err error
|
||||
self.vdi, err = c.client.VDI.GetByUUID(c.session, vdiUuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = ConnectVdi(c, instance, self.vdi, self.VdiType)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error attaching VDI '%s': '%s'", vdiUuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Attached VDI '%s'", vdiUuid)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepAttachVdi) Cleanup(state multistep.StateBag) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
c := state.Get("client").(*Connection)
|
||||
if config.ShouldKeepVM(state) {
|
||||
return
|
||||
}
|
||||
|
||||
if self.vdi == "" {
|
||||
return
|
||||
}
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
vmRef, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
log.Printf("Unable to get VM from UUID '%s': %s", uuid, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
vdiUuid := state.Get(self.VdiUuidKey).(string)
|
||||
|
||||
err = DisconnectVdi(c, vmRef, self.vdi)
|
||||
if err != nil {
|
||||
log.Printf("Unable to disconnect VDI '%s': %s", vdiUuid, err.Error())
|
||||
return
|
||||
}
|
||||
log.Printf("Detached VDI '%s'", vdiUuid)
|
||||
}
|
33
builder/xenserver/common/step_boot_wait.go
Normal file
33
builder/xenserver/common/step_boot_wait.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepBootWait struct{}
|
||||
|
||||
func (self *StepBootWait) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
c := state.Get("client").(*Connection)
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
instance, _ := c.client.VM.GetByUUID(c.session, state.Get("instance_uuid").(string))
|
||||
ui.Say("Unpausing VM " + state.Get("instance_uuid").(string))
|
||||
Unpause(c, instance)
|
||||
|
||||
if int64(config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait))
|
||||
err := InterruptibleWait{Timeout: config.BootWait}.Wait(state)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepBootWait) Cleanup(state multistep.StateBag) {}
|
53
builder/xenserver/common/step_detach_vdi.go
Normal file
53
builder/xenserver/common/step_detach_vdi.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepDetachVdi struct {
|
||||
VdiUuidKey string
|
||||
}
|
||||
|
||||
func (self *StepDetachVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
var vdiUuid string
|
||||
if vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey); ok {
|
||||
vdiUuid = vdiUuidRaw.(string)
|
||||
} else {
|
||||
log.Printf("Skipping detach of '%s'", self.VdiUuidKey)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
vdi, err := c.client.VDI.GetByUUID(c.session, vdiUuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = DisconnectVdi(c, instance, vdi)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to detach VDI '%s': %s", vdiUuid, err.Error()))
|
||||
//return multistep.ActionHalt
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
log.Printf("Detached VDI '%s'", vdiUuid)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepDetachVdi) Cleanup(state multistep.StateBag) {}
|
278
builder/xenserver/common/step_export.go
Normal file
278
builder/xenserver/common/step_export.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepExport struct{}
|
||||
|
||||
func downloadFile(url, filename string, ui packer.Ui) (err error) {
|
||||
|
||||
// Create the file
|
||||
fh, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
// Define a new transport which allows self-signed certs
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
// Create a client
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
// Create request and download file
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
var progress uint
|
||||
var total uint
|
||||
var percentage uint
|
||||
var marker_len uint
|
||||
|
||||
progress = uint(0)
|
||||
total = uint(resp.ContentLength)
|
||||
percentage = uint(0)
|
||||
marker_len = uint(5)
|
||||
|
||||
var buffer [4096]byte
|
||||
for {
|
||||
n, err := resp.Body.Read(buffer[:])
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
progress += uint(n)
|
||||
|
||||
if _, write_err := fh.Write(buffer[:n]); write_err != nil {
|
||||
return write_err
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
// Increment percentage in multiples of marker_len
|
||||
cur_percentage := ((progress * 100 / total) / marker_len) * marker_len
|
||||
if cur_percentage > percentage {
|
||||
percentage = cur_percentage
|
||||
ui.Message(fmt.Sprintf("Downloading... %d%%", percentage))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
instance_uuid := state.Get("instance_uuid").(string)
|
||||
suffix := ".vhd"
|
||||
extrauri := "&format=vhd"
|
||||
|
||||
instance, err := c.client.VM.GetByUUID(c.session, instance_uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get VM with UUID '%s': %s", instance_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(config.ExportNetworkNames) > 0 {
|
||||
vifs, err := c.client.VM.GetVIFs(c.session, instance)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error occured getting VIFs: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
for _, vif := range vifs {
|
||||
err := c.client.VIF.Destroy(c.session, vif)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Destroy vif fail: '%s': %s", vif, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
for i, networkNameLabel := range config.ExportNetworkNames {
|
||||
networks, err := c.client.Network.GetByNameLabel(c.session, networkNameLabel)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error occured getting Network by name-label: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(networks) == 0:
|
||||
ui.Error(fmt.Sprintf("Couldn't find a network with the specified name-label '%s'. Aborting.", networkNameLabel))
|
||||
return multistep.ActionHalt
|
||||
case len(networks) > 1:
|
||||
ui.Error(fmt.Sprintf("Found more than one network with the name '%s'. The name must be unique. Aborting.", networkNameLabel))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
//we need the VIF index string
|
||||
vifIndexString := fmt.Sprintf("%d", i)
|
||||
_, err = ConnectNetwork(c, networks[0], instance, vifIndexString)
|
||||
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Step: export artifact")
|
||||
|
||||
compress_option_xe := "compress=false"
|
||||
compress_option_url := ""
|
||||
|
||||
switch config.Format {
|
||||
case "none":
|
||||
ui.Say("Skipping export")
|
||||
return multistep.ActionContinue
|
||||
|
||||
case "xva_compressed":
|
||||
compress_option_xe = "compress=true"
|
||||
compress_option_url = "use_compression=true&"
|
||||
fallthrough
|
||||
case "xva":
|
||||
// export the VM
|
||||
|
||||
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.VMName)
|
||||
|
||||
use_xe := os.Getenv("USE_XE") == "1"
|
||||
if xe, e := exec.LookPath("xe"); e == nil && use_xe {
|
||||
cmd := exec.Command(
|
||||
xe,
|
||||
"-s", c.Host,
|
||||
"-p", "443",
|
||||
"-u", c.Username,
|
||||
"-pw", c.Password,
|
||||
"vm-export",
|
||||
"vm="+instance_uuid,
|
||||
compress_option_xe,
|
||||
"filename="+export_filename,
|
||||
)
|
||||
|
||||
ui.Say(fmt.Sprintf("Getting XVA %+v %+v", cmd.Path, cmd.Args))
|
||||
|
||||
err = cmd.Run()
|
||||
} else {
|
||||
export_url := fmt.Sprintf("https://%s/export?%suuid=%s&session_id=%s",
|
||||
c.Host,
|
||||
compress_option_url,
|
||||
instance_uuid,
|
||||
c.GetSession(),
|
||||
)
|
||||
|
||||
ui.Say("Getting XVA " + export_url)
|
||||
err = downloadFile(export_url, export_filename, ui)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not download XVA: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
case "vdi_raw":
|
||||
suffix = ".raw"
|
||||
extrauri = ""
|
||||
fallthrough
|
||||
case "vdi_vhd":
|
||||
// export the disks
|
||||
|
||||
disks, err := GetDisks(c, instance)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get VM disks: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, disk := range disks {
|
||||
disk_uuid, err := c.client.VDI.GetUUID(c.session, disk)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get disk with UUID '%s': %s", disk_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Work out XenServer version
|
||||
hosts, err := c.client.Host.GetAll(c.session)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
host := hosts[0]
|
||||
host_software_versions, err := c.client.Host.GetSoftwareVersion(c.session, host)
|
||||
xs_version := host_software_versions["product_version"]
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get the software version: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var disk_export_url string
|
||||
|
||||
// @todo: check for 6.5 SP1
|
||||
if xs_version <= "6.5.0" && config.Format == "vdi_vhd" {
|
||||
// Export the VHD using a Transfer VM
|
||||
|
||||
disk_export_url, err = Expose(c, disk, "vhd")
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Failed to expose disk %s: %s", disk_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Use the preferred direct export from XAPI
|
||||
// Basic auth in URL request is required as session token is not
|
||||
// accepted for some reason.
|
||||
// @todo: raise with XAPI team.
|
||||
disk_export_url = fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s%s",
|
||||
c.Username,
|
||||
c.Password,
|
||||
c.Host,
|
||||
disk_uuid,
|
||||
extrauri)
|
||||
|
||||
}
|
||||
|
||||
disk_export_filename := fmt.Sprintf("%s/%s%s", config.OutputDir, disk_uuid, suffix)
|
||||
|
||||
ui.Say("Getting VDI " + disk_export_url)
|
||||
err = downloadFile(disk_export_url, disk_export_filename, ui)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not download VDI: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Call unexpose in case a TVM was used. The call is harmless
|
||||
// if that is not the case.
|
||||
Unexpose(c, disk)
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown export format '%s'", config.Format))
|
||||
}
|
||||
|
||||
ui.Say("Download completed: " + config.OutputDir)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (StepExport) Cleanup(state multistep.StateBag) {}
|
49
builder/xenserver/common/step_find_vdi.go
Normal file
49
builder/xenserver/common/step_find_vdi.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepFindVdi struct {
|
||||
VdiName string
|
||||
ImagePathFunc func() string
|
||||
VdiUuidKey string
|
||||
}
|
||||
|
||||
func (self *StepFindVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
// Ignore if VdiName is not specified
|
||||
if self.VdiName == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
vdis, err := c.client.VDI.GetByNameLabel(c.session, self.VdiName)
|
||||
|
||||
switch {
|
||||
case len(vdis) == 0:
|
||||
ui.Error(fmt.Sprintf("Couldn't find a VDI named '%s'", self.VdiName))
|
||||
return multistep.ActionHalt
|
||||
case len(vdis) > 1:
|
||||
ui.Error(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", self.VdiName))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vdi := vdis[0]
|
||||
|
||||
vdiUuid, err := c.client.VDI.GetUUID(c.session, vdi)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put(self.VdiUuidKey, vdiUuid)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepFindVdi) Cleanup(state multistep.StateBag) {}
|
50
builder/xenserver/common/step_forward_port_over_ssh.go
Normal file
50
builder/xenserver/common/step_forward_port_over_ssh.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepForwardPortOverSSH struct {
|
||||
RemotePort func(state multistep.StateBag) (int, error)
|
||||
RemoteDest func(state multistep.StateBag) (string, error)
|
||||
|
||||
HostPortMin uint
|
||||
HostPortMax uint
|
||||
|
||||
ResultKey string
|
||||
}
|
||||
|
||||
func (self *StepForwardPortOverSSH) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Find a free local port:
|
||||
|
||||
l, sshHostPort := FindPort(self.HostPortMin, self.HostPortMax)
|
||||
|
||||
if l == nil || sshHostPort == 0 {
|
||||
ui.Error("Error: unable to find free host port. Try providing a larger range [host_port_min, host_port_max]")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating a local port forward over SSH on local port %d", sshHostPort))
|
||||
|
||||
hostAddress, _ := state.Get("ssh_address").(string)
|
||||
remotePort, _ := self.RemotePort(state)
|
||||
remoteDest, _ := self.RemoteDest(state)
|
||||
|
||||
go ssh_port_forward(l, remotePort, remoteDest, hostAddress, config.Username, config.Password)
|
||||
ui.Say(fmt.Sprintf("Port forward setup. %d ---> %s:%d on %s", sshHostPort, remoteDest, remotePort, hostAddress))
|
||||
|
||||
// Provide the local port to future steps.
|
||||
state.Put(self.ResultKey, sshHostPort)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepForwardPortOverSSH) Cleanup(state multistep.StateBag) {}
|
51
builder/xenserver/common/step_get_vnc_port.go
Normal file
51
builder/xenserver/common/step_get_vnc_port.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepGetVNCPort struct{}
|
||||
|
||||
func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Step: forward the instances VNC port over SSH")
|
||||
|
||||
domid := state.Get("domid").(int)
|
||||
cmd := fmt.Sprintf("xenstore-read /local/domain/%d/console/vnc-port", domid)
|
||||
|
||||
remote_vncport, err := ExecuteHostSSHCmd(state, cmd)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VNC port (is the VM running?): %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
remote_port, err := strconv.ParseUint(remote_vncport, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to convert '%s' to an int", remote_vncport))
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("instance_vnc_port", uint(remote_port))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepGetVNCPort) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
|
||||
func InstanceVNCPort(state multistep.StateBag) (uint, error) {
|
||||
vncPort := state.Get("instance_vnc_port").(uint)
|
||||
return vncPort, nil
|
||||
}
|
||||
|
||||
func InstanceVNCIP(state multistep.StateBag) (string, error) {
|
||||
// The port is in Dom0, so we want to forward from localhost
|
||||
return "127.0.0.1", nil
|
||||
}
|
96
builder/xenserver/common/step_http_server.go
Normal file
96
builder/xenserver/common/step_http_server.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package common
|
||||
|
||||
// Taken from hashicorp/packer/builder/qemu/step_http_server.go
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
// This step creates and runs the HTTP server that is serving files from the
|
||||
// directory specified by the 'http_directory` configuration parameter in the
|
||||
// template.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// http_port int - The port the HTTP server started on.
|
||||
type StepHTTPServer struct {
|
||||
Chan chan<- string
|
||||
|
||||
l net.Listener
|
||||
}
|
||||
|
||||
type IPSnooper struct {
|
||||
ch chan<- string
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func (snooper IPSnooper) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("HTTP: %s %s %s", req.RemoteAddr, req.Method, req.URL)
|
||||
ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil && ip != "" {
|
||||
select {
|
||||
case snooper.ch <- ip:
|
||||
log.Printf("Remembering remote address '%s'", ip)
|
||||
default:
|
||||
// if ch is already full, don't block waiting to send the address, just drop it
|
||||
}
|
||||
}
|
||||
snooper.handler.ServeHTTP(resp, req)
|
||||
}
|
||||
|
||||
func (s *StepHTTPServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var httpPort uint = 0
|
||||
if config.HTTPDir == "" {
|
||||
// the packer provision steps assert this type is an int
|
||||
// so this cannot be a uint like the rest of the code
|
||||
state.Put("http_port", int(httpPort))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
s.l, httpPort = FindPort(config.HTTPPortMin, config.HTTPPortMax)
|
||||
|
||||
if s.l == nil || httpPort == 0 {
|
||||
ui.Error("Error: unable to find free HTTP server port. Try providing a larger range [http_port_min, http_port_max]")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
|
||||
|
||||
// Start the HTTP server and run it in the background
|
||||
fileServer := http.FileServer(http.Dir(config.HTTPDir))
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", httpPort),
|
||||
Handler: IPSnooper{
|
||||
ch: s.Chan,
|
||||
handler: fileServer,
|
||||
},
|
||||
}
|
||||
go server.Serve(s.l)
|
||||
|
||||
// Save the address into the state so it can be accessed in the future
|
||||
// the packer provision steps assert this type is an int
|
||||
// so this cannot be a uint like the rest of the code
|
||||
state.Put("http_port", int(httpPort))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepHTTPServer) Cleanup(multistep.StateBag) {
|
||||
if s.l != nil {
|
||||
// Close the listener so that the HTTP server stops
|
||||
s.l.Close()
|
||||
}
|
||||
}
|
54
builder/xenserver/common/step_prepare_output_dir.go
Normal file
54
builder/xenserver/common/step_prepare_output_dir.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package common
|
||||
|
||||
/* Taken from https://raw.githubusercontent.com/hashicorp/packer/master/builder/qemu/step_prepare_output_dir.go */
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepPrepareOutputDir struct {
|
||||
Force bool
|
||||
Path string
|
||||
}
|
||||
|
||||
func (self *StepPrepareOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if _, err := os.Stat(self.Path); err == nil && self.Force {
|
||||
ui.Say("Deleting previous output directory...")
|
||||
os.RemoveAll(self.Path)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(self.Path, 0755); err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepPrepareOutputDir) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting output directory...")
|
||||
for i := 0; i < 5; i++ {
|
||||
err := os.RemoveAll(self.Path)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error removing output dir: %s", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
43
builder/xenserver/common/step_set_vm_host_ssh_address.go
Normal file
43
builder/xenserver/common/step_set_vm_host_ssh_address.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepSetVmHostSshAddress struct{}
|
||||
|
||||
func (self *StepSetVmHostSshAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
c := state.Get("client").(*Connection)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Step: Set SSH address to VM host IP")
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
host, err := c.client.VM.GetResidentOn(c.session, instance)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM Host for VM '%s': %s", uuid, err.Error()))
|
||||
}
|
||||
|
||||
address, err := c.client.Host.GetAddress(c.session, host)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get address from VM Host: %s", err.Error()))
|
||||
}
|
||||
|
||||
state.Put("ssh_address", address)
|
||||
ui.Say(fmt.Sprintf("Set host SSH address to '%s'.", address))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepSetVmHostSshAddress) Cleanup(state multistep.StateBag) {}
|
35
builder/xenserver/common/step_set_vm_to_template.go
Normal file
35
builder/xenserver/common/step_set_vm_to_template.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepSetVmToTemplate struct{}
|
||||
|
||||
func (StepSetVmToTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
instance_uuid := state.Get("instance_uuid").(string)
|
||||
|
||||
instance, err := c.client.VM.GetByUUID(c.session, instance_uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get VM with UUID '%s': %s", instance_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = c.client.VM.SetIsATemplate(c.session, instance, true)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("failed to set VM '%s' as a template with error: %v", instance_uuid, err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message("Successfully set VM as a template")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (StepSetVmToTemplate) Cleanup(state multistep.StateBag) {}
|
82
builder/xenserver/common/step_shutdown.go
Normal file
82
builder/xenserver/common/step_shutdown.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
xenapi "github.com/terra-farm/go-xen-api-client"
|
||||
)
|
||||
|
||||
type StepShutdown struct{}
|
||||
|
||||
func (StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
instance_uuid := state.Get("instance_uuid").(string)
|
||||
|
||||
instance, err := c.client.VM.GetByUUID(c.session, instance_uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get VM with UUID '%s': %s", instance_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Step: Shutting down VM")
|
||||
|
||||
// Shutdown the VM
|
||||
success := func() bool {
|
||||
if config.ShutdownCommand != "" {
|
||||
ui.Message("Executing shutdown command...")
|
||||
|
||||
_, err := ExecuteGuestSSHCmd(state, config.ShutdownCommand)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Shutdown command failed: %s", err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
ui.Message("Waiting for VM to enter Halted state...")
|
||||
|
||||
err = InterruptibleWait{
|
||||
Predicate: func() (bool, error) {
|
||||
power_state, err := c.client.VM.GetPowerState(c.session, instance)
|
||||
return power_state == xenapi.VMPowerStateHalted, err
|
||||
},
|
||||
PredicateInterval: 5 * time.Second,
|
||||
Timeout: 300 * time.Second,
|
||||
}.Wait(state)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for VM to halt: %s", err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
} else {
|
||||
ui.Message("Attempting to cleanly shutdown the VM...")
|
||||
|
||||
err = c.client.VM.CleanShutdown(c.session, instance)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not shut down VM: %s", err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
}()
|
||||
|
||||
if !success {
|
||||
ui.Say("WARNING: Forcing hard shutdown of the VM...")
|
||||
err = c.client.VM.HardShutdown(c.session, instance)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not hard shut down VM -- giving up: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message("Successfully shut down VM")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (StepShutdown) Cleanup(state multistep.StateBag) {}
|
137
builder/xenserver/common/step_start_on_himn.go
Normal file
137
builder/xenserver/common/step_start_on_himn.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type StepStartOnHIMN struct{}
|
||||
|
||||
/*
|
||||
* This step starts the installed guest on the Host Internal Management Network
|
||||
* as there exists an API to obtain the IP allocated to the VM by XAPI.
|
||||
* This in turn will allow Packer to SSH into the VM, provided NATing has been
|
||||
* enabled on the host.
|
||||
*
|
||||
*/
|
||||
|
||||
func (self *StepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
ui.Say("Step: Start VM on the Host Internal Mangement Network")
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Find the HIMN Ref
|
||||
networks, err := c.client.Network.GetByNameLabel(c.session, "Host internal management network")
|
||||
if err != nil || len(networks) == 0 {
|
||||
ui.Error("Unable to find a host internal management network")
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
himn := networks[0]
|
||||
|
||||
// Create a VIF for the HIMN
|
||||
himn_vif, err := ConnectNetwork(c, himn, instance, "0")
|
||||
if err != nil {
|
||||
ui.Error("Error creating VIF")
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Start the VM
|
||||
c.client.VM.Start(c.session, instance, false, false)
|
||||
|
||||
var himn_iface_ip string = ""
|
||||
|
||||
// Obtain the allocated IP
|
||||
err = InterruptibleWait{
|
||||
Predicate: func() (found bool, err error) {
|
||||
ips, err := c.client.Network.GetAssignedIps(c.session, himn)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Can't get assigned IPs: %s", err.Error())
|
||||
}
|
||||
log.Printf("IPs: %s", ips)
|
||||
log.Printf("Ref: %s", instance)
|
||||
|
||||
//Check for instance.Ref in map
|
||||
if vm_ip, ok := ips[*himn_vif]; ok && vm_ip != "" {
|
||||
ui.Say("Found the VM's IP: " + vm_ip)
|
||||
himn_iface_ip = vm_ip
|
||||
return true, nil
|
||||
}
|
||||
|
||||
ui.Say("Wait for IP address...")
|
||||
return false, nil
|
||||
},
|
||||
PredicateInterval: 10 * time.Second,
|
||||
Timeout: 100 * time.Second,
|
||||
}.Wait(state)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to find an IP on the Host-internal management interface: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("himn_ssh_address", himn_iface_ip)
|
||||
ui.Say("Stored VM's IP " + himn_iface_ip)
|
||||
|
||||
// Wait for the VM to boot, and check we can ping this interface
|
||||
|
||||
ping_cmd := fmt.Sprintf("ping -c 1 %s", himn_iface_ip)
|
||||
|
||||
err = InterruptibleWait{
|
||||
Predicate: func() (success bool, err error) {
|
||||
ui.Message(fmt.Sprintf("Attempting to ping interface: %s", ping_cmd))
|
||||
_, err = ExecuteHostSSHCmd(state, ping_cmd)
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// ping succeeded
|
||||
return true, nil
|
||||
case *gossh.ExitError:
|
||||
// ping failed, try again
|
||||
return false, nil
|
||||
default:
|
||||
// unknown error
|
||||
return false, err
|
||||
}
|
||||
},
|
||||
PredicateInterval: 10 * time.Second,
|
||||
Timeout: 300 * time.Second,
|
||||
}.Wait(state)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to ping interface. (Has the VM not booted?): %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message("Ping success! Continuing...")
|
||||
return multistep.ActionContinue
|
||||
|
||||
}
|
||||
|
||||
func (self *StepStartOnHIMN) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
func HimnSSHIP(state multistep.StateBag) (string, error) {
|
||||
ip := state.Get("himn_ssh_address").(string)
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func HimnSSHPort(state multistep.StateBag) (uint, error) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
return config.SSHPort, nil
|
||||
}
|
58
builder/xenserver/common/step_start_vm_paused.go
Normal file
58
builder/xenserver/common/step_start_vm_paused.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepStartVmPaused struct {
|
||||
VmCleanup
|
||||
}
|
||||
|
||||
func (self *StepStartVmPaused) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
||||
c := state.Get("client").(*Connection)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
ui.Say("Step: Start VM Paused")
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// note that here "cd" means boot from hard drive ('c') first, then CDROM ('d')
|
||||
err = c.client.VM.SetHVMBootPolicy(c.session, instance, "BIOS order")
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to set HVM boot params: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = c.client.VM.SetHVMBootParams(c.session, instance, map[string]string{"order": "cd", "firmware": config.Firmware})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to set HVM boot params: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = c.client.VM.Start(c.session, instance, true, false)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to start VM with UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
domid, err := c.client.VM.GetDomid(c.session, instance)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get domid of VM with UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("domid", domid)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
272
builder/xenserver/common/step_type_boot_command.go
Normal file
272
builder/xenserver/common/step_type_boot_command.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package common
|
||||
|
||||
/* Heavily borrowed from builder/quemu/step_type_boot_command.go */
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
"github.com/mitchellh/go-vnc"
|
||||
)
|
||||
|
||||
const KeyLeftShift uint = 0xFFE1
|
||||
|
||||
type bootCommandTemplateData struct {
|
||||
Name string
|
||||
HTTPIP string
|
||||
HTTPPort uint
|
||||
}
|
||||
|
||||
type StepTypeBootCommand struct {
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (self *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
httpPort := state.Get("http_port").(int)
|
||||
|
||||
// skip this step if we have nothing to type
|
||||
if len(config.BootCommand) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
vmRef, err := c.client.VM.GetByNameLabel(c.session, config.VMName)
|
||||
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(vmRef) != 1 {
|
||||
ui.Error(fmt.Sprintf("expected to find a single VM, instead found '%d'. Ensure the VM name is unique", len(vmRef)))
|
||||
}
|
||||
|
||||
consoles, err := c.client.VM.GetConsoles(c.session, vmRef[0])
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(consoles) != 1 {
|
||||
ui.Error(fmt.Sprintf("expected to find a VM console, instead found '%d'. Ensure there is only one console", len(consoles)))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
location, err := c.client.Console.GetLocation(c.session, consoles[0])
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
locationPieces := strings.SplitAfter(location, "/")
|
||||
consoleHost := strings.TrimSuffix(locationPieces[2], "/")
|
||||
ui.Say(fmt.Sprintf("Connecting to the VM console VNC over xapi via %s", consoleHost))
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:443", consoleHost))
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to VNC: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
||||
consoleLocation := strings.TrimSpace(fmt.Sprintf("/%s", locationPieces[len(locationPieces)-1]))
|
||||
httpReq := fmt.Sprintf("CONNECT %s HTTP/1.0\r\nHost: %s\r\nCookie: session_id=%s\r\n\r\n", consoleLocation, consoleHost, c.session)
|
||||
fmt.Printf("Sending the follow http req: %v", httpReq)
|
||||
|
||||
ui.Say(fmt.Sprintf("Making HTTP request to initiate VNC connection: %s", httpReq))
|
||||
_, err = io.WriteString(tlsConn, httpReq)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("failed to start vnc session: %v", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
buffer := make([]byte, 10000)
|
||||
_, err = tlsConn.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
err := fmt.Errorf("failed to read vnc session response: %v", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Received response: %s", string(buffer)))
|
||||
|
||||
vncClient, err := vnc.Client(tlsConn, &vnc.ClientConfig{Exclusive: true})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error establishing VNC session: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
defer vncClient.Close()
|
||||
|
||||
log.Printf("Connected to the VNC console: %s", vncClient.DesktopName)
|
||||
|
||||
// find local ip
|
||||
envVar, err := ExecuteHostSSHCmd(state, "echo $SSH_CLIENT")
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error detecting local IP: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if envVar == "" {
|
||||
ui.Error("Error detecting local IP: $SSH_CLIENT was empty")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
localIp := strings.Split(envVar, " ")[0]
|
||||
ui.Message(fmt.Sprintf("Found local IP: %s", localIp))
|
||||
|
||||
self.Ctx.Data = &bootCommandTemplateData{
|
||||
config.VMName,
|
||||
localIp,
|
||||
uint(httpPort),
|
||||
}
|
||||
|
||||
ui.Say("Typing boot commands over VNC...")
|
||||
for _, command := range config.BootCommand {
|
||||
|
||||
command, err := interpolate.Render(command, &self.Ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Check for interrupts
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vncSendString(vncClient, command)
|
||||
}
|
||||
|
||||
ui.Say("Finished typing.")
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||
|
||||
// Taken from qemu's builder plugin - not an exported function.
|
||||
func vncSendString(c *vnc.ClientConn, original string) {
|
||||
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
|
||||
special := make(map[string]uint32)
|
||||
special["<bs>"] = 0xFF08
|
||||
special["<del>"] = 0xFFFF
|
||||
special["<enter>"] = 0xFF0D
|
||||
special["<esc>"] = 0xFF1B
|
||||
special["<f1>"] = 0xFFBE
|
||||
special["<f2>"] = 0xFFBF
|
||||
special["<f3>"] = 0xFFC0
|
||||
special["<f4>"] = 0xFFC1
|
||||
special["<f5>"] = 0xFFC2
|
||||
special["<f6>"] = 0xFFC3
|
||||
special["<f7>"] = 0xFFC4
|
||||
special["<f8>"] = 0xFFC5
|
||||
special["<f9>"] = 0xFFC6
|
||||
special["<f10>"] = 0xFFC7
|
||||
special["<f11>"] = 0xFFC8
|
||||
special["<f12>"] = 0xFFC9
|
||||
special["<return>"] = 0xFF0D
|
||||
special["<tab>"] = 0xFF09
|
||||
special["<up>"] = 0xFF52
|
||||
special["<down>"] = 0xFF54
|
||||
special["<left>"] = 0xFF51
|
||||
special["<right>"] = 0xFF53
|
||||
special["<spacebar>"] = 0x020
|
||||
special["<insert>"] = 0xFF63
|
||||
special["<home>"] = 0xFF50
|
||||
special["<end>"] = 0xFF57
|
||||
special["<pageUp>"] = 0xFF55
|
||||
special["<pageDown>"] = 0xFF56
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
|
||||
for len(original) > 0 {
|
||||
var keyCode uint32
|
||||
keyShift := false
|
||||
|
||||
if strings.HasPrefix(original, "<wait>") {
|
||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||
time.Sleep(1 * time.Second)
|
||||
original = original[len("<wait>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait5>") {
|
||||
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
original = original[len("<wait5>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait10>") {
|
||||
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
original = original[len("<wait10>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(original, specialCode) {
|
||||
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
|
||||
keyCode = specialValue
|
||||
original = original[len(specialCode):]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if keyCode == 0 {
|
||||
r, size := utf8.DecodeRuneInString(original)
|
||||
original = original[size:]
|
||||
keyCode = uint32(r)
|
||||
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||
|
||||
log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift)
|
||||
}
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(uint32(KeyLeftShift), true)
|
||||
}
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(time.Second / 10)
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(time.Second / 10)
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(uint32(KeyLeftShift), false)
|
||||
}
|
||||
|
||||
// no matter what, wait a small period
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
145
builder/xenserver/common/step_upload_vdi.go
Normal file
145
builder/xenserver/common/step_upload_vdi.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
xenapi "github.com/terra-farm/go-xen-api-client"
|
||||
)
|
||||
|
||||
type StepUploadVdi struct {
|
||||
VdiNameFunc func() string
|
||||
ImagePathFunc func() string
|
||||
VdiUuidKey string
|
||||
}
|
||||
|
||||
func (self *StepUploadVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
imagePath := self.ImagePathFunc()
|
||||
vdiName := self.VdiNameFunc()
|
||||
if imagePath == "" {
|
||||
// skip if no disk image to attach
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Step: Upload VDI '%s'", vdiName))
|
||||
|
||||
// Create VDI for the image
|
||||
srs, err := c.client.SR.GetAll(c.session)
|
||||
ui.Say(fmt.Sprintf("Step: Found SRs '%v'", srs))
|
||||
|
||||
sr, err := config.GetISOSR(c)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get SR: %v", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Open the file for reading (NB: HTTPUpload closes the file for us)
|
||||
fh, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to open disk image '%s': %s", imagePath, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Get file length
|
||||
fstat, err := fh.Stat()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to stat disk image '%s': %s", imagePath, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
fileLength := fstat.Size()
|
||||
|
||||
// Create the VDI
|
||||
// vdi, err := sr.CreateVdi(vdiName, fileLength)
|
||||
vdi, err := c.client.VDI.Create(c.session, xenapi.VDIRecord{
|
||||
NameLabel: vdiName,
|
||||
VirtualSize: int(fileLength),
|
||||
Type: "user",
|
||||
Sharable: false,
|
||||
ReadOnly: false,
|
||||
SR: sr,
|
||||
OtherConfig: map[string]string{
|
||||
"temp": "temp",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to create VDI '%s': %s", vdiName, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vdiUuid, err := c.client.VDI.GetUUID(c.session, vdi)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", vdiName, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put(self.VdiUuidKey, vdiUuid)
|
||||
|
||||
_, err = HTTPUpload(fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s",
|
||||
c.Host,
|
||||
vdi,
|
||||
c.GetSession(),
|
||||
), fh, state)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to upload VDI: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (self *StepUploadVdi) Cleanup(state multistep.StateBag) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
vdiName := self.VdiNameFunc()
|
||||
|
||||
if config.ShouldKeepVM(state) {
|
||||
return
|
||||
}
|
||||
|
||||
vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey)
|
||||
if !ok {
|
||||
// VDI doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
vdiUuid := vdiUuidRaw.(string)
|
||||
if vdiUuid == "" {
|
||||
// VDI already cleaned up
|
||||
return
|
||||
}
|
||||
|
||||
vdi, err := c.client.VDI.GetByUUID(c.session, vdiUuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Can't get VDI '%s': %s", vdiUuid, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// an interrupted import_raw_vdi takes a while to release the VDI
|
||||
// so try several times
|
||||
for i := 0; i < 3; i++ {
|
||||
log.Printf("Trying to destroy VDI...")
|
||||
err = c.client.VDI.Destroy(c.session, vdi)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Can't destroy VDI '%s': %s", vdiUuid, err.Error()))
|
||||
return
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Destroyed VDI '%s'", vdiName))
|
||||
|
||||
state.Put(self.VdiUuidKey, "")
|
||||
}
|
96
builder/xenserver/common/step_wait_for_ip.go
Normal file
96
builder/xenserver/common/step_wait_for_ip.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
"github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
type StepWaitForIP struct {
|
||||
VmCleanup
|
||||
Chan <-chan string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (self *StepWaitForIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("client").(*Connection)
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
|
||||
ui.Say("Step: Wait for VM's IP to become known to us.")
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var ip string
|
||||
err = InterruptibleWait{
|
||||
Timeout: self.Timeout,
|
||||
PredicateInterval: 5 * time.Second,
|
||||
Predicate: func() (result bool, err error) {
|
||||
|
||||
if config.IPGetter == "auto" || config.IPGetter == "http" {
|
||||
|
||||
// Snoop IP from HTTP fetch
|
||||
select {
|
||||
case ip = <-self.Chan:
|
||||
ui.Message(fmt.Sprintf("Got IP '%s' from HTTP request", ip))
|
||||
return true, nil
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if config.IPGetter == "auto" || config.IPGetter == "tools" {
|
||||
|
||||
// Look for PV IP
|
||||
m, err := c.client.VM.GetGuestMetrics(c.session, instance)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if m != "" {
|
||||
metrics, err := c.client.VMGuestMetrics.GetRecord(c.session, m)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
networks := metrics.Networks
|
||||
var ok bool
|
||||
if ip, ok = networks["0/ip"]; ok {
|
||||
if ip != "" {
|
||||
ui.Message(fmt.Sprintf("Got IP '%s' from XenServer tools", ip))
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false, nil
|
||||
},
|
||||
}.Wait(state)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get IP address of VM: %s", err.Error()))
|
||||
// @todo: give advice on what went wrong (no HTTP server? no PV drivers?)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Got IP address '%s'", ip))
|
||||
state.Put("instance_ssh_address", ip)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func InstanceSSHIP(state multistep.StateBag) (string, error) {
|
||||
ip := state.Get("instance_ssh_address").(string)
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func InstanceSSHPort(state multistep.StateBag) (int, error) {
|
||||
return 22, nil
|
||||
}
|
31
builder/xenserver/common/vm_cleanup.go
Normal file
31
builder/xenserver/common/vm_cleanup.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
type VmCleanup struct{}
|
||||
|
||||
func (self *VmCleanup) Cleanup(state multistep.StateBag) {
|
||||
config := state.Get("commonconfig").(CommonConfig)
|
||||
c := state.Get("client").(*Connection)
|
||||
|
||||
if config.ShouldKeepVM(state) {
|
||||
return
|
||||
}
|
||||
|
||||
uuid := state.Get("instance_uuid").(string)
|
||||
instance, err := c.client.VM.GetByUUID(c.session, uuid)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.client.VM.HardShutdown(c.session, instance)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("Unable to force shutdown VM '%s': %s", uuid, err.Error()))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user