diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 08e3f4eac..ef4e9c48d 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -94,6 +94,8 @@ type Driver interface { ExportVirtualMachine(string, string) error + PreserveLegacyExportBehaviour(string, string) error + CompactDisks(string) error RestartVirtualMachine(string) error diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index 53c7f959b..28efa397d 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -186,6 +186,11 @@ type DriverMock struct { ExportVirtualMachine_Path string ExportVirtualMachine_Err error + PreserveLegacyExportBehaviour_Called bool + PreserveLegacyExportBehaviour_SrcPath string + PreserveLegacyExportBehaviour_DstPath string + PreserveLegacyExportBehaviour_Err error + CompactDisks_Called bool CompactDisks_Path string CompactDisks_Err error @@ -481,6 +486,13 @@ func (d *DriverMock) ExportVirtualMachine(vmName string, path string) error { return d.ExportVirtualMachine_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) CompactDisks(path string) error { d.CompactDisks_Called = true d.CompactDisks_Path = path diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 964f4b6a4..b013b2598 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -219,6 +219,10 @@ func (d *HypervPS4Driver) ExportVirtualMachine(vmName string, path string) error return hyperv.ExportVirtualMachine(vmName, path) } +func (d *HypervPS4Driver) PreserveLegacyExportBehaviour(srcPath string, dstPath string) error { + return hyperv.PreserveLegacyExportBehaviour(srcPath, dstPath) +} + func (d *HypervPS4Driver) CompactDisks(path string) error { return hyperv.CompactDisks(path) } diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go index f582c30f6..0eb42a7c0 100644 --- a/builder/hyperv/common/step_export_vm.go +++ b/builder/hyperv/common/step_export_vm.go @@ -3,6 +3,7 @@ package common import ( "context" "fmt" + "path/filepath" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -59,6 +60,21 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste return multistep.ActionHalt } + // Shuffle around the exported folders to maintain backwards + // compatibility. This moves the 'Snapshots', 'Virtual Hard Disks' and + // 'Virtual Machines' directories from / so + // they appear directly under . The empty '/' directory is removed when complete. + // The 'Snapshots' folder will not be moved into the output directory + // if it is empty. + exportPath := filepath.Join(s.OutputDir, vmName) + 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 } diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index d15bd6bd4..d51eef74c 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -665,6 +665,62 @@ if (Test-Path -Path ([IO.Path]::Combine($path, $vmName, 'Virtual Machines', '*.V return err } +func PreserveLegacyExportBehaviour(srcPath, dstPath string) error { + + var script = ` +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, srcPath, dstPath) + + return err +} + func CompactDisks(path string) error { var script = ` param([string]$srcPath)