diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 07ab0f9fe..ba9ab5c4c 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -64,9 +64,9 @@ type Driver interface { DeleteVirtualSwitch(string) error - CreateVirtualMachine(string, string, string, int64, int64, string, uint) error + CreateVirtualMachine(string, string, string, string, int64, int64, string, uint) error - CloneVirtualMachine(string, string, bool, string, string, int64, string) error + CloneVirtualMachine(string, string, string, bool, string, string, string, int64, string) error DeleteVirtualMachine(string) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 4c45f9c5c..c836137d2 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -170,12 +170,12 @@ func (d *HypervPS4Driver) CreateVirtualSwitch(switchName string, switchType stri return hyperv.CreateVirtualSwitch(switchName, switchType) } -func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, vhdPath string, ram int64, diskSize int64, switchName string, generation uint) error { - return hyperv.CreateVirtualMachine(vmName, path, vhdPath, ram, diskSize, switchName, generation) +func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, switchName string, generation uint) error { + return hyperv.CreateVirtualMachine(vmName, path, harddrivePath, vhdPath, ram, diskSize, switchName, generation) } -func (d *HypervPS4Driver) CloneVirtualMachine(cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, ram int64, switchName string) error { - return hyperv.CloneVirtualMachine(cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots, vmName, path, ram, switchName) +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) DeleteVirtualMachine(vmName string) error { diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go index 025a1bb06..9b005422d 100644 --- a/builder/hyperv/common/step_clone_vm.go +++ b/builder/hyperv/common/step_clone_vm.go @@ -2,9 +2,12 @@ package common import ( "fmt" + "log" + "strings" + "path/filepath" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" + "github.com/hashicorp/packer/packer" ) // This step clones an existing virtual machine. @@ -12,6 +15,7 @@ import ( // Produces: // VMName string - The name of the VM type StepCloneVM struct { + CloneFromVMXCPath string CloneFromVMName string CloneFromSnapshotName string CloneAllSnapshots bool @@ -31,11 +35,24 @@ func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Cloning virtual machine...") path := state.Get("packerTempDir").(string) + + // Determine if we even have an existing virtual harddrive to attach + harddrivePath := "" + if harddrivePathRaw, ok := state.GetOk("iso_path"); ok { + extension := strings.ToLower(filepath.Ext(harddrivePathRaw.(string))) + if extension == "vhd" || extension == "vhdx" { + harddrivePath = harddrivePathRaw.(string) + } else { + log.Println("No existing virtual harddrive, not attaching.") + } + } else { + log.Println("No existing virtual harddrive, not attaching.") + } // convert the MB to bytes ramSize := int64(s.RamSize * 1024 * 1024) - err := driver.CloneVirtualMachine(s.CloneFromVMName, s.CloneFromSnapshotName, s.CloneAllSnapshots, s.VMName, path, ramSize, s.SwitchName) + err := driver.CloneVirtualMachine(s.CloneFromVMXCPath, 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) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 624746d0b..46c4964cb 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -2,7 +2,10 @@ package common import ( "fmt" - + "log" + "strings" + "path/filepath" + "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" ) @@ -14,6 +17,7 @@ import ( type StepCreateVM struct { VMName string SwitchName string + HarddrivePath string RamSize uint DiskSize uint Generation uint @@ -30,13 +34,26 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Creating virtual machine...") path := state.Get("packerTempDir").(string) - vhdPath := state.Get("packerVhdTempDir").(string) - + + // Determine if we even have an existing virtual harddrive to attach + harddrivePath := "" + if harddrivePathRaw, ok := state.GetOk("iso_path"); ok { + extension := strings.ToLower(filepath.Ext(harddrivePathRaw.(string))) + if extension == "vhd" || extension == "vhdx" { + harddrivePath = harddrivePathRaw.(string) + } else { + log.Println("No existing virtual harddrive, not attaching.") + } + } else { + log.Println("No existing virtual harddrive, not attaching.") + } + + vhdPath := state.Get("packerVhdTempDir").(string) // convert the MB to bytes ramSize := int64(s.RamSize * 1024 * 1024) diskSize := int64(s.DiskSize * 1024 * 1024) - err := driver.CreateVirtualMachine(s.VMName, path, vhdPath, ramSize, diskSize, s.SwitchName, s.Generation) + err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, vhdPath, ramSize, diskSize, s.SwitchName, s.Generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index f7f206408..382c35394 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -6,6 +6,7 @@ import ( "log" "os" "strings" + "path/filepath" hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" "github.com/hashicorp/packer/common" @@ -117,16 +118,26 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { warnings = append(warnings, isoWarnings...) errs = packer.MultiErrorAppend(errs, isoErrs...) + if len(b.config.ISOConfig.ISOUrls) > 0 { + extension := strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) + if extension == "vhd" || extension == "vhdx" { + b.config.ISOConfig.TargetExtension = extension + } + } + errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) - errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) - err = b.checkDiskSize() - if err != nil { - errs = packer.MultiErrorAppend(errs, err) + if b.config.ISOConfig.TargetExtension != "vhd" && b.config.ISOConfig.TargetExtension != "vhdx" { + //We only create a new hard drive if an existing one to copy from does not exist + err = b.checkDiskSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } } err = b.checkRamSize() @@ -163,6 +174,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) // Errors + if b.config.GuestAdditionsMode == "" { if b.config.GuestAdditionsPath != "" { b.config.GuestAdditionsMode = "attach" diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 70df63832..1c655f475 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -235,7 +235,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { delete(config, "iso_url") delete(config, "iso_urls") - // Test both epty + // Test both empty config["iso_url"] = "" b = Builder{} warns, err := b.Prepare(config) diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 0282d1f18..b75667742 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -7,15 +7,16 @@ import ( "os" "strings" + hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" + "github.com/hashicorp/packer/common" + powershell "github.com/hashicorp/packer/common/powershell" + "github.com/hashicorp/packer/common/powershell/hyperv" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template/interpolate" "github.com/mitchellh/multistep" - hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" - "github.com/mitchellh/packer/common" - powershell "github.com/mitchellh/packer/common/powershell" - "github.com/mitchellh/packer/common/powershell/hyperv" - "github.com/mitchellh/packer/helper/communicator" - "github.com/mitchellh/packer/helper/config" - "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/template/interpolate" + "path/filepath" ) const ( @@ -69,6 +70,9 @@ type Config struct { // The path to the integration services iso 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"` + // This is the name of the virtual machine to clone from. CloneFromVMName string `mapstructure:"clone_from_vm_name"` @@ -122,6 +126,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) warnings = append(warnings, isoWarnings...) errs = packer.MultiErrorAppend(errs, isoErrs...) + + extension := strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) + if extension == "vhd" || extension == "vhdx" { + b.config.ISOConfig.TargetExtension = extension + } } errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) @@ -153,7 +162,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Generation = 1 if b.config.CloneFromVMName == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vm_name must be specified.")) + 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.")) + } } else { virtualMachineExists, err := powershell.DoesVirtualMachineExist(b.config.CloneFromVMName) if err != nil { @@ -190,8 +201,21 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } } + + if b.config.CloneFromVMXCPath == "" { + 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.")) + } + } else { + if _, err := os.Stat(b.config.CloneFromVMXCPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("CloneFromVMXCPath does not exist: %s", err)) + } + } + } - if b.config.Generation != 2 { + if b.config.Generation != 1 || b.config.Generation != 2 { b.config.Generation = 1 } @@ -377,6 +401,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, CloneFromVMName: b.config.CloneFromVMName, CloneFromSnapshotName: b.config.CloneFromSnapshotName, CloneAllSnapshots: b.config.CloneAllSnapshots, diff --git a/builder/hyperv/vmcx/builder_test.go b/builder/hyperv/vmcx/builder_test.go index c2ea23c14..f0b7736bd 100644 --- a/builder/hyperv/vmcx/builder_test.go +++ b/builder/hyperv/vmcx/builder_test.go @@ -4,7 +4,9 @@ import ( "reflect" "testing" - "github.com/mitchellh/packer/packer" + "github.com/hashicorp/packer/packer" + "io/ioutil" + "os" ) func testConfig() map[string]interface{} { @@ -15,8 +17,8 @@ func testConfig() map[string]interface{} { "shutdown_command": "yes", "ssh_username": "foo", "ram_size": 64, - "disk_size": 256, "guest_additions_mode": "none", + "clone_from_vmxc_path": "generated", packer.BuildNameConfigKey: "foo", } } @@ -33,6 +35,14 @@ func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + config["clone_from_vmxc_path"] = td + warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -46,41 +56,17 @@ func TestBuilderPrepare_Defaults(t *testing.T) { } } -func TestBuilderPrepare_DiskSize(t *testing.T) { +func TestBuilderPrepare_InvalidKey(t *testing.T) { var b Builder config := testConfig() - delete(config, "disk_size") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("bad err: %s", err) - } - - if b.config.DiskSize != 40*1024 { - t.Fatalf("bad size: %d", b.config.DiskSize) - } - - config["disk_size"] = 256 - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") if err != nil { - t.Fatalf("should not have error: %s", err) + t.Fatalf("err: %s", err) } - - if b.config.DiskSize != 256 { - t.Fatalf("bad size: %d", b.config.DiskSize) - } -} - -func TestBuilderPrepare_InvalidKey(t *testing.T) { - var b Builder - config := testConfig() + defer os.RemoveAll(td) + config["clone_from_vmxc_path"] = td // Add a random key config["i_should_not_be_valid"] = true @@ -97,6 +83,14 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { var b Builder config := testConfig() + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + config["clone_from_vmxc_path"] = td + // Test bad config["iso_checksum"] = "" warns, err := b.Prepare(config) @@ -127,6 +121,14 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { var b Builder config := testConfig() + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + config["clone_from_vmxc_path"] = td + // Test bad config["iso_checksum_type"] = "" warns, err := b.Prepare(config) @@ -182,18 +184,27 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { func TestBuilderPrepare_ISOUrl(t *testing.T) { var b Builder config := testConfig() + + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + config["clone_from_vmxc_path"] = td + delete(config, "iso_url") delete(config, "iso_urls") - // Test both epty + // Test both empty (should be allowed, as we cloning a vm so we probably don't need an ISO file) config["iso_url"] = "" b = Builder{} warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } - if err == nil { - t.Fatal("should have error") + if err != nil { + t.Fatal("should not have an error") } // Test iso_url set diff --git a/command/plugin.go b/command/plugin.go index 373c7320f..6524f47b4 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -26,8 +26,12 @@ import ( filebuilder "github.com/hashicorp/packer/builder/file" googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute" hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso" +<<<<<<< HEAD lxcbuilder "github.com/hashicorp/packer/builder/lxc" lxdbuilder "github.com/hashicorp/packer/builder/lxd" +======= + hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx" +>>>>>>> Can specify an iso, vhd or vhdx for download. If it is a vhd or vhdx it is used as the hard drive for spinning up a new machine, importing an exported virtual machine or cloning a virtual machine. nullbuilder "github.com/hashicorp/packer/builder/null" oneandonebuilder "github.com/hashicorp/packer/builder/oneandone" openstackbuilder "github.com/hashicorp/packer/builder/openstack" diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index 9937ed3b7..4d6e78b65 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -187,28 +187,38 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null return err } -func CreateVirtualMachine(vmName string, path string, vhdRoot string, ram int64, diskSize int64, switchName string, generation uint) error { +func CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdRoot string, ram int64, diskSize int64, switchName string, generation uint) error { if generation == 2 { var script = ` -param([string]$vmName, [string]$path, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) +param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx -New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation +if ($harddrivePath){ + Copy-Item -Path $harddrivePath -Destination $vhdPath + New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation +} else { + New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation +} ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10)) + err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10)) return err } else { var script = ` -param([string]$vmName, [string]$path, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx -New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +if ($harddrivePath){ + Copy-Item -Path $harddrivePath -Destination $vhdPath + New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName +} else { + New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +} ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) - + err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) + if err != nil { return err } @@ -234,58 +244,111 @@ if ((Get-Command Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) { return err } -func DisableAutomaticCheckpoints(vmName string) error { +func ExportVmxcVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error { var script = ` -param([string]$vmName) -if ((Get-Command Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) { - Set-Vm -Name $vmName -AutomaticCheckpointsEnabled $false } -` - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName) - return err -} +param([string]$exportPath, [string]$vmName, [string]$snapshotName, [string]$allSnapshotsString) -func CloneVirtualMachine(cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, ram int64, switchName string) error { +$WorkingPath = Join-Path $exportPath $vmName - var script = ` -param([string]$CloneFromVMName, [string]$CloneFromSnapshotName, [string]$CloneAllSnapshotsString, [string]$vmName, [string]$path, [long]$memoryStartupBytes, [string]$switchName) - -$CloneAllSnapshots = [System.Boolean]::Parse($CloneAllSnapshotsString) +if (Test-Path $WorkingPath) { + throw "Export path working directory: $WorkingPath already exists!" +} -$ExportPath = Join-Path $path $VMName +$allSnapshots = [System.Boolean]::Parse($allSnapshotsString) -if ($CloneFromSnapshotName) { - $snapshot = Get-VMSnapshot -VMName $CloneFromVMName -Name $CloneFromSnapshotName - Export-VMSnapshot -VMSnapshot $snapshot -Path $ExportPath -ErrorAction Stop +if ($snapshotName) { + $snapshot = Get-VMSnapshot -VMName $vmName -Name $snapshotName + Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop } else { - if (!$CloneAllSnapshots) { + if (!$allSnapshots) { #Use last snapshot if one was not specified - $snapshot = Get-VMSnapshot -VMName $CloneFromVMName | Select -Last 1 + $snapshot = Get-VMSnapshot -VMName $vmName | Select -Last 1 } else { $snapshot = $null } if (!$snapshot) { #No snapshot clone - Export-VM -Name $CloneFromVMName -Path $ExportPath -ErrorAction Stop + Export-VM -Name $vmName -Path $exportPath -ErrorAction Stop } else { #Snapshot clone - Export-VMSnapshot -VMSnapshot $snapshot -Path $ExportPath -ErrorAction Stop + Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop } } -$result = Get-ChildItem -Path (Join-Path $ExportPath $CloneFromVMName) | Move-Item -Destination $ExportPath -Force -$result = Remove-Item -Path (Join-Path $ExportPath $CloneFromVMName) +$result = Get-ChildItem -Path $WorkingPath | Move-Item -Destination $exportPath -Force +$result = Remove-Item -Path $WorkingPath + ` + + allSnapshotsString := "False" + if allSnapshots { + allSnapshotsString = "True" + } + + var ps powershell.PowerShellCmd + err := ps.Run(script, exportPath, vmName, snapshotName, allSnapshotsString) + + return err +} + +func CopyVmxcVirtualMachine(exportPath string, cloneFromVmxcPath string) error { + var script = ` +param([string]$exportPath, [string]$cloneFromVmxcPath) +if (!(Test-Path $cloneFromVmxcPath)){ + throw "Clone from vmxc directory: $cloneFromVmxcPath does not exist!" +} + +if (!(Test-Path $exportPath)){ + New-Item -ItemType Directory -Force -Path $exportPath +} +$cloneFromVmxcPath = Join-Path $cloneFromVmxcPath '\*' +Copy-Item $cloneFromVmxcPath $exportPath -Recurse -Force + ` + + var ps powershell.PowerShellCmd + err := ps.Run(script, exportPath, cloneFromVmxcPath) + + return err +} + +func ImportVmxcVirtualMachine(importPath string, vmName string, harddrivePath string, ram int64, switchName string) error { + var script = ` +param([string]$importPath, [string]$vmName, [string]$harddrivePath, [long]$memoryStartupBytes, [string]$switchName) + +$VirtualHarddisksPath = Join-Path -Path $importPath -ChildPath 'Virtual Hard Disks' +if (!(Test-Path $VirtualHarddisksPath)) { + New-Item -ItemType Directory -Force -Path $VirtualHarddisksPath +} -$VirtualMachinePath = Get-ChildItem -Path (Join-Path $ExportPath 'Virtual Machines') -Filter *.vmcx -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} +$vhdPath = "" +if ($harddrivePath){ + $vhdx = $vmName + '.vhdx' + $vhdPath = Join-Path -Path $VirtualHarddisksPath -ChildPath $vhdx +} + +$VirtualMachinesPath = Join-Path $importPath 'Virtual Machines' +if (!(Test-Path $VirtualMachinesPath)) { + New-Item -ItemType Directory -Force -Path $VirtualMachinesPath +} + +$VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.vmcx -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} if (!$VirtualMachinePath){ - $VirtualMachinePath = Get-ChildItem -Path (Join-Path $ExportPath 'Virtual Machines') -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} + $VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} } if (!$VirtualMachinePath){ - $VirtualMachinePath = Get-ChildItem -Path $ExportPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} + $VirtualMachinePath = Get-ChildItem -Path $importPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} } -$compatibilityReport = Compare-VM -Path $VirtualMachinePath -VirtualMachinePath $ExportPath -SmartPagingFilePath $ExportPath -SnapshotFilePath $ExportPath -VhdDestinationPath (Join-Path -Path $ExportPath -ChildPath 'Virtual Hard Disks') -GenerateNewId -Copy:$false +$compatibilityReport = Compare-VM -Path $VirtualMachinePath -VirtualMachinePath $importPath -SmartPagingFilePath $importPath -SnapshotFilePath $importPath -VhdDestinationPath $VirtualHarddisksPath -GenerateNewId -Copy:$false +if ($vhdPath){ + Copy-Item -Path $harddrivePath -Destination $vhdPath + $existingFirstHarddrive = $compatibilityReport.VM.HardDrives | Select -First 1 + if ($existingFirstHarddrive) { + $existingFirstHarddrive | Set-VMHardDiskDrive -Path $vhdPath + } else { + Add-VMHardDiskDrive -VM $compatibilityReport.VM -Path $vhdPath + } +} Set-VMMemory -VM $compatibilityReport.VM -StartupBytes $memoryStartupBytes $networkAdaptor = $compatibilityReport.VM.NetworkAdapters | Select -First 1 Disconnect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor @@ -295,22 +358,35 @@ $vm = Import-VM -CompatibilityReport $compatibilityReport if ($vm) { $result = Rename-VM -VM $vm -NewName $VMName } -` + ` - CloneAllSnapshotsString := "False" - if cloneAllSnapshots { - CloneAllSnapshotsString = "True" + var ps powershell.PowerShellCmd + err := ps.Run(script, importPath, vmName, harddrivePath, strconv.FormatInt(ram, 10), switchName) + + return err +} + +func CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error { + if cloneFromVmName != "" { + err := ExportVmxcVirtualMachine(path, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots) + if err != nil { + return err + } } - var ps powershell.PowerShellCmd - err := ps.Run(script, cloneFromVmName, cloneFromSnapshotName, CloneAllSnapshotsString, vmName, path, strconv.FormatInt(ram, 10), switchName) + if cloneFromVmxcPath != "" { + err := CopyVmxcVirtualMachine(path, cloneFromVmxcPath) + if err != nil { + return err + } + } + err := ImportVmxcVirtualMachine(path, vmName, harddrivePath, ram, switchName) if err != nil { return err } return DeleteAllDvdDrives(vmName) - } func GetVirtualMachineGeneration(vmName string) (uint, error) { diff --git a/website/source/docs/builders/hyperv-iso.html.md b/website/source/docs/builders/hyperv-iso.html.md index f2daf1e41..f762cf3cc 100644 --- a/website/source/docs/builders/hyperv-iso.html.md +++ b/website/source/docs/builders/hyperv-iso.html.md @@ -53,21 +53,22 @@ can be configured for this builder. ### Required: -- `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO - files are so large, this is required and Packer will verify it prior - to booting a virtual machine with the ISO attached. The type of the - checksum is specified with `iso_checksum_type`, documented below. +- `iso_checksum` (string) - The checksum for the OS ISO file or virtual + harddrive file. Because these files are so large, this is required and + Packer will verify it prior to booting a virtual machine with the ISO or + virtual harddrive attached. The type of the checksum is specified with + `iso_checksum_type`, documented below. - `iso_checksum_type` (string) - The type of the checksum specified in `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or "sha512" currently. While "none" will skip checksumming, this is not - recommended since ISO files are generally large and corruption does happen - from time to time. + recommended since ISO files and virtual harddrive files are generally large + and corruption does happen from time to time. -- `iso_url` (string) - A URL to the ISO containing the installation image. - This URL can be either an HTTP URL or a file URL (or path to a file). - If this is an HTTP URL, Packer will download iso and cache it between - runs. +- `iso_url` (string) - A URL to the ISO containing the installation image or + virtual harddrive vhd or vhdx file to clone. This URL can be either an HTTP + URL or a file URL (or path to a file). If this is an HTTP URL, Packer will + download the file and cache it between runs. ### Optional: diff --git a/website/source/docs/builders/hyperv-vmcx.html.md b/website/source/docs/builders/hyperv-vmcx.html.md index c2aa3c539..0277e18da 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md +++ b/website/source/docs/builders/hyperv-vmcx.html.md @@ -9,12 +9,14 @@ page_title: "Hyper-V Builder (from an vmcx)" Type: `hyperv-vmcx` -The Hyper-V Packer builder is able to clone [Hyper-V](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) -virtual machines and export them. +The Hyper-V Packer builder is able to use exported virtual machines or clone existing +[Hyper-V](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) +virtual machines. -The builder clones an existing virtual machine boots it, and provisioning software within -the OS, then shutting it down. The result of the Hyper-V builder is a directory -containing all the files necessary to run the virtual machine portably. +The builder imports a virtual machine or clones an existing virtual machine boots it, +and provisioning software within the OS, then shutting 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 @@ -22,6 +24,18 @@ 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: +Import from folder: +```javascript +{ + "type": "hyperv-vmcx", + "clone_from_vmxc_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" +} +``` + +Clone from existing virtual machine: ```javascript { "type": "hyperv-vmcx", @@ -46,7 +60,11 @@ In addition to the options listed here, a [communicator](/docs/templates/communicator.html) can be configured for this builder. -### Required: +### Required for virtual machine import: +- `clone_from_vmxc_path` (string) - The path to the exported + virtual machine folder. + +### Required for virtual machine clone: - `clone_from_vm_name` (string) - The name of the vm to clone from. Ideally the machine to clone from should be shutdown.