Skip to content

Commit

Permalink
skip_convert_to_template: return a running VM, update force flag to w…
Browse files Browse the repository at this point in the history
…ork with VM builds
  • Loading branch information
mpywell committed Oct 29, 2024
1 parent 568c55f commit f2a0326
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 14 deletions.
14 changes: 13 additions & 1 deletion builder/proxmox/common/step_finalize_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)

// stepFinalizeTemplateConfig does any required modifications to the configuration _after_
// stepFinalizeConfig does any required modifications to the configuration _after_
// the VM has been converted into a template, such as updating name and description, or
// unmounting the installation ISO.
type stepFinalizeConfig struct{}
Expand Down Expand Up @@ -149,6 +149,18 @@ func (s *stepFinalizeConfig) Run(ctx context.Context, state multistep.StateBag)
}
}

// When build artifact is to be a VM, return a running VM
if c.SkipConvertToTemplate {
ui.Say("skip_convert_to_template set, resuming 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
}

Expand Down
48 changes: 35 additions & 13 deletions builder/proxmox/common/step_start_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ type vmStarter interface {
GetVmConfig(vmr *proxmox.VmRef) (vmConfig map[string]interface{}, err error)
GetVmRefsByName(vmName string) (vmrs []*proxmox.VmRef, err error)
SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
GetVmState(vmr *proxmox.VmRef) (vmState map[string]interface{}, err error)
StartVm(*proxmox.VmRef) (string, error)
StopVm(*proxmox.VmRef) (string, error)
}

var (
Expand All @@ -45,7 +47,7 @@ var (

// Check if the given builder configuration maps to an existing VM template on the Proxmox cluster.
// Returns an empty *proxmox.VmRef when no matching ID or name is found.
func getExistingTemplate(c *Config, client vmStarter) (*proxmox.VmRef, error) {
func getExistingTemplate(c *Config, client vmStarter) (*proxmox.VmRef, string, error) {
vmRef := &proxmox.VmRef{}
if c.VMID > 0 {
log.Printf("looking up VM with ID %d", c.VMID)
Expand All @@ -57,9 +59,9 @@ func getExistingTemplate(c *Config, client vmStarter) (*proxmox.VmRef, error) {
notFoundError := fmt.Sprintf("vm '%d' not found", c.VMID)
if err.Error() == notFoundError {
log.Println(err.Error())
return &proxmox.VmRef{}, nil
return &proxmox.VmRef{}, "", nil
}
return &proxmox.VmRef{}, err
return &proxmox.VmRef{}, "", err
}
log.Printf("found VM with ID %d", vmRef.VmId())
} else {
Expand All @@ -71,30 +73,33 @@ func getExistingTemplate(c *Config, client vmStarter) (*proxmox.VmRef, error) {
notFoundError := fmt.Sprintf("vm '%s' not found", c.TemplateName)
if err.Error() == notFoundError {
log.Println(err.Error())
return &proxmox.VmRef{}, nil
return &proxmox.VmRef{}, "", nil
}
return &proxmox.VmRef{}, err
return &proxmox.VmRef{}, "", err
}
if len(vmRefs) > 1 {
vmIDs := []int{}
for _, vmr := range vmRefs {
vmIDs = append(vmIDs, vmr.VmId())
}
return &proxmox.VmRef{}, fmt.Errorf("found multiple VMs with name '%s', IDs: %v", c.TemplateName, vmIDs)
return &proxmox.VmRef{}, "", fmt.Errorf("found multiple VMs with name '%s', IDs: %v", c.TemplateName, vmIDs)
}
vmRef = vmRefs[0]
log.Printf("found VM with name '%s' (ID: %d)", c.TemplateName, vmRef.VmId())
}
if c.SkipConvertToTemplate {
return vmRef, "VM", nil
}
log.Printf("check if VM %d is a template", vmRef.VmId())
vmConfig, err := client.GetVmConfig(vmRef)
if err != nil {
return &proxmox.VmRef{}, err
return &proxmox.VmRef{}, "", err
}
log.Printf("VM %d template: %d", vmRef.VmId(), vmConfig["template"])
if vmConfig["template"] == nil {
return &proxmox.VmRef{}, fmt.Errorf("found matching VM (ID: %d, name: %s), but it is not a template", vmRef.VmId(), vmConfig["name"])
return &proxmox.VmRef{}, "", fmt.Errorf("found matching VM (ID: %d, name: %s), but it is not a template", vmRef.VmId(), vmConfig["name"])
}
return vmRef, nil
return vmRef, "VM template", nil
}

func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
Expand Down Expand Up @@ -162,21 +167,38 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist

if c.PackerForce {
ui.Say("Force set, checking for existing artifact on PVE cluster")
vmRef, err := getExistingTemplate(c, client)
vmRef, vmType, err := getExistingTemplate(c, client)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if vmRef.VmId() != 0 {
ui.Say(fmt.Sprintf("found existing VM template with ID %d on PVE node %s, deleting it", vmRef.VmId(), vmRef.Node()))
ui.Say(fmt.Sprintf("found existing %s with ID %d on PVE node %s, deleting it", vmType, vmRef.VmId(), vmRef.Node()))
// If building a VM artifact and c.PackerForce is true,
// running VMs can't be deleted. Stop before deleting.
vmState, err := client.GetVmState(vmRef)
if err != nil {
state.Put("error", err)
ui.Error(fmt.Sprintf("error getting VM state: %s", err.Error()))
return multistep.ActionHalt
}
if vmState["status"] == "running" {
log.Printf("VM %d running, stopping for deletion", vmRef.VmId())
_, err = client.StopVm(vmRef)
if err != nil {
state.Put("error", err)
ui.Error(fmt.Sprintf("error stopping %s: %s", vmType, err.Error()))
return multistep.ActionHalt
}
}
_, err = client.DeleteVm(vmRef)
if err != nil {
state.Put("error", err)
ui.Error(fmt.Sprintf("error deleting VM template: %s", err.Error()))
ui.Error(fmt.Sprintf("error deleting %s: %s", vmType, err.Error()))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Successfully deleted VM template %d", vmRef.VmId()))
ui.Say(fmt.Sprintf("Successfully deleted %s %d", vmType, vmRef.VmId()))
} else {
ui.Say("No existing artifact found")
}
Expand Down
27 changes: 27 additions & 0 deletions builder/proxmox/common/step_start_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ func TestCleanupStartVM(t *testing.T) {
type startVMMock struct {
create func(*proxmox.VmRef, proxmox.ConfigQemu, multistep.StateBag) error
startVm func(*proxmox.VmRef) (string, error)
stopVm func(*proxmox.VmRef) (string, error)
setVmConfig func(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
getNextID func(id int) (int, error)
getVmConfig func(vmr *proxmox.VmRef) (vmConfig map[string]interface{}, err error)
getVmState func(vmr *proxmox.VmRef) (vmState map[string]interface{}, err error)
checkVmRef func(vmr *proxmox.VmRef) (err error)
getVmByName func(vmName string) (vmrs []*proxmox.VmRef, err error)
deleteVm func(vmr *proxmox.VmRef) (exitStatus string, err error)
Expand All @@ -130,6 +132,9 @@ func (m *startVMMock) Create(vmRef *proxmox.VmRef, config proxmox.ConfigQemu, st
func (m *startVMMock) StartVm(vmRef *proxmox.VmRef) (string, error) {
return m.startVm(vmRef)
}
func (m *startVMMock) StopVm(vmRef *proxmox.VmRef) (string, error) {
return m.stopVm(vmRef)
}
func (m *startVMMock) SetVmConfig(vmRef *proxmox.VmRef, config map[string]interface{}) (interface{}, error) {
return m.setVmConfig(vmRef, config)
}
Expand All @@ -139,6 +144,9 @@ func (m *startVMMock) GetNextID(id int) (int, error) {
func (m *startVMMock) GetVmConfig(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return m.getVmConfig(vmr)
}
func (m *startVMMock) GetVmState(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return m.getVmState(vmr)
}
func (m *startVMMock) CheckVmRef(vmr *proxmox.VmRef) (err error) {
return m.checkVmRef(vmr)
}
Expand Down Expand Up @@ -189,6 +197,9 @@ func TestStartVM(t *testing.T) {
startVm: func(*proxmox.VmRef) (string, error) {
return "", nil
},
stopVm: func(*proxmox.VmRef) (string, error) {
return "", nil
},
setVmConfig: func(*proxmox.VmRef, map[string]interface{}) (interface{}, error) {
return nil, nil
},
Expand Down Expand Up @@ -308,6 +319,7 @@ func TestStartVMWithForce(t *testing.T) {
expectedAction multistep.StepAction
mockGetVmRefsByName func(vmName string) (vmrs []*proxmox.VmRef, err error)
mockGetVmConfig func(vmr *proxmox.VmRef) (map[string]interface{}, error)
mockGetVmState func(vmr *proxmox.VmRef) (map[string]interface{}, error)
}{
{
name: "Delete existing VM when it's a template and force is enabled",
Expand All @@ -323,6 +335,9 @@ func TestStartVMWithForce(t *testing.T) {
// proxmox-api-go returns a float for "template"
return map[string]interface{}{"template": 1.0}, nil
},
mockGetVmState: func(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return map[string]interface{}{"status": "stopped"}, nil
},
},
{
name: "Don't delete VM when it's not a template",
Expand All @@ -337,6 +352,9 @@ func TestStartVMWithForce(t *testing.T) {
mockGetVmConfig: func(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return map[string]interface{}{}, nil
},
mockGetVmState: func(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return map[string]interface{}{"status": "stopped"}, nil
},
},
{
name: "Don't delete VM when force disabled",
Expand Down Expand Up @@ -390,6 +408,9 @@ func TestStartVMWithForce(t *testing.T) {
mockGetVmConfig: func(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return map[string]interface{}{"template": 1.0}, nil
},
mockGetVmState: func(vmr *proxmox.VmRef) (map[string]interface{}, error) {
return map[string]interface{}{"status": "running"}, nil
},
},
}

Expand All @@ -403,6 +424,9 @@ func TestStartVMWithForce(t *testing.T) {
startVm: func(*proxmox.VmRef) (string, error) {
return "", nil
},
stopVm: func(*proxmox.VmRef) (string, error) {
return "", nil
},
setVmConfig: func(*proxmox.VmRef, map[string]interface{}) (interface{}, error) {
return nil, nil
},
Expand All @@ -418,6 +442,9 @@ func TestStartVMWithForce(t *testing.T) {
getVmConfig: func(vmr *proxmox.VmRef) (config map[string]interface{}, err error) {
return c.mockGetVmConfig(vmr)
},
getVmState: func(vmr *proxmox.VmRef) (config map[string]interface{}, err error) {
return c.mockGetVmState(vmr)
},
deleteVm: func(vmr *proxmox.VmRef) (exitStatus string, err error) {
deleteWasCalled = true
return "", nil
Expand Down

0 comments on commit f2a0326

Please sign in to comment.