Skip to content

Commit 5510a80

Browse files
committed
feat: add support for datastore clusters
Adds support for the use of datastore clusters in the applicable builders and post-processors. Signed-off-by: Ryan Johnson <[email protected]>
1 parent cc8ebd4 commit 5510a80

27 files changed

+1128
-119
lines changed

.web-docs/components/builder/vsphere-clone/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,8 +1022,8 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
10221022
Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
10231023
section for more details.
10241024

1025-
- `host` (string) - The ESXi host where the virtual machine is created. A full path must be
1026-
specified if the ESXi host is in a folder. For example `folder/host`.
1025+
- `host` (string) - The ESX host where the virtual machine is created. A full path must be specified
1026+
if the ESX host is in a folder. For example `folder/host`.
10271027
Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
10281028
section for more details.
10291029

@@ -1036,9 +1036,17 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
10361036
a nested path might resemble 'rp-packer/rp-linux-images'.
10371037

10381038
- `datastore` (string) - The datastore where the virtual machine is created.
1039-
Required if `host` is a cluster, or if `host` has multiple datastores.
1039+
Required if `host` is a cluster or if `host` has multiple datastores,
1040+
unless `datastore_cluster` is specified.
1041+
1042+
~> **Note:** Cannot be used with `datastore_cluster`.
1043+
1044+
- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
1045+
When specified, Storage DRS will automatically select the optimal datastore.
1046+
1047+
~> **Note:** Cannot be used with `datastore`.
10401048

1041-
- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
1049+
- `set_host_for_datastore_uploads` (bool) - The ESX host used for uploading files to the datastore.
10421050
Defaults to `false`.
10431051

10441052
<!-- End of code generated from the comments of the LocationConfig struct in builder/vsphere/common/config_location.go; -->

.web-docs/components/builder/vsphere-iso/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
142142
Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
143143
section for more details.
144144

145-
- `host` (string) - The ESXi host where the virtual machine is created. A full path must be
146-
specified if the ESXi host is in a folder. For example `folder/host`.
145+
- `host` (string) - The ESX host where the virtual machine is created. A full path must be specified
146+
if the ESX host is in a folder. For example `folder/host`.
147147
Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
148148
section for more details.
149149

@@ -156,9 +156,17 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
156156
a nested path might resemble 'rp-packer/rp-linux-images'.
157157

158158
- `datastore` (string) - The datastore where the virtual machine is created.
159-
Required if `host` is a cluster, or if `host` has multiple datastores.
159+
Required if `host` is a cluster or if `host` has multiple datastores,
160+
unless `datastore_cluster` is specified.
161+
162+
~> **Note:** Cannot be used with `datastore_cluster`.
163+
164+
- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
165+
When specified, Storage DRS will automatically select the optimal datastore.
166+
167+
~> **Note:** Cannot be used with `datastore`.
160168

161-
- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
169+
- `set_host_for_datastore_uploads` (bool) - The ESX host used for uploading files to the datastore.
162170
Defaults to `false`.
163171

164172
<!-- End of code generated from the comments of the LocationConfig struct in builder/vsphere/common/config_location.go; -->

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ The following configuration options are available for the post-processor.
2323

2424
<!-- Code generated from the comments of the Config struct in post-processor/vsphere/post-processor.go; DO NOT EDIT MANUALLY -->
2525

26-
- `cluster` (string) - The cluster or ESX host to upload the virtual machine.
27-
This can be either the name of the vSphere cluster or the fully qualified domain name (FQDN)
28-
or IP address of the ESX host.
26+
- `cluster` (string) - The cluster or ESX host to upload the virtual machine. This can be either the
27+
name of the vSphere cluster or the fully qualified domain name (FQDN) or IP
28+
address of the ESX host.
2929

3030
- `datacenter` (string) - The name of the vSphere datacenter object to place the virtual machine.
3131
This is _not required_ if `resource_pool` is specified.
3232

33-
- `datastore` (string) - The name of the vSphere datastore to place the virtual machine.
34-
3533
- `host` (string) - The fully qualified domain name or IP address of the vCenter instance or ESX host.
3634

3735
- `password` (string) - The password to use to authenticate to the vSphere endpoint.
@@ -45,6 +43,13 @@ The following configuration options are available for the post-processor.
4543

4644
<!-- Code generated from the comments of the Config struct in post-processor/vsphere/post-processor.go; DO NOT EDIT MANUALLY -->
4745

46+
- `datastore` (string) - The name of the vSphere datastore to place the virtual machine.
47+
Mutually exclusive with `datastore_cluster`.
48+
49+
- `datastore_cluster` (string) - The name of the vSphere datastore cluster to place the virtual machine.
50+
When specified, Storage DRS will automatically select the optimal datastore.
51+
Mutually exclusive with `datastore`.
52+
4853
- `disk_mode` (string) - The disk format of the target virtual machine. One of `thin`, `thick`,
4954

5055
- `esxi_host` (string) - The fully qualified domain name or IP address of the ESX host to upload the
@@ -55,7 +60,8 @@ The following configuration options are available for the post-processor.
5560
- `options` ([]string) - Options to send to `ovftool` when uploading the virtual machine.
5661
Use `ovftool --help` to list all the options available.
5762

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

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

@@ -68,14 +74,15 @@ The following configuration options are available for the post-processor.
6874

6975
- `hardware_version` (string) - The maximum virtual hardware version for the deployed virtual machine.
7076

71-
It does not upgrade the virtual hardware version of the source VM. Instead, it limits the
72-
virtual hardware version of the deployed virtual machine to the specified version.
73-
If the source virtual machine's hardware version is higher than the specified version, the
74-
deployed virtual machine's hardware version will be downgraded to the specified version.
77+
It does not upgrade the virtual hardware version of the source VM. Instead, it
78+
limits the virtual hardware version of the deployed virtual machine to the
79+
specified version. If the source virtual machine's hardware version is higher
80+
than the specified version, the deployed virtual machine's hardware version will
81+
be downgraded to the specified version.
7582

76-
If the source virtual machine's hardware version is lower than or equal to the specified
77-
version, the deployed virtual machine's hardware version will be the same as the source
78-
virtual machine's.
83+
If the source virtual machine's hardware version is lower than or equal to the
84+
specified version, the deployed virtual machine's hardware version will be the
85+
same as the source virtual machine's.
7986

8087
This option is useful when deploying to vCenter instance or an ESX host whose
8188
version is different than the one used to create the artifact.

builder/vsphere/clone/builder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
4444
&common.StepConnect{
4545
Config: &b.config.ConnectConfig,
4646
},
47+
&common.StepResolveDatastore{
48+
Datastore: b.config.Datastore,
49+
DatastoreCluster: b.config.DatastoreCluster,
50+
DiskCount: len(b.config.StorageConfig.Storage),
51+
},
4752
&commonsteps.StepCreateCD{
4853
Files: b.config.CDFiles,
4954
Content: b.config.CDContent,

builder/vsphere/clone/config.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.

builder/vsphere/clone/step_clone.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package clone
99
import (
1010
"context"
1111
"fmt"
12+
"log"
1213
"path"
1314
"strings"
1415

@@ -17,6 +18,7 @@ import (
1718
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
1819
"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
1920
"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
21+
"github.com/vmware/govmomi/vim25/types"
2022
)
2123

2224
type vAppConfig struct {
@@ -159,13 +161,61 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
159161
})
160162
}
161163

164+
datastoreName := s.Location.Datastore
165+
var primaryDatastore driver.Datastore
166+
if ds, ok := state.GetOk("datastore"); ok {
167+
primaryDatastore = ds.(driver.Datastore)
168+
datastoreName = primaryDatastore.Name()
169+
}
170+
171+
// If no datastore was resolved and no datastore was specified, return an error.
172+
if datastoreName == "" && s.Location.DatastoreCluster == "" {
173+
state.Put("error", fmt.Errorf("no datastore specified and no datastore resolved from cluster"))
174+
return multistep.ActionHalt
175+
}
176+
177+
// Handle multi-disk placement when using a datastore cluster.
178+
var datastoreRefs []*types.ManagedObjectReference
179+
if s.Location.DatastoreCluster != "" && len(disks) > 1 {
180+
if vcDriver, ok := d.(*driver.VCenterDriver); ok {
181+
// Request Storage DRS recommendations for all disks at once for optimal placement.
182+
ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks))
183+
184+
diskDatastores, method, err := vcDriver.SelectDatastoresForDisks(s.Location.DatastoreCluster, disks)
185+
if err != nil {
186+
ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err)
187+
if primaryDatastore != nil {
188+
ref := primaryDatastore.Reference()
189+
for i := 0; i < len(disks); i++ {
190+
datastoreRefs = append(datastoreRefs, &ref)
191+
}
192+
}
193+
} else {
194+
// Use the first disk's datastore as the primary datastore.
195+
if len(diskDatastores) > 0 {
196+
datastoreName = diskDatastores[0].Name()
197+
}
198+
199+
for i, ds := range diskDatastores {
200+
ref := ds.Reference()
201+
if method == driver.SelectionMethodDRS {
202+
log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name())
203+
} else {
204+
log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name())
205+
}
206+
datastoreRefs = append(datastoreRefs, &ref)
207+
}
208+
}
209+
}
210+
}
211+
162212
vm, err := template.Clone(ctx, &driver.CloneConfig{
163213
Name: s.Location.VMName,
164214
Folder: s.Location.Folder,
165215
Cluster: s.Location.Cluster,
166216
Host: s.Location.Host,
167217
ResourcePool: s.Location.ResourcePool,
168-
Datastore: s.Location.Datastore,
218+
Datastore: datastoreName,
169219
LinkedClone: s.Config.LinkedClone,
170220
Network: s.Config.Network,
171221
MacAddress: strings.ToLower(s.Config.MacAddress),
@@ -175,6 +225,7 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
175225
StorageConfig: driver.StorageConfig{
176226
DiskControllerType: s.Config.StorageConfig.DiskControllerType,
177227
Storage: disks,
228+
DatastoreRefs: datastoreRefs,
178229
},
179230
})
180231
if err != nil {

builder/vsphere/common/config_location.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type LocationConfig struct {
2121
// Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
2222
// section for more details.
2323
Cluster string `mapstructure:"cluster"`
24-
// The ESXi host where the virtual machine is created. A full path must be
25-
// specified if the ESXi host is in a folder. For example `folder/host`.
24+
// The ESX host where the virtual machine is created. A full path must be specified
25+
// if the ESX host is in a folder. For example `folder/host`.
2626
// Refer to the [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
2727
// section for more details.
2828
Host string `mapstructure:"host"`
@@ -35,9 +35,17 @@ type LocationConfig struct {
3535
// a nested path might resemble 'rp-packer/rp-linux-images'.
3636
ResourcePool string `mapstructure:"resource_pool"`
3737
// The datastore where the virtual machine is created.
38-
// Required if `host` is a cluster, or if `host` has multiple datastores.
38+
// Required if `host` is a cluster or if `host` has multiple datastores,
39+
// unless `datastore_cluster` is specified.
40+
//
41+
// ~> **Note:** Cannot be used with `datastore_cluster`.
3942
Datastore string `mapstructure:"datastore"`
40-
// The ESXI host used for uploading files to the datastore.
43+
// The datastore cluster where the virtual machine is created.
44+
// When specified, Storage DRS will automatically select the optimal datastore.
45+
//
46+
// ~> **Note:** Cannot be used with `datastore`.
47+
DatastoreCluster string `mapstructure:"datastore_cluster"`
48+
// The ESX host used for uploading files to the datastore.
4149
// Defaults to `false`.
4250
SetHostForDatastoreUploads bool `mapstructure:"set_host_for_datastore_uploads"`
4351
}
@@ -52,7 +60,10 @@ func (c *LocationConfig) Prepare() []error {
5260
errs = append(errs, fmt.Errorf("'host' or 'cluster' is required"))
5361
}
5462

55-
// clean Folder path and remove leading slash as folders are relative within vsphere
63+
if c.Datastore != "" && c.DatastoreCluster != "" {
64+
errs = append(errs, fmt.Errorf("'datastore' and 'datastore_cluster' are mutually exclusive; specify only one"))
65+
}
66+
5667
c.Folder = path.Clean(c.Folder)
5768
c.Folder = strings.TrimLeft(c.Folder, "/")
5869

builder/vsphere/common/config_location.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.

builder/vsphere/common/step_add_floppy.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,18 @@ func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multist
6363
if floppyPath, ok := state.GetOk("floppy_path"); ok {
6464
ui.Say("Uploading floppy image...")
6565

66-
ds, err := d.FindDatastore(s.Datastore, s.Host)
67-
if err != nil {
68-
state.Put("error", err)
69-
return multistep.ActionHalt
66+
var ds driver.Datastore
67+
var err error
68+
69+
// If a datastore was resolved (from datastore or datastore_cluster), use it.
70+
if resolvedDs, ok := state.GetOk("datastore"); ok {
71+
ds = resolvedDs.(driver.Datastore)
72+
} else {
73+
ds, err = d.FindDatastore(s.Datastore, s.Host)
74+
if err != nil {
75+
state.Put("error", err)
76+
return multistep.ActionHalt
77+
}
7078
}
7179
vmDir, err := vm.GetDir()
7280
if err != nil {
@@ -123,10 +131,18 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
123131
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
124132
ui.Say("Deleting floppy image...")
125133

126-
ds, err := d.FindDatastore(s.Datastore, s.Host)
127-
if err != nil {
128-
state.Put("error", err)
129-
return
134+
var ds driver.Datastore
135+
var err error
136+
137+
// If a datastore was resolved (from datastore or datastore_cluster), use it.
138+
if resolvedDs, ok := state.GetOk("datastore"); ok {
139+
ds = resolvedDs.(driver.Datastore)
140+
} else {
141+
ds, err = d.FindDatastore(s.Datastore, s.Host)
142+
if err != nil {
143+
state.Put("error", err)
144+
return
145+
}
130146
}
131147

132148
err = ds.Delete(UploadedFloppyPath.(string))

builder/vsphere/common/step_download.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type StepDownload struct {
4646
}
4747

4848
func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
49-
driver := state.Get("driver").(driver.Driver)
49+
d := state.Get("driver").(driver.Driver)
5050
ui := state.Get("ui").(packersdk.Ui)
5151

5252
// Set the remote cache datastore. If not set, use the default datastore for the build.
@@ -55,11 +55,19 @@ func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multis
5555
remoteCacheDatastore = s.RemoteCacheDatastore
5656
}
5757

58-
// Find the datastore to use for the remote cache.
59-
ds, err := driver.FindDatastore(remoteCacheDatastore, s.Host)
60-
if err != nil {
61-
state.Put("error", fmt.Errorf("error finding the datastore: %v", err))
62-
return multistep.ActionHalt
58+
var ds driver.Datastore
59+
var err error
60+
61+
// If a datastore was resolved (from datastore or datastore_cluster), use it.
62+
if resolvedDs, ok := state.GetOk("datastore"); ok && remoteCacheDatastore == s.Datastore {
63+
ds = resolvedDs.(driver.Datastore)
64+
} else {
65+
// Find the datastore to use for the remote cache.
66+
ds, err = d.FindDatastore(remoteCacheDatastore, s.Host)
67+
if err != nil {
68+
state.Put("error", fmt.Errorf("error finding the datastore: %v", err))
69+
return multistep.ActionHalt
70+
}
6371
}
6472

6573
// Set the remote cache path. If not set, use the default cache path.

0 commit comments

Comments
 (0)