Skip to content

Commit 495050b

Browse files
committed
feat: add override option
Adds an `override` (bool) option to the `vsphere-template` post-processor, allowing the overwrite of an existing template if set to `true`. Signed-off-by: Ryan Johnson <ryan@tenthirtyam.org>
1 parent 1f30c27 commit 495050b

File tree

11 files changed

+264
-22
lines changed

11 files changed

+264
-22
lines changed

.web-docs/components/post-processor/vsphere-template/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ The following configuration options are available for the post-processor.
5252

5353
- `snapshot_description` (string) - A description for the snapshot. Required when `snapshot_enable` is `true`.
5454

55-
- `reregister_vm` (boolean) - Keepe the virtual machine registered after marking as a template.
55+
- `reregister_vm` (boolean) - Keep the virtual machine registered after marking as a template.
56+
57+
- `override` (bool) - Overwrite existing template. Defaults to `false`.
5658

5759
<!-- End of code generated from the comments of the Config struct in post-processor/vsphere-template/post-processor.go; -->
5860

.web-docs/components/post-processor/vsphere/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ The following configuration options are available for the post-processor.
5555
- `options` ([]string) - Options to send to `ovftool` when uploading the virtual machine.
5656
Use `ovftool --help` to list all the options available.
5757

58-
- `overwrite` (bool) - Overwrite existing files.
59-
If `true`, forces overwrites of existing files. Defaults to `false`.
58+
- `overwrite` (bool) - Overwrite existing files. Defaults to `false`.
6059

6160
- `resource_pool` (string) - The name of the resource pool to place the virtual machine.
6261

docs-partials/post-processor/vsphere-template/Config-not-required.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
- `snapshot_description` (string) - A description for the snapshot. Required when `snapshot_enable` is `true`.
1818

19-
- `reregister_vm` (boolean) - Keepe the virtual machine registered after marking as a template.
19+
- `reregister_vm` (boolean) - Keep the virtual machine registered after marking as a template.
20+
21+
- `override` (bool) - Overwrite existing template. Defaults to `false`.
2022

2123
<!-- End of code generated from the comments of the Config struct in post-processor/vsphere-template/post-processor.go; -->

docs-partials/post-processor/vsphere/Config-not-required.mdx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
- `options` ([]string) - Options to send to `ovftool` when uploading the virtual machine.
1111
Use `ovftool --help` to list all the options available.
1212

13-
- `overwrite` (bool) - Overwrite existing files.
14-
If `true`, forces overwrites of existing files. Defaults to `false`.
13+
- `overwrite` (bool) - Overwrite existing files. Defaults to `false`.
1514

1615
- `resource_pool` (string) - The name of the resource pool to place the virtual machine.
1716

post-processor/vsphere-template/post-processor.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ type Config struct {
6262
SnapshotName string `mapstructure:"snapshot_name"`
6363
// A description for the snapshot. Required when `snapshot_enable` is `true`.
6464
SnapshotDescription string `mapstructure:"snapshot_description"`
65-
// Keepe the virtual machine registered after marking as a template.
65+
// Keep the virtual machine registered after marking as a template.
6666
ReregisterVM config.Trilean `mapstructure:"reregister_vm"`
67+
// Overwrite existing template. Defaults to `false`.
68+
Override bool `mapstructure:"override"`
6769

6870
ctx interpolate.Context
6971
}

post-processor/vsphere-template/post-processor.hcl2spec.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

post-processor/vsphere-template/post-processor_test.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func getTestConfig() Config {
1515
}
1616
}
1717

18-
func TestConfigure_Good(t *testing.T) {
18+
func TestConfigure_Valid(t *testing.T) {
1919
var p PostProcessor
2020

2121
config := getTestConfig()
@@ -26,7 +26,7 @@ func TestConfigure_Good(t *testing.T) {
2626
}
2727
}
2828

29-
func TestConfigure_ReRegisterVM(t *testing.T) {
29+
func TestConfigure_ReregisterVM_Default(t *testing.T) {
3030
var p PostProcessor
3131

3232
config := getTestConfig()
@@ -40,3 +40,47 @@ func TestConfigure_ReRegisterVM(t *testing.T) {
4040
t.Errorf("error: should be unset, not false")
4141
}
4242
}
43+
44+
func TestConfigure_Override(t *testing.T) {
45+
tests := []struct {
46+
name string
47+
override *bool
48+
expected bool
49+
}{
50+
{
51+
name: "default",
52+
override: nil,
53+
expected: false,
54+
},
55+
{
56+
name: "true",
57+
override: &[]bool{true}[0],
58+
expected: true,
59+
},
60+
{
61+
name: "false",
62+
override: &[]bool{false}[0],
63+
expected: false,
64+
},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
var p PostProcessor
70+
config := getTestConfig()
71+
72+
if tt.override != nil {
73+
config.Override = *tt.override
74+
}
75+
76+
err := p.Configure(config)
77+
if err != nil {
78+
t.Errorf("error: %s", err)
79+
}
80+
81+
if p.config.Override != tt.expected {
82+
t.Errorf("expected override to be %v, got %v", tt.expected, p.config.Override)
83+
}
84+
})
85+
}
86+
}

post-processor/vsphere-template/step_create_snapshot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (s *StepCreateSnapshot) Run(ctx context.Context, state multistep.StateBag)
5252

5353
ui.Say("Creating virtual machine snapshot...")
5454

55-
vm, err := findRuntimeVM(cli, dcPath, s.VMName, s.RemoteFolder)
55+
vm, err := findVirtualMachine(cli, dcPath, s.VMName, s.RemoteFolder)
5656
if err != nil {
5757
state.Put("error", err)
5858
ui.Errorf("%s", err)

post-processor/vsphere-template/step_mark_as_template.go

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type StepMarkAsTemplate struct {
2424
TemplateName string
2525
RemoteFolder string
2626
ReregisterVM config.Trilean
27+
Override bool
2728
}
2829

2930
func NewStepMarkAsTemplate(artifact packersdk.Artifact, p *PostProcessor) *StepMarkAsTemplate {
@@ -48,6 +49,7 @@ func NewStepMarkAsTemplate(artifact packersdk.Artifact, p *PostProcessor) *StepM
4849
TemplateName: p.config.TemplateName,
4950
RemoteFolder: remoteFolder,
5051
ReregisterVM: p.config.ReregisterVM,
52+
Override: p.config.Override,
5153
}
5254
}
5355

@@ -57,13 +59,25 @@ func (s *StepMarkAsTemplate) Run(ctx context.Context, state multistep.StateBag)
5759
folder := state.Get("folder").(*object.Folder)
5860
dcPath := state.Get("dcPath").(string)
5961

60-
vm, err := findRuntimeVM(cli, dcPath, s.VMName, s.RemoteFolder)
62+
vm, err := findVirtualMachine(cli, dcPath, s.VMName, s.RemoteFolder)
6163
if err != nil {
6264
state.Put("error", err)
6365
ui.Errorf("%s", err)
6466
return multistep.ActionHalt
6567
}
6668

69+
templateName := s.VMName
70+
if s.TemplateName != "" {
71+
templateName = s.TemplateName
72+
}
73+
74+
action, err := handleExistingTemplate(cli, folder, templateName, s.Override, ui)
75+
if err != nil {
76+
state.Put("error", err)
77+
ui.Errorf("%s", err)
78+
return action
79+
}
80+
6781
// Use the MarkAsTemplate method unless the `reregister_vm` is set to `true`.
6882
if s.ReregisterVM.False() {
6983
ui.Say("Marking as a template...")
@@ -96,15 +110,23 @@ func (s *StepMarkAsTemplate) Run(ctx context.Context, state multistep.StateBag)
96110
return multistep.ActionHalt
97111
}
98112

99-
if err := unregisterPreviousVM(cli, folder, s.VMName); err != nil {
113+
artifactName := s.VMName
114+
if s.TemplateName != "" {
115+
artifactName = s.TemplateName
116+
}
117+
118+
if err := unregisterVirtualMachine(cli, folder, artifactName); err != nil {
100119
state.Put("error", err)
101-
ui.Errorf("unregisterPreviousVM: %s", err)
120+
ui.Errorf("unregisterVirtualMachine: %s", err)
102121
return multistep.ActionHalt
103122
}
104123

105-
artifactName := s.VMName
106-
if s.TemplateName != "" {
107-
artifactName = s.TemplateName
124+
// Check if a template with the target name already exists in the destination folder.
125+
action, err = handleExistingTemplate(cli, folder, artifactName, s.Override, ui)
126+
if err != nil {
127+
state.Put("error", err)
128+
ui.Errorf("%s", err)
129+
return action
108130
}
109131

110132
ui.Say("Registering virtual machine as a template: " + artifactName)
@@ -155,7 +177,7 @@ func datastorePath(vm *object.VirtualMachine) (*object.DatastorePath, error) {
155177
}, nil
156178
}
157179

158-
func findRuntimeVM(cli *govmomi.Client, dcPath, name, remoteFolder string) (*object.VirtualMachine, error) {
180+
func findVirtualMachine(cli *govmomi.Client, dcPath, name, remoteFolder string) (*object.VirtualMachine, error) {
159181
si := object.NewSearchIndex(cli.Client)
160182
fullPath := path.Join(dcPath, "vm", remoteFolder, name)
161183

@@ -175,7 +197,7 @@ func findRuntimeVM(cli *govmomi.Client, dcPath, name, remoteFolder string) (*obj
175197
return vm, nil
176198
}
177199

178-
func unregisterPreviousVM(cli *govmomi.Client, folder *object.Folder, name string) error {
200+
func unregisterVirtualMachine(cli *govmomi.Client, folder *object.Folder, name string) error {
179201
si := object.NewSearchIndex(cli.Client)
180202
fullPath := path.Join(folder.InventoryPath, name)
181203

@@ -187,11 +209,52 @@ func unregisterPreviousVM(cli *govmomi.Client, folder *object.Folder, name strin
187209
if ref != nil {
188210
if vm, ok := ref.(*object.VirtualMachine); ok {
189211
return vm.Unregister(context.Background())
190-
} else {
191-
return fmt.Errorf("object name '%v' already exists", name)
192212
}
213+
return fmt.Errorf("object name '%v' already exists", name)
193214
}
194215
return nil
195216
}
196217

218+
func findTemplate(cli *govmomi.Client, folder *object.Folder, name string) (*object.VirtualMachine, error) {
219+
si := object.NewSearchIndex(cli.Client)
220+
fullPath := path.Join(folder.InventoryPath, name)
221+
222+
ref, err := si.FindByInventoryPath(context.Background(), fullPath)
223+
if err != nil {
224+
return nil, err
225+
}
226+
227+
if ref != nil {
228+
if vm, ok := ref.(*object.VirtualMachine); ok {
229+
return vm, nil
230+
}
231+
}
232+
return nil, nil
233+
}
234+
235+
func handleExistingTemplate(cli *govmomi.Client, folder *object.Folder, templateName string, override bool, ui packersdk.Ui) (multistep.StepAction, error) {
236+
existingTemplate, err := findTemplate(cli, folder, templateName)
237+
if err != nil {
238+
return multistep.ActionHalt, fmt.Errorf("error checking for existing template: %s", err)
239+
}
240+
241+
if existingTemplate != nil {
242+
if !override {
243+
return multistep.ActionHalt, fmt.Errorf("template '%s' already exists. Set 'override = true' to replace existing templates", templateName)
244+
}
245+
246+
ui.Say(fmt.Sprintf("Removing existing template '%s'...", templateName))
247+
task, err := existingTemplate.Destroy(context.Background())
248+
if err != nil {
249+
return multistep.ActionHalt, fmt.Errorf("failed to remove existing template '%s': %s", templateName, err)
250+
}
251+
if err = task.Wait(context.Background()); err != nil {
252+
return multistep.ActionHalt, fmt.Errorf("failed to remove existing template '%s': %s", templateName, err)
253+
}
254+
ui.Say(fmt.Sprintf("Successfully removed existing template '%s'", templateName))
255+
}
256+
257+
return multistep.ActionContinue, nil
258+
}
259+
197260
func (s *StepMarkAsTemplate) Cleanup(multistep.StateBag) {}

0 commit comments

Comments
 (0)