diff --git a/builder/vsphere/clone/builder.go b/builder/vsphere/clone/builder.go index ea7118113..100670754 100644 --- a/builder/vsphere/clone/builder.go +++ b/builder/vsphere/clone/builder.go @@ -139,7 +139,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } artifact := &common.Artifact{ Name: b.config.VMName, - VM: state.Get("vm").(*driver.VirtualMachine), + VM: state.Get("vm").(*driver.VirtualMachineDriver), StateData: map[string]interface{}{"generated_data": state.Get("generated_data")}, } if b.config.Export != nil { diff --git a/builder/vsphere/clone/step_clone.go b/builder/vsphere/clone/step_clone.go index b0a776036..ec55a3ecd 100644 --- a/builder/vsphere/clone/step_clone.go +++ b/builder/vsphere/clone/step_clone.go @@ -67,7 +67,7 @@ type StepCloneVM struct { func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - d := state.Get("driver").(*driver.Driver) + d := state.Get("driver").(*driver.VCenterDriver) vmPath := path.Join(s.Location.Folder, s.Location.VMName) err := d.PreCleanVM(ui, vmPath, s.Force) @@ -128,7 +128,7 @@ func (s *StepCloneVM) Cleanup(state multistep.StateBag) { if st == nil { return } - vm := st.(*driver.VirtualMachine) + vm := st.(*driver.VirtualMachineDriver) ui.Say("Destroying VM...") diff --git a/builder/vsphere/clone/step_customize.go b/builder/vsphere/clone/step_customize.go index 0ad5a6aa4..109be74ae 100644 --- a/builder/vsphere/clone/step_customize.go +++ b/builder/vsphere/clone/step_customize.go @@ -123,7 +123,7 @@ func (c *CustomizeConfig) Prepare() []error { } func (s *StepCustomize) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) ui := state.Get("ui").(packer.Ui) identity, err := s.identitySettings() diff --git a/builder/vsphere/common/artifact.go b/builder/vsphere/common/artifact.go index d81a45a29..267b03d8b 100644 --- a/builder/vsphere/common/artifact.go +++ b/builder/vsphere/common/artifact.go @@ -11,7 +11,7 @@ const BuilderId = "jetbrains.vsphere" type Artifact struct { Outconfig *OutputConfig Name string - VM *driver.VirtualMachine + VM *driver.VirtualMachineDriver // StateData should store data such as GeneratedData // to be shared with post-processors diff --git a/builder/vsphere/common/step_boot_command.go b/builder/vsphere/common/step_boot_command.go index 4ad4a760c..5f831f070 100644 --- a/builder/vsphere/common/step_boot_command.go +++ b/builder/vsphere/common/step_boot_command.go @@ -43,7 +43,7 @@ type StepBootCommand struct { func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { debug := state.Get("debug").(bool) ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if s.Config.BootCommand == nil { return multistep.ActionContinue diff --git a/builder/vsphere/common/step_config_params.go b/builder/vsphere/common/step_config_params.go index 2ef09ed91..54e92082b 100644 --- a/builder/vsphere/common/step_config_params.go +++ b/builder/vsphere/common/step_config_params.go @@ -33,7 +33,7 @@ type StepConfigParams struct { func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) configParams := make(map[string]string) if s.Config.ConfigParams != nil { diff --git a/builder/vsphere/common/step_export.go b/builder/vsphere/common/step_export.go index 8fb49adde..a50a329b4 100644 --- a/builder/vsphere/common/step_export.go +++ b/builder/vsphere/common/step_export.go @@ -154,7 +154,7 @@ func (s *StepExport) Cleanup(multistep.StateBag) { func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) ui.Message("Starting export...") lease, err := vm.Export() diff --git a/builder/vsphere/common/step_hardware.go b/builder/vsphere/common/step_hardware.go index 28ff14e1e..5f16a79b7 100644 --- a/builder/vsphere/common/step_hardware.go +++ b/builder/vsphere/common/step_hardware.go @@ -65,7 +65,7 @@ type StepConfigureHardware struct { func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if *s.Config != (HardwareConfig{}) { ui.Say("Customizing hardware...") diff --git a/builder/vsphere/common/step_import_to_content_library.go b/builder/vsphere/common/step_import_to_content_library.go index d47f99cc7..2e5be7a39 100644 --- a/builder/vsphere/common/step_import_to_content_library.go +++ b/builder/vsphere/common/step_import_to_content_library.go @@ -118,7 +118,7 @@ type StepImportToContentLibrary struct { func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) var err error if s.ContentLibConfig.Ovf { @@ -142,7 +142,7 @@ func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.Stat return multistep.ActionContinue } -func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachine) error { +func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachineDriver) error { ovf := vcenter.OVF{ Spec: vcenter.CreateSpec{ Name: s.ContentLibConfig.Name, @@ -154,7 +154,7 @@ func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachine return vm.ImportOvfToContentLibrary(ovf) } -func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachine) error { +func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachineDriver) error { template := vcenter.Template{ Name: s.ContentLibConfig.Name, Description: s.ContentLibConfig.Description, diff --git a/builder/vsphere/common/step_run.go b/builder/vsphere/common/step_run.go index e18c4ee4a..9fcdadb1e 100644 --- a/builder/vsphere/common/step_run.go +++ b/builder/vsphere/common/step_run.go @@ -24,7 +24,7 @@ type StepRun struct { func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if s.Config.BootOrder != "" { ui.Say("Set boot order...") @@ -55,7 +55,7 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste func (s *StepRun) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if s.Config.BootOrder == "" && s.SetOrder { ui.Say("Clear boot order...") diff --git a/builder/vsphere/common/step_shutdown.go b/builder/vsphere/common/step_shutdown.go index 0666b9666..f85749f40 100644 --- a/builder/vsphere/common/step_shutdown.go +++ b/builder/vsphere/common/step_shutdown.go @@ -48,7 +48,7 @@ type StepShutdown struct { func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) comm := state.Get("communicator").(packer.Communicator) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if off, _ := vm.IsPoweredOff(); off { // Probably power off initiated by last provisioner, though disable_shutdown is not set diff --git a/builder/vsphere/common/step_snapshot.go b/builder/vsphere/common/step_snapshot.go index c577cf035..09a698698 100644 --- a/builder/vsphere/common/step_snapshot.go +++ b/builder/vsphere/common/step_snapshot.go @@ -14,7 +14,7 @@ type StepCreateSnapshot struct { func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if s.CreateSnapshot { ui.Say("Creating snapshot...") diff --git a/builder/vsphere/common/step_ssh_key_pair.go b/builder/vsphere/common/step_ssh_key_pair.go index d094a0cb5..4fe202893 100644 --- a/builder/vsphere/common/step_ssh_key_pair.go +++ b/builder/vsphere/common/step_ssh_key_pair.go @@ -81,7 +81,7 @@ func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) mult s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine s.Comm.SSHClearAuthorizedKeys = true - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) err = vm.AddPublicKeys(ctx, string(s.Comm.SSHPublicKey)) if err != nil { state.Put("error", fmt.Errorf("error saving temporary keypair in the vm: %s", err)) diff --git a/builder/vsphere/common/step_template.go b/builder/vsphere/common/step_template.go index fad2b9470..96dc57408 100644 --- a/builder/vsphere/common/step_template.go +++ b/builder/vsphere/common/step_template.go @@ -14,7 +14,7 @@ type StepConvertToTemplate struct { func (s *StepConvertToTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if s.ConvertToTemplate { ui.Say("Convert VM into template...") diff --git a/builder/vsphere/common/step_wait_for_ip.go b/builder/vsphere/common/step_wait_for_ip.go index ab23d1829..d3ddb4ee8 100644 --- a/builder/vsphere/common/step_wait_for_ip.go +++ b/builder/vsphere/common/step_wait_for_ip.go @@ -75,7 +75,7 @@ func (c *WaitIpConfig) GetIPNet() *net.IPNet { func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) var ip string var err error @@ -128,7 +128,7 @@ func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multi } } -func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig) (string, error) { +func doGetIp(vm *driver.VirtualMachineDriver, ctx context.Context, c *WaitIpConfig) (string, error) { var prevIp = "" var stopTime time.Time var interval time.Duration diff --git a/builder/vsphere/common/testing/utility.go b/builder/vsphere/common/testing/utility.go index af10c4b10..e44f0533e 100644 --- a/builder/vsphere/common/testing/utility.go +++ b/builder/vsphere/common/testing/utility.go @@ -34,7 +34,7 @@ func RenderConfig(config map[string]interface{}) string { return string(j) } -func TestConn(t *testing.T) *driver.Driver { +func TestConn(t *testing.T) driver.Driver { username := os.Getenv("VSPHERE_USERNAME") if username == "" { username = "root" @@ -56,7 +56,7 @@ func TestConn(t *testing.T) *driver.Driver { return d } -func GetVM(t *testing.T, d *driver.Driver, artifacts []packer.Artifact) *driver.VirtualMachine { +func GetVM(t *testing.T, d driver.Driver, artifacts []packer.Artifact) driver.VirtualMachine { artifactRaw := artifacts[0] artifact, _ := artifactRaw.(*common.Artifact) diff --git a/builder/vsphere/driver/cluster.go b/builder/vsphere/driver/cluster.go index fa48648e1..d17031b82 100644 --- a/builder/vsphere/driver/cluster.go +++ b/builder/vsphere/driver/cluster.go @@ -3,11 +3,11 @@ package driver import "github.com/vmware/govmomi/object" type Cluster struct { - driver *Driver + driver *VCenterDriver cluster *object.ClusterComputeResource } -func (d *Driver) FindCluster(name string) (*Cluster, error) { +func (d *VCenterDriver) FindCluster(name string) (*Cluster, error) { c, err := d.finder.ClusterComputeResource(d.ctx, name) if err != nil { return nil, err diff --git a/builder/vsphere/driver/datastore.go b/builder/vsphere/driver/datastore.go index 9a18c3e1b..d429325e8 100644 --- a/builder/vsphere/driver/datastore.go +++ b/builder/vsphere/driver/datastore.go @@ -13,20 +13,31 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -type Datastore struct { - ds *object.Datastore - driver *Driver +type Datastore interface { + Info(params ...string) (*mo.Datastore, error) + FileExists(path string) bool + Name() string + ResolvePath(path string) string + UploadFile(src, dst, host string, setHost bool) error + Delete(path string) error + MakeDirectory(path string) error + Reference() types.ManagedObjectReference } -func (d *Driver) NewDatastore(ref *types.ManagedObjectReference) *Datastore { - return &Datastore{ +type DatastoreDriver struct { + ds *object.Datastore + driver *VCenterDriver +} + +func (d *VCenterDriver) NewDatastore(ref *types.ManagedObjectReference) Datastore { + return &DatastoreDriver{ ds: object.NewDatastore(d.client.Client, *ref), driver: d, } } // If name is an empty string, then resolve host's one -func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) { +func (d *VCenterDriver) FindDatastore(name string, host string) (Datastore, error) { if name == "" { h, err := d.FindHost(host) if err != nil { @@ -55,13 +66,13 @@ func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) { return nil, err } - return &Datastore{ + return &DatastoreDriver{ ds: ds, driver: d, }, nil } -func (d *Driver) GetDatastoreName(id string) (string, error) { +func (d *VCenterDriver) GetDatastoreName(id string) (string, error) { obj := types.ManagedObjectReference{ Type: "Datastore", Value: id, @@ -76,7 +87,7 @@ func (d *Driver) GetDatastoreName(id string) (string, error) { return me.Name, nil } -func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) { +func (ds *DatastoreDriver) Info(params ...string) (*mo.Datastore, error) { var p []string if len(params) == 0 { p = []string{"*"} @@ -91,21 +102,25 @@ func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) { return &info, nil } -func (ds *Datastore) FileExists(path string) bool { +func (ds *DatastoreDriver) FileExists(path string) bool { _, err := ds.ds.Stat(ds.driver.ctx, path) return err == nil } -func (ds *Datastore) Name() string { +func (ds *DatastoreDriver) Name() string { return ds.ds.Name() } -func (ds *Datastore) ResolvePath(path string) string { +func (ds *DatastoreDriver) Reference() types.ManagedObjectReference { + return ds.ds.Reference() +} + +func (ds *DatastoreDriver) ResolvePath(path string) string { return ds.ds.Path(path) } // The file ID isn't available via the API, so we use DatastoreBrowser to search -func (d *Driver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) { +func (d *VCenterDriver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) { ref := types.ManagedObjectReference{Type: "Datastore", Value: datastoreID} ds := object.NewDatastore(d.vimClient, ref) @@ -140,11 +155,11 @@ func (d *Driver) GetDatastoreFilePath(datastoreID, dir, filename string) (string return res.File[0].GetFileInfo().Path, nil } -func (ds *Datastore) UploadFile(src, dst, host string, set_host_for_datastore_uploads bool) error { +func (ds *DatastoreDriver) UploadFile(src, dst, host string, setHost bool) error { p := soap.DefaultUpload ctx := ds.driver.ctx - if set_host_for_datastore_uploads && host != "" { + if setHost && host != "" { h, err := ds.driver.FindHost(host) if err != nil { return err @@ -155,7 +170,7 @@ func (ds *Datastore) UploadFile(src, dst, host string, set_host_for_datastore_up return ds.ds.UploadFile(ctx, src, dst, &p) } -func (ds *Datastore) Delete(path string) error { +func (ds *DatastoreDriver) Delete(path string) error { dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath) if err != nil { return err @@ -164,7 +179,7 @@ func (ds *Datastore) Delete(path string) error { return fm.Delete(ds.driver.ctx, path) } -func (ds *Datastore) MakeDirectory(path string) error { +func (ds *DatastoreDriver) MakeDirectory(path string) error { dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath) if err != nil { return err diff --git a/builder/vsphere/driver/datastore_mock.go b/builder/vsphere/driver/datastore_mock.go new file mode 100644 index 000000000..52fbc7c6b --- /dev/null +++ b/builder/vsphere/driver/datastore_mock.go @@ -0,0 +1,47 @@ +package driver + +import ( + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type DatastoreMock struct { + FileExistsCalled bool + MakeDirectoryCalled bool + UploadFileCalled bool +} + +func (ds *DatastoreMock) Info(params ...string) (*mo.Datastore, error) { + return nil, nil +} + +func (ds *DatastoreMock) FileExists(path string) bool { + ds.FileExistsCalled = true + return false +} + +func (ds *DatastoreMock) Name() string { + return "datastore-mock" +} + +func (ds *DatastoreMock) Reference() types.ManagedObjectReference { + return types.ManagedObjectReference{} +} + +func (ds *DatastoreMock) ResolvePath(path string) string { + return "" +} + +func (ds *DatastoreMock) UploadFile(src, dst, host string, setHost bool) error { + ds.UploadFileCalled = true + return nil +} + +func (ds *DatastoreMock) Delete(path string) error { + return nil +} + +func (ds *DatastoreMock) MakeDirectory(path string) error { + ds.MakeDirectoryCalled = true + return nil +} diff --git a/builder/vsphere/driver/driver.go b/builder/vsphere/driver/driver.go index 18eb6d5e3..1fbb766bd 100644 --- a/builder/vsphere/driver/driver.go +++ b/builder/vsphere/driver/driver.go @@ -6,16 +6,46 @@ import ( "net/url" "time" + "github.com/hashicorp/packer/packer" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/session" + "github.com/vmware/govmomi/vapi/library" "github.com/vmware/govmomi/vapi/rest" "github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25/soap" + "github.com/vmware/govmomi/vim25/types" ) -type Driver struct { +type Driver interface { + NewVM(ref *types.ManagedObjectReference) VirtualMachine + FindVM(name string) (VirtualMachine, error) + FindCluster(name string) (*Cluster, error) + PreCleanVM(ui packer.Ui, vmPath string, force bool) error + CreateVM(config *CreateConfig) (VirtualMachine, error) + + NewDatastore(ref *types.ManagedObjectReference) Datastore + FindDatastore(name string, host string) (Datastore, error) + GetDatastoreName(id string) (string, error) + GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) + + NewFolder(ref *types.ManagedObjectReference) *Folder + FindFolder(name string) (*Folder, error) + NewHost(ref *types.ManagedObjectReference) *Host + FindHost(name string) (*Host, error) + NewNetwork(ref *types.ManagedObjectReference) *Network + FindNetwork(name string) (*Network, error) + FindNetworks(name string) ([]*Network, error) + NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool + FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) + + FindContentLibraryByName(name string) (*Library, error) + FindContentLibraryItem(libraryId string, name string) (*library.Item, error) + FindContentLibraryFileDatastorePath(isoPath string) (string, error) +} + +type VCenterDriver struct { // context that controls the authenticated sessions used to run the VM commands ctx context.Context client *govmomi.Client @@ -33,7 +63,7 @@ type ConnectConfig struct { Datacenter string } -func NewDriver(config *ConnectConfig) (*Driver, error) { +func NewDriver(config *ConnectConfig) (Driver, error) { ctx := context.TODO() vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer)) @@ -67,7 +97,8 @@ func NewDriver(config *ConnectConfig) (*Driver, error) { } finder.SetDatacenter(datacenter) - d := Driver{ + var d Driver + d = &VCenterDriver{ ctx: ctx, client: client, vimClient: vimClient, @@ -78,7 +109,7 @@ func NewDriver(config *ConnectConfig) (*Driver, error) { datacenter: datacenter, finder: finder, } - return &d, nil + return d, nil } // The rest.Client requires vCenter. diff --git a/builder/vsphere/driver/driver_mock.go b/builder/vsphere/driver/driver_mock.go new file mode 100644 index 000000000..e1bbe1348 --- /dev/null +++ b/builder/vsphere/driver/driver_mock.go @@ -0,0 +1,106 @@ +package driver + +import ( + "fmt" + + "github.com/hashicorp/packer/packer" + "github.com/vmware/govmomi/vapi/library" + "github.com/vmware/govmomi/vim25/types" +) + +type DriverMock struct { + FindDatastoreCalled bool + DatastoreMock *DatastoreMock + + PreCleanShouldFail bool + PreCleanVMCalled bool + PreCleanForce bool + PreCleanVMPath string + + CreateVMShouldFail bool + CreateVMCalled bool + CreateConfig *CreateConfig + VM VirtualMachine +} + +func NewDriverMock() *DriverMock { + return new(DriverMock) +} + +func (d *DriverMock) FindDatastore(name string, host string) (Datastore, error) { + d.FindDatastoreCalled = true + if d.DatastoreMock == nil { + d.DatastoreMock = new(DatastoreMock) + } + return d.DatastoreMock, nil +} + +func (d *DriverMock) NewVM(ref *types.ManagedObjectReference) VirtualMachine { + return nil +} + +func (d *DriverMock) FindVM(name string) (VirtualMachine, error) { + return nil, nil +} + +func (d *DriverMock) FindCluster(name string) (*Cluster, error) { + return nil, nil +} + +func (d *DriverMock) PreCleanVM(ui packer.Ui, vmPath string, force bool) error { + d.PreCleanVMCalled = true + if d.PreCleanShouldFail { + return fmt.Errorf("pre clean failed") + } + d.PreCleanForce = true + d.PreCleanVMPath = vmPath + return nil +} + +func (d *DriverMock) CreateVM(config *CreateConfig) (VirtualMachine, error) { + d.CreateVMCalled = true + if d.CreateVMShouldFail { + return nil, fmt.Errorf("create vm failed") + } + d.CreateConfig = config + d.VM = new(VirtualMachineDriver) + return d.VM, nil +} + +func (d *DriverMock) NewDatastore(ref *types.ManagedObjectReference) Datastore { return nil } + +func (d *DriverMock) GetDatastoreName(id string) (string, error) { return "", nil } + +func (d *DriverMock) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) { + return "", nil +} + +func (d *DriverMock) NewFolder(ref *types.ManagedObjectReference) *Folder { return nil } + +func (d *DriverMock) FindFolder(name string) (*Folder, error) { return nil, nil } + +func (d *DriverMock) NewHost(ref *types.ManagedObjectReference) *Host { return nil } + +func (d *DriverMock) FindHost(name string) (*Host, error) { return nil, nil } + +func (d *DriverMock) NewNetwork(ref *types.ManagedObjectReference) *Network { return nil } + +func (d *DriverMock) FindNetwork(name string) (*Network, error) { return nil, nil } + +func (d *DriverMock) FindNetworks(name string) ([]*Network, error) { return nil, nil } + +func (d *DriverMock) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool { return nil } + +func (d *DriverMock) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) { + return nil, nil +} + +func (d *DriverMock) FindContentLibraryByName(name string) (*Library, error) { return nil, nil } + +func (d *DriverMock) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) { + return nil, nil +} + +func (d *DriverMock) FindContentLibraryFileDatastorePath(isoPath string) (string, error) { + return "", nil +} diff --git a/builder/vsphere/driver/driver_test.go b/builder/vsphere/driver/driver_test.go index 65e377724..555b8ea2f 100644 --- a/builder/vsphere/driver/driver_test.go +++ b/builder/vsphere/driver/driver_test.go @@ -11,7 +11,7 @@ import ( // Defines whether acceptance tests should be run const TestHostName = "esxi-1.vsphere65.test" -func newTestDriver(t *testing.T) *Driver { +func newTestDriver(t *testing.T) Driver { username := os.Getenv("VSPHERE_USERNAME") if username == "" { username = "root" diff --git a/builder/vsphere/driver/folder.go b/builder/vsphere/driver/folder.go index 9ceccd65e..75e07e594 100644 --- a/builder/vsphere/driver/folder.go +++ b/builder/vsphere/driver/folder.go @@ -12,18 +12,18 @@ import ( ) type Folder struct { - driver *Driver + driver *VCenterDriver folder *object.Folder } -func (d *Driver) NewFolder(ref *types.ManagedObjectReference) *Folder { +func (d *VCenterDriver) NewFolder(ref *types.ManagedObjectReference) *Folder { return &Folder{ folder: object.NewFolder(d.client.Client, *ref), driver: d, } } -func (d *Driver) FindFolder(name string) (*Folder, error) { +func (d *VCenterDriver) FindFolder(name string) (*Folder, error) { if name != "" { // create folders if they don't exist parent := "" diff --git a/builder/vsphere/driver/host.go b/builder/vsphere/driver/host.go index 7ceaea4ff..31c193589 100644 --- a/builder/vsphere/driver/host.go +++ b/builder/vsphere/driver/host.go @@ -7,18 +7,18 @@ import ( ) type Host struct { - driver *Driver + driver *VCenterDriver host *object.HostSystem } -func (d *Driver) NewHost(ref *types.ManagedObjectReference) *Host { +func (d *VCenterDriver) NewHost(ref *types.ManagedObjectReference) *Host { return &Host{ host: object.NewHostSystem(d.client.Client, *ref), driver: d, } } -func (d *Driver) FindHost(name string) (*Host, error) { +func (d *VCenterDriver) FindHost(name string) (*Host, error) { h, err := d.finder.HostSystem(d.ctx, name) if err != nil { return nil, err diff --git a/builder/vsphere/driver/library.go b/builder/vsphere/driver/library.go index 3c7ac387b..25d7d6880 100644 --- a/builder/vsphere/driver/library.go +++ b/builder/vsphere/driver/library.go @@ -10,11 +10,11 @@ import ( ) type Library struct { - driver *Driver + driver *VCenterDriver library *library.Library } -func (d *Driver) FindContentLibraryByName(name string) (*Library, error) { +func (d *VCenterDriver) FindContentLibraryByName(name string) (*Library, error) { lm := library.NewManager(d.restClient.client) l, err := lm.GetLibraryByName(d.ctx, name) if err != nil { @@ -26,7 +26,7 @@ func (d *Driver) FindContentLibraryByName(name string) (*Library, error) { }, nil } -func (d *Driver) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) { +func (d *VCenterDriver) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) { lm := library.NewManager(d.restClient.client) items, err := lm.GetLibraryItems(d.ctx, libraryId) if err != nil { @@ -40,7 +40,7 @@ func (d *Driver) FindContentLibraryItem(libraryId string, name string) (*library return nil, fmt.Errorf("Item %s not found", name) } -func (d *Driver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) { +func (d *VCenterDriver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) { log.Printf("Check if ISO path is a Content Library path") err := d.restClient.Login(d.ctx) if err != nil { diff --git a/builder/vsphere/driver/network.go b/builder/vsphere/driver/network.go index ff1dba678..f2d90d187 100644 --- a/builder/vsphere/driver/network.go +++ b/builder/vsphere/driver/network.go @@ -9,18 +9,18 @@ import ( ) type Network struct { - driver *Driver + driver *VCenterDriver network object.NetworkReference } -func (d *Driver) NewNetwork(ref *types.ManagedObjectReference) *Network { +func (d *VCenterDriver) NewNetwork(ref *types.ManagedObjectReference) *Network { return &Network{ network: object.NewNetwork(d.client.Client, *ref), driver: d, } } -func (d *Driver) FindNetwork(name string) (*Network, error) { +func (d *VCenterDriver) FindNetwork(name string) (*Network, error) { n, err := d.finder.Network(d.ctx, name) if err != nil { return nil, err @@ -31,7 +31,7 @@ func (d *Driver) FindNetwork(name string) (*Network, error) { }, nil } -func (d *Driver) FindNetworks(name string) ([]*Network, error) { +func (d *VCenterDriver) FindNetworks(name string) ([]*Network, error) { ns, err := d.finder.NetworkList(d.ctx, name) if err != nil { return nil, err diff --git a/builder/vsphere/driver/resource_pool.go b/builder/vsphere/driver/resource_pool.go index c81f1c23f..eba3dbb39 100644 --- a/builder/vsphere/driver/resource_pool.go +++ b/builder/vsphere/driver/resource_pool.go @@ -10,17 +10,17 @@ import ( type ResourcePool struct { pool *object.ResourcePool - driver *Driver + driver *VCenterDriver } -func (d *Driver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool { +func (d *VCenterDriver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool { return &ResourcePool{ pool: object.NewResourcePool(d.client.Client, *ref), driver: d, } } -func (d *Driver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) { +func (d *VCenterDriver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) { var res string if cluster != "" { res = cluster diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go index 23ca3d54e..9e48ba5af 100644 --- a/builder/vsphere/driver/vm.go +++ b/builder/vsphere/driver/vm.go @@ -21,9 +21,43 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -type VirtualMachine struct { +type VirtualMachine interface { + Info(params ...string) (*mo.VirtualMachine, error) + Devices() (object.VirtualDeviceList, error) + Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) + updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) + AddPublicKeys(ctx context.Context, publicKeys string) error + Properties(ctx context.Context) (*mo.VirtualMachine, error) + Destroy() error + Configure(config *HardwareConfig) error + Customize(spec types.CustomizationSpec) error + ResizeDisk(diskSize int64) error + PowerOn() error + WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) + PowerOff() error + IsPoweredOff() (bool, error) + StartShutdown() error + WaitForShutdown(ctx context.Context, timeout time.Duration) error + CreateSnapshot(name string) error + ConvertToTemplate() error + ImportOvfToContentLibrary(ovf vcenter.OVF) error + ImportToContentLibrary(template vcenter.Template) error + GetDir() (string, error) + AddCdrom(controllerType string, datastoreIsoPath string) error + AddFloppy(imgPath string) error + SetBootOrder(order []string) error + RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error + addDevice(device types.BaseVirtualDevice) error + AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error + Export() (*nfc.Lease, error) + CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) + NewOvfManager() *ovf.Manager + GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) +} + +type VirtualMachineDriver struct { vm *object.VirtualMachine - driver *Driver + driver *VCenterDriver } type CloneConfig struct { @@ -87,25 +121,25 @@ type Disk struct { ControllerIndex int } -func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine { - return &VirtualMachine{ +func (d *VCenterDriver) NewVM(ref *types.ManagedObjectReference) VirtualMachine { + return &VirtualMachineDriver{ vm: object.NewVirtualMachine(d.client.Client, *ref), driver: d, } } -func (d *Driver) FindVM(name string) (*VirtualMachine, error) { +func (d *VCenterDriver) FindVM(name string) (VirtualMachine, error) { vm, err := d.finder.VirtualMachine(d.ctx, name) if err != nil { return nil, err } - return &VirtualMachine{ + return &VirtualMachineDriver{ vm: vm, driver: d, }, nil } -func (d *Driver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error { +func (d *VCenterDriver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error { vm, err := d.FindVM(vmPath) if err != nil { if _, ok := err.(*find.NotFoundError); !ok { @@ -130,7 +164,7 @@ func (d *Driver) PreCleanVM(ui packer.Ui, vmPath string, force bool) error { return nil } -func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) { +func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) { createSpec := types.VirtualMachineConfigSpec{ Name: config.Name, Annotation: config.Annotation, @@ -219,7 +253,7 @@ func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) { return d.NewVM(&vmRef), nil } -func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) { +func (vm *VirtualMachineDriver) Info(params ...string) (*mo.VirtualMachine, error) { var p []string if len(params) == 0 { p = []string{"*"} @@ -234,7 +268,7 @@ func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) { return &info, nil } -func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) { +func (vm *VirtualMachineDriver) Devices() (object.VirtualDeviceList, error) { vmInfo, err := vm.Info("config.hardware.device") if err != nil { return nil, err @@ -243,7 +277,7 @@ func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) { return vmInfo.Config.Hardware.Device, nil } -func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*VirtualMachine, error) { +func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) { folder, err := vm.driver.FindFolder(config.Folder) if err != nil { return nil, err @@ -262,7 +296,7 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt if err != nil { return nil, err } - datastoreRef := datastore.ds.Reference() + datastoreRef := datastore.Reference() relocateSpec.Datastore = &datastoreRef var cloneSpec types.VirtualMachineCloneSpec @@ -350,7 +384,7 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt return created, nil } -func (vm *VirtualMachine) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) { +func (vm *VirtualMachineDriver) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) { if len(newProps) == 0 { return nil, nil } @@ -398,7 +432,7 @@ func (vm *VirtualMachine) updateVAppConfig(ctx context.Context, newProps map[str }, nil } -func (vm *VirtualMachine) AddPublicKeys(ctx context.Context, publicKeys string) error { +func (vm *VirtualMachineDriver) AddPublicKeys(ctx context.Context, publicKeys string) error { newProps := map[string]string{"public-keys": publicKeys} config, err := vm.updateVAppConfig(ctx, newProps) if err != nil { @@ -415,7 +449,7 @@ func (vm *VirtualMachine) AddPublicKeys(ctx context.Context, publicKeys string) return err } -func (vm *VirtualMachine) Properties(ctx context.Context) (*mo.VirtualMachine, error) { +func (vm *VirtualMachineDriver) Properties(ctx context.Context) (*mo.VirtualMachine, error) { log.Printf("fetching properties for VM %q", vm.vm.InventoryPath) var props mo.VirtualMachine if err := vm.vm.Properties(ctx, vm.vm.Reference(), nil, &props); err != nil { @@ -424,7 +458,7 @@ func (vm *VirtualMachine) Properties(ctx context.Context) (*mo.VirtualMachine, e return &props, nil } -func (vm *VirtualMachine) Destroy() error { +func (vm *VirtualMachineDriver) Destroy() error { task, err := vm.vm.Destroy(vm.driver.ctx) if err != nil { return err @@ -433,7 +467,7 @@ func (vm *VirtualMachine) Destroy() error { return err } -func (vm *VirtualMachine) Configure(config *HardwareConfig) error { +func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error { var confSpec types.VirtualMachineConfigSpec confSpec.NumCPUs = config.CPUs confSpec.NumCoresPerSocket = config.CpuCores @@ -524,7 +558,7 @@ func (vm *VirtualMachine) Configure(config *HardwareConfig) error { return err } -func (vm *VirtualMachine) Customize(spec types.CustomizationSpec) error { +func (vm *VirtualMachineDriver) Customize(spec types.CustomizationSpec) error { task, err := vm.vm.Customize(vm.driver.ctx, spec) if err != nil { return err @@ -532,7 +566,7 @@ func (vm *VirtualMachine) Customize(spec types.CustomizationSpec) error { return task.Wait(vm.driver.ctx) } -func (vm *VirtualMachine) ResizeDisk(diskSize int64) error { +func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) error { var confSpec types.VirtualMachineConfigSpec devices, err := vm.vm.Device(vm.driver.ctx) @@ -581,7 +615,7 @@ func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) { return nil, errors.New("VM has multiple disks") } -func (vm *VirtualMachine) PowerOn() error { +func (vm *VirtualMachineDriver) PowerOn() error { task, err := vm.vm.PowerOn(vm.driver.ctx) if err != nil { return err @@ -590,7 +624,7 @@ func (vm *VirtualMachine) PowerOn() error { return err } -func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) { +func (vm *VirtualMachineDriver) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) { netIP, err := vm.vm.WaitForNetIP(ctx, false) if err != nil { return "", err @@ -614,7 +648,7 @@ func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (stri return "", fmt.Errorf("unable to find an IP") } -func (vm *VirtualMachine) PowerOff() error { +func (vm *VirtualMachineDriver) PowerOff() error { state, err := vm.vm.PowerState(vm.driver.ctx) if err != nil { return err @@ -632,7 +666,7 @@ func (vm *VirtualMachine) PowerOff() error { return err } -func (vm *VirtualMachine) IsPoweredOff() (bool, error) { +func (vm *VirtualMachineDriver) IsPoweredOff() (bool, error) { state, err := vm.vm.PowerState(vm.driver.ctx) if err != nil { return false, err @@ -641,12 +675,12 @@ func (vm *VirtualMachine) IsPoweredOff() (bool, error) { return state == types.VirtualMachinePowerStatePoweredOff, nil } -func (vm *VirtualMachine) StartShutdown() error { +func (vm *VirtualMachineDriver) StartShutdown() error { err := vm.vm.ShutdownGuest(vm.driver.ctx) return err } -func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Duration) error { +func (vm *VirtualMachineDriver) WaitForShutdown(ctx context.Context, timeout time.Duration) error { shutdownTimer := time.After(timeout) for { off, err := vm.IsPoweredOff() @@ -670,7 +704,7 @@ func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Dura return nil } -func (vm *VirtualMachine) CreateSnapshot(name string) error { +func (vm *VirtualMachineDriver) CreateSnapshot(name string) error { task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false) if err != nil { return err @@ -679,11 +713,11 @@ func (vm *VirtualMachine) CreateSnapshot(name string) error { return err } -func (vm *VirtualMachine) ConvertToTemplate() error { +func (vm *VirtualMachineDriver) ConvertToTemplate() error { return vm.vm.MarkAsTemplate(vm.driver.ctx) } -func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error { +func (vm *VirtualMachineDriver) ImportOvfToContentLibrary(ovf vcenter.OVF) error { err := vm.driver.restClient.Login(vm.driver.ctx) if err != nil { return err @@ -717,7 +751,7 @@ func (vm *VirtualMachine) ImportOvfToContentLibrary(ovf vcenter.OVF) error { return vm.driver.restClient.Logout(vm.driver.ctx) } -func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) error { +func (vm *VirtualMachineDriver) ImportToContentLibrary(template vcenter.Template) error { err := vm.driver.restClient.Login(vm.driver.ctx) if err != nil { return err @@ -769,7 +803,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro if err != nil { return err } - template.VMHomeStorage.Datastore = d.ds.Reference().Value + template.VMHomeStorage.Datastore = d.Reference().Value } vcm := vcenter.NewManager(vm.driver.restClient.client) @@ -781,7 +815,7 @@ func (vm *VirtualMachine) ImportToContentLibrary(template vcenter.Template) erro return vm.driver.restClient.Logout(vm.driver.ctx) } -func (vm *VirtualMachine) GetDir() (string, error) { +func (vm *VirtualMachineDriver) GetDir() (string, error) { vmInfo, err := vm.Info("name", "layoutEx.file") if err != nil { return "", err @@ -789,14 +823,14 @@ func (vm *VirtualMachine) GetDir() (string, error) { vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name) for _, file := range vmInfo.LayoutEx.File { - if strings.HasSuffix(file.Name, vmxName) { + if strings.Contains(file.Name, vmInfo.Name) { return RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil } } return "", fmt.Errorf("cannot find '%s'", vmxName) } -func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) { +func addDisk(_ *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) { if len(config.Storage) == 0 { return nil, errors.New("no storage devices have been defined") } @@ -839,7 +873,7 @@ func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig) return devices, nil } -func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) { +func addNetwork(d *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) { if len(config.NICs) == 0 { return nil, errors.New("no network adapters have been defined") } @@ -872,7 +906,7 @@ func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfi return devices, nil } -func findNetwork(network string, host string, d *Driver) (object.NetworkReference, error) { +func findNetwork(network string, host string, d *VCenterDriver) (object.NetworkReference, error) { if network != "" { var err error networks, err := d.FindNetworks(network) @@ -941,7 +975,7 @@ func newVGPUProfile(vGPUProfile string) types.VirtualPCIPassthrough { } } -func (vm *VirtualMachine) AddCdrom(controllerType string, datastoreIsoPath string) error { +func (vm *VirtualMachineDriver) AddCdrom(controllerType string, datastoreIsoPath string) error { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err @@ -985,7 +1019,7 @@ func (vm *VirtualMachine) AddCdrom(controllerType string, datastoreIsoPath strin return vm.addDevice(cdrom) } -func (vm *VirtualMachine) AddFloppy(imgPath string) error { +func (vm *VirtualMachineDriver) AddFloppy(imgPath string) error { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err @@ -1003,7 +1037,7 @@ func (vm *VirtualMachine) AddFloppy(imgPath string) error { return vm.addDevice(floppy) } -func (vm *VirtualMachine) SetBootOrder(order []string) error { +func (vm *VirtualMachineDriver) SetBootOrder(order []string) error { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err @@ -1016,11 +1050,11 @@ func (vm *VirtualMachine) SetBootOrder(order []string) error { return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions) } -func (vm *VirtualMachine) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error { +func (vm *VirtualMachineDriver) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error { return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...) } -func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error { +func (vm *VirtualMachineDriver) addDevice(device types.BaseVirtualDevice) error { newDevices := object.VirtualDeviceList{device} confSpec := types.VirtualMachineConfigSpec{} var err error @@ -1038,7 +1072,7 @@ func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error { return err } -func (vm *VirtualMachine) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error { +func (vm *VirtualMachineDriver) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error { var confSpec types.VirtualMachineConfigSpec var ov []types.BaseOptionValue @@ -1066,19 +1100,19 @@ func (vm *VirtualMachine) AddConfigParams(params map[string]string, info *types. return nil } -func (vm *VirtualMachine) Export() (*nfc.Lease, error) { +func (vm *VirtualMachineDriver) Export() (*nfc.Lease, error) { return vm.vm.Export(vm.driver.ctx) } -func (vm *VirtualMachine) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) { +func (vm *VirtualMachineDriver) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) { return m.CreateDescriptor(vm.driver.ctx, vm.vm, cdp) } -func (vm *VirtualMachine) NewOvfManager() *ovf.Manager { +func (vm *VirtualMachineDriver) NewOvfManager() *ovf.Manager { return ovf.NewManager(vm.vm.Client()) } -func (vm *VirtualMachine) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) { +func (vm *VirtualMachineDriver) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) { var mgr mo.OvfManager err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr) if err != nil { diff --git a/builder/vsphere/driver/vm_cdrom.go b/builder/vsphere/driver/vm_cdrom.go index 264e87d10..0eab0d196 100644 --- a/builder/vsphere/driver/vm_cdrom.go +++ b/builder/vsphere/driver/vm_cdrom.go @@ -10,12 +10,12 @@ var ( ErrNoSataController = errors.New("no available SATA controller") ) -func (vm *VirtualMachine) AddSATAController() error { +func (vm *VirtualMachineDriver) AddSATAController() error { sata := &types.VirtualAHCIController{} return vm.addDevice(sata) } -func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, error) { +func (vm *VirtualMachineDriver) FindSATAController() (*types.VirtualAHCIController, error) { l, err := vm.Devices() if err != nil { return nil, err @@ -29,7 +29,7 @@ func (vm *VirtualMachine) FindSATAController() (*types.VirtualAHCIController, er return c.(*types.VirtualAHCIController), nil } -func (vm *VirtualMachine) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) { +func (vm *VirtualMachineDriver) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) { l, err := vm.Devices() if err != nil { return nil, err @@ -52,7 +52,7 @@ func (vm *VirtualMachine) CreateCdrom(c *types.VirtualController) (*types.Virtua return device, nil } -func (vm *VirtualMachine) RemoveCdroms() error { +func (vm *VirtualMachineDriver) RemoveCdroms() error { devices, err := vm.Devices() if err != nil { return err @@ -69,7 +69,7 @@ func (vm *VirtualMachine) RemoveCdroms() error { return nil } -func (vm *VirtualMachine) EjectCdroms() error { +func (vm *VirtualMachineDriver) EjectCdroms() error { devices, err := vm.Devices() if err != nil { return err diff --git a/builder/vsphere/driver/vm_clone_acc_test.go b/builder/vsphere/driver/vm_clone_acc_test.go index 062eb1d48..98d37d6cf 100644 --- a/builder/vsphere/driver/vm_clone_acc_test.go +++ b/builder/vsphere/driver/vm_clone_acc_test.go @@ -13,7 +13,7 @@ func TestVMAcc_clone(t *testing.T) { testCases := []struct { name string config *CloneConfig - checkFunction func(*testing.T, *VirtualMachine, *CloneConfig) + checkFunction func(*testing.T, VirtualMachine, *CloneConfig) }{ {"Default", &CloneConfig{}, cloneDefaultCheck}, {"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck}, @@ -53,8 +53,8 @@ func TestVMAcc_clone(t *testing.T) { } } -func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { - d := vm.driver +func cloneDefaultCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) { + d := vm.(*VirtualMachineDriver).driver // Check that the clone can be found by its name if _, err := d.FindVM(config.Name); err != nil { @@ -112,7 +112,7 @@ func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { } } -func configureCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { +func configureCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) { log.Printf("[DEBUG] Configuring the vm") hwConfig := &HardwareConfig{ CPUs: 2, @@ -170,7 +170,7 @@ func configureCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { } } -func configureRAMReserveAllCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { +func configureRAMReserveAllCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) { log.Printf("[DEBUG] Configuring the vm") err := vm.Configure(&HardwareConfig{RAMReserveAll: true}) if err != nil { @@ -188,7 +188,7 @@ func configureRAMReserveAllCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfi } } -func cloneLinkedCloneCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { +func cloneLinkedCloneCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) { vmInfo, err := vm.Info("layoutEx.disk") if err != nil { t.Fatalf("Cannot read VM properties: %v", err) @@ -199,13 +199,13 @@ func cloneLinkedCloneCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { } } -func cloneFolderCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { +func cloneFolderCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) { vmInfo, err := vm.Info("parent") if err != nil { t.Fatalf("Cannot read VM properties: %v", err) } - f := vm.driver.NewFolder(vmInfo.Parent) + f := vm.(*VirtualMachineDriver).driver.NewFolder(vmInfo.Parent) path, err := f.Path() if err != nil { t.Fatalf("Cannot read folder name: %v", err) @@ -215,13 +215,13 @@ func cloneFolderCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { } } -func cloneResourcePoolCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { +func cloneResourcePoolCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) { vmInfo, err := vm.Info("resourcePool") if err != nil { t.Fatalf("Cannot read VM properties: %v", err) } - p := vm.driver.NewResourcePool(vmInfo.ResourcePool) + p := vm.(*VirtualMachineDriver).driver.NewResourcePool(vmInfo.ResourcePool) path, err := p.Path() if err != nil { t.Fatalf("Cannot read resource pool name: %v", err) @@ -231,7 +231,7 @@ func cloneResourcePoolCheck(t *testing.T, vm *VirtualMachine, config *CloneConfi } } -func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { +func startAndStopCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) { stopper := startVM(t, vm, config.Name) defer stopper() @@ -253,7 +253,7 @@ func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { } } -func snapshotCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { +func snapshotCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) { stopper := startVM(t, vm, config.Name) defer stopper() @@ -273,7 +273,7 @@ func snapshotCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { } } -func templateCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { +func templateCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) { err := vm.ConvertToTemplate() if err != nil { t.Fatalf("Failed to convert to template: %v", err) @@ -286,7 +286,7 @@ func templateCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { } } -func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) { +func startVM(t *testing.T, vm VirtualMachine, vmName string) (stopper func()) { log.Printf("[DEBUG] Starting the vm") if err := vm.PowerOn(); err != nil { t.Fatalf("Cannot start vm '%v': %v", vmName, err) @@ -299,14 +299,14 @@ func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) { } } -func destroyVM(t *testing.T, vm *VirtualMachine, vmName string) { +func destroyVM(t *testing.T, vm VirtualMachine, vmName string) { log.Printf("[DEBUG] Deleting the VM") if err := vm.Destroy(); err != nil { t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err) } // Check that the clone is no longer exists - if _, err := vm.driver.FindVM(vmName); err == nil { + if _, err := vm.(*VirtualMachineDriver).driver.FindVM(vmName); err == nil { t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName) } } diff --git a/builder/vsphere/driver/vm_create_acc_test.go b/builder/vsphere/driver/vm_create_acc_test.go index 32b2c2b40..c2c2d833f 100644 --- a/builder/vsphere/driver/vm_create_acc_test.go +++ b/builder/vsphere/driver/vm_create_acc_test.go @@ -10,7 +10,7 @@ func TestVMAcc_create(t *testing.T) { testCases := []struct { name string config *CreateConfig - checkFunction func(*testing.T, *VirtualMachine, *CreateConfig) + checkFunction func(*testing.T, VirtualMachine, *CreateConfig) }{ {"MinimalConfiguration", &CreateConfig{}, createDefaultCheck}, } @@ -36,8 +36,8 @@ func TestVMAcc_create(t *testing.T) { } } -func createDefaultCheck(t *testing.T, vm *VirtualMachine, config *CreateConfig) { - d := vm.driver +func createDefaultCheck(t *testing.T, vm VirtualMachine, config *CreateConfig) { + d := vm.(*VirtualMachineDriver).driver // Check that the clone can be found by its name if _, err := d.FindVM(config.Name); err != nil { diff --git a/builder/vsphere/driver/vm_keyboard.go b/builder/vsphere/driver/vm_keyboard.go index b11041225..d50e66e07 100644 --- a/builder/vsphere/driver/vm_keyboard.go +++ b/builder/vsphere/driver/vm_keyboard.go @@ -13,7 +13,7 @@ type KeyInput struct { Shift bool } -func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) { +func (vm *VirtualMachineDriver) TypeOnKeyboard(input KeyInput) (int32, error) { var spec types.UsbScanCodeSpec spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ diff --git a/builder/vsphere/driver/vm_mock.go b/builder/vsphere/driver/vm_mock.go new file mode 100644 index 000000000..cd7f12422 --- /dev/null +++ b/builder/vsphere/driver/vm_mock.go @@ -0,0 +1,147 @@ +package driver + +import ( + "context" + "net" + "time" + + "github.com/vmware/govmomi/nfc" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/ovf" + "github.com/vmware/govmomi/vapi/vcenter" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type VirtualMachineMock struct { + DestroyError error + DestroyCalled bool +} + +func (vm *VirtualMachineMock) Info(params ...string) (*mo.VirtualMachine, error) { + return nil, nil +} + +func (vm *VirtualMachineMock) Devices() (object.VirtualDeviceList, error) { + return object.VirtualDeviceList{}, nil +} + +func (vm *VirtualMachineMock) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) { + return nil, nil +} + +func (vm *VirtualMachineMock) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) { + return nil, nil +} + +func (vm *VirtualMachineMock) AddPublicKeys(ctx context.Context, publicKeys string) error { + return nil +} + +func (vm *VirtualMachineMock) Properties(ctx context.Context) (*mo.VirtualMachine, error) { + return nil, nil +} + +func (vm *VirtualMachineMock) Destroy() error { + vm.DestroyCalled = true + if vm.DestroyError != nil { + return vm.DestroyError + } + return nil +} + +func (vm *VirtualMachineMock) Configure(config *HardwareConfig) error { + return nil +} + +func (vm *VirtualMachineMock) Customize(spec types.CustomizationSpec) error { + return nil +} + +func (vm *VirtualMachineMock) ResizeDisk(diskSize int64) error { + return nil +} + +func (vm *VirtualMachineMock) PowerOn() error { + return nil +} + +func (vm *VirtualMachineMock) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) { + return "", nil +} + +func (vm *VirtualMachineMock) PowerOff() error { + return nil +} + +func (vm *VirtualMachineMock) IsPoweredOff() (bool, error) { + return false, nil +} + +func (vm *VirtualMachineMock) StartShutdown() error { + return nil +} + +func (vm *VirtualMachineMock) WaitForShutdown(ctx context.Context, timeout time.Duration) error { + return nil +} + +func (vm *VirtualMachineMock) CreateSnapshot(name string) error { + return nil +} + +func (vm *VirtualMachineMock) ConvertToTemplate() error { + return nil +} + +func (vm *VirtualMachineMock) ImportOvfToContentLibrary(ovf vcenter.OVF) error { + return nil +} + +func (vm *VirtualMachineMock) ImportToContentLibrary(template vcenter.Template) error { + return nil +} + +func (vm *VirtualMachineMock) GetDir() (string, error) { + return "", nil +} + +func (vm *VirtualMachineMock) AddCdrom(controllerType string, datastoreIsoPath string) error { + return nil +} + +func (vm *VirtualMachineMock) AddFloppy(imgPath string) error { + return nil +} + +func (vm *VirtualMachineMock) SetBootOrder(order []string) error { + return nil +} + +func (vm *VirtualMachineMock) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error { + return nil +} + +func (vm *VirtualMachineMock) addDevice(device types.BaseVirtualDevice) error { + return nil +} + +func (vm *VirtualMachineMock) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error { + return nil +} + +func (vm *VirtualMachineMock) Export() (*nfc.Lease, error) { + return nil, nil +} + +func (vm *VirtualMachineMock) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) { + return nil, nil +} + +func (vm *VirtualMachineMock) NewOvfManager() *ovf.Manager { + return nil +} + +func (vm *VirtualMachineMock) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) { + return nil, nil +} diff --git a/builder/vsphere/iso/builder.go b/builder/vsphere/iso/builder.go index 38a2a120a..0665b2088 100644 --- a/builder/vsphere/iso/builder.go +++ b/builder/vsphere/iso/builder.go @@ -170,7 +170,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack artifact := &common.Artifact{ Name: b.config.VMName, - VM: state.Get("vm").(*driver.VirtualMachine), + VM: state.Get("vm").(*driver.VirtualMachineDriver), StateData: map[string]interface{}{"generated_data": state.Get("generated_data")}, } diff --git a/builder/vsphere/iso/common_test.go b/builder/vsphere/iso/common_test.go new file mode 100644 index 000000000..583660a92 --- /dev/null +++ b/builder/vsphere/iso/common_test.go @@ -0,0 +1,17 @@ +package iso + +import ( + "bytes" + + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +func basicStateBag() *multistep.BasicStateBag { + state := new(multistep.BasicStateBag) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/builder/vsphere/iso/step_add_cdrom.go b/builder/vsphere/iso/step_add_cdrom.go index e3691f0d9..6a6c22c1f 100644 --- a/builder/vsphere/iso/step_add_cdrom.go +++ b/builder/vsphere/iso/step_add_cdrom.go @@ -42,7 +42,7 @@ func (c *CDRomConfig) Prepare() []error { func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) if s.Config.CdromType == "sata" { if _, err := vm.FindSATAController(); err == driver.ErrNoSataController { diff --git a/builder/vsphere/iso/step_add_floppy.go b/builder/vsphere/iso/step_add_floppy.go index 722993236..ee69a9c5c 100644 --- a/builder/vsphere/iso/step_add_floppy.go +++ b/builder/vsphere/iso/step_add_floppy.go @@ -37,8 +37,8 @@ type StepAddFloppy struct { func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) - d := state.Get("driver").(*driver.Driver) + vm := state.Get("vm").(*driver.VirtualMachineDriver) + d := state.Get("driver").(*driver.VCenterDriver) if floppyPath, ok := state.GetOk("floppy_path"); ok { ui.Say("Uploading created floppy image") @@ -90,7 +90,7 @@ func (s *StepAddFloppy) Cleanup(state multistep.StateBag) { } ui := state.Get("ui").(packer.Ui) - d := state.Get("driver").(*driver.Driver) + d := state.Get("driver").(*driver.VCenterDriver) if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok { ui.Say("Deleting Floppy image ...") diff --git a/builder/vsphere/iso/step_create.go b/builder/vsphere/iso/step_create.go index 937990d82..fe76da9c9 100644 --- a/builder/vsphere/iso/step_create.go +++ b/builder/vsphere/iso/step_create.go @@ -216,7 +216,7 @@ type StepCreateVM struct { func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - d := state.Get("driver").(*driver.Driver) + d := state.Get("driver").(driver.Driver) vmPath := path.Join(s.Location.Folder, s.Location.VMName) err := d.PreCleanVM(ui, vmPath, s.Force) @@ -287,7 +287,7 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) { if st == nil { return } - vm := st.(*driver.VirtualMachine) + vm := st.(driver.VirtualMachine) ui.Say("Destroying VM...") err := vm.Destroy() diff --git a/builder/vsphere/iso/step_create_test.go b/builder/vsphere/iso/step_create_test.go new file mode 100644 index 000000000..814ff6877 --- /dev/null +++ b/builder/vsphere/iso/step_create_test.go @@ -0,0 +1,351 @@ +package iso + +import ( + "context" + "errors" + "io/ioutil" + "path" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/packer/builder/vsphere/common" + "github.com/hashicorp/packer/builder/vsphere/driver" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +func TestCreateConfig_Prepare(t *testing.T) { + // Empty config - check defaults + config := &CreateConfig{} + if errs := config.Prepare(); len(errs) != 0 { + t.Fatalf("Config preprare should not fail") + } + if config.GuestOSType != "otherGuest" { + t.Fatalf("GuestOSType should default to 'otherGuest'") + } + if len(config.DiskControllerType) != 1 { + t.Fatalf("DiskControllerType should have at least one element as default") + } + + // Data validation + tc := []struct { + name string + config *CreateConfig + fail bool + expectedErrMsg string + }{ + { + name: "Storage validate disk_size", + config: &CreateConfig{ + Storage: []DiskConfig{ + { + DiskSize: 0, + DiskThinProvisioned: true, + }, + }, + }, + fail: true, + expectedErrMsg: "storage[0].'disk_size' is required", + }, + { + name: "Storage validate disk_controller_index", + config: &CreateConfig{ + Storage: []DiskConfig{ + { + DiskSize: 32768, + DiskControllerIndex: 3, + }, + }, + }, + fail: true, + expectedErrMsg: "storage[0].'disk_controller_index' references an unknown disk controller", + }, + { + name: "USBController validate 'usb' and 'xhci' can be set together", + config: &CreateConfig{ + USBController: []string{"usb", "xhci"}, + }, + fail: false, + }, + { + name: "USBController validate '1' and '0' can be set together", + config: &CreateConfig{ + USBController: []string{"1", "0"}, + }, + fail: false, + }, + { + name: "USBController validate 'true' and 'false' can be set together", + config: &CreateConfig{ + USBController: []string{"true", "false"}, + }, + fail: false, + }, + { + name: "USBController validate 'true' and 'usb' cannot be set together", + config: &CreateConfig{ + USBController: []string{"true", "usb"}, + }, + fail: true, + expectedErrMsg: "there can only be one usb controller and one xhci controller", + }, + { + name: "USBController validate '1' and 'usb' cannot be set together", + config: &CreateConfig{ + USBController: []string{"1", "usb"}, + }, + fail: true, + expectedErrMsg: "there can only be one usb controller and one xhci controller", + }, + { + name: "USBController validate 'xhci' cannot be set more that once", + config: &CreateConfig{ + USBController: []string{"xhci", "xhci"}, + }, + fail: true, + expectedErrMsg: "there can only be one usb controller and one xhci controller", + }, + { + name: "USBController validate unknown value cannot be set", + config: &CreateConfig{ + USBController: []string{"unknown"}, + }, + fail: true, + expectedErrMsg: "usb_controller[0] references an unknown usb controller", + }, + } + + for _, c := range tc { + errs := c.config.Prepare() + if c.fail { + if len(errs) == 0 { + t.Fatalf("Config preprare should fail") + } + if errs[0].Error() != c.expectedErrMsg { + t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error()) + } + } else { + if len(errs) != 0 { + t.Fatalf("Config preprare should not fail") + } + } + } +} + +func TestStepCreateVM_Run(t *testing.T) { + state := basicStateBag() + driverMock := driver.NewDriverMock() + state.Put("driver", driverMock) + step := basicStepCreateVM() + step.Force = true + vmPath := path.Join(step.Location.Folder, step.Location.VMName) + + if action := step.Run(context.TODO(), state); action == multistep.ActionHalt { + t.Fatalf("Should not halt.") + } + + // Pre clean VM + if !driverMock.PreCleanVMCalled { + t.Fatalf("driver.PreCleanVM should be called.") + } + if driverMock.PreCleanForce != step.Force { + t.Fatalf("Force PreCleanVM should be %t but was %t.", step.Force, driverMock.PreCleanForce) + } + if driverMock.PreCleanVMPath != vmPath { + t.Fatalf("VM path expected to be %s but was %s", vmPath, driverMock.PreCleanVMPath) + } + + if !driverMock.CreateVMCalled { + t.Fatalf("driver.CreateVM should be called.") + } + if diff := cmp.Diff(driverMock.CreateConfig, driverCreateConfig(step.Config, step.Location)); diff != "" { + t.Fatalf("wrog driver.CreateConfig: %s", diff) + } + vm, ok := state.GetOk("vm") + if !ok { + t.Fatal("state must contain the VM") + } + if vm != driverMock.VM { + t.Fatalf("state doesn't contain the created VM.") + } +} + +func TestStepCreateVM_RunHalt(t *testing.T) { + state := basicStateBag() + step := basicStepCreateVM() + + // PreCleanVM fails + driverMock := driver.NewDriverMock() + driverMock.PreCleanShouldFail = true + state.Put("driver", driverMock) + if action := step.Run(context.TODO(), state); action != multistep.ActionHalt { + t.Fatalf("Step should halt.") + } + if !driverMock.PreCleanVMCalled { + t.Fatalf("driver.PreCleanVM should be called") + } + + // CreateVM fails + driverMock = driver.NewDriverMock() + driverMock.CreateVMShouldFail = true + state.Put("driver", driverMock) + if action := step.Run(context.TODO(), state); action != multistep.ActionHalt { + t.Fatalf("Step should halt.") + } + if !driverMock.PreCleanVMCalled { + t.Fatalf("driver.PreCleanVM should be called") + } + if !driverMock.CreateVMCalled { + t.Fatalf("driver.PreCleanVM should be called") + } + if _, ok := state.GetOk("vm"); ok { + t.Fatal("state should not contain a VM") + } +} + +func TestStepCreateVM_Cleanup(t *testing.T) { + state := basicStateBag() + step := basicStepCreateVM() + vm := new(driver.VirtualMachineMock) + state.Put("vm", vm) + + // Clean up when state is cancelled + state.Put(multistep.StateCancelled, true) + step.Cleanup(state) + if !vm.DestroyCalled { + t.Fatalf("vm.Destroy should be called") + } + vm.DestroyCalled = false + state.Remove(multistep.StateCancelled) + + // Clean up when state is halted + state.Put(multistep.StateHalted, true) + step.Cleanup(state) + if !vm.DestroyCalled { + t.Fatalf("vm.Destroy should be called") + } + vm.DestroyCalled = false + state.Remove(multistep.StateHalted) + + // Clean up when state is destroy_vm is set + state.Put("destroy_vm", true) + step.Cleanup(state) + if !vm.DestroyCalled { + t.Fatalf("vm.Destroy should be called") + } + vm.DestroyCalled = false + state.Remove("destroy_vm") + + // Don't clean up if state is not set with previous values + step.Cleanup(state) + if vm.DestroyCalled { + t.Fatalf("vm.Destroy should not be called") + } + + // Destroy fail + errorBuffer := &strings.Builder{} + ui := &packer.BasicUi{ + Reader: strings.NewReader(""), + Writer: ioutil.Discard, + ErrorWriter: errorBuffer, + } + state.Put("ui", ui) + state.Put(multistep.StateCancelled, true) + vm.DestroyError = errors.New("destroy failed") + + step.Cleanup(state) + if !vm.DestroyCalled { + t.Fatalf("vm.Destroy should be called") + } + if !strings.Contains(errorBuffer.String(), vm.DestroyError.Error()) { + t.Fatalf("Destroy should fail with error message '%s' but failed with '%s'", vm.DestroyError.Error(), errorBuffer.String()) + } + vm.DestroyCalled = false + state.Remove(multistep.StateCancelled) + + // Should not destroy if VM is not set + state.Remove("vm") + state.Put(multistep.StateCancelled, true) + step.Cleanup(state) + if vm.DestroyCalled { + t.Fatalf("vm.Destroy should not be called") + } +} + +func basicStepCreateVM() *StepCreateVM { + step := &StepCreateVM{ + Config: createConfig(), + Location: basicLocationConfig(), + } + return step +} + +func basicLocationConfig() *common.LocationConfig { + return &common.LocationConfig{ + VMName: "test-vm", + Folder: "test-folder", + Cluster: "test-cluster", + Host: "test-host", + ResourcePool: "test-resource-pool", + Datastore: "test-datastore", + } +} + +func createConfig() *CreateConfig { + return &CreateConfig{ + Version: 1, + GuestOSType: "ubuntu64Guest", + DiskControllerType: []string{"pvscsi"}, + Storage: []DiskConfig{ + { + DiskSize: 32768, + DiskThinProvisioned: true, + }, + }, + NICs: []NIC{ + { + Network: "VM Network", + NetworkCard: "vmxnet3", + }, + }, + } +} + +func driverCreateConfig(config *CreateConfig, location *common.LocationConfig) *driver.CreateConfig { + var networkCards []driver.NIC + for _, nic := range config.NICs { + networkCards = append(networkCards, driver.NIC{ + Network: nic.Network, + NetworkCard: nic.NetworkCard, + MacAddress: nic.MacAddress, + Passthrough: nic.Passthrough, + }) + } + + var disks []driver.Disk + for _, disk := range config.Storage { + disks = append(disks, driver.Disk{ + DiskSize: disk.DiskSize, + DiskEagerlyScrub: disk.DiskEagerlyScrub, + DiskThinProvisioned: disk.DiskThinProvisioned, + ControllerIndex: disk.DiskControllerIndex, + }) + } + + return &driver.CreateConfig{ + DiskControllerType: config.DiskControllerType, + Storage: disks, + Annotation: config.Notes, + Name: location.VMName, + Folder: location.Folder, + Cluster: location.Cluster, + Host: location.Host, + ResourcePool: location.ResourcePool, + Datastore: location.Datastore, + GuestOS: config.GuestOSType, + NICs: networkCards, + USBController: config.USBController, + Version: config.Version, + } +} diff --git a/builder/vsphere/iso/step_remote_upload.go b/builder/vsphere/iso/step_remote_upload.go index 9b0eeaeb0..7161962b8 100644 --- a/builder/vsphere/iso/step_remote_upload.go +++ b/builder/vsphere/iso/step_remote_upload.go @@ -18,7 +18,7 @@ type StepRemoteUpload struct { func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - d := state.Get("driver").(*driver.Driver) + d := state.Get("driver").(driver.Driver) if path, ok := state.GetOk("iso_path"); ok { filename := filepath.Base(path.(string)) diff --git a/builder/vsphere/iso/step_remote_upload_test.go b/builder/vsphere/iso/step_remote_upload_test.go new file mode 100644 index 000000000..1e19f747c --- /dev/null +++ b/builder/vsphere/iso/step_remote_upload_test.go @@ -0,0 +1,67 @@ +package iso + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/packer/builder/vsphere/driver" + "github.com/hashicorp/packer/helper/multistep" +) + +func TestStepRemoteUpload_Run(t *testing.T) { + state := basicStateBag() + driverMock := driver.NewDriverMock() + state.Put("driver", driverMock) + state.Put("iso_path", "[datastore] iso/path") + + step := &StepRemoteUpload{ + Datastore: "datastore", + Host: "host", + SetHostForDatastoreUploads: false, + } + + if action := step.Run(context.TODO(), state); action == multistep.ActionHalt { + t.Fatalf("Should not halt.") + } + + if !driverMock.FindDatastoreCalled { + t.Fatalf("driver.FindDatastore should be called.") + } + if !driverMock.DatastoreMock.FileExistsCalled { + t.Fatalf("datastore.FindDatastore should be called.") + } + if !driverMock.DatastoreMock.MakeDirectoryCalled { + t.Fatalf("datastore.MakeDirectory should be called.") + } + if !driverMock.DatastoreMock.UploadFileCalled { + t.Fatalf("datastore.UploadFile should be called.") + } + remotePath, ok := state.GetOk("iso_remote_path") + if !ok { + t.Fatalf("state should contain iso_remote_path") + } + expectedRemovePath := fmt.Sprintf("[%s] packer_cache//path", driverMock.DatastoreMock.Name()) + if remotePath != expectedRemovePath { + t.Fatalf("iso_remote_path expected to be %s but was %s", expectedRemovePath, remotePath) + } +} + +func TestStepRemoteUpload_SkipRun(t *testing.T) { + state := basicStateBag() + driverMock := driver.NewDriverMock() + state.Put("driver", driverMock) + + step := &StepRemoteUpload{} + + if action := step.Run(context.TODO(), state); action == multistep.ActionHalt { + t.Fatalf("Should not halt.") + } + + if driverMock.FindDatastoreCalled { + t.Fatalf("driver.FindDatastore should not be called.") + } + if _, ok := state.GetOk("iso_remote_path"); ok { + t.Fatalf("state should not contain iso_remote_path") + } +} diff --git a/builder/vsphere/iso/step_remove_cdrom.go b/builder/vsphere/iso/step_remove_cdrom.go index de943e675..9aba1b46a 100644 --- a/builder/vsphere/iso/step_remove_cdrom.go +++ b/builder/vsphere/iso/step_remove_cdrom.go @@ -22,7 +22,7 @@ type StepRemoveCDRom struct { func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) + vm := state.Get("vm").(*driver.VirtualMachineDriver) ui.Say("Eject CD-ROM drives...") err := vm.EjectCdroms() diff --git a/builder/vsphere/iso/step_remove_floppy.go b/builder/vsphere/iso/step_remove_floppy.go index 39d4f9b54..012b7be23 100644 --- a/builder/vsphere/iso/step_remove_floppy.go +++ b/builder/vsphere/iso/step_remove_floppy.go @@ -16,8 +16,8 @@ type StepRemoveFloppy struct { func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - vm := state.Get("vm").(*driver.VirtualMachine) - d := state.Get("driver").(*driver.Driver) + vm := state.Get("vm").(*driver.VirtualMachineDriver) + d := state.Get("driver").(*driver.VCenterDriver) ui.Say("Deleting Floppy drives...") devices, err := vm.Devices()