Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .web-docs/components/builder/vsphere-clone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,15 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
a nested path might resemble 'rp-packer/rp-linux-images'.

- `datastore` (string) - The datastore where the virtual machine is created.
Required if `host` is a cluster, or if `host` has multiple datastores.
Required if `host` is a cluster or if `host` has multiple datastores,
unless `datastore_cluster` is specified.

~> **Note:** Cannot be used with `datastore_cluster`.

- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
When specified, Storage DRS will automatically select the optimal datastore.

~> **Note:** Cannot be used with `datastore`.

- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
Defaults to `false`.
Expand Down
10 changes: 9 additions & 1 deletion .web-docs/components/builder/vsphere-iso/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,15 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
a nested path might resemble 'rp-packer/rp-linux-images'.

- `datastore` (string) - The datastore where the virtual machine is created.
Required if `host` is a cluster, or if `host` has multiple datastores.
Required if `host` is a cluster or if `host` has multiple datastores,
unless `datastore_cluster` is specified.

~> **Note:** Cannot be used with `datastore_cluster`.

- `datastore_cluster` (string) - The datastore cluster where the virtual machine is created.
When specified, Storage DRS will automatically select the optimal datastore.

~> **Note:** Cannot be used with `datastore`.

- `set_host_for_datastore_uploads` (bool) - The ESXI host used for uploading files to the datastore.
Defaults to `false`.
Expand Down
5 changes: 5 additions & 0 deletions builder/vsphere/clone/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
&common.StepResolveDatastore{
Datastore: b.config.Datastore,
DatastoreCluster: b.config.DatastoreCluster,
DiskCount: len(b.config.StorageConfig.Storage),
},
&commonsteps.StepCreateCD{
Files: b.config.CDFiles,
Content: b.config.CDContent,
Expand Down
2 changes: 2 additions & 0 deletions builder/vsphere/clone/config.hcl2spec.go

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

53 changes: 52 additions & 1 deletion builder/vsphere/clone/step_clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package clone
import (
"context"
"fmt"
"log"
"path"
"strings"

Expand All @@ -17,6 +18,7 @@ import (
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
"github.com/vmware/govmomi/vim25/types"
)

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

datastoreName := s.Location.Datastore
var primaryDatastore driver.Datastore
if ds, ok := state.GetOk("datastore"); ok {
primaryDatastore = ds.(driver.Datastore)
datastoreName = primaryDatastore.Name()
}

// If no datastore was resolved and no datastore was specified, return an error
if datastoreName == "" && s.Location.DatastoreCluster == "" {
state.Put("error", fmt.Errorf("no datastore specified and no datastore resolved from cluster"))
return multistep.ActionHalt
}

// Handle multi-disk placement when using a datastore cluster.
var datastoreRefs []*types.ManagedObjectReference
if s.Location.DatastoreCluster != "" && len(disks) > 1 {
if vcDriver, ok := d.(*driver.VCenterDriver); ok {
// Request Storage DRS recommendations for all disks at once for optimal placement.
ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks))

diskDatastores, method, err := vcDriver.SelectDatastoresForDisks(s.Location.DatastoreCluster, disks)
if err != nil {
ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err)
if primaryDatastore != nil {
ref := primaryDatastore.Reference()
for i := 0; i < len(disks); i++ {
datastoreRefs = append(datastoreRefs, &ref)
}
}
} else {
// Use the first disk's datastore as the primary datastore.
if len(diskDatastores) > 0 {
datastoreName = diskDatastores[0].Name()
}

for i, ds := range diskDatastores {
ref := ds.Reference()
if method == driver.SelectionMethodDRS {
log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name())
} else {
log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name())
}
datastoreRefs = append(datastoreRefs, &ref)
}
}
}
}

vm, err := template.Clone(ctx, &driver.CloneConfig{
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
Datastore: datastoreName,
LinkedClone: s.Config.LinkedClone,
Network: s.Config.Network,
MacAddress: strings.ToLower(s.Config.MacAddress),
Expand All @@ -175,6 +225,7 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
StorageConfig: driver.StorageConfig{
DiskControllerType: s.Config.StorageConfig.DiskControllerType,
Storage: disks,
DatastoreRefs: datastoreRefs,
},
})
if err != nil {
Expand Down
15 changes: 13 additions & 2 deletions builder/vsphere/common/config_location.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,16 @@ type LocationConfig struct {
// a nested path might resemble 'rp-packer/rp-linux-images'.
ResourcePool string `mapstructure:"resource_pool"`
// The datastore where the virtual machine is created.
// Required if `host` is a cluster, or if `host` has multiple datastores.
// Required if `host` is a cluster or if `host` has multiple datastores,
// unless `datastore_cluster` is specified.
//
// ~> **Note:** Cannot be used with `datastore_cluster`.
Datastore string `mapstructure:"datastore"`
// The datastore cluster where the virtual machine is created.
// When specified, Storage DRS will automatically select the optimal datastore.
//
// ~> **Note:** Cannot be used with `datastore`.
DatastoreCluster string `mapstructure:"datastore_cluster"`
// The ESXI host used for uploading files to the datastore.
// Defaults to `false`.
SetHostForDatastoreUploads bool `mapstructure:"set_host_for_datastore_uploads"`
Expand All @@ -52,7 +60,10 @@ func (c *LocationConfig) Prepare() []error {
errs = append(errs, fmt.Errorf("'host' or 'cluster' is required"))
}

// clean Folder path and remove leading slash as folders are relative within vsphere
if c.Datastore != "" && c.DatastoreCluster != "" {
errs = append(errs, fmt.Errorf("'datastore' and 'datastore_cluster' are mutually exclusive; specify only one"))
}

c.Folder = path.Clean(c.Folder)
c.Folder = strings.TrimLeft(c.Folder, "/")

Expand Down
2 changes: 2 additions & 0 deletions builder/vsphere/common/config_location.hcl2spec.go

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

32 changes: 24 additions & 8 deletions builder/vsphere/common/step_add_floppy.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,18 @@ func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multist
if floppyPath, ok := state.GetOk("floppy_path"); ok {
ui.Say("Uploading floppy image...")

ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
var ds driver.Datastore
var err error

// If a datastore was resolved (from datastore or datastore_cluster), use it.
if resolvedDs, ok := state.GetOk("datastore"); ok {
ds = resolvedDs.(driver.Datastore)
} else {
ds, err = d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
vmDir, err := vm.GetDir()
if err != nil {
Expand Down Expand Up @@ -123,10 +131,18 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting floppy image...")

ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return
var ds driver.Datastore
var err error

// If a datastore was resolved (from datastore or datastore_cluster), use it.
if resolvedDs, ok := state.GetOk("datastore"); ok {
ds = resolvedDs.(driver.Datastore)
} else {
ds, err = d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return
}
}

err = ds.Delete(UploadedFloppyPath.(string))
Expand Down
20 changes: 14 additions & 6 deletions builder/vsphere/common/step_download.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type StepDownload struct {
}

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

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

// Find the datastore to use for the remote cache.
ds, err := driver.FindDatastore(remoteCacheDatastore, s.Host)
if err != nil {
state.Put("error", fmt.Errorf("error finding the datastore: %v", err))
return multistep.ActionHalt
var ds driver.Datastore
var err error

// If a datastore was resolved (from datastore or datastore_cluster), use it.
if resolvedDs, ok := state.GetOk("datastore"); ok && remoteCacheDatastore == s.Datastore {
ds = resolvedDs.(driver.Datastore)
} else {
// Find the datastore to use for the remote cache.
ds, err = d.FindDatastore(remoteCacheDatastore, s.Host)
if err != nil {
state.Put("error", fmt.Errorf("error finding the datastore: %v", err))
return multistep.ActionHalt
}
}

// Set the remote cache path. If not set, use the default cache path.
Expand Down
38 changes: 27 additions & 11 deletions builder/vsphere/common/step_remote_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult

if path, ok := state.GetOk("iso_path"); ok {
// user-supplied boot iso
fullRemotePath, err := s.uploadFile(path.(string), d, ui)
fullRemotePath, err := s.uploadFile(path.(string), d, ui, state)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
Expand All @@ -41,7 +41,7 @@ func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
}
if cdPath, ok := state.GetOk("cd_path"); ok {
// Packer-created cd_files disk
fullRemotePath, err := s.uploadFile(cdPath.(string), d, ui)
fullRemotePath, err := s.uploadFile(cdPath.(string), d, ui, state)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
Expand All @@ -66,18 +66,26 @@ func GetRemoteDirectoryAndPath(path string, ds driver.Datastore, remoteCachePath
return filename, remotePath, remoteDirectory, fullRemotePath
}

func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packersdk.Ui) (string, error) {
func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packersdk.Ui, state multistep.StateBag) (string, error) {

// Set the remote cache datastore. If not set, use the default datastore for the build.
remoteCacheDatastore := s.Datastore
if s.RemoteCacheDatastore != "" {
remoteCacheDatastore = s.RemoteCacheDatastore
}

// Find the datastore to use for the remote cache.
ds, err := d.FindDatastore(remoteCacheDatastore, s.Host)
if err != nil {
return "", fmt.Errorf("error finding the remote cache datastore: %v", err)
var ds driver.Datastore
var err error

// If a datastore was resolved (from datastore or datastore_cluster), use it.
if resolvedDs, ok := state.GetOk("datastore"); ok && remoteCacheDatastore == s.Datastore {
ds = resolvedDs.(driver.Datastore)
} else {
// Find the datastore to use for the remote cache.
ds, err = d.FindDatastore(remoteCacheDatastore, s.Host)
if err != nil {
return "", fmt.Errorf("error finding the remote cache datastore: %v", err)
}
}

// Set the remote cache path. If not set, use the default cache path.
Expand Down Expand Up @@ -143,10 +151,18 @@ func (s *StepRemoteUpload) Cleanup(state multistep.StateBag) {
d := state.Get("driver").(*driver.VCenterDriver)
ui.Sayf("Removing %s...", UploadedCDPath)

ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
ui.Sayf("Unable to find the remote cache datastore. Please remove the item manually: %s", err)
return
var ds driver.Datastore
var err error

// If a datastore was resolved (from datastore or datastore_cluster), use it.
if resolvedDs, ok := state.GetOk("datastore"); ok {
ds = resolvedDs.(driver.Datastore)
} else {
ds, err = d.FindDatastore(s.Datastore, s.Host)
if err != nil {
ui.Sayf("Unable to find the remote cache datastore. Please remove the item manually: %s", err)
return
}
}

err = ds.Delete(UploadedCDPath.(string))
Expand Down
16 changes: 12 additions & 4 deletions builder/vsphere/common/step_remove_floppy.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,18 @@ func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) mult

if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting floppy image...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt

var ds driver.Datastore

// If a datastore was resolved (from datastore or datastore_cluster), use it.
if resolvedDs, ok := state.GetOk("datastore"); ok {
ds = resolvedDs.(driver.Datastore)
} else {
ds, err = d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
if err := ds.Delete(UploadedFloppyPath.(string)); err != nil {
state.Put("error", err)
Expand Down
Loading
Loading