151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
package proxmox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/Telmate/proxmox-api-go/proxmox"
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
"github.com/hashicorp/packer/packer"
|
|
)
|
|
|
|
// stepStartVM takes the given configuration and starts a VM on the given Proxmox node.
|
|
//
|
|
// It sets the vmRef state which is used throughout the later steps to reference the VM
|
|
// in API calls.
|
|
type stepStartVM struct{}
|
|
|
|
func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
ui := state.Get("ui").(packer.Ui)
|
|
client := state.Get("proxmoxClient").(*proxmox.Client)
|
|
c := state.Get("config").(*Config)
|
|
|
|
ui.Say("Creating VM")
|
|
config := proxmox.ConfigQemu{
|
|
Name: c.VMName,
|
|
Agent: "1",
|
|
Description: "Packer ephemeral build VM",
|
|
Memory: c.Memory,
|
|
QemuCores: c.Cores,
|
|
QemuSockets: c.Sockets,
|
|
QemuOs: c.OS,
|
|
QemuIso: c.ISOFile,
|
|
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
|
|
QemuDisks: generateProxmoxDisks(c.Disks),
|
|
}
|
|
|
|
if c.VMID == 0 {
|
|
ui.Say("No VM ID given, getting next free from Proxmox")
|
|
for n := 0; n < 5; n++ {
|
|
id, err := proxmox.MaxVmId(client)
|
|
if err != nil {
|
|
log.Printf("Error getting max used VM ID: %v (attempt %d/5)", err, n+1)
|
|
continue
|
|
}
|
|
c.VMID = id + 1
|
|
break
|
|
}
|
|
if c.VMID == 0 {
|
|
err := fmt.Errorf("Failed to get free VM ID")
|
|
state.Put("error", err)
|
|
ui.Error(err.Error())
|
|
return multistep.ActionHalt
|
|
}
|
|
}
|
|
vmRef := proxmox.NewVmRef(c.VMID)
|
|
vmRef.SetNode(c.Node)
|
|
|
|
err := config.CreateVm(vmRef, client)
|
|
if err != nil {
|
|
state.Put("error", err)
|
|
ui.Error(err.Error())
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
// Store the vm id for later
|
|
state.Put("vmRef", vmRef)
|
|
|
|
ui.Say("Starting VM")
|
|
_, err = client.StartVm(vmRef)
|
|
if err != nil {
|
|
err := fmt.Errorf("Error starting VM: %s", err)
|
|
state.Put("error", err)
|
|
ui.Error(err.Error())
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
return multistep.ActionContinue
|
|
}
|
|
|
|
func generateProxmoxNetworkAdapters(nics []nicConfig) proxmox.QemuDevices {
|
|
devs := make(proxmox.QemuDevices)
|
|
for idx := range nics {
|
|
devs[idx] = make(proxmox.QemuDevice)
|
|
setDeviceParamIfDefined(devs[idx], "model", nics[idx].Model)
|
|
setDeviceParamIfDefined(devs[idx], "macaddr", nics[idx].MACAddress)
|
|
setDeviceParamIfDefined(devs[idx], "bridge", nics[idx].Bridge)
|
|
setDeviceParamIfDefined(devs[idx], "tag", nics[idx].VLANTag)
|
|
}
|
|
return devs
|
|
}
|
|
func generateProxmoxDisks(disks []diskConfig) proxmox.QemuDevices {
|
|
devs := make(proxmox.QemuDevices)
|
|
for idx := range disks {
|
|
devs[idx] = make(proxmox.QemuDevice)
|
|
setDeviceParamIfDefined(devs[idx], "type", disks[idx].Type)
|
|
setDeviceParamIfDefined(devs[idx], "size", disks[idx].Size)
|
|
setDeviceParamIfDefined(devs[idx], "storage", disks[idx].StoragePool)
|
|
setDeviceParamIfDefined(devs[idx], "storage_type", disks[idx].StoragePoolType)
|
|
setDeviceParamIfDefined(devs[idx], "cache", disks[idx].CacheMode)
|
|
setDeviceParamIfDefined(devs[idx], "format", disks[idx].DiskFormat)
|
|
}
|
|
return devs
|
|
}
|
|
|
|
func setDeviceParamIfDefined(dev proxmox.QemuDevice, key, value string) {
|
|
if value != "" {
|
|
dev[key] = value
|
|
}
|
|
}
|
|
|
|
type startedVMCleaner interface {
|
|
StopVm(*proxmox.VmRef) (string, error)
|
|
DeleteVm(*proxmox.VmRef) (string, error)
|
|
}
|
|
|
|
var _ startedVMCleaner = &proxmox.Client{}
|
|
|
|
func (s *stepStartVM) Cleanup(state multistep.StateBag) {
|
|
vmRefUntyped, ok := state.GetOk("vmRef")
|
|
// If not ok, we probably errored out before creating the VM
|
|
if !ok {
|
|
return
|
|
}
|
|
vmRef := vmRefUntyped.(*proxmox.VmRef)
|
|
|
|
// The vmRef will actually refer to the created template if everything
|
|
// finished successfully, so in that case we shouldn't cleanup
|
|
if _, ok := state.GetOk("success"); ok {
|
|
return
|
|
}
|
|
|
|
client := state.Get("proxmoxClient").(startedVMCleaner)
|
|
ui := state.Get("ui").(packer.Ui)
|
|
|
|
// Destroy the server we just created
|
|
ui.Say("Stopping VM")
|
|
_, err := client.StopVm(vmRef)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error stop VM. Please stop and delete it manually: %s", err))
|
|
return
|
|
}
|
|
|
|
ui.Say("Deleting VM")
|
|
_, err = client.DeleteVm(vmRef)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error deleting VM. Please delete it manually: %s", err))
|
|
return
|
|
}
|
|
}
|