Merge pull request #6393 from DanHam/fix-hyperv-export

Various improvements to Hyper-V ISO and VMCX builders
This commit is contained in:
Megan Marsh 2018-08-24 15:52:49 -07:00 committed by GitHub
commit c5b346b07b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1487 additions and 394 deletions

View File

@ -70,7 +70,7 @@ type Driver interface {
DeleteVirtualSwitch(string) error
CreateVirtualMachine(string, string, string, string, int64, int64, int64, string, uint, bool, bool) error
CreateVirtualMachine(string, string, string, int64, int64, int64, string, uint, bool, bool) error
AddVirtualMachineHardDrive(string, string, string, int64, int64, string) error
@ -94,9 +94,11 @@ type Driver interface {
ExportVirtualMachine(string, string) error
CompactDisks(string, string) error
PreserveLegacyExportBehaviour(string, string) error
CopyExportedVirtualMachine(string, string, string, string) error
MoveCreatedVHDsToOutputDir(string, string) error
CompactDisks(string) (string, error)
RestartVirtualMachine(string) error

View File

@ -124,7 +124,6 @@ type DriverMock struct {
CreateVirtualMachine_VmName string
CreateVirtualMachine_Path string
CreateVirtualMachine_HarddrivePath string
CreateVirtualMachine_VhdPath string
CreateVirtualMachine_Ram int64
CreateVirtualMachine_DiskSize int64
CreateVirtualMachine_DiskBlockSize int64
@ -135,7 +134,7 @@ type DriverMock struct {
CreateVirtualMachine_Err error
CloneVirtualMachine_Called bool
CloneVirtualMachine_CloneFromVmxcPath string
CloneVirtualMachine_CloneFromVmcxPath string
CloneVirtualMachine_CloneFromVmName string
CloneVirtualMachine_CloneFromSnapshotName string
CloneVirtualMachine_CloneAllSnapshots bool
@ -186,17 +185,20 @@ type DriverMock struct {
ExportVirtualMachine_Path string
ExportVirtualMachine_Err error
CompactDisks_Called bool
CompactDisks_ExpPath string
CompactDisks_VhdDir string
CompactDisks_Err error
PreserveLegacyExportBehaviour_Called bool
PreserveLegacyExportBehaviour_SrcPath string
PreserveLegacyExportBehaviour_DstPath string
PreserveLegacyExportBehaviour_Err error
CopyExportedVirtualMachine_Called bool
CopyExportedVirtualMachine_ExpPath string
CopyExportedVirtualMachine_OutputPath string
CopyExportedVirtualMachine_VhdDir string
CopyExportedVirtualMachine_VmDir string
CopyExportedVirtualMachine_Err error
MoveCreatedVHDsToOutputDir_Called bool
MoveCreatedVHDsToOutputDir_SrcPath string
MoveCreatedVHDsToOutputDir_DstPath string
MoveCreatedVHDsToOutputDir_Err error
CompactDisks_Called bool
CompactDisks_Path string
CompactDisks_Result string
CompactDisks_Err error
RestartVirtualMachine_Called bool
RestartVirtualMachine_VmName string
@ -393,7 +395,8 @@ func (d *DriverMock) CreateVirtualSwitch(switchName string, switchType string) (
return d.CreateVirtualSwitch_Return, d.CreateVirtualSwitch_Err
}
func (d *DriverMock) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, vhdDiskBlockSize int64, controllerType string) error {
func (d *DriverMock) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string,
vhdSizeBytes int64, vhdDiskBlockSize int64, controllerType string) error {
d.AddVirtualMachineHardDrive_Called = true
d.AddVirtualMachineHardDrive_VmName = vmName
d.AddVirtualMachineHardDrive_VhdFile = vhdFile
@ -404,12 +407,13 @@ func (d *DriverMock) AddVirtualMachineHardDrive(vmName string, vhdFile string, v
return d.AddVirtualMachineHardDrive_Err
}
func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool) error {
func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddrivePath string,
ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint,
diffDisks bool, fixedVHD bool) error {
d.CreateVirtualMachine_Called = true
d.CreateVirtualMachine_VmName = vmName
d.CreateVirtualMachine_Path = path
d.CreateVirtualMachine_HarddrivePath = harddrivePath
d.CreateVirtualMachine_VhdPath = vhdPath
d.CreateVirtualMachine_Ram = ram
d.CreateVirtualMachine_DiskSize = diskSize
d.CreateVirtualMachine_DiskBlockSize = diskBlockSize
@ -419,9 +423,11 @@ func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddriveP
return d.CreateVirtualMachine_Err
}
func (d *DriverMock) CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
func (d *DriverMock) CloneVirtualMachine(cloneFromVmcxPath string, cloneFromVmName string,
cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string,
harddrivePath string, ram int64, switchName string) error {
d.CloneVirtualMachine_Called = true
d.CloneVirtualMachine_CloneFromVmxcPath = cloneFromVmxcPath
d.CloneVirtualMachine_CloneFromVmcxPath = cloneFromVmcxPath
d.CloneVirtualMachine_CloneFromVmName = cloneFromVmName
d.CloneVirtualMachine_CloneFromSnapshotName = cloneFromSnapshotName
d.CloneVirtualMachine_CloneAllSnapshots = cloneAllSnapshots
@ -489,20 +495,25 @@ func (d *DriverMock) ExportVirtualMachine(vmName string, path string) error {
return d.ExportVirtualMachine_Err
}
func (d *DriverMock) CompactDisks(expPath string, vhdDir string) error {
d.CompactDisks_Called = true
d.CompactDisks_ExpPath = expPath
d.CompactDisks_VhdDir = vhdDir
return d.CompactDisks_Err
func (d *DriverMock) PreserveLegacyExportBehaviour(srcPath string, dstPath string) error {
d.PreserveLegacyExportBehaviour_Called = true
d.PreserveLegacyExportBehaviour_SrcPath = srcPath
d.PreserveLegacyExportBehaviour_DstPath = dstPath
return d.PreserveLegacyExportBehaviour_Err
}
func (d *DriverMock) CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error {
d.CopyExportedVirtualMachine_Called = true
d.CopyExportedVirtualMachine_ExpPath = expPath
d.CopyExportedVirtualMachine_OutputPath = outputPath
d.CopyExportedVirtualMachine_VhdDir = vhdDir
d.CopyExportedVirtualMachine_VmDir = vmDir
return d.CopyExportedVirtualMachine_Err
func (d *DriverMock) MoveCreatedVHDsToOutputDir(srcPath string, dstPath string) error {
d.MoveCreatedVHDsToOutputDir_Called = true
d.MoveCreatedVHDsToOutputDir_SrcPath = srcPath
d.MoveCreatedVHDsToOutputDir_DstPath = dstPath
return d.MoveCreatedVHDsToOutputDir_Err
}
func (d *DriverMock) CompactDisks(path string) (result string, err error) {
d.CompactDisks_Called = true
d.CompactDisks_Path = path
d.CompactDisks_Result = "Mock compact result msg: mockdisk.vhdx. Disk size reduced by 20%"
return d.CompactDisks_Result, d.CompactDisks_Err
}
func (d *DriverMock) RestartVirtualMachine(vmName string) error {
@ -519,7 +530,8 @@ func (d *DriverMock) CreateDvdDrive(vmName string, isoPath string, generation ui
return d.CreateDvdDrive_ControllerNumber, d.CreateDvdDrive_ControllerLocation, d.CreateDvdDrive_Err
}
func (d *DriverMock) MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error {
func (d *DriverMock) MountDvdDrive(vmName string, path string, controllerNumber uint,
controllerLocation uint) error {
d.MountDvdDrive_Called = true
d.MountDvdDrive_VmName = vmName
d.MountDvdDrive_Path = path
@ -528,7 +540,8 @@ func (d *DriverMock) MountDvdDrive(vmName string, path string, controllerNumber
return d.MountDvdDrive_Err
}
func (d *DriverMock) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error {
func (d *DriverMock) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint,
generation uint) error {
d.SetBootDvdDrive_Called = true
d.SetBootDvdDrive_VmName = vmName
d.SetBootDvdDrive_ControllerNumber = controllerNumber

View File

@ -175,16 +175,24 @@ func (d *HypervPS4Driver) CreateVirtualSwitch(switchName string, switchType stri
return hyperv.CreateVirtualSwitch(switchName, switchType)
}
func (d *HypervPS4Driver) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, diskBlockSize int64, controllerType string) error {
return hyperv.AddVirtualMachineHardDiskDrive(vmName, vhdFile, vhdName, vhdSizeBytes, diskBlockSize, controllerType)
func (d *HypervPS4Driver) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string,
vhdSizeBytes int64, diskBlockSize int64, controllerType string) error {
return hyperv.AddVirtualMachineHardDiskDrive(vmName, vhdFile, vhdName, vhdSizeBytes,
diskBlockSize, controllerType)
}
func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool) error {
return hyperv.CreateVirtualMachine(vmName, path, harddrivePath, vhdPath, ram, diskSize, diskBlockSize, switchName, generation, diffDisks, fixedVHD)
func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64,
diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool,
fixedVHD bool) error {
return hyperv.CreateVirtualMachine(vmName, path, harddrivePath, ram, diskSize, diskBlockSize, switchName,
generation, diffDisks, fixedVHD)
}
func (d *HypervPS4Driver) CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
return hyperv.CloneVirtualMachine(cloneFromVmxcPath, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots, vmName, path, harddrivePath, ram, switchName)
func (d *HypervPS4Driver) CloneVirtualMachine(cloneFromVmcxPath string, cloneFromVmName string,
cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string,
ram int64, switchName string) error {
return hyperv.CloneVirtualMachine(cloneFromVmcxPath, cloneFromVmName, cloneFromSnapshotName,
cloneAllSnapshots, vmName, path, harddrivePath, ram, switchName)
}
func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error {
@ -211,7 +219,8 @@ func (d *HypervPS4Driver) SetVirtualMachineVirtualizationExtensions(vmName strin
return hyperv.SetVirtualMachineVirtualizationExtensions(vmName, enable)
}
func (d *HypervPS4Driver) EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error {
func (d *HypervPS4Driver) EnableVirtualMachineIntegrationService(vmName string,
integrationServiceName string) error {
return hyperv.EnableVirtualMachineIntegrationService(vmName, integrationServiceName)
}
@ -219,12 +228,16 @@ func (d *HypervPS4Driver) ExportVirtualMachine(vmName string, path string) error
return hyperv.ExportVirtualMachine(vmName, path)
}
func (d *HypervPS4Driver) CompactDisks(expPath string, vhdDir string) error {
return hyperv.CompactDisks(expPath, vhdDir)
func (d *HypervPS4Driver) PreserveLegacyExportBehaviour(srcPath string, dstPath string) error {
return hyperv.PreserveLegacyExportBehaviour(srcPath, dstPath)
}
func (d *HypervPS4Driver) CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error {
return hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
func (d *HypervPS4Driver) MoveCreatedVHDsToOutputDir(srcPath string, dstPath string) error {
return hyperv.MoveCreatedVHDsToOutputDir(srcPath, dstPath)
}
func (d *HypervPS4Driver) CompactDisks(path string) (result string, err error) {
return hyperv.CompactDisks(path)
}
func (d *HypervPS4Driver) RestartVirtualMachine(vmName string) error {
@ -235,11 +248,13 @@ func (d *HypervPS4Driver) CreateDvdDrive(vmName string, isoPath string, generati
return hyperv.CreateDvdDrive(vmName, isoPath, generation)
}
func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error {
func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNumber uint,
controllerLocation uint) error {
return hyperv.MountDvdDrive(vmName, path, controllerNumber, controllerLocation)
}
func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error {
func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint,
generation uint) error {
return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, generation)
}

View File

@ -16,7 +16,7 @@ import (
// Produces:
// VMName string - The name of the VM
type StepCloneVM struct {
CloneFromVMXCPath string
CloneFromVMCXPath string
CloneFromVMName string
CloneFromSnapshotName string
CloneAllSnapshots bool
@ -37,7 +37,7 @@ func (s *StepCloneVM) Run(_ context.Context, state multistep.StateBag) multistep
ui := state.Get("ui").(packer.Ui)
ui.Say("Cloning virtual machine...")
path := state.Get("packerTempDir").(string)
path := state.Get("build_dir").(string)
// Determine if we even have an existing virtual harddrive to attach
harddrivePath := ""
@ -55,7 +55,8 @@ func (s *StepCloneVM) Run(_ context.Context, state multistep.StateBag) multistep
// convert the MB to bytes
ramSize := int64(s.RamSize * 1024 * 1024)
err := driver.CloneVirtualMachine(s.CloneFromVMXCPath, s.CloneFromVMName, s.CloneFromSnapshotName, s.CloneAllSnapshots, s.VMName, path, harddrivePath, ramSize, s.SwitchName)
err := driver.CloneVirtualMachine(s.CloneFromVMCXPath, s.CloneFromVMName, s.CloneFromSnapshotName,
s.CloneAllSnapshots, s.VMName, path, harddrivePath, ramSize, s.SwitchName)
if err != nil {
err := fmt.Errorf("Error cloning virtual machine: %s", err)
state.Put("error", err)

View File

@ -0,0 +1,69 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCollateArtifacts struct {
OutputDir string
SkipExport bool
}
// Runs the step required to collate all build artifacts under the
// specified output directory
func (s *StepCollateArtifacts) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Collating build artifacts...")
if s.SkipExport {
// Get the path to the main build directory from the statebag
var buildDir string
if v, ok := state.GetOk("build_dir"); ok {
buildDir = v.(string)
}
// If the user has chosen to skip a full export of the VM the only
// artifacts that they are interested in will be the VHDs. The
// called function searches for all disks under the given source
// directory and moves them to a 'Virtual Hard Disks' folder under
// the destination directory
err := driver.MoveCreatedVHDsToOutputDir(buildDir, s.OutputDir)
if err != nil {
err = fmt.Errorf("Error moving VHDs from build dir to output dir: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
// Get the full path to the export directory from the statebag
var exportPath string
if v, ok := state.GetOk("export_path"); ok {
exportPath = v.(string)
}
// The export process exports the VM into a folder named 'vm name'
// under the output directory. However, to maintain backwards
// compatibility, we now need to shuffle around the exported folders
// so the 'Snapshots', 'Virtual Hard Disks' and 'Virtual Machines'
// directories appear *directly* under <output directory>.
// The empty '<output directory>/<vm name>' directory is removed
// when complete.
// The 'Snapshots' folder will not be moved into the output
// directory if it is empty.
err := driver.PreserveLegacyExportBehaviour(exportPath, s.OutputDir)
if err != nil {
// No need to halt here; Just warn the user instead
err = fmt.Errorf("WARNING: Error restoring legacy export dir structure: %s", err)
ui.Error(err.Error())
}
}
return multistep.ActionContinue
}
// Cleanup does nothing
func (s *StepCollateArtifacts) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,92 @@
package common
import (
"context"
"path/filepath"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepCollateArtifacts_impl(t *testing.T) {
var _ multistep.Step = new(StepCollateArtifacts)
}
func TestStepCollateArtifacts_exportedArtifacts(t *testing.T) {
state := testState(t)
step := new(StepCollateArtifacts)
step.OutputDir = "foopath"
vmName := "foo"
// Uses export path from the state bag
exportPath := filepath.Join(step.OutputDir, vmName)
state.Put("export_path", exportPath)
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
// Test the driver
if !driver.PreserveLegacyExportBehaviour_Called {
t.Fatal("Should have called PreserveLegacyExportBehaviour")
}
if driver.PreserveLegacyExportBehaviour_SrcPath != exportPath {
t.Fatalf("Should call with correct srcPath. Got: %s Wanted: %s",
driver.PreserveLegacyExportBehaviour_SrcPath, exportPath)
}
if driver.PreserveLegacyExportBehaviour_DstPath != step.OutputDir {
t.Fatalf("Should call with correct dstPath. Got: %s Wanted: %s",
driver.PreserveLegacyExportBehaviour_DstPath, step.OutputDir)
}
// Should only be called when skip_export is true
if driver.MoveCreatedVHDsToOutputDir_Called {
t.Fatal("Should NOT have called MoveCreatedVHDsToOutputDir")
}
}
func TestStepCollateArtifacts_skipExportArtifacts(t *testing.T) {
state := testState(t)
step := new(StepCollateArtifacts)
// Needs the path to the main output directory and build directory
step.OutputDir = "foopath"
buildDir := "fooBuildPath"
state.Put("build_dir", buildDir)
// Export has been skipped
step.SkipExport = true
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
// Test the driver
if !driver.MoveCreatedVHDsToOutputDir_Called {
t.Fatal("Should have called MoveCreatedVHDsToOutputDir")
}
if driver.MoveCreatedVHDsToOutputDir_SrcPath != buildDir {
t.Fatalf("Should call with correct srcPath. Got: %s Wanted: %s",
driver.MoveCreatedVHDsToOutputDir_SrcPath, buildDir)
}
if driver.MoveCreatedVHDsToOutputDir_DstPath != step.OutputDir {
t.Fatalf("Should call with correct dstPath. Got: %s Wanted: %s",
driver.MoveCreatedVHDsToOutputDir_DstPath, step.OutputDir)
}
if driver.PreserveLegacyExportBehaviour_Called {
t.Fatal("Should NOT have called PreserveLegacyExportBehaviour")
}
}

View File

@ -0,0 +1,50 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCompactDisk struct {
SkipCompaction bool
}
// Run runs a compaction/optimisation process on attached VHD/VHDX disks
func (s *StepCompactDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if s.SkipCompaction {
ui.Say("Skipping disk compaction...")
return multistep.ActionContinue
}
// Get the dir used to store the VMs files during the build process
var buildDir string
if v, ok := state.GetOk("build_dir"); ok {
buildDir = v.(string)
}
ui.Say("Compacting disks...")
// CompactDisks searches for all VHD/VHDX files under the supplied
// path and runs the compacting process on each of them. If no disks
// are found under the supplied path this is treated as a 'soft' error
// and a warning message is printed. All other errors halt the build.
result, err := driver.CompactDisks(buildDir)
if err != nil {
err := fmt.Errorf("Error compacting disks: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Report disk compaction results/warn if no disks were found
ui.Message(result)
return multistep.ActionContinue
}
// Cleanup does nothing
func (s *StepCompactDisk) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,63 @@
package common
import (
"context"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepCompactDisk_impl(t *testing.T) {
var _ multistep.Step = new(StepCompactDisk)
}
func TestStepCompactDisk(t *testing.T) {
state := testState(t)
step := new(StepCompactDisk)
// Set up the path to the build directory
buildDir := "foopath"
state.Put("build_dir", buildDir)
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
// Test the driver
if !driver.CompactDisks_Called {
t.Fatal("Should have called CompactDisks")
}
if driver.CompactDisks_Path != buildDir {
t.Fatalf("Should call with correct path. Got: %s Wanted: %s", driver.CompactDisks_Path, buildDir)
}
}
func TestStepCompactDisk_skip(t *testing.T) {
state := testState(t)
step := new(StepCompactDisk)
step.SkipCompaction = true
// Set up the path to the build directory
state.Put("build_dir", "foopath")
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatalf("Should NOT have error")
}
// Test the driver
if driver.CompactDisks_Called {
t.Fatal("Should NOT have called CompactDisks")
}
}

View File

@ -0,0 +1,66 @@
package common
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCreateBuildDir struct {
// User supplied directory under which we create the main build
// directory. The build directory is used to house the VM files and
// folders during the build. If unspecified the default temp directory
// for the OS is used
TempPath string
// The full path to the build directory. This is the concatenation of
// TempPath plus a directory uniquely named for the build
buildDir string
}
// Creates the main directory used to house the VMs files and folders
// during the build
func (s *StepCreateBuildDir) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating build directory...")
if s.TempPath == "" {
s.TempPath = os.TempDir()
}
var err error
s.buildDir, err = ioutil.TempDir(s.TempPath, "packerhv")
if err != nil {
err = fmt.Errorf("Error creating build directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Created build directory: %s", s.buildDir)
// Record the build directory location for later steps
state.Put("build_dir", s.buildDir)
return multistep.ActionContinue
}
// Cleanup removes the build directory
func (s *StepCreateBuildDir) Cleanup(state multistep.StateBag) {
if s.buildDir == "" {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting build directory...")
err := os.RemoveAll(s.buildDir)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting build directory: %s", err))
}
}

View File

@ -0,0 +1,113 @@
package common
import (
"context"
"os"
"path/filepath"
"regexp"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepCreateBuildDir_imp(t *testing.T) {
var _ multistep.Step = new(StepCreateBuildDir)
}
func TestStepCreateBuildDir_Defaults(t *testing.T) {
state := testState(t)
step := new(StepCreateBuildDir)
// Default is for the user not to supply value for TempPath. When
// nothing is set the step should use the OS temp directory as the root
// for the build directory
step.TempPath = ""
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
if v, ok := state.GetOk("build_dir"); !ok {
t.Fatal("Should store path to build directory in statebag as 'build_dir'")
} else {
// On windows convert everything to forward slash separated paths
// This prevents the regexp interpreting backslashes as escape sequences
stateBuildDir := filepath.ToSlash(v.(string))
expectedBuildDirRe := regexp.MustCompile(
filepath.ToSlash(filepath.Join(os.TempDir(), "packerhv") + `[[:digit:]]{9}$`))
match := expectedBuildDirRe.MatchString(stateBuildDir)
if !match {
t.Fatalf("Got path that doesn't match expected format in 'build_dir': %s", stateBuildDir)
}
}
// Test Cleanup
step.Cleanup(state)
if _, err := os.Stat(step.buildDir); err == nil {
t.Fatalf("Build directory should NOT exist after Cleanup: %s", step.buildDir)
}
}
func TestStepCreateBuildDir_UserDefinedTempPath(t *testing.T) {
state := testState(t)
step := new(StepCreateBuildDir)
// Create a directory we'll use as the user supplied temp_path
step.TempPath = genTestDirPath("userTempDir")
err := os.Mkdir(step.TempPath, 0755) // The directory must exist
if err != nil {
t.Fatal("Error creating test directory")
}
defer os.RemoveAll(step.TempPath)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
if v, ok := state.GetOk("build_dir"); !ok {
t.Fatal("Should store path to build directory in statebag as 'build_dir'")
} else {
// On windows convert everything to forward slash separated paths
// This prevents the regexp interpreting backslashes as escape sequences
stateBuildDir := filepath.ToSlash(v.(string))
expectedBuildDirRe := regexp.MustCompile(
filepath.ToSlash(filepath.Join(step.TempPath, "packerhv") + `[[:digit:]]{9}$`))
match := expectedBuildDirRe.MatchString(stateBuildDir)
if !match {
t.Fatalf("Got path that doesn't match expected format in 'build_dir': %s", stateBuildDir)
}
}
// Test Cleanup
step.Cleanup(state)
if _, err := os.Stat(step.buildDir); err == nil {
t.Fatalf("Build directory should NOT exist after Cleanup: %s", step.buildDir)
}
if _, err := os.Stat(step.TempPath); err != nil {
t.Fatal("User supplied root for build directory should NOT be deleted by Cleanup")
}
}
func TestStepCreateBuildDir_BadTempPath(t *testing.T) {
state := testState(t)
step := new(StepCreateBuildDir)
// Bad
step.TempPath = genTestDirPath("iDontExist")
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatal("Should have error due to bad path")
}
}

View File

@ -9,7 +9,7 @@ import (
"github.com/hashicorp/packer/packer"
)
// This step creates switch for VM.
// This step creates an external switch for the VM.
//
// Produces:
// SwitchName string - The name of the Switch
@ -18,6 +18,9 @@ type StepCreateExternalSwitch struct {
oldSwitchName string
}
// Run runs the step required to create an external switch. Depending on
// the connectivity of the host machine, the external switch will allow the
// build VM to connect to the outside world.
func (s *StepCreateExternalSwitch) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
@ -30,10 +33,12 @@ func (s *StepCreateExternalSwitch) Run(_ context.Context, state multistep.StateB
packerExternalSwitchName := "paes_" + uuid.TimeOrderedUUID()
// CreateExternalVirtualSwitch checks for an existing external switch,
// creating one if required, and connects the VM to it
err = driver.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName)
if err != nil {
err := fmt.Errorf("Error creating switch: %s", err)
state.Put(errorMsg, err)
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
s.SwitchName = ""
return multistep.ActionHalt

View File

@ -1,85 +0,0 @@
package common
import (
"context"
"fmt"
"io/ioutil"
"os"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCreateTempDir struct {
// The user-supplied root directories into which we create subdirectories.
TempPath string
VhdTempPath string
// The subdirectories with the randomly generated name.
dirPath string
vhdDirPath string
}
func (s *StepCreateTempDir) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary directory...")
if s.TempPath == "" {
s.TempPath = os.TempDir()
}
packerTempDir, err := ioutil.TempDir(s.TempPath, "packerhv")
if err != nil {
err := fmt.Errorf("Error creating temporary directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.dirPath = packerTempDir
state.Put("packerTempDir", packerTempDir)
if s.VhdTempPath == "" {
// Fall back to regular temp dir if no separate VHD temp dir set.
state.Put("packerVhdTempDir", packerTempDir)
} else {
packerVhdTempDir, err := ioutil.TempDir(s.VhdTempPath, "packerhv-vhd")
if err != nil {
err := fmt.Errorf("Error creating temporary VHD directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.vhdDirPath = packerVhdTempDir
state.Put("packerVhdTempDir", packerVhdTempDir)
}
// ui.Say("packerTempDir = '" + packerTempDir + "'")
return multistep.ActionContinue
}
func (s *StepCreateTempDir) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if s.dirPath != "" {
ui.Say("Deleting temporary directory...")
err := os.RemoveAll(s.dirPath)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting temporary directory: %s", err))
}
}
if s.vhdDirPath != "" && s.dirPath != s.vhdDirPath {
ui.Say("Deleting temporary VHD directory...")
err := os.RemoveAll(s.vhdDirPath)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting temporary VHD directory: %s", err))
}
}
}

View File

@ -32,8 +32,6 @@ type StepCreateVM struct {
AdditionalDiskSize []uint
DifferencingDisk bool
MacAddress string
SkipExport bool
OutputDir string
FixedVHD bool
}
@ -42,7 +40,10 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating virtual machine...")
path := state.Get("packerTempDir").(string)
var path string
if v, ok := state.GetOk("build_dir"); ok {
path = v.(string)
}
// Determine if we even have an existing virtual harddrive to attach
harddrivePath := ""
@ -57,19 +58,13 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
log.Println("No existing virtual harddrive, not attaching.")
}
vhdPath := state.Get("packerVhdTempDir").(string)
// inline vhd path if export is skipped
if s.SkipExport {
vhdPath = filepath.Join(s.OutputDir, "Virtual Hard Disks")
}
// convert the MB to bytes
ramSize := int64(s.RamSize * 1024 * 1024)
diskSize := int64(s.DiskSize * 1024 * 1024)
diskBlockSize := int64(s.DiskBlockSize * 1024 * 1024)
err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, vhdPath, ramSize, diskSize, diskBlockSize, s.SwitchName, s.Generation, s.DifferencingDisk, s.FixedVHD)
err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, ramSize, diskSize, diskBlockSize,
s.SwitchName, s.Generation, s.DifferencingDisk, s.FixedVHD)
if err != nil {
err := fmt.Errorf("Error creating virtual machine: %s", err)
state.Put("error", err)
@ -128,7 +123,7 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
for index, size := range s.AdditionalDiskSize {
diskSize := int64(size * 1024 * 1024)
diskFile := fmt.Sprintf("%s-%d.vhdx", s.VMName, index)
err = driver.AddVirtualMachineHardDrive(s.VMName, vhdPath, diskFile, diskSize, diskBlockSize, "SCSI")
err = driver.AddVirtualMachineHardDrive(s.VMName, path, diskFile, diskSize, diskBlockSize, "SCSI")
if err != nil {
err := fmt.Errorf("Error creating and attaching additional disk drive: %s", err)
state.Put("error", err)

View File

@ -3,84 +3,49 @@ package common
import (
"context"
"fmt"
"io/ioutil"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
const (
vhdDir string = "Virtual Hard Disks"
vmDir string = "Virtual Machines"
)
type StepExportVm struct {
OutputDir string
SkipCompaction bool
SkipExport bool
OutputDir string
SkipExport bool
}
func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
var err error
var errorMsg string
vmName := state.Get("vmName").(string)
tmpPath := state.Get("packerTempDir").(string)
outputPath := s.OutputDir
expPath := s.OutputDir
if s.SkipExport {
ui.Say("Skipping export of virtual machine...")
return multistep.ActionContinue
}
// create temp path to export vm
errorMsg = "Error creating temp export path: %s"
vmExportPath, err := ioutil.TempDir(tmpPath, "export")
ui.Say("Exporting virtual machine...")
// The VM name is needed for the export command
var vmName string
if v, ok := state.GetOk("vmName"); ok {
vmName = v.(string)
}
// The export process exports the VM to a folder named 'vmName' under
// the output directory. This contains the usual 'Snapshots', 'Virtual
// Hard Disks' and 'Virtual Machines' directories.
err := driver.ExportVirtualMachine(vmName, s.OutputDir)
if err != nil {
err := fmt.Errorf(errorMsg, err)
err = fmt.Errorf("Error exporting vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if !s.SkipExport {
ui.Say("Exporting vm...")
err = driver.ExportVirtualMachine(vmName, vmExportPath)
if err != nil {
errorMsg = "Error exporting vm: %s"
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// copy to output dir
expPath = filepath.Join(vmExportPath, vmName)
}
// Store the path to the export directory for later steps
exportPath := filepath.Join(s.OutputDir, vmName)
state.Put("export_path", exportPath)
if s.SkipCompaction {
ui.Say("Skipping disk compaction...")
} else {
ui.Say("Compacting disks...")
err = driver.CompactDisks(expPath, vhdDir)
if err != nil {
errorMsg = "Error compacting disks: %s"
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if !s.SkipExport {
ui.Say("Copying to output dir...")
err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
if err != nil {
errorMsg = "Error exporting vm: %s"
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}

View File

@ -0,0 +1,87 @@
package common
import (
"context"
"path/filepath"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepExportVm_impl(t *testing.T) {
var _ multistep.Step = new(StepExportVm)
}
func TestStepExportVm(t *testing.T) {
state := testState(t)
step := new(StepExportVm)
// ExportVirtualMachine needs the VM name and a path to export to
vmName := "foo"
state.Put("vmName", vmName)
outputDir := "foopath"
step.OutputDir = outputDir
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
// Test the driver
if !driver.ExportVirtualMachine_Called {
t.Fatal("Should have called ExportVirtualMachine")
}
if driver.ExportVirtualMachine_Path != outputDir {
t.Fatalf("Should call with correct path. Got: %s Wanted: %s",
driver.ExportVirtualMachine_Path, outputDir)
}
if driver.ExportVirtualMachine_VmName != vmName {
t.Fatalf("Should call with correct vm name. Got: %s Wanted: %s",
driver.ExportVirtualMachine_VmName, vmName)
}
// Test we stored the export path in the statebag and it is correct
expectedPath := filepath.Join(outputDir, vmName)
if exportPath, ok := state.GetOk("export_path"); !ok {
t.Fatal("Should set export_path")
} else if exportPath != expectedPath {
t.Fatalf("Bad path stored for export_path. Got: %#v Wanted: %#v", exportPath, expectedPath)
}
}
func TestStepExportVm_skip(t *testing.T) {
state := testState(t)
step := new(StepExportVm)
step.SkipExport = true
// ExportVirtualMachine needs the VM name and a path to export to
vmName := "foo"
state.Put("vmName", vmName)
outputDir := "foopath"
step.OutputDir = outputDir
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatalf("Should NOT have error")
}
// Test the driver
if driver.ExportVirtualMachine_Called {
t.Fatal("Should NOT have called ExportVirtualMachine")
}
// Should not store the export path in the statebag
if _, ok := state.GetOk("export_path"); ok {
t.Fatal("Should NOT have stored export_path in the statebag")
}
}

View File

@ -56,7 +56,8 @@ func (s *StepMountGuestAdditions) Run(_ context.Context, state multistep.StateBa
return multistep.ActionHalt
}
log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation))
log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath,
controllerNumber, controllerLocation))
return multistep.ActionContinue
}

View File

@ -58,7 +58,8 @@ func (s *StepMountSecondaryDvdImages) Run(_ context.Context, state multistep.Sta
return multistep.ActionHalt
}
log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation))
log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber,
controllerLocation))
}
return multistep.ActionContinue

View File

@ -0,0 +1,146 @@
package common
import (
"context"
"os"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepOutputDir_imp(t *testing.T) {
var _ multistep.Step = new(StepOutputDir)
}
func TestStepOutputDir_Default(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
step.Path = genTestDirPath("packerHypervOutput")
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("Should NOT have error")
}
// The directory should have been created
if _, err := os.Stat(step.Path); err != nil {
t.Fatal("Should have created output directory")
}
// Remove the directory created due to test
err := os.RemoveAll(step.Path)
if err != nil {
t.Fatalf("Error encountered removing directory created by test: %s", err)
}
}
func TestStepOutputDir_DirectoryAlreadyExistsNoForce(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
step.Path = genTestDirPath("packerHypervOutput")
// Create the directory so that we can test
err := os.Mkdir(step.Path, 0755)
if err != nil {
t.Fatal("Test failed to create directory for test of Cancel and Cleanup")
}
defer os.RemoveAll(step.Path) // Ensure we clean up if something goes wrong
step.Force = false // Default
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("Should halt when directory exists and 'Force' is false. Bad action: %v", action)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatal("Should error when directory exists and 'Force' is false")
}
}
func TestStepOutputDir_DirectoryAlreadyExistsForce(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
step.Path = genTestDirPath("packerHypervOutput")
// Create the directory so that we can test
err := os.Mkdir(step.Path, 0755)
if err != nil {
t.Fatal("Test failed to create directory for test of Cancel and Cleanup")
}
defer os.RemoveAll(step.Path) // Ensure we clean up if something goes wrong
step.Force = true // User specified that existing directory and contents should be discarded
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("Should complete when directory exists and 'Force' is true. Bad action: %v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatalf("Should NOT error when directory exists and 'Force' is true: %s", err)
}
}
func TestStepOutputDir_CleanupBuildCancelled(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
step.Path = genTestDirPath("packerHypervOutput")
// Create the directory so that we can test the cleanup
err := os.Mkdir(step.Path, 0755)
if err != nil {
t.Fatal("Test failed to create directory for test of Cancel and Cleanup")
}
defer os.RemoveAll(step.Path) // Ensure we clean up if something goes wrong
// 'Cancel' the build
state.Put(multistep.StateCancelled, true)
// Ensure the directory isn't removed if the cleanup flag is false
step.cleanup = false
step.Cleanup(state)
if _, err := os.Stat(step.Path); err != nil {
t.Fatal("Output dir should NOT be removed if on 'Cancel' if cleanup flag is unset/false")
}
// Ensure the directory is removed if the cleanup flag is true
step.cleanup = true
step.Cleanup(state)
if _, err := os.Stat(step.Path); err == nil {
t.Fatalf("Output directory should NOT exist after 'Cancel' and Cleanup: %s", step.Path)
}
}
func TestStepOutputDir_CleanupBuildHalted(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
step.Path = genTestDirPath("packerHypervOutput")
// Create the directory so that we can test the cleanup
err := os.Mkdir(step.Path, 0755)
if err != nil {
t.Fatal("Test failed to create directory for test of Cancel and Cleanup")
}
defer os.RemoveAll(step.Path) // Ensure we clean up if something goes wrong
// 'Halt' the build and test the directory is removed
state.Put(multistep.StateHalted, true)
// Ensure the directory isn't removed if the cleanup flag is false
step.cleanup = false
step.Cleanup(state)
if _, err := os.Stat(step.Path); err != nil {
t.Fatal("Output dir should NOT be removed if on 'Halt' if cleanup flag is unset/false")
}
// Ensure the directory is removed if the cleanup flag is true
step.cleanup = true
step.Cleanup(state)
if _, err := os.Stat(step.Path); err == nil {
t.Fatalf("Output directory should NOT exist after 'Halt' and Cleanup: %s", step.Path)
}
}

View File

@ -17,10 +17,9 @@ import (
//
// Uses:
// communicator packer.Communicator
// dir OutputDir
// driver Driver
// ui packer.Ui
// vmx_path string
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
// <nothing>

View File

@ -18,7 +18,8 @@ func (s *StepSleep) Run(_ context.Context, state multistep.StateBag) multistep.S
ui := state.Get("ui").(packer.Ui)
if len(s.ActionName) > 0 {
ui.Say(s.ActionName + "! Waiting for " + fmt.Sprintf("%v", uint(s.Minutes)) + " minutes to let the action to complete...")
ui.Say(s.ActionName + "! Waiting for " + fmt.Sprintf("%v", uint(s.Minutes)) +
" minutes to let the action to complete...")
}
time.Sleep(time.Minute * s.Minutes)

View File

@ -0,0 +1,28 @@
package common
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("driver", new(DriverMock))
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
// Generates an absolute path to a directory under OS temp with a name
// beginning with prefix and a UUID appended to the end
func genTestDirPath(prefix string) string {
return filepath.Join(os.TempDir(), prefix+"-"+uuid.TimeOrderedUUID())
}

View File

@ -27,7 +27,8 @@ func (s *StepUnmountDvdDrive) Run(_ context.Context, state multistep.StateBag) m
dvdController := dvdControllerState.(DvdControllerProperties)
if dvdController.Existing {
ui.Say(fmt.Sprintf("Unmounting os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation))
ui.Say(fmt.Sprintf("Unmounting os dvd drives controller %d location %d ...",
dvdController.ControllerNumber, dvdController.ControllerLocation))
err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation)
if err != nil {
err := fmt.Errorf("Error unmounting os dvd drive: %s", err)
@ -36,7 +37,8 @@ func (s *StepUnmountDvdDrive) Run(_ context.Context, state multistep.StateBag) m
return multistep.ActionHalt
}
} else {
ui.Say(fmt.Sprintf("Delete os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation))
ui.Say(fmt.Sprintf("Delete os dvd drives controller %d location %d ...",
dvdController.ControllerNumber, dvdController.ControllerLocation))
err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation)
if err != nil {
err := fmt.Errorf("Error deleting os dvd drive: %s", err)

View File

@ -27,7 +27,8 @@ func (s *StepUnmountGuestAdditions) Run(_ context.Context, state multistep.State
dvdController := dvdControllerState.(DvdControllerProperties)
if dvdController.Existing {
ui.Say(fmt.Sprintf("Unmounting Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation))
ui.Say(fmt.Sprintf("Unmounting Integration Services dvd drives controller %d location %d ...",
dvdController.ControllerNumber, dvdController.ControllerLocation))
err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation)
if err != nil {
err := fmt.Errorf("Error unmounting Integration Services dvd drive: %s", err)
@ -36,7 +37,8 @@ func (s *StepUnmountGuestAdditions) Run(_ context.Context, state multistep.State
return multistep.ActionHalt
}
} else {
ui.Say(fmt.Sprintf("Delete Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation))
ui.Say(fmt.Sprintf("Delete Integration Services dvd drives controller %d location %d ...",
dvdController.ControllerNumber, dvdController.ControllerLocation))
err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation)
if err != nil {
err := fmt.Errorf("Error deleting Integration Services dvd drive: %s", err)

View File

@ -28,7 +28,8 @@ func (s *StepUnmountSecondaryDvdImages) Run(_ context.Context, state multistep.S
for _, dvdController := range dvdControllers {
if dvdController.Existing {
ui.Say(fmt.Sprintf("Unmounting secondary dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation))
ui.Say(fmt.Sprintf("Unmounting secondary dvd drives controller %d location %d ...",
dvdController.ControllerNumber, dvdController.ControllerLocation))
err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation)
if err != nil {
err := fmt.Errorf("Error unmounting secondary dvd drive: %s", err)
@ -37,7 +38,8 @@ func (s *StepUnmountSecondaryDvdImages) Run(_ context.Context, state multistep.S
return multistep.ActionHalt
}
} else {
ui.Say(fmt.Sprintf("Delete secondary dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation))
ui.Say(fmt.Sprintf("Delete secondary dvd drives controller %d location %d ...",
dvdController.ControllerNumber, dvdController.ControllerLocation))
err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation)
if err != nil {
err := fmt.Errorf("Error deleting secondary dvd drive: %s", err)

View File

@ -96,11 +96,6 @@ type Config struct {
EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"`
TempPath string `mapstructure:"temp_path"`
// A separate path can be used for storing the VM's disk image. The purpose is to enable
// reading and writing to take place on different physical disks (read from VHD temp path
// write to regular temp path while exporting the VM) to eliminate a single-disk bottleneck.
VhdTempPath string `mapstructure:"vhd_temp_path"`
Communicator string `mapstructure:"communicator"`
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
@ -150,7 +145,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
if len(b.config.ISOConfig.ISOUrls) < 1 || (strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhd" && strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhdx") {
if len(b.config.ISOConfig.ISOUrls) < 1 ||
(strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhd" &&
strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhdx") {
//We only create a new hard drive if an existing one to copy from does not exist
err = b.checkDiskSize()
if err != nil {
@ -254,25 +251,35 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if b.config.Generation < 2 && numberOfIsos > 2 {
if b.config.GuestAdditionsMode == "attach" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, "+
"so we can't support guest additions and these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, "+
"so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
}
} else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 {
if b.config.GuestAdditionsMode == "attach" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available "+
"for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available "+
"for scsi (limited to 16), so we can't support these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
}
}
if b.config.EnableVirtualizationExtensions {
hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions()
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization extensions support: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization "+
"extensions support: %s", err))
} else {
if !hasVirtualMachineVirtualizationExtensions {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support virtual machine virtualization extension. Please use Windows 10 or Windows Server 2016 or newer."))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support "+
"virtual machine virtualization extension. Please use Windows 10 or Windows Server "+
"2016 or newer."))
}
}
}
@ -307,24 +314,29 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if b.config.EnableVirtualizationExtensions {
if b.config.EnableDynamicMemory {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, dynamic memory should not be allowed.")
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " +
"dynamic memory should not be allowed.")
warnings = appendWarnings(warnings, warning)
}
if !b.config.EnableMacSpoofing {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, mac spoofing should be allowed.")
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " +
"mac spoofing should be allowed.")
warnings = appendWarnings(warnings, warning)
}
if b.config.RamSize < MinNestedVirtualizationRamSize {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start any nested VMs.")
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " +
"there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start " +
"any nested VMs.")
warnings = appendWarnings(warnings, warning)
}
}
if b.config.SwitchVlanId != "" {
if b.config.SwitchVlanId != b.config.VlanId {
warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor vlan. The switch will not be able to see traffic from the VM.")
warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor " +
"vlan. The switch will not be able to see traffic from the VM.")
warnings = appendWarnings(warnings, warning)
}
}
@ -355,9 +367,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui)
steps := []multistep.Step{
&hypervcommon.StepCreateTempDir{
TempPath: b.config.TempPath,
VhdTempPath: b.config.VhdTempPath,
&hypervcommon.StepCreateBuildDir{
TempPath: b.config.TempPath,
},
&hypervcommon.StepOutputDir{
Force: b.config.PackerForce,
@ -399,8 +410,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
AdditionalDiskSize: b.config.AdditionalDiskSize,
DifferencingDisk: b.config.DifferencingDisk,
SkipExport: b.config.SkipExport,
OutputDir: b.config.OutputDir,
MacAddress: b.config.MacAddress,
FixedVHD: b.config.FixedVHD,
},
@ -467,10 +476,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&hypervcommon.StepUnmountFloppyDrive{
Generation: b.config.Generation,
},
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
&hypervcommon.StepCompactDisk{
SkipCompaction: b.config.SkipCompaction,
SkipExport: b.config.SkipExport,
},
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
SkipExport: b.config.SkipExport,
},
&hypervcommon.StepCollateArtifacts{
OutputDir: b.config.OutputDir,
SkipExport: b.config.SkipExport,
},
// the clean up actions for each step will be executed reverse order
@ -527,11 +542,14 @@ func (b *Builder) checkDiskSize() error {
log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize))
if b.config.DiskSize < MinDiskSize {
return fmt.Errorf("disk_size: Virtual machine requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024)
return fmt.Errorf("disk_size: Virtual machine requires disk space >= %v GB, but defined: %v",
MinDiskSize, b.config.DiskSize/1024)
} else if b.config.DiskSize > MaxDiskSize && !b.config.FixedVHD {
return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024)
return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v",
MaxDiskSize, b.config.DiskSize/1024)
} else if b.config.DiskSize > MaxVHDSize && b.config.FixedVHD {
return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", MaxVHDSize/1024, b.config.DiskSize/1024)
return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v",
MaxVHDSize/1024, b.config.DiskSize/1024)
}
return nil
@ -545,9 +563,11 @@ func (b *Builder) checkDiskBlockSize() error {
log.Println(fmt.Sprintf("%s: %v", "DiskBlockSize", b.config.DiskBlockSize))
if b.config.DiskBlockSize < MinDiskBlockSize {
return fmt.Errorf("disk_block_size: Virtual machine requires disk block size >= %v MB, but defined: %v", MinDiskBlockSize, b.config.DiskBlockSize)
return fmt.Errorf("disk_block_size: Virtual machine requires disk block size >= %v MB, but defined: %v",
MinDiskBlockSize, b.config.DiskBlockSize)
} else if b.config.DiskBlockSize > MaxDiskBlockSize {
return fmt.Errorf("disk_block_size: Virtual machine requires disk block size <= %v MB, but defined: %v", MaxDiskBlockSize, b.config.DiskBlockSize)
return fmt.Errorf("disk_block_size: Virtual machine requires disk block size <= %v MB, but defined: %v",
MaxDiskBlockSize, b.config.DiskBlockSize)
}
return nil
@ -561,9 +581,11 @@ func (b *Builder) checkRamSize() error {
log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize))
if b.config.RamSize < MinRamSize {
return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize)
return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v",
MinRamSize, b.config.RamSize)
} else if b.config.RamSize > MaxRamSize {
return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize)
return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v",
MaxRamSize, b.config.RamSize)
}
return nil

View File

@ -104,7 +104,8 @@ func TestBuilderPrepare_DiskBlockSize(t *testing.T) {
t.Fatalf("bad err: %s", err)
}
if b.config.DiskBlockSize != expected_default_block_size {
t.Fatalf("bad default block size with empty config: %d. Expected %d", b.config.DiskBlockSize, expected_default_block_size)
t.Fatalf("bad default block size with empty config: %d. Expected %d", b.config.DiskBlockSize,
expected_default_block_size)
}
test_sizes := []uint{0, 1, 32, 256, 512, 1 * 1024, 32 * 1024}
@ -117,7 +118,8 @@ func TestBuilderPrepare_DiskBlockSize(t *testing.T) {
t.Fatalf("bad, should have no warns: %#v", warns)
}
if err == nil {
t.Fatalf("bad, should have error but didn't. disk_block_size=%d outside expected valid range [%d,%d]", test_size, expected_min_block_size, expected_max_block_size)
t.Fatalf("bad, should have error. disk_block_size=%d outside expected valid range [%d,%d]",
test_size, expected_min_block_size, expected_max_block_size)
}
} else {
if len(warns) > 0 {
@ -128,11 +130,13 @@ func TestBuilderPrepare_DiskBlockSize(t *testing.T) {
}
if test_size == 0 {
if b.config.DiskBlockSize != expected_default_block_size {
t.Fatalf("bad default block size with 0 value config: %d. Expected: %d", b.config.DiskBlockSize, expected_default_block_size)
t.Fatalf("bad default block size with 0 value config: %d. Expected: %d",
b.config.DiskBlockSize, expected_default_block_size)
}
} else {
if b.config.DiskBlockSize != test_size {
t.Fatalf("bad block size with 0 value config: %d. Expected: %d", b.config.DiskBlockSize, expected_default_block_size)
t.Fatalf("bad block size with 0 value config: %d. Expected: %d", b.config.DiskBlockSize,
expected_default_block_size)
}
}
}
@ -147,7 +151,8 @@ func TestBuilderPrepare_FixedVHDFormat(t *testing.T) {
config["skip_compaction"] = true
config["differencing_disk"] = false
//use_fixed_vhd_format should work with generation = 1, skip_compaction = true, and differencing_disk = false
// use_fixed_vhd_format should work with generation = 1, skip_compaction
// = true, and differencing_disk = false
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)

View File

@ -62,7 +62,7 @@ type Config struct {
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
// This is the path to a directory containing an exported virtual machine.
CloneFromVMXCPath string `mapstructure:"clone_from_vmxc_path"`
CloneFromVMCXPath string `mapstructure:"clone_from_vmcx_path"`
// This is the name of the virtual machine to clone from.
CloneFromVMName string `mapstructure:"clone_from_vm_name"`
@ -91,6 +91,7 @@ type Config struct {
EnableSecureBoot bool `mapstructure:"enable_secure_boot"`
SecureBootTemplate string `mapstructure:"secure_boot_template"`
EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"`
TempPath string `mapstructure:"temp_path"`
Communicator string `mapstructure:"communicator"`
@ -157,36 +158,45 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.Generation = 1
if b.config.CloneFromVMName == "" {
if b.config.CloneFromVMXCPath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vm_name must be specified if clone_from_vmxc_path is not specified."))
if b.config.CloneFromVMCXPath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vm_name must be specified if "+
"clone_from_vmcx_path is not specified."))
}
} else {
virtualMachineExists, err := powershell.DoesVirtualMachineExist(b.config.CloneFromVMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine to clone from exists: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine to clone "+
"from exists: %s", err))
} else {
if !virtualMachineExists {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine '%s' to clone from does not exist.", b.config.CloneFromVMName))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine '%s' to clone from does not "+
"exist.", b.config.CloneFromVMName))
} else {
b.config.Generation, err = powershell.GetVirtualMachineGeneration(b.config.CloneFromVMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine to clone from generation: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine to clone "+
"from generation: %s", err))
}
if b.config.CloneFromSnapshotName != "" {
virtualMachineSnapshotExists, err := powershell.DoesVirtualMachineSnapshotExist(b.config.CloneFromVMName, b.config.CloneFromSnapshotName)
virtualMachineSnapshotExists, err := powershell.DoesVirtualMachineSnapshotExist(
b.config.CloneFromVMName, b.config.CloneFromSnapshotName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine snapshot to clone from exists: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine "+
"snapshot to clone from exists: %s", err))
} else {
if !virtualMachineSnapshotExists {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine snapshot '%s' on virtual machine '%s' to clone from does not exist.", b.config.CloneFromSnapshotName, b.config.CloneFromVMName))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine snapshot '%s' on "+
"virtual machine '%s' to clone from does not exist.",
b.config.CloneFromSnapshotName, b.config.CloneFromVMName))
}
}
}
virtualMachineOn, err := powershell.IsVirtualMachineOn(b.config.CloneFromVMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine to clone is running: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine to "+
"clone is running: %s", err))
} else {
if virtualMachineOn {
warning := fmt.Sprintf("Cloning from a virtual machine that is running.")
@ -197,15 +207,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if b.config.CloneFromVMXCPath == "" {
if b.config.CloneFromVMCXPath == "" {
if b.config.CloneFromVMName == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vmxc_path be specified if clone_from_vm_name must is not specified."))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vmcx_path be specified if "+
"clone_from_vm_name must is not specified."))
}
} else {
if _, err := os.Stat(b.config.CloneFromVMXCPath); os.IsNotExist(err) {
if _, err := os.Stat(b.config.CloneFromVMCXPath); os.IsNotExist(err) {
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("CloneFromVMXCPath does not exist: %s", err))
errs, fmt.Errorf("CloneFromVMCXPath does not exist: %s", err))
}
}
}
@ -276,25 +287,36 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if b.config.Generation < 2 && numberOfIsos > 2 {
if b.config.GuestAdditionsMode == "attach" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so "+
"we can't support guest additions and these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so "+
"we can't support these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
}
} else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 {
if b.config.GuestAdditionsMode == "attach" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for "+
"scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for "+
"scsi (limited to 16), so we can't support these secondary dvds: %s",
strings.Join(b.config.SecondaryDvdImages, ", ")))
}
}
if b.config.EnableVirtualizationExtensions {
hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions()
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization extensions support: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization "+
"extensions support: %s", err))
} else {
if !hasVirtualMachineVirtualizationExtensions {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support virtual machine virtualization extension. Please use Windows 10 or Windows Server 2016 or newer."))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support "+
"virtual machine virtualization extension. Please use Windows 10 or Windows Server 2016 "+
"or newer."))
}
}
}
@ -314,24 +336,29 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if b.config.EnableVirtualizationExtensions {
if b.config.EnableDynamicMemory {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, dynamic memory should not be allowed.")
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " +
"dynamic memory should not be allowed.")
warnings = appendWarnings(warnings, warning)
}
if !b.config.EnableMacSpoofing {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, mac spoofing should be allowed.")
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " +
"mac spoofing should be allowed.")
warnings = appendWarnings(warnings, warning)
}
if b.config.RamSize < MinNestedVirtualizationRamSize {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start any nested VMs.")
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, " +
"there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start " +
"any nested VMs.")
warnings = appendWarnings(warnings, warning)
}
}
if b.config.SwitchVlanId != "" {
if b.config.SwitchVlanId != b.config.VlanId {
warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor vlan. The switch will not be able to see traffic from the VM.")
warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor " +
"vlan. The switch will not be able to see traffic from the VM.")
warnings = appendWarnings(warnings, warning)
}
}
@ -362,7 +389,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("ui", ui)
steps := []multistep.Step{
&hypervcommon.StepCreateTempDir{},
&hypervcommon.StepCreateBuildDir{
TempPath: b.config.TempPath,
},
&hypervcommon.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
@ -397,7 +426,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SwitchName: b.config.SwitchName,
},
&hypervcommon.StepCloneVM{
CloneFromVMXCPath: b.config.CloneFromVMXCPath,
CloneFromVMCXPath: b.config.CloneFromVMCXPath,
CloneFromVMName: b.config.CloneFromVMName,
CloneFromSnapshotName: b.config.CloneFromSnapshotName,
CloneAllSnapshots: b.config.CloneAllSnapshots,
@ -476,10 +505,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&hypervcommon.StepUnmountFloppyDrive{
Generation: b.config.Generation,
},
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
&hypervcommon.StepCompactDisk{
SkipCompaction: b.config.SkipCompaction,
SkipExport: b.config.SkipExport,
},
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
SkipExport: b.config.SkipExport,
},
&hypervcommon.StepCollateArtifacts{
OutputDir: b.config.OutputDir,
SkipExport: b.config.SkipExport,
},
// the clean up actions for each step will be executed reverse order
@ -536,9 +571,11 @@ func (b *Builder) checkRamSize() error {
log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize))
if b.config.RamSize < MinRamSize {
return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize)
return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v",
MinRamSize, b.config.RamSize)
} else if b.config.RamSize > MaxRamSize {
return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize)
return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v",
MaxRamSize, b.config.RamSize)
}
return nil

View File

@ -23,7 +23,7 @@ func testConfig() map[string]interface{} {
"ssh_username": "foo",
"ram_size": 64,
"guest_additions_mode": "none",
"clone_from_vmxc_path": "generated",
"clone_from_vmcx_path": "generated",
packer.BuildNameConfigKey: "foo",
}
}
@ -40,13 +40,13 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
warns, err := b.Prepare(config)
if len(warns) > 0 {
@ -65,13 +65,13 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
// Add a random key
config["i_should_not_be_valid"] = true
@ -87,7 +87,7 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
func TestBuilderPrepare_CloneFromExistingMachineOrImportFromExportedMachineSettingsRequired(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "clone_from_vmxc_path")
delete(config, "clone_from_vmcx_path")
warns, err := b.Prepare(config)
if len(warns) > 0 {
@ -102,7 +102,7 @@ func TestBuilderPrepare_ExportedMachinePathDoesNotExist(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
@ -111,7 +111,7 @@ func TestBuilderPrepare_ExportedMachinePathDoesNotExist(t *testing.T) {
//Delete the folder immediately
os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
warns, err := b.Prepare(config)
if len(warns) > 0 {
@ -126,7 +126,7 @@ func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
@ -135,7 +135,7 @@ func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) {
//Only delete afterwards
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
warns, err := b.Prepare(config)
if len(warns) > 0 {
@ -146,10 +146,10 @@ func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) {
}
}
func disabled_TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmxcPathRequired(t *testing.T) {
func disabled_TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmcxPathRequired(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "clone_from_vmxc_path")
delete(config, "clone_from_vmcx_path")
config["clone_from_vm_name"] = "test_machine_name_that_does_not_exist"
@ -162,7 +162,8 @@ func disabled_TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmxcPathRequ
t.Fatal("should have error")
} else {
errorMessage := err.Error()
if errorMessage != "1 error(s) occurred:\n\n* Virtual machine 'test_machine_name_that_does_not_exist' to clone from does not exist." {
if errorMessage != "1 error(s) occurred:\n\n* Virtual machine 'test_machine_name_that_does_not_exist' "+
"to clone from does not exist." {
t.Fatalf("should not have error: %s", err)
}
}
@ -172,13 +173,13 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
// Test bad
config["iso_checksum"] = ""
@ -210,13 +211,13 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
// Test bad
config["iso_checksum_type"] = ""
@ -274,13 +275,13 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
delete(config, "iso_url")
delete(config, "iso_urls")
@ -353,13 +354,13 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
delete(config, "floppy_files")
warns, err := b.Prepare(config)
@ -395,13 +396,13 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
b = Builder{}
@ -420,13 +421,13 @@ func TestBuilderPrepare_CommConfig(t *testing.T) {
{
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
config["communicator"] = "winrm"
config["winrm_username"] = "username"
@ -457,13 +458,13 @@ func TestBuilderPrepare_CommConfig(t *testing.T) {
{
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
config["communicator"] = "ssh"
config["ssh_username"] = "username"
@ -495,13 +496,13 @@ func TestUserVariablesInBootCommand(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
//Create vmcx folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["clone_from_vmcx_path"] = td
config[packer.UserVariablesConfigKey] = map[string]string{"test-variable": "test"}
config["boot_command"] = []string{"blah {{user `test-variable`}} blah"}

View File

@ -153,6 +153,10 @@ Fixes that are run:
docker-email Removes "login_email" from the Docker builder
powershell-escapes Removes PowerShell escapes from user env vars and
elevated username and password strings
hyperv-deprecations Removes the deprecated "vhd_temp_path" setting from
Hyper-V ISO builder templates
hyperv-vmxc-typo Corrects a typo in the "clone_from_vmxc_path"
setting. Replaces with "clone_from_vmcx_path".
Options:

View File

@ -111,7 +111,8 @@ Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Cont
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10),
strconv.FormatInt(int64(controllerLocation), 10))
return err
}
@ -124,7 +125,8 @@ Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Cont
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10),
strconv.FormatInt(int64(controllerLocation), 10))
return err
}
@ -146,7 +148,8 @@ if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
Hyper-V\Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction SilentlyContinue
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10),
strconv.FormatInt(int64(controllerLocation), 10))
return err
}
}
@ -160,7 +163,8 @@ Hyper-V\Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -C
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10),
strconv.FormatInt(int64(controllerLocation), 10))
return err
}
@ -198,13 +202,15 @@ Hyper-V\Set-VMFloppyDiskDrive -VMName $vmName -Path $null
return err
}
func CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdRoot string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool) error {
func CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64,
diskSize int64, diskBlockSize int64, switchName string, generation uint,
diffDisks bool, fixedVHD bool) error {
if generation == 2 {
var script = `
param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [int]$generation, [string]$diffDisks)
param([string]$vmName, [string]$path, [string]$harddrivePath, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [int]$generation, [string]$diffDisks)
$vhdx = $vmName + '.vhdx'
$vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
if ($harddrivePath){
if($diffDisks -eq "true"){
New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes
@ -218,21 +224,24 @@ if ($harddrivePath){
}
`
var ps powershell.PowerShellCmd
if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10), switchName, strconv.FormatInt(int64(generation), 10), strconv.FormatBool(diffDisks)); err != nil {
if err := ps.Run(script, vmName, path, harddrivePath, strconv.FormatInt(ram, 10),
strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10),
switchName, strconv.FormatInt(int64(generation), 10),
strconv.FormatBool(diffDisks)); err != nil {
return err
}
return DisableAutomaticCheckpoints(vmName)
} else {
var script = `
param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [string]$diffDisks, [string]$fixedVHD)
param([string]$vmName, [string]$path, [string]$harddrivePath, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [string]$diffDisks, [string]$fixedVHD)
if($fixedVHD -eq "true"){
$vhdx = $vmName + '.vhd'
}
else{
$vhdx = $vmName + '.vhdx'
}
$vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
if ($harddrivePath){
if($diffDisks -eq "true"){
New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes
@ -252,7 +261,9 @@ if ($harddrivePath){
}
`
var ps powershell.PowerShellCmd
if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10), switchName, strconv.FormatBool(diffDisks), strconv.FormatBool(fixedVHD)); err != nil {
if err := ps.Run(script, vmName, path, harddrivePath, strconv.FormatInt(ram, 10),
strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10),
switchName, strconv.FormatBool(diffDisks), strconv.FormatBool(fixedVHD)); err != nil {
return err
}
@ -275,7 +286,7 @@ if ((Get-Command Hyper-V\Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
return err
}
func ExportVmxcVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error {
func ExportVmcxVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error {
var script = `
param([string]$exportPath, [string]$vmName, [string]$snapshotName, [string]$allSnapshotsString)
@ -322,22 +333,22 @@ $result = Remove-Item -Path $WorkingPath
return err
}
func CopyVmxcVirtualMachine(exportPath string, cloneFromVmxcPath string) error {
func CopyVmcxVirtualMachine(exportPath string, cloneFromVmcxPath string) error {
var script = `
param([string]$exportPath, [string]$cloneFromVmxcPath)
if (!(Test-Path $cloneFromVmxcPath)){
throw "Clone from vmxc directory: $cloneFromVmxcPath does not exist!"
param([string]$exportPath, [string]$cloneFromVmcxPath)
if (!(Test-Path $cloneFromVmcxPath)){
throw "Clone from vmcx directory: $cloneFromVmcxPath does not exist!"
}
if (!(Test-Path $exportPath)){
New-Item -ItemType Directory -Force -Path $exportPath
}
$cloneFromVmxcPath = Join-Path $cloneFromVmxcPath '\*'
Copy-Item $cloneFromVmxcPath $exportPath -Recurse -Force
$cloneFromVmcxPath = Join-Path $cloneFromVmcxPath '\*'
Copy-Item $cloneFromVmcxPath $exportPath -Recurse -Force
`
var ps powershell.PowerShellCmd
err := ps.Run(script, exportPath, cloneFromVmxcPath)
err := ps.Run(script, exportPath, cloneFromVmcxPath)
return err
}
@ -354,7 +365,9 @@ Hyper-V\Set-VMNetworkAdapter $vmName -staticmacaddress $mac
return err
}
func ImportVmxcVirtualMachine(importPath string, vmName string, harddrivePath string, ram int64, switchName string) error {
func ImportVmcxVirtualMachine(importPath string, vmName string, harddrivePath string,
ram int64, switchName string) error {
var script = `
param([string]$importPath, [string]$vmName, [string]$harddrivePath, [long]$memoryStartupBytes, [string]$switchName)
@ -409,20 +422,24 @@ if ($vm) {
return err
}
func CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
func CloneVirtualMachine(cloneFromVmcxPath string, cloneFromVmName string,
cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string,
path string, harddrivePath string, ram int64, switchName string) error {
if cloneFromVmName != "" {
if err := ExportVmxcVirtualMachine(path, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots); err != nil {
if err := ExportVmcxVirtualMachine(path, cloneFromVmName,
cloneFromSnapshotName, cloneAllSnapshots); err != nil {
return err
}
}
if cloneFromVmxcPath != "" {
if err := CopyVmxcVirtualMachine(path, cloneFromVmxcPath); err != nil {
if cloneFromVmcxPath != "" {
if err := CopyVmcxVirtualMachine(path, cloneFromVmcxPath); err != nil {
return err
}
}
if err := ImportVmxcVirtualMachine(path, vmName, harddrivePath, ram, switchName); err != nil {
if err := ImportVmcxVirtualMachine(path, vmName, harddrivePath, ram, switchName); err != nil {
return err
}
@ -665,33 +682,146 @@ if (Test-Path -Path ([IO.Path]::Combine($path, $vmName, 'Virtual Machines', '*.V
return err
}
func CompactDisks(expPath string, vhdDir string) error {
func PreserveLegacyExportBehaviour(srcPath, dstPath string) error {
var script = `
param([string]$srcPath, [string]$vhdDirName)
Get-ChildItem "$srcPath/$vhdDirName" -Filter *.vhd* | %{
Optimize-VHD -Path $_.FullName -Mode Full
param([string]$srcPath, [string]$dstPath)
# Validate the paths returning an error if they are empty or don't exist
$srcPath, $dstPath | % {
if ($_) {
if (! (Test-Path $_)) {
[System.Console]::Error.WriteLine("Path $_ does not exist")
exit
}
} else {
[System.Console]::Error.WriteLine("A supplied path is empty")
exit
}
}
# Export-VM should just create directories at the root of the export path
# but, just in case, move all files as well...
Move-Item -Path (Join-Path (Get-Item $srcPath).FullName "*.*") -Destination (Get-Item $dstPath).FullName
# Move directories with content; Delete empty directories
$dirObj = Get-ChildItem $srcPath -Directory | % {
New-Object PSObject -Property @{
FullName=$_.FullName;
HasContent=$(if ($_.GetFileSystemInfos().Count -gt 0) {$true} else {$false})
}
}
foreach ($directory in $dirObj) {
if ($directory.HasContent) {
Move-Item -Path $directory.FullName -Destination (Get-Item $dstPath).FullName
} else {
Remove-Item -Path $directory.FullName
}
}
# Only remove the source directory if it is now empty
if ( $((Get-Item $srcPath).GetFileSystemInfos().Count) -eq 0 ) {
Remove-Item -Path $srcPath
} else {
# 'Return' an error message to PowerShellCmd as the directory should
# always be empty at the end of the script. The check is here to stop
# the Remove-Item command from doing any damage if some unforeseen
# error has occured
[System.Console]::Error.WriteLine("Refusing to remove $srcPath as it is not empty")
exit
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, expPath, vhdDir)
err := ps.Run(script, srcPath, dstPath)
return err
}
func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error {
func MoveCreatedVHDsToOutputDir(srcPath, dstPath string) error {
var script = `
param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$vmDir)
Move-Item -Path (Join-Path (Get-Item $srcPath).FullName "*.*") -Destination $dstPath
Move-Item -Path (Join-Path (Get-Item $srcPath).FullName $vhdDirName) -Destination $dstPath
Move-Item -Path (Join-Path (Get-Item $srcPath).FullName $vmDir) -Destination $dstPath
param([string]$srcPath, [string]$dstPath)
# Validate the paths returning an error if the supplied path is empty
# or if the paths don't exist
$srcPath, $dstPath | % {
if ($_) {
if (! (Test-Path $_)) {
[System.Console]::Error.WriteLine("Path $_ does not exist")
exit
}
} else {
[System.Console]::Error.WriteLine("A supplied path is empty")
exit
}
}
# Convert to absolute paths if required
$srcPathAbs = (Get-Item($srcPath)).FullName
$dstPathAbs = (Get-Item($dstPath)).FullName
# Get the full path to all disks under the directory or exit if none are found
$disks = Get-ChildItem -Path $srcPathAbs -Recurse -Filter *.vhd* -ErrorAction SilentlyContinue | % { $_.FullName }
if ($disks.Length -eq 0) {
[System.Console]::Error.WriteLine("No disks found under $srcPathAbs")
exit
}
# Set up directory for VHDs in the destination directory
$vhdDstDir = Join-Path -Path $dstPathAbs -ChildPath 'Virtual Hard Disks'
if (! (Test-Path $vhdDstDir)) {
New-Item -ItemType Directory -Force -Path $vhdDstDir
}
# Move the disks
foreach ($disk in $disks) {
Move-Item -Path $disk -Destination $vhdDstDir
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, expPath, outputPath, vhdDir, vmDir)
err := ps.Run(script, srcPath, dstPath)
return err
}
func CompactDisks(path string) (result string, err error) {
var script = `
param([string]$srcPath)
$disks = Get-ChildItem -Path $srcPath -Recurse -Filter *.vhd* -ErrorAction SilentlyContinue | % { $_.FullName }
# Failure to find any disks is treated as a 'soft' error. Simply print out
# a warning and exit
if ($disks.Length -eq 0) {
Write-Output "WARNING: No disks found under $srcPath"
exit
}
foreach ($disk in $disks) {
Write-Output "Compacting disk: $(Split-Path $disk -leaf)"
$sizeBefore = $disk.Length
Optimize-VHD -Path $disk -Mode Full
$sizeAfter = $disk.Length
# Calculate the percentage change in disk size
if ($sizeAfter -gt 0) { # Protect against division by zero
$percentChange = ( ( $sizeAfter / $sizeBefore ) * 100 ) - 100
switch($percentChange) {
{$_ -lt 0} {Write-Output "Disk size reduced by: $(([math]::Abs($_)).ToString("#.#"))%"}
{$_ -eq 0} {Write-Output "Disk size is unchanged"}
{$_ -gt 0} {Write-Output "WARNING: Disk size increased by: $($_.ToString("#.#"))%"}
}
}
}
`
var ps powershell.PowerShellCmd
result, err = ps.Output(script, path)
return
}
func CreateVirtualSwitch(switchName string, switchType string) (bool, error) {
var script = `
@ -905,7 +1035,8 @@ Hyper-V\Get-VMNetworkAdapter -VMName $vmName | Hyper-V\Connect-VMNetworkAdapter
return err
}
func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64, vhdBlockSize int64, controllerType string) error {
func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64,
vhdBlockSize int64, controllerType string) error {
var script = `
param([string]$vmName,[string]$vhdRoot, [string]$vhdName, [string]$vhdSizeInBytes, [string]$vhdBlockSizeInByte, [string]$controllerType)

View File

@ -36,6 +36,8 @@ func init() {
"amazon-private-ip": new(FixerAmazonPrivateIP),
"docker-email": new(FixerDockerEmail),
"powershell-escapes": new(FixerPowerShellEscapes),
"hyperv-deprecations": new(FixerHypervDeprecations),
"hyperv-vmxc-typo": new(FixerHypervVmxcTypo),
}
FixerOrder = []string{
@ -55,5 +57,7 @@ func init() {
"amazon-private-ip",
"docker-email",
"powershell-escapes",
"hyperv-deprecations",
"hyperv-vmxc-typo",
}
}

View File

@ -0,0 +1,50 @@
package fix
import (
"github.com/mitchellh/mapstructure"
)
// FixerHypervDeprecations removes the deprecated "vhd_temp_path" setting
// from Hyper-V ISO builder templates
type FixerHypervDeprecations struct{}
func (FixerHypervDeprecations) Fix(input map[string]interface{}) (map[string]interface{}, error) {
// The type we'll decode into; we only care about builders
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
for _, builder := range tpl.Builders {
builderTypeRaw, ok := builder["type"]
if !ok {
continue
}
builderType, ok := builderTypeRaw.(string)
if !ok {
continue
}
if builderType != "hyperv-iso" {
continue
}
_, ok = builder["vhd_temp_path"]
if ok {
delete(builder, "vhd_temp_path")
}
}
input["builders"] = tpl.Builders
return input, nil
}
func (FixerHypervDeprecations) Synopsis() string {
return `Removes the deprecated "vhd_temp_path" setting from Hyper-V ISO builder templates`
}

View File

@ -0,0 +1,60 @@
package fix
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFixerHypervDeprecations_impl(t *testing.T) {
var _ Fixer = new(FixerHypervDeprecations)
}
func TestFixerHypervDeprecations_Fix(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Expected map[string]interface{}
}{
// No vhd_temp_path field in template - noop
{
Input: map[string]interface{}{
"type": "hyperv-iso",
},
Expected: map[string]interface{}{
"type": "hyperv-iso",
},
},
// Deprecated vhd_temp_path field in template should be deleted
{
Input: map[string]interface{}{
"type": "hyperv-iso",
"vhd_temp_path": "foopath",
},
Expected: map[string]interface{}{
"type": "hyperv-iso",
},
},
}
for _, tc := range cases {
var f FixerHypervDeprecations
input := map[string]interface{}{
"builders": []map[string]interface{}{tc.Input},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{tc.Expected},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
assert.Equal(t, expected, output, "Should be equal")
}
}

View File

@ -0,0 +1,52 @@
package fix
import (
"github.com/mitchellh/mapstructure"
)
// FixerHypervVmxcTypo fixes the typo in "clone_from_vmxc_path" replacing
// it with "clone_from_vmcx_path" in Hyper-V VMCX builder templates
type FixerHypervVmxcTypo struct{}
func (FixerHypervVmxcTypo) Fix(input map[string]interface{}) (map[string]interface{}, error) {
// The type we'll decode into; we only care about builders
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
for _, builder := range tpl.Builders {
builderTypeRaw, ok := builder["type"]
if !ok {
continue
}
builderType, ok := builderTypeRaw.(string)
if !ok {
continue
}
if builderType != "hyperv-vmcx" {
continue
}
path, ok := builder["clone_from_vmxc_path"]
if ok {
delete(builder, "clone_from_vmxc_path")
builder["clone_from_vmcx_path"] = path
}
}
input["builders"] = tpl.Builders
return input, nil
}
func (FixerHypervVmxcTypo) Synopsis() string {
return `Fixes a typo replacing "clone_from_vmxc_path" with "clone_from_vmcx_path" ` +
`in Hyper-V VMCX builder templates`
}

View File

@ -0,0 +1,64 @@
package fix
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFixerHypervVmxcTypo_impl(t *testing.T) {
var _ Fixer = new(FixerHypervVmxcTypo)
}
func TestFixerHypervVmxcTypo_Fix(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Expected map[string]interface{}
}{
// No "clone_from_vmxc_path" in template - noop
{
Input: map[string]interface{}{
"type": "hyperv-vmcx",
"temp_path": "C:/some/temp/path",
},
Expected: map[string]interface{}{
"type": "hyperv-vmcx",
"temp_path": "C:/some/temp/path",
},
},
// "clone_from_vmxc_path" should be replaced with
// "clone_from_vmcx_path" in template
{
Input: map[string]interface{}{
"type": "hyperv-vmcx",
"clone_from_vmxc_path": "C:/some/vmcx/path",
},
Expected: map[string]interface{}{
"type": "hyperv-vmcx",
"clone_from_vmcx_path": "C:/some/vmcx/path",
},
},
}
for _, tc := range cases {
var f FixerHypervVmxcTypo
input := map[string]interface{}{
"builders": []map[string]interface{}{tc.Input},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{tc.Expected},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
assert.Equal(t, expected, output, "Should be equal")
}
}

View File

@ -201,12 +201,13 @@ builder.
the default virtual network card. The MAC address must be a string with
no delimiters, for example "0000deadbeef".
- `output_directory` (string) - This is the path to the directory where the
resulting virtual machine will be created. This may be relative or
absolute. If relative, the path is relative to the working directory when
`packer` is executed. This directory must not exist or be empty prior to
running the builder. By default this is "output-BUILDNAME" where
"BUILDNAME" is the name of the build.
- `output_directory` (string) - This setting specifies the directory that
artifacts from the build, such as the virtual machine files and disks,
will be output to. The path to the directory may be relative or
absolute. If relative, the path is relative to the working directory
`packer` is executed from. This directory must not exist or, if
created, must be empty prior to running the builder. By default this is
"output-BUILDNAME" where "BUILDNAME" is the name of the build.
- `ram_size` (number) - The amount, in megabytes, of RAM to assign to the
VM. By default, this is 1 GB.
@ -238,11 +239,11 @@ builder.
- `skip_compaction` (boolean) - If `true` skip compacting the hard disk for
the virtual machine when exporting. This defaults to `false`.
- `skip_export` (boolean) - If `true` Packer will skip the export of the
VM. If you are interested only in the VHD/VHDX files, you can enable
this option. This will create inline disks which improves the build
performance. There will not be any copying of source VHDs to the temp
directory. This defaults to `false`.
- `skip_export` (boolean) - If `true` Packer will skip the export of the VM.
If you are interested only in the VHD/VHDX files, you can enable this
option. The resulting VHD/VHDX file will be output to
`<output_directory>/Virtual Hard Disks`. By default this option is `false`
and Packer will export the VM to `output_directory`.
- `switch_name` (string) - The name of the switch to connect the virtual
machine to. By default, leaving this value unset will cause Packer to
@ -254,8 +255,15 @@ builder.
set on the switch's network card. If this value is set it should match
the VLAN specified in by `vlan_id`.
- `temp_path` (string) - This is the temporary path in which Packer will
create the virtual machine. By default the value is the system `%temp%`.
- `temp_path` (string) - The location under which Packer will create a
directory to house all the VM files and folders during the build.
By default `%TEMP%` is used which, for most systems, will evaluate to
`%USERPROFILE%/AppData/Local/Temp`.
The build directory housed under `temp_path` will have a name similar
to `packerhv1234567`. The seven digit number at the end of the name is
automatically generated by Packer to ensure the directory name is
unique.
- `use_fixed_vhd_format` (boolean) - If true, creates the boot disk on the
virtual machine as a fixed VHD format disk. The default is `false`, which
@ -266,11 +274,6 @@ builder.
option is outputing a disk that is in the format required for upload to
Azure.
- `vhd_temp_path` (string) - A separate path to be used for storing the VM's
disk image. The purpose is to enable reading and writing to take place on
different physical disks (read from VHD temp path, write to regular temp
path while exporting the VM) to eliminate a single-disk bottleneck.
- `vlan_id` (string) - This is the VLAN of the virtual machine's network
card for the new virtual machine. By default none is set. If none is set
then VLANs are not set on the virtual machine's network card.

View File

@ -22,18 +22,18 @@ boots it, provisions software within the OS, and then shuts it down. The
result of the Hyper-V builder is a directory containing all the files
necessary to run the virtual machine portably.
## Basic Example
## Basic Examples
Here is a basic example. This example is not functional. It will start the OS
installer but then fail because we don't provide the preseed file for Ubuntu
to self-install. Still, the example serves to show the basic configuration:
Here are some basic examples. Neither example would really do anything more
than producing a copy of the source virtual machine. However, the examples
could be used as a starting point for more advanced templates.
Import from folder:
``` json
{
"type": "hyperv-vmcx",
"clone_from_vmxc_path": "c:\\virtual machines\\ubuntu-12.04.5-server-amd64",
"clone_from_vmcx_path": "c:/virtual machines/ubuntu-12.04.5-server-amd64",
"ssh_username": "packer",
"ssh_password": "packer",
"shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
@ -71,12 +71,14 @@ builder.
### Required for virtual machine import:
- `clone_from_vmxc_path` (string) - The path to the exported virtual machine
folder.
- `clone_from_vmcx_path` (string) - The path to a directory containing a
previously exported virtual machine. The exported machine will be used
as the source for new VM.
### Required for virtual machine clone:
- `clone_from_vm_name` (string) - The name of the vm to clone from. Ideally
- `clone_from_vm_name` (string) - The name of the VM to clone from. Ideally
the machine to clone from should be shutdown.
### Optional:
@ -94,11 +96,25 @@ builder.
Packer to wait for 1 minute 30 seconds before typing the boot command.
The default duration is "10s" (10 seconds).
- `clone_all_snapshots` (boolean) - If set to `true` all snapshots will be
cloned when the machine is cloned.
- `clone_all_snapshots` (boolean) - If set to `true` all snapshots
present in the source machine will be copied when the machine is
cloned. The final result of the build will be an exported virtual
machine that contains all the snapshots of the parent.
- `clone_from_snapshot_name` (string) - The name of the snapshot to clone
from.
If `clone_from_vmcx_path` is set, this setting has no effect and any
snapshots associated with the source VM will also be present in the
resultant VM.
If `clone_from_snapshot_name` is configured, this setting has no
effect and the resultant machine will not contain any snapshots.
- `clone_from_snapshot_name` (string) - The name of a snapshot in the
source machine to use as a starting point for the clone. If the value
given is an empty string, the last snapshot present in the source will
be chosen as the starting point for the new VM.
This setting only has an effect if using `clone_from_vm_name` and is
ignored otherwise.
- `cpu` (number) - The number of CPUs the virtual machine should use. If
this isn't specified, the default is 1 CPU.
@ -202,12 +218,13 @@ builder.
the default virtual network card. The MAC address must be a string with
no delimiters, for example "0000deadbeef".
- `output_directory` (string) - This is the path to the directory where the
resulting virtual machine will be created. This may be relative or
absolute. If relative, the path is relative to the working directory when
`packer` is executed. This directory must not exist or be empty prior to
running the builder. By default this is "output-BUILDNAME" where
"BUILDNAME" is the name of the build.
- `output_directory` (string) - This setting specifies the directory that
artifacts from the build, such as the virtual machine files and disks,
will be output to. The path to the directory may be relative or
absolute. If relative, the path is relative to the working directory
`packer` is executed from. This directory must not exist or, if
created, must be empty prior to running the builder. By default this is
"output-BUILDNAME" where "BUILDNAME" is the name of the build.
- `ram_size` (number) - The amount, in megabytes, of RAM to assign to the
VM. By default, this is 1 GB.
@ -239,11 +256,11 @@ builder.
- `skip_compaction` (boolean) - If `true` skip compacting the hard disk for
the virtual machine when exporting. This defaults to `false`.
- `skip_export` (boolean) - If `true` Packer will skip the export of the
VM. If you are interested only in the VHD/VHDX files, you can enable
this option. This will create inline disks which improves the build
performance. There will not be any copying of source VHDs to the temp
directory. This defaults to `false`.
- `skip_export` (boolean) - If `true` Packer will skip the export of the VM.
If you are interested only in the VHD/VHDX files, you can enable this
option. The resulting VHD/VHDX file will be output to
`<output_directory>/Virtual Hard Disks`. By default this option is `false`
and Packer will export the VM to `output_directory`.
- `switch_name` (string) - The name of the switch to connect the virtual
machine to. By default, leaving this value unset will cause Packer to
@ -255,6 +272,16 @@ builder.
set on the switch's network card. If this value is set it should match
the VLAN specified in by `vlan_id`.
- `temp_path` (string) - The location under which Packer will create a
directory to house all the VM files and folders during the build.
By default `%TEMP%` is used which, for most systems, will evaluate to
`%USERPROFILE%/AppData/Local/Temp`.
The build directory housed under `temp_path` will have a name similar
to `packerhv1234567`. The seven digit number at the end of the name is
automatically generated by Packer to ensure the directory name is
unique.
- `vlan_id` (string) - This is the VLAN of the virtual machine's network
card for the new virtual machine. By default none is set. If none is set
then VLANs are not set on the virtual machine's network card.