From 534fc4a4731156e6d6f89ef56e611f2e81531d8c Mon Sep 17 00:00:00 2001 From: DanHam Date: Wed, 4 Jul 2018 13:03:32 +0100 Subject: [PATCH] Ensure the export directory structure matches that of previous versions Commit 3fc2defb6 altered the directory structure associated with an exported VM. The changes mean that the export process now stores the exported machine files and folders under a folder with name 'vm_name' in the output directory. This commit restores the previous behaviour whereby the exported machine files and folders were stored directly in the output directory. This allows us to keep the efficiency improvements introduced with 3fc2defb6 while maintaining backward compatibility. By default the Export-VM command creates three folders in the specified export directory - 'Virtual Hard Disks', 'Virtual Machines' and 'Snapshots'. When a machine with no associated snapshots is exported the 'Snapshots' directory is empty. Prior to 3fc2defb6 the Snapshots folder was not copied/incorporated into the output directory at all. This was a bug. This commit preserves the legacy behaviour by not including an empty Snapshots directory in the export. However, if there *are* Snapshots associated with the VM, they are now moved into the output directory along with the usual directories containing disks and VM metadata. This prevents warnings/errors on import due to missing snapshots. --- builder/hyperv/common/driver.go | 2 + builder/hyperv/common/driver_mock.go | 12 ++++++ builder/hyperv/common/driver_ps_4.go | 4 ++ builder/hyperv/common/step_export_vm.go | 16 +++++++ common/powershell/hyperv/hyperv.go | 56 +++++++++++++++++++++++++ 5 files changed, 90 insertions(+) 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)