diff --git a/builder/proxmox/common/step_finalize_config.go b/builder/proxmox/common/step_finalize_config.go index ec44befe..9ac850fb 100644 --- a/builder/proxmox/common/step_finalize_config.go +++ b/builder/proxmox/common/step_finalize_config.go @@ -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{} @@ -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 } diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 3781c6b1..8f3b11d3 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -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 ( @@ -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) @@ -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 { @@ -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 { @@ -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") } diff --git a/builder/proxmox/common/step_start_vm_test.go b/builder/proxmox/common/step_start_vm_test.go index a228778b..25280834 100644 --- a/builder/proxmox/common/step_start_vm_test.go +++ b/builder/proxmox/common/step_start_vm_test.go @@ -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) @@ -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) } @@ -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) } @@ -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 }, @@ -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", @@ -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", @@ -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", @@ -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 + }, }, } @@ -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 }, @@ -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