Skip to content

Commit

Permalink
common: add skip_convert_to_template option
Browse files Browse the repository at this point in the history
  • Loading branch information
mpywell committed Sep 28, 2024
1 parent 38a075c commit b8c44e9
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .web-docs/components/builder/clone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ boot time.
- `template_description` (string) - Description of the template, visible in
the Proxmox interface.

- `skip_convert_to_template` (bool) - Skip converting the VM to a template on completion of build.
Defaults to `false`

- `cloud_init` (bool) - If true, add an empty Cloud-Init CDROM drive after the virtual
machine has been converted to a template. Defaults to `false`.

Expand Down
3 changes: 3 additions & 0 deletions .web-docs/components/builder/iso/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ in the image's Cloud-Init settings for provisioning.
- `template_description` (string) - Description of the template, visible in
the Proxmox interface.

- `skip_convert_to_template` (bool) - Skip converting the VM to a template on completion of build.
Defaults to `false`

- `cloud_init` (bool) - If true, add an empty Cloud-Init CDROM drive after the virtual
machine has been converted to a template. Defaults to `false`.

Expand Down
2 changes: 2 additions & 0 deletions builder/proxmox/clone/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions builder/proxmox/common/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import (

type Artifact struct {
builderID string
templateID int
artifactID int
artifactType string
proxmoxClient *proxmox.Client

// StateData should store data such as GeneratedData
Expand All @@ -34,19 +35,19 @@ func (*Artifact) Files() []string {
}

func (a *Artifact) Id() string {
return strconv.Itoa(a.templateID)
return strconv.Itoa(a.artifactID)
}

func (a *Artifact) String() string {
return fmt.Sprintf("A template was created: %d", a.templateID)
return fmt.Sprintf("A %s was created: %d", a.artifactType, a.artifactID)
}

func (a *Artifact) State(name string) interface{} {
return a.StateData[name]
}

func (a *Artifact) Destroy() error {
log.Printf("Destroying template: %d", a.templateID)
_, err := a.proxmoxClient.DeleteVm(proxmox.NewVmRef(a.templateID))
log.Printf("Destroying %s: %d", a.artifactType, a.artifactID)
_, err := a.proxmoxClient.DeleteVm(proxmox.NewVmRef(a.artifactID))
return err
}
17 changes: 11 additions & 6 deletions builder/proxmox/common/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook,
},
&stepRemoveCloudInitDrive{},
&stepConvertToTemplate{},
&stepFinalizeTemplateConfig{},
&stepFinalizeConfig{},
&stepSuccess{},
}
preSteps := b.preSteps
Expand Down Expand Up @@ -118,19 +118,24 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook,
return nil, errors.New("build was cancelled")
}

// Verify that the template_id was set properly, otherwise we didn't progress through the last step
tplID, ok := state.Get("template_id").(int)
// Verify that the artifact_id and artifact_type was set properly, otherwise we didn't progress through the last step
artifactID, ok := state.Get("artifact_id").(int)
if !ok {
return nil, fmt.Errorf("template ID could not be determined")
return nil, fmt.Errorf("artifact ID could not be determined")
}

artifactType, ok := state.Get("artifact_type").(string)
if !ok {
return nil, fmt.Errorf("artifact type could not be determined")
}

artifact := &Artifact{
builderID: b.id,
templateID: tplID,
artifactID: artifactID,
artifactType: artifactType,
proxmoxClient: b.proxmoxClient,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}

return artifact, nil
}

Expand Down
3 changes: 3 additions & 0 deletions builder/proxmox/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ type Config struct {
// Description of the template, visible in
// the Proxmox interface.
TemplateDescription string `mapstructure:"template_description"`
// Skip converting the VM to a template on completion of build.
// Defaults to `false`
SkipConvertToTemplate bool `mapstructure:"skip_convert_to_template"`

// If true, add an empty Cloud-Init CDROM drive after the virtual
// machine has been converted to a template. Defaults to `false`.
Expand Down
2 changes: 2 additions & 0 deletions builder/proxmox/common/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 18 additions & 10 deletions builder/proxmox/common/step_convert_to_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// stepConvertToTemplate takes the running VM configured in earlier steps, stops it, and
// converts it into a Proxmox template.
//
// It sets the template_id state which is used for Artifact lookup.
// It sets the artifact_id state which is used for Artifact lookup.
type stepConvertToTemplate struct{}

type templateConverter interface {
Expand All @@ -30,6 +30,7 @@ func (s *stepConvertToTemplate) Run(ctx context.Context, state multistep.StateBa
ui := state.Get("ui").(packersdk.Ui)
client := state.Get("proxmoxClient").(templateConverter)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
c := state.Get("config").(*Config)

ui.Say("Stopping VM")
_, err := client.ShutdownVm(vmRef)
Expand All @@ -40,16 +41,23 @@ func (s *stepConvertToTemplate) Run(ctx context.Context, state multistep.StateBa
return multistep.ActionHalt
}

ui.Say("Converting VM to template")
err = client.CreateTemplate(vmRef)
if err != nil {
err := fmt.Errorf("Error converting VM to template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
if c.SkipConvertToTemplate {
ui.Say("skip_convert_to_template set, skipping conversion to template")
state.Put("artifact_type", "VM")
} else {
ui.Say("Converting VM to template")
err = client.CreateTemplate(vmRef)
if err != nil {
err := fmt.Errorf("Error converting VM to template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("artifact_type", "template")
}
log.Printf("template_id: %d", vmRef.VmId())
state.Put("template_id", vmRef.VmId())

log.Printf("artifact_id: %d", vmRef.VmId())
state.Put("artifact_id", vmRef.VmId())

return multistep.ActionContinue
}
Expand Down
46 changes: 37 additions & 9 deletions builder/proxmox/common/step_convert_to_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,45 @@ func TestConvertToTemplate(t *testing.T) {
expectCallCreateTemplate bool
createTemplateErr error
expectedAction multistep.StepAction
expectTemplateIdSet bool
expectArtifactIdSet bool
expectArtifactType string
builderConfig *Config
}{
{
name: "no errors returns continue and sets template id",
expectCallCreateTemplate: true,
expectedAction: multistep.ActionContinue,
expectTemplateIdSet: true,
expectArtifactIdSet: true,
expectArtifactType: "template",
builderConfig: &Config{},
},
{
name: "no errors returns continue and sets vm id",
expectCallCreateTemplate: true,
expectedAction: multistep.ActionContinue,
expectArtifactIdSet: true,
expectArtifactType: "VM",
builderConfig: &Config{
SkipConvertToTemplate: true,
},
},
{
name: "when shutdown fails, don't try to create template and halt",
shutdownErr: fmt.Errorf("failed to stop vm"),
expectCallCreateTemplate: false,
expectedAction: multistep.ActionHalt,
expectTemplateIdSet: false,
expectArtifactIdSet: false,
expectArtifactType: "",
builderConfig: &Config{},
},
{
name: "when create template fails, halt",
expectCallCreateTemplate: true,
createTemplateErr: fmt.Errorf("failed to stop vm"),
expectedAction: multistep.ActionHalt,
expectTemplateIdSet: false,
expectArtifactIdSet: false,
expectArtifactType: "",
builderConfig: &Config{},
},
}

Expand Down Expand Up @@ -85,21 +103,31 @@ func TestConvertToTemplate(t *testing.T) {
state.Put("ui", packersdk.TestUi(t))
state.Put("vmRef", proxmox.NewVmRef(vmid))
state.Put("proxmoxClient", converter)
state.Put("config", c.builderConfig)

step := stepConvertToTemplate{}
action := step.Run(context.TODO(), state)
if action != c.expectedAction {
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
}

id, wasSet := state.GetOk("template_id")
artifactId, artifactIdWasSet := state.GetOk("artifact_id")
artifactType, artifactTypeWasSet := state.GetOk("artifact_type")

if c.expectArtifactIdSet != artifactIdWasSet {
t.Errorf("Expected artifact_id state present=%v was present=%v", c.expectArtifactIdSet, artifactIdWasSet)
}

if c.expectArtifactIdSet && artifactId != vmid {
t.Errorf("Expected artifact_id state to be set to %d, got %v", vmid, artifactId)
}

if c.expectTemplateIdSet != wasSet {
t.Errorf("Expected template_id state present=%v was present=%v", c.expectTemplateIdSet, wasSet)
if c.expectArtifactType == "" && artifactTypeWasSet {
t.Errorf("Expected artifact_type state present=%v was present=%v", c.expectArtifactType, artifactTypeWasSet)
}

if c.expectTemplateIdSet && id != vmid {
t.Errorf("Expected template_id state to be set to %d, got %v", vmid, id)
if artifactTypeWasSet && c.expectArtifactType != artifactType {
t.Errorf("Expected artifact_type state to be set to %s, got %s", c.expectArtifactType, artifactType)
}
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ import (
// stepFinalizeTemplateConfig 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 stepFinalizeTemplateConfig struct{}
type stepFinalizeConfig struct{}

type templateFinalizer interface {
type finalizer interface {
GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error)
SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
StartVm(*proxmox.VmRef) (string, error)
}

var _ templateFinalizer = &proxmox.Client{}
var _ finalizer = &proxmox.Client{}

func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *stepFinalizeConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
client := state.Get("proxmoxClient").(templateFinalizer)
client := state.Get("proxmoxClient").(finalizer)
c := state.Get("config").(*Config)
vmRef := state.Get("vmRef").(*proxmox.VmRef)

Expand Down Expand Up @@ -151,4 +152,4 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
return multistep.ActionContinue
}

func (s *stepFinalizeTemplateConfig) Cleanup(state multistep.StateBag) {}
func (s *stepFinalizeConfig) Cleanup(state multistep.StateBag) {}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
type finalizerMock struct {
getConfig func() (map[string]interface{}, error)
setConfig func(map[string]interface{}) (string, error)
startVm func() (string, error)
}

func (m finalizerMock) GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error) {
Expand All @@ -27,7 +28,11 @@ func (m finalizerMock) SetVmConfig(vmref *proxmox.VmRef, c map[string]interface{
return m.setConfig(c)
}

var _ templateFinalizer = finalizerMock{}
func (m finalizerMock) StartVm(*proxmox.VmRef) (string, error) {
return m.startVm()
}

var _ finalizer = finalizerMock{}

func TestTemplateFinalize(t *testing.T) {
cs := []struct {
Expand Down Expand Up @@ -218,7 +223,7 @@ func TestTemplateFinalize(t *testing.T) {
state.Put("vmRef", proxmox.NewVmRef(1))
state.Put("proxmoxClient", finalizer)

step := stepFinalizeTemplateConfig{}
step := stepFinalizeConfig{}
action := step.Run(context.TODO(), state)
if action != c.expectedAction {
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
Expand Down
2 changes: 2 additions & 0 deletions builder/proxmox/iso/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions docs-partials/builder/proxmox/common/Config-not-required.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
- `template_description` (string) - Description of the template, visible in
the Proxmox interface.

- `skip_convert_to_template` (bool) - Skip converting the VM to a template on completion of build.
Defaults to `false`

- `cloud_init` (bool) - If true, add an empty Cloud-Init CDROM drive after the virtual
machine has been converted to a template. Defaults to `false`.

Expand Down

0 comments on commit b8c44e9

Please sign in to comment.