From 6fd7f0877ddc2d94a2eb719f1701125c6191068c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Mar 2017 11:31:31 +0000 Subject: [PATCH 01/17] Initial check in to add a builder that can clone existing hyper v machines --- builder/hyperv/common/driver.go | 4 + builder/hyperv/common/driver_ps_4.go | 8 + builder/hyperv/common/step_clone_vm.go | 122 ++++++ builder/hyperv/vmcx/builder.go | 536 +++++++++++++++++++++++++ builder/hyperv/vmcx/builder_test.go | 250 ++++++++++++ common/powershell/hyperv/hyperv.go | 106 +++++ common/powershell/powershell.go | 55 +++ 7 files changed, 1081 insertions(+) create mode 100644 builder/hyperv/common/step_clone_vm.go create mode 100644 builder/hyperv/vmcx/builder.go create mode 100644 builder/hyperv/vmcx/builder_test.go diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index fce6da7df..07ab0f9fe 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -66,8 +66,12 @@ type Driver interface { CreateVirtualMachine(string, string, string, int64, int64, string, uint) error + CloneVirtualMachine(string, string, bool, string, string, int64, string) error + DeleteVirtualMachine(string) error + GetVirtualMachineGeneration(string) (uint, error) + SetVirtualMachineCpuCount(string, uint) error SetVirtualMachineMacSpoofing(string, bool) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index bcdb9ca53..4c45f9c5c 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -107,6 +107,10 @@ func (d *HypervPS4Driver) GetHostName(ip string) (string, error) { return powershell.GetHostName(ip) } +func (d *HypervPS4Driver) GetVirtualMachineGeneration(vmName string) (uint, error) { + return hyperv.GetVirtualMachineGeneration(vmName) +} + // Finds the IP address of a host adapter connected to switch func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName) @@ -170,6 +174,10 @@ func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, vhdPa return hyperv.CreateVirtualMachine(vmName, path, 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) DeleteVirtualMachine(vmName string) error { return hyperv.DeleteVirtualMachine(vmName) } diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go new file mode 100644 index 000000000..0159d2b9e --- /dev/null +++ b/builder/hyperv/common/step_clone_vm.go @@ -0,0 +1,122 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// This step clones an existing virtual machine. +// +// Produces: +// VMName string - The name of the VM +type StepCloneVM struct { + CloneFromVMName string + CloneFromSnapshotName string + CloneAllSnapshots bool + VMName string + SwitchName string + RamSize uint + Cpu uint + EnableMacSpoofing bool + EnableDynamicMemory bool + EnableSecureBoot bool + EnableVirtualizationExtensions bool +} + +func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Creating virtual machine...") + + path := state.Get("packerTempDir").(string) + + // 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) + if err != nil { + err := fmt.Errorf("Error cloning virtual machine: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + err = driver.SetVirtualMachineCpuCount(s.VMName, s.Cpu) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if s.EnableDynamicMemory { + err = driver.SetVirtualMachineDynamicMemory(s.VMName, s.EnableDynamicMemory) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine dynamic memory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if s.EnableMacSpoofing { + err = driver.SetVirtualMachineMacSpoofing(s.VMName, s.EnableMacSpoofing) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine mac spoofing: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + generation, err := driver.GetVirtualMachineGeneration(s.VMName) + if err != nil { + err := fmt.Errorf("Error detecting vm generation: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if generation == 2 { + err = driver.SetVirtualMachineSecureBoot(s.VMName, s.EnableSecureBoot) + if err != nil { + err := fmt.Errorf("Error setting secure boot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if s.EnableVirtualizationExtensions { + //This is only supported on Windows 10 and Windows Server 2016 onwards + err = driver.SetVirtualMachineVirtualizationExtensions(s.VMName, s.EnableVirtualizationExtensions) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine virtualization extensions: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + // Set the final name in the state bag so others can use it + state.Put("vmName", s.VMName) + + return multistep.ActionContinue +} + +func (s *StepCloneVM) Cleanup(state multistep.StateBag) { + if s.VMName == "" { + return + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Unregistering and deleting virtual machine...") + + err := driver.DeleteVirtualMachine(s.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err)) + } +} diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go new file mode 100644 index 000000000..f46b0a976 --- /dev/null +++ b/builder/hyperv/vmcx/builder.go @@ -0,0 +1,536 @@ +package vmcx + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + + "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" +) + +const ( + DefaultRamSize = 1 * 1024 // 1GB + MinRamSize = 32 // 32MB + MaxRamSize = 32 * 1024 // 32GB + MinNestedVirtualizationRamSize = 4 * 1024 // 4GB + + LowRam = 256 // 256MB + + DefaultUsername = "" + DefaultPassword = "" +) + +// Builder implements packer.Builder and builds the actual Hyperv +// images. +type Builder struct { + config Config + runner multistep.Runner +} + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + common.HTTPConfig `mapstructure:",squash"` + common.ISOConfig `mapstructure:",squash"` + hypervcommon.FloppyConfig `mapstructure:",squash"` + hypervcommon.OutputConfig `mapstructure:",squash"` + hypervcommon.SSHConfig `mapstructure:",squash"` + hypervcommon.RunConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` + + // The size, in megabytes, of the computer memory in the VM. + // By default, this is 1024 (about 1 GB). + RamSize uint `mapstructure:"ram_size"` + // A list of files to place onto a floppy disk that is attached when the + // VM is booted. This is most useful for unattended Windows installs, + // which look for an Autounattend.xml file on removable media. By default, + // no floppy will be attached. All files listed in this setting get + // placed into the root directory of the floppy and the floppy is attached + // as the first floppy device. Currently, no support exists for creating + // sub-directories on the floppy. Wildcard characters (*, ?, and []) + // are allowed. Directory names are also allowed, which will add all + // the files found in the directory to the floppy. + FloppyFiles []string `mapstructure:"floppy_files"` + // + SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` + + // Should integration services iso be mounted + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + + // The path to the integration services iso + GuestAdditionsPath string `mapstructure:"guest_additions_path"` + + // This is the name of the virtual machine to clone from. + CloneFromVMName string `mapstructure:"clone_from_vm_name"` + + // This is the name of the snapshot to clone from. A blank snapshot name will use the latest snapshot. + CloneFromSnapshotName string `mapstructure:"clone_from_snapshot_name"` + + // This will clone all snapshots if true. It will clone latest snapshot if false. + CloneAllSnapshots bool `mapstructure:"clone_all_snapshots"` + + // This is the name of the new virtual machine. + // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. + VMName string `mapstructure:"vm_name"` + + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + SwitchVlanId string `mapstructure:"switch_vlan_id"` + VlanId string `mapstructure:"vlan_id"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` + EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` + EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` + + Communicator string `mapstructure:"communicator"` + + SkipCompaction bool `mapstructure:"skip_compaction"` + + ctx interpolate.Context +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + err := config.Decode(&b.config, &config.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "boot_command", + }, + }, + }, raws...) + if err != nil { + return nil, err + } + + // Accumulate any errors and warnings + var errs *packer.MultiError + warnings := make([]string, 0) + + isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) + warnings = append(warnings, isoWarnings...) + errs = packer.MultiErrorAppend(errs, isoErrs...) + + 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)...) + + err = b.checkRamSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + + if b.config.VMName == "" { + b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) + } + + log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) + + if b.config.SwitchName == "" { + b.config.SwitchName = b.detectSwitchName() + } + + if b.config.Cpu < 1 { + b.config.Cpu = 1 + } + + if b.config.Generation != 2 { + b.config.Generation = 1 + } + + if b.config.Generation == 2 { + if len(b.config.FloppyFiles) > 0 { + err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") + errs = packer.MultiErrorAppend(errs, err) + } + } + + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) + log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) + + // Errors + if b.config.GuestAdditionsMode == "" { + if b.config.GuestAdditionsPath != "" { + b.config.GuestAdditionsMode = "attach" + } else { + b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + b.config.GuestAdditionsPath = "" + b.config.GuestAdditionsMode = "none" + } else { + b.config.GuestAdditionsMode = "attach" + } + } + } + } + + if b.config.GuestAdditionsPath == "" && b.config.GuestAdditionsMode == "attach" { + b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + b.config.GuestAdditionsPath = "" + } + } + } + + for _, isoPath := range b.config.SecondaryDvdImages { + if _, err := os.Stat(isoPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) + } + } + } + + numberOfIsos := len(b.config.SecondaryDvdImages) + + if b.config.GuestAdditionsMode == "attach" { + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) + } + } + + numberOfIsos = numberOfIsos + 1 + } + + 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, ", "))) + } 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, ", "))) + } + } 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, ", "))) + } 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, ", "))) + } + } + + 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)) + } 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.")) + } + } + } + + 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)) + } else { + if !virtualMachineExists { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine '%s' to clone from does not exist.", b.config.CloneFromVMName)) + } else { + if 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)) + } 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)) + } + } + } + + 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)) + } else { + if virtualMachineOn { + warning := fmt.Sprintf("Cloning from a virtual machine that is running.") + warnings = appendWarnings(warnings, warning) + } + } + } + } + + // Warnings + + if b.config.ShutdownCommand == "" { + warnings = appendWarnings(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } + + warning := b.checkHostAvailableMemory() + if warning != "" { + warnings = appendWarnings(warnings, warning) + } + + 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.") + warnings = appendWarnings(warnings, warning) + } + + if !b.config.EnableMacSpoofing { + 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.") + 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.") + warnings = appendWarnings(warnings, warning) + } + } + + if errs != nil && len(errs.Errors) > 0 { + return warnings, errs + } + + return warnings, nil +} + +// Run executes a Packer build and returns a packer.Artifact representing +// a Hyperv appliance. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + // Create the driver that we'll use to communicate with Hyperv + driver, err := hypervcommon.NewHypervPS4Driver() + if err != nil { + return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err) + } + + // Set up the state. + state := new(multistep.BasicStateBag) + state.Put("cache", cache) + state.Put("config", &b.config) + state.Put("debug", b.config.PackerDebug) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + steps := []multistep.Step{ + &hypervcommon.StepCreateTempDir{}, + &hypervcommon.StepOutputDir{ + Force: b.config.PackerForce, + Path: b.config.OutputDir, + }, + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: b.config.ISOUrls, + Extension: b.config.TargetExtension, + TargetPath: b.config.TargetPath, + }, + &common.StepCreateFloppy{ + Files: b.config.FloppyFiles, + }, + &common.StepHTTPServer{ + HTTPDir: b.config.HTTPDir, + HTTPPortMin: b.config.HTTPPortMin, + HTTPPortMax: b.config.HTTPPortMax, + }, + &hypervcommon.StepCreateSwitch{ + SwitchName: b.config.SwitchName, + }, + &hypervcommon.StepCloneVM{ + CloneFromVMName: b.config.CloneFromVMName, + CloneFromSnapshotName: b.config.CloneFromSnapshotName, + CloneAllSnapshots: b.config.CloneAllSnapshots, + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSize: b.config.RamSize, + Cpu: b.config.Cpu, + EnableMacSpoofing: b.config.EnableMacSpoofing, + EnableDynamicMemory: b.config.EnableDynamicMemory, + EnableSecureBoot: b.config.EnableSecureBoot, + EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, + }, + + &hypervcommon.StepEnableIntegrationService{}, + + &hypervcommon.StepMountDvdDrive{ + Generation: b.config.Generation, + }, + &hypervcommon.StepMountFloppydrive{ + Generation: b.config.Generation, + }, + + &hypervcommon.StepMountGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsPath: b.config.GuestAdditionsPath, + Generation: b.config.Generation, + }, + + &hypervcommon.StepMountSecondaryDvdImages{ + IsoPaths: b.config.SecondaryDvdImages, + Generation: b.config.Generation, + }, + + &hypervcommon.StepConfigureVlan{ + VlanId: b.config.VlanId, + SwitchVlanId: b.config.SwitchVlanId, + }, + + &hypervcommon.StepRun{ + BootWait: b.config.BootWait, + }, + + &hypervcommon.StepTypeBootCommand{ + BootCommand: b.config.BootCommand, + SwitchName: b.config.SwitchName, + Ctx: b.config.ctx, + }, + + // configure the communicator ssh, winrm + &communicator.StepConnect{ + Config: &b.config.SSHConfig.Comm, + Host: hypervcommon.CommHost, + SSHConfig: hypervcommon.SSHConfigFunc(&b.config.SSHConfig), + }, + + // provision requires communicator to be setup + &common.StepProvision{}, + + &hypervcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + + // wait for the vm to be powered off + &hypervcommon.StepWaitForPowerOff{}, + + // remove the secondary dvd images + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, + &hypervcommon.StepUnmountGuestAdditions{}, + &hypervcommon.StepUnmountDvdDrive{}, + &hypervcommon.StepUnmountFloppyDrive{ + Generation: b.config.Generation, + }, + &hypervcommon.StepExportVm{ + OutputDir: b.config.OutputDir, + SkipCompaction: b.config.SkipCompaction, + }, + + // the clean up actions for each step will be executed reverse order + } + + // Run the steps. + if b.config.PackerDebug { + pauseFn := common.MultistepDebugFn(ui) + state.Put("pauseFn", pauseFn) + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: pauseFn, + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + + b.runner.Run(state) + + // Report any errors. + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return hypervcommon.NewArtifact(b.config.OutputDir) +} + +// Cancel. +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} + +func appendWarnings(slice []string, data ...string) []string { + m := len(slice) + n := m + len(data) + if n > cap(slice) { // if necessary, reallocate + // allocate double what's needed, for future growth. + newSlice := make([]string, (n+1)*2) + copy(newSlice, slice) + slice = newSlice + } + slice = slice[0:n] + copy(slice[m:n], data) + return slice +} + +func (b *Builder) checkRamSize() error { + if b.config.RamSize == 0 { + b.config.RamSize = DefaultRamSize + } + + 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) + } 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 nil +} + +func (b *Builder) checkHostAvailableMemory() string { + powershellAvailable, _, _ := powershell.IsPowershellAvailable() + + if powershellAvailable { + freeMB := powershell.GetHostAvailableMemory() + + if (freeMB - float64(b.config.RamSize)) < LowRam { + return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + } + } + + return "" +} + +func (b *Builder) detectSwitchName() string { + powershellAvailable, _, _ := powershell.IsPowershellAvailable() + + if powershellAvailable { + // no switch name, try to get one attached to a online network adapter + onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() + if onlineSwitchName != "" && err == nil { + return onlineSwitchName + } + } + + return fmt.Sprintf("packer-%s", b.config.PackerBuildName) +} diff --git a/builder/hyperv/vmcx/builder_test.go b/builder/hyperv/vmcx/builder_test.go new file mode 100644 index 000000000..c2ea23c14 --- /dev/null +++ b/builder/hyperv/vmcx/builder_test.go @@ -0,0 +1,250 @@ +package vmcx + +import ( + "reflect" + "testing" + + "github.com/mitchellh/packer/packer" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.packer.io", + "shutdown_command": "yes", + "ssh_username": "foo", + "ram_size": 64, + "disk_size": 256, + "guest_additions_mode": "none", + packer.BuildNameConfigKey: "foo", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Error("Builder must implement builder.") + } +} + +func TestBuilderPrepare_Defaults(t *testing.T) { + var b Builder + config := testConfig() + + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.VMName != "packer-foo" { + t.Errorf("bad vm name: %s", b.config.VMName) + } +} + +func TestBuilderPrepare_DiskSize(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) + } + if err != nil { + t.Fatalf("should not have error: %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() + + // Add a random key + config["i_should_not_be_valid"] = true + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ISOChecksum(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum"] = "FOo" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksum != "foo" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksum) + } +} + +func TestBuilderPrepare_ISOChecksumType(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum_type"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum_type"] = "mD5" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "md5" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } + + // Test unknown + config["iso_checksum_type"] = "fake" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test none + config["iso_checksum_type"] = "none" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) == 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "none" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } +} + +func TestBuilderPrepare_ISOUrl(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + + // Test both epty + 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") + } + + // Test iso_url set + config["iso_url"] = "http://www.packer.io" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected := []string{"http://www.packer.io"} + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test both set + config["iso_url"] = "http://www.packer.io" + config["iso_urls"] = []string{"http://www.packer.io"} + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test just iso_urls set + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index 6ea4d1903..461d4120a 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -234,6 +234,112 @@ if ((Get-Command Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) { return err } +func DisableAutomaticCheckpoints(vmName string) 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 +} + +func CloneVirtualMachine(cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, ram int64, switchName string) error { + + var script = ` +param([string]$CloneFromVMName, [string]$CloneFromSnapshotName, [string]CloneAllSnapshotsString, [string]$vmName, [string]$path, [long]$memoryStartupBytes, [string]$switchName) + +$CloneAllSnapshots = [System.Boolean]::Parse($CloneAllSnapshotsString) + +$ExportPath = Join-Path $path $VMName + +if ($CloneFromSnapshotName) { + $snapshot = Get-VMSnapshot -VMName $CloneFromVMName -Name $CloneFromSnapshotName + Export-VMSnapshot -VMSnapshot $snapshot -Path $ExportPath -ErrorAction Stop +} else { + if (!$CloneAllSnapshots) { + #Use last snapshot if one was not specified + $snapshot = Get-VMSnapshot -VMName $CloneFromVMName | Select -Last 1 + } else { + $snapshot = $null + } + + if (!$snapshot) { + #No snapshot clone + Export-VM -Name $CloneFromVMName -Path $ExportPath -ErrorAction Stop + } else { + #Snapshot clone + 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) + +$VirtualMachinePath = Get-ChildItem -Path (Join-Path $ExportPath 'Virtual Machines') -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} +} +if (!$VirtualMachinePath){ + $VirtualMachinePath = Get-ChildItem -Path $ExportPath -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 +Set-VMMemory -VM $compatibilityReport.VM -StartupBytes $memoryStartupBytes +$networkAdaptor = $compatibilityReport.VM.NetworkAdapters | Select -First 1 +Disconnect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor +Connect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor -SwitchName $switchName +$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, cloneFromVmName, cloneFromSnapshotName, CloneAllSnapshotsString, vmName, path, strconv.FormatInt(ram, 10), switchName) + + if err != nil { + return err + } + + return DeleteAllDvdDrives(vmName) + +} + +func GetVirtualMachineGeneration(vmName string) (uint, error) { + var script = ` +param([string]$vmName) +$generation = Get-Vm -Name $vmName | %{$_.Generation} +if (!$generation){ + $generation = 1 +} +return $generation +` + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return 0, err + } + + generationUint32, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 32) + + if err != nil { + return 0, err + } + + generation := uint(generationUint32) + + return generation, err +} + func SetVirtualMachineCpuCount(vmName string, cpu uint) error { var script = ` diff --git a/common/powershell/powershell.go b/common/powershell/powershell.go index 83190d1a4..9927ffaea 100644 --- a/common/powershell/powershell.go +++ b/common/powershell/powershell.go @@ -246,6 +246,61 @@ func HasVirtualMachineVirtualizationExtensions() (bool, error) { return hasVirtualMachineVirtualizationExtensions, err } +func DoesVirtualMachineExist(vmName string) (bool, error) { + + var script = ` +param([string]$vmName) +return (Get-VM | ?{$_.Name -eq $vmName}) -ne $null +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return false, err + } + + var exists = strings.TrimSpace(cmdOut) == "True" + return exists, err +} + +func DoesVirtualMachineSnapshotExist(vmName string, snapshotName string) (bool, error) { + + var script = ` +param([string]$vmName, [string]$snapshotName) +return (Get-VMSnapshot -VMName $vmName | ?{$_.Name -eq $snapshotName}) -ne $null +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script, vmName, snapshotName) + + if err != nil { + return false, err + } + + var exists = strings.TrimSpace(cmdOut) == "True" + return exists, err +} + +func IsVirtualMachineOn(vmName string) (bool, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return false, err + } + + var isRunning = strings.TrimSpace(cmdOut) == "True" + return isRunning, err +} + func SetUnattendedProductKey(path string, productKey string) error { var script = ` From 429e1bc3adfefc025510ae6b619e94c6a8884733 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Mar 2017 17:31:56 +0000 Subject: [PATCH 02/17] Adding an ISO is now optional for hyperv vmcx Add documentation for hyperv vmcx --- builder/hyperv/vmcx/builder.go | 89 +- common/powershell/powershell.go | 27 + .../source/docs/builders/hyperv-vmcx.html.md | 985 ++++++++++++++++++ website/source/docs/builders/hyperv.html.md | 7 +- 4 files changed, 1068 insertions(+), 40 deletions(-) create mode 100644 website/source/docs/builders/hyperv-vmcx.html.md diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index f46b0a976..791c9ae46 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -87,11 +87,11 @@ type Config struct { SwitchVlanId string `mapstructure:"switch_vlan_id"` VlanId string `mapstructure:"vlan_id"` Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` - EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` - EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` - EnableSecureBoot bool `mapstructure:"enable_secure_boot"` - EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` + Generation uint + EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` + EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` Communicator string `mapstructure:"communicator"` @@ -118,9 +118,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { var errs *packer.MultiError warnings := make([]string, 0) - isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) - warnings = append(warnings, isoWarnings...) - errs = packer.MultiErrorAppend(errs, isoErrs...) + if b.config.RawSingleISOUrl != "" || len(b.config.ISOUrls) > 0 { + isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) + warnings = append(warnings, isoWarnings...) + errs = packer.MultiErrorAppend(errs, isoErrs...) + } errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) @@ -148,6 +150,47 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Cpu = 1 } + b.config.Generation = 1 + + if b.config.CloneFromVMName == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vm_name must be 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)) + } else { + if !virtualMachineExists { + 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)) + } + + if 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)) + } 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)) + } + } + } + + 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)) + } else { + if virtualMachineOn { + warning := fmt.Sprintf("Cloning from a virtual machine that is running.") + warnings = appendWarnings(warnings, warning) + } + } + } + } + } + if b.config.Generation != 2 { b.config.Generation = 1 } @@ -237,36 +280,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - 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)) - } else { - if !virtualMachineExists { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine '%s' to clone from does not exist.", b.config.CloneFromVMName)) - } else { - if 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)) - } 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)) - } - } - } - - 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)) - } else { - if virtualMachineOn { - warning := fmt.Sprintf("Cloning from a virtual machine that is running.") - warnings = appendWarnings(warnings, warning) - } - } - } - } - // Warnings if b.config.ShutdownCommand == "" { diff --git a/common/powershell/powershell.go b/common/powershell/powershell.go index 9927ffaea..a41915474 100644 --- a/common/powershell/powershell.go +++ b/common/powershell/powershell.go @@ -301,6 +301,33 @@ $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running return isRunning, err } +func GetVirtualMachineGeneration(vmName string) (uint, error) { + var script = ` +param([string]$vmName) +$generation = Get-Vm -Name $vmName | %{$_.Generation} +if (!$generation){ + $generation = 1 +} +return $generation +` + var ps PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return 0, err + } + + generationUint32, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 32) + + if err != nil { + return 0, err + } + + generation := uint(generationUint32) + + return generation, err +} + func SetUnattendedProductKey(path string, productKey string) error { var script = ` diff --git a/website/source/docs/builders/hyperv-vmcx.html.md b/website/source/docs/builders/hyperv-vmcx.html.md new file mode 100644 index 000000000..c2aa3c539 --- /dev/null +++ b/website/source/docs/builders/hyperv-vmcx.html.md @@ -0,0 +1,985 @@ +--- +description: |- + The Hyper-V Packer builder is able to clone an existing Hyper-V virtual machine and export them. +layout: "docs" +page_title: "Hyper-V Builder (from an vmcx)" +--- + +# Hyper-V Builder (from a 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 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. + +## Basic Example + +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: + +```javascript +{ + "type": "hyperv-vmcx", + "clone_from_vm_name": "ubuntu-12.04.5-server-amd64", + "ssh_username": "packer", + "ssh_password": "packer", + "shutdown_command": "echo 'packer' | sudo -S shutdown -P now" +} +``` + +It is important to add a `shutdown_command`. By default Packer halts the +virtual machine and the file system may not be sync'd. Thus, changes made in a +provisioner might not be saved. + +## Configuration Reference + +There are many configuration options available for the Hyper-V builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +In addition to the options listed here, a +[communicator](/docs/templates/communicator.html) +can be configured for this builder. + +### Required: +- `clone_from_vm_name` (string) - The name of the vm to clone from. + Ideally the machine to clone from should be shutdown. + +### Optional: +- `clone_from_snapshot_name` (string) - The name of the snapshot + +- `clone_all_snapshots` (boolean) - Should all snapshots be cloned + when the machine is cloned. + +- `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is first booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +- `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +- `cpu` (integer) - The number of cpus the virtual machine should use. If this isn't specified, + the default is 1 cpu. + +- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. + This defaults to false. + +- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. + This defaults to false. + +- `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. + This defaults to false. + +- `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. + This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory + and have at least 4GB of RAM for virtual machine. + +- `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +- `guest_additions_mode` (string) - How should guest additions be installed. + If value `attach` then attach iso image with by specified by `guest_additions_path`. + Otherwise guest additions is not installed. + +- `guest_additions_path` (string) - The path to the iso image for guest additions. + +- `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +- `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +- `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_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. + +- `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_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +- `iso_target_extension` (string) - The extension of the iso file after + download. This defaults to "iso". + +- `iso_target_path` (string) - The path where the iso should be saved after + download. By default will go in the packer cache, with a hash of the + original filename as its name. + +- `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. + +- `ram_size` (integer) - The size, in megabytes, of the ram to create + for the VM. By default, this is 1 GB. + +* `secondary_iso_images` (array of strings) - A list of iso paths to attached to a + VM when it is booted. This is most useful for unattended Windows installs, which + look for an `Autounattend.xml` file on removable media. By default, no + secondary iso will be attached. + +- `shutdown_command` (string) - The command to use to gracefully shut down the machine once all + the provisioning is done. By default this is an empty string, which tells Packer to just + forcefully shut down the machine unless a shutdown command takes place inside script so this may + safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last script. + +- `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +- `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when + exporting. This defaults to false. + +- `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting + this to an empty string, Packer will try to determine the switch to use by looking for + external switch that is up and running. + +- `switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. + By default none is set. If none is set then a vlan is not set on the switch's network card. + If this value is set it should match the vlan specified in by `vlan_id`. + +- `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. + +- `vm_name` (string) - This is the name of the virtual machine for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character over the virtual keyboard +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +- `` - Backspace + +- `` - Delete + +- `` and `` - Simulates an actual "enter" or "return" keypress. + +- `` - Simulates pressing the escape key. + +- `` - Simulates pressing the tab key. + +- `` - `` - Simulates pressing a function key. + +- `` `` `` `` - Simulates pressing an arrow key. + +- `` - Simulates pressing the spacebar. + +- `` - Simulates pressing the insert key. + +- `` `` - Simulates pressing the home and end keys. + +- `` `` - Simulates pressing the page up and page down keys. + +- `` `` - Simulates pressing the alt key. + +- `` `` - Simulates pressing the ctrl key. + +- `` `` - Simulates pressing the shift key. + +- `` `` - Simulates pressing and holding the alt key. + +- `` `` - Simulates pressing and holding the ctrl key. + +- `` `` - Simulates pressing and holding the shift key. + +- `` `` - Simulates releasing a held alt key. + +- `` `` - Simulates releasing a held ctrl key. + +- `` `` - Simulates releasing a held shift key. + +- `` `` `` - Adds a 1, 5 or 10 second pause before + sending any additional keys. This is useful if you have to generally wait + for the UI to update before typing more. + +When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, otherwise they will be held down until the machine reboots. Use lowercase characters as well inside modifiers. For example: to simulate ctrl+c use `c`. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will + be blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +```text +[ + "", + "/install/vmlinuz noapic ", + "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{ .Name }} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false ", + "initrd=/install/initrd.gz -- " +] +``` + +## Integration Services + +Packer will automatically attach the integration services iso as a dvd drive +for the version of Hyper-V that is running. + +## Generation 1 vs Generation 2 + +Floppy drives are no longer supported by generation 2 machines. This requires you to +take another approach when dealing with preseed or answer files. Two possible options +are using virtual dvd drives or using the built in web server. + +When dealing with Windows you need to enable UEFI drives for generation 2 virtual machines. + +## Creating iso from directory + +Programs like mkisofs can be used to create an iso from a directory. +There is a [windows version of mkisofs](http://opensourcepack.blogspot.co.uk/p/cdrtools.html). + +Example powershell script. This is an actually working powershell script used to create a Windows answer iso: + +```text +$isoFolder = "answer-iso" +if (test-path $isoFolder){ + remove-item $isoFolder -Force -Recurse +} + +if (test-path windows\windows-2012R2-serverdatacenter-amd64\answer.iso){ + remove-item windows\windows-2012R2-serverdatacenter-amd64\answer.iso -Force +} + +mkdir $isoFolder + +copy windows\windows-2012R2-serverdatacenter-amd64\Autounattend.xml $isoFolder\ +copy windows\windows-2012R2-serverdatacenter-amd64\sysprep-unattend.xml $isoFolder\ +copy windows\common\set-power-config.ps1 $isoFolder\ +copy windows\common\microsoft-updates.ps1 $isoFolder\ +copy windows\common\win-updates.ps1 $isoFolder\ +copy windows\common\run-sysprep.ps1 $isoFolder\ +copy windows\common\run-sysprep.cmd $isoFolder\ + +$textFile = "$isoFolder\Autounattend.xml" + +$c = Get-Content -Encoding UTF8 $textFile + +# Enable UEFI and disable Non EUFI +$c | % { $_ -replace '','','Finish Non UEFI -->' } | % { $_ -replace '' } | % { $_ -replace 'Finish UEFI compatible -->','' } | sc -Path $textFile + +& .\mkisofs.exe -r -iso-level 4 -UDF -o windows\windows-2012R2-serverdatacenter-amd64\answer.iso $isoFolder + +if (test-path $isoFolder){ + remove-item $isoFolder -Force -Recurse +} +``` + + +## Example For Windows Server 2012 R2 Generation 2 + +Packer config: + +```javascript +{ + "builders": [ + { + "vm_name":"windows2012r2", + "type": "hyperv-iso", + "disk_size": 61440, + "floppy_files": [], + "secondary_iso_images": [ + "./windows/windows-2012R2-serverdatacenter-amd64/answer.iso" + ], + "http_directory": "./windows/common/http/", + "boot_wait": "0s", + "boot_command": [ + "aaa" + ], + "iso_url": "http://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.16384.WINBLUE_RTM.130821-1623_X64FRE_SERVER_EVAL_EN-US-IRM_SSS_X64FREE_EN-US_DV5.ISO", + "iso_checksum_type": "md5", + "iso_checksum": "458ff91f8abc21b75cb544744bf92e6a", + "communicator":"winrm", + "winrm_username": "vagrant", + "winrm_password": "vagrant", + "winrm_timeout" : "4h", + "shutdown_command": "f:\\run-sysprep.cmd", + "ram_size": 4096, + "cpu": 4, + "generation": 2, + "switch_name":"LAN", + "enable_secure_boot":true + }], + "provisioners": [{ + "type": "powershell", + "elevated_user":"vagrant", + "elevated_password":"vagrant", + "scripts": [ + "./windows/common/install-7zip.ps1", + "./windows/common/install-chef.ps1", + "./windows/common/compile-dotnet-assemblies.ps1", + "./windows/common/cleanup.ps1", + "./windows/common/ultradefrag.ps1", + "./windows/common/sdelete.ps1" + ] + }], + "post-processors": [ + { + "type": "vagrant", + "keep_input_artifact": false, + "output": "{{.Provider}}_windows-2012r2_chef.box" + } + ] +} +``` + +autounattend.xml: + +```xml + + + + + + en-US + + en-US + en-US + en-US + en-US + en-US + + + + + + + + Primary + 1 + 350 + + + 2 + Primary + true + + + + + true + NTFS + + 1 + 1 + + + NTFS + + C + 2 + 2 + + + 0 + true + + + + + + + /IMAGE/NAME + Windows Server 2012 R2 SERVERSTANDARD + + + + 0 + 2 + + + + + + + + + + + + OnError + + true + Vagrant + Vagrant + + + + + + + false + + vagrant-2012r2 + Coordinated Universal Time + + + + true + + + false + false + + + true + + + true + + + + + + + + vagrant + true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>vagrant</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 64 Bit</Description> + <Order>1</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 32 Bit</Description> + <Order>2</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm quickconfig -q</CommandLine> + <Description>winrm quickconfig -q</Description> + <Order>3</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm quickconfig -transport:http</CommandLine> + <Description>winrm quickconfig -transport:http</Description> + <Order>4</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config @{MaxTimeoutms="1800000"}</CommandLine> + <Description>Win RM MaxTimoutms</Description> + <Order>5</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxMemoryPerShellMB="300"}</CommandLine> + <Description>Win RM MaxMemoryPerShellMB</Description> + <Order>6</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/service @{AllowUnencrypted="true"}</CommandLine> + <Description>Win RM AllowUnencrypted</Description> + <Order>7</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/service/auth @{Basic="true"}</CommandLine> + <Description>Win RM auth Basic</Description> + <Order>8</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/client/auth @{Basic="true"}</CommandLine> + <Description>Win RM client auth Basic</Description> + <Order>9</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} </CommandLine> + <Description>Win RM listener Address/Port</Description> + <Order>10</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes </CommandLine> + <Description>Win RM adv firewall enable</Description> + <Order>11</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow</CommandLine> + <Description>Win RM port open</Description> + <Order>12</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow</CommandLine> + <Description>Win RM port open</Description> + <Order>13</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c net stop winrm </CommandLine> + <Description>Stop Win RM Service </Description> + <Order>14</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c sc config winrm start= disabled</CommandLine> + <Description>Win RM Autostart</Description> + <Order>15</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>16</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>17</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>18</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>19</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>20</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>21</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='vagrant'" set PasswordExpires=FALSE</CommandLine> + <Order>22</Order> + <Description>Disable password expiration for vagrant user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxShellsPerUser="30"}</CommandLine> + <Description>Win RM MaxShellsPerUser</Description> + <Order>23</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxProcessesPerShell="25"}</CommandLine> + <Description>Win RM MaxProcessesPerShell</Description> + <Order>24</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD "HKLM\System\CurrentControlSet\Services\Netlogon\Parameters" /v DisablePasswordChange /t REG_DWORD /d 1 /f</CommandLine> + <Description>Turn off computer password</Description> + <Order>25</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow</CommandLine> + <Description>ICMP open for ping</Description> + <Order>26</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <!-- WITH WINDOWS UPDATES --> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\set-power-config.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\set-power-config.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\set-power-config.ps1)</CommandLine> + <Order>97</Order> + <Description>Turn off all power saving and timeouts</Description> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\microsoft-updates.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\microsoft-updates.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\microsoft-updates.ps1)</CommandLine> + <Order>98</Order> + <Description>Enable Microsoft Updates</Description> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\win-updates.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\win-updates.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\win-updates.ps1)</CommandLine> + <Description>Install Windows Updates</Description> + <Order>100</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <!-- END WITH WINDOWS UPDATES --> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <RegisteredOwner /> + <TimeZone>Coordinated Universal Time</TimeZone> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <cpi:offlineImage cpi:source="wim:c:/projects/baseboxes/9600.16384.winblue_rtm.130821-1623_x64fre_server_eval_en-us-irm_sss_x64free_en-us_dv5_slipstream/sources/install.wim#Windows Server 2012 R2 SERVERDATACENTER" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> + +``` + +sysprep-unattend.xml: + +```text +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="generalize"> + <component language="neutral" name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="oobeSystem"> +<!-- Setup proxy after sysprep + <component name="Microsoft-Windows-IE-ClientNetworkProtocolImplementation" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <POLICYProxySettingsPerUser>1</POLICYProxySettingsPerUser> + <HKLMProxyEnable>false</HKLMProxyEnable> + <HKLMProxyServer>cache-proxy:3142</HKLMProxyServer> + </component> +Finish proxy after sysprep --> + <component language="neutral" name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0809:00000809</InputLocale> + <SystemLocale>en-GB</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-GB</UserLocale> + </component> + <component language="neutral" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipUserOOBE>true</SkipUserOOBE> + <SkipMachineOOBE>true</SkipMachineOOBE> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <DisableAutoDaylightTimeSet>true</DisableAutoDaylightTimeSet> + <TimeZone>Coordinated Universal Time</TimeZone> + <VisualEffects> + <SystemDefaultBackgroundColor>2</SystemDefaultBackgroundColor> + </VisualEffects> + </component> + </settings> +</unattend> +``` + +## Example For Ubuntu Vivid Generation 2 + +If you are running Windows under virtualization, you may need to create +a virtual switch with an `External` connection type. + +### Packer config: + +```javascript +{ + "variables": { + "vm_name": "ubuntu-xenial", + "cpu": "2", + "ram_size": "1024", + "disk_size": "21440", + "iso_url": "http://releases.ubuntu.com/16.04/ubuntu-16.04.1-server-amd64.iso", + "iso_checksum_type": "sha1", + "iso_checksum": "DE5EE8665048F009577763EFBF4A6F0558833E59" + }, + "builders": [ + { + "vm_name":"{{user `vm_name`}}", + "type": "hyperv-iso", + "disk_size": "{{user `disk_size`}}", + "guest_additions_mode": "disable", + "iso_url": "{{user `iso_url`}}", + "iso_checksum_type": "{{user `iso_checksum_type`}}", + "iso_checksum": "{{user `iso_checksum`}}", + "communicator":"ssh", + "ssh_username": "packer", + "ssh_password": "packer", + "ssh_timeout" : "4h", + "http_directory": "./", + "boot_wait": "5s", + "boot_command": [ + "<esc><wait10><esc><esc><enter><wait>", + "set gfxpayload=1024x768<enter>", + "linux /install/vmlinuz ", + "preseed/url=http://{{.HTTPIP}}:{{.HTTPPort}}/hyperv-taliesins.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{.Name}} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false <enter>", + "initrd /install/initrd.gz<enter>", + "boot<enter>" + ], + "shutdown_command": "echo 'packer' | sudo -S -E shutdown -P now", + "ram_size": "{{user `ram_size`}}", + "cpu": "{{user `cpu`}}", + "generation": 2, + "enable_secure_boot": false + }] +} +``` + +### preseed.cfg: + +```text +## Options to set on the command line +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +d-i netcfg/get_hostname string nl-ams-basebox3 +d-i netcfg/get_domain string unassigned-domain + +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +d-i kbd-chooser/method select American English + +d-i netcfg/wireless_wep string + +d-i base-installer/kernel/override-image string linux-server + +d-i debconf debconf/frontend select Noninteractive + +d-i pkgsel/install-language-support boolean false +tasksel tasksel/first multiselect standard, ubuntu-server + +## Partitioning +d-i partman-auto/method string lvm + +d-i partman-lvm/confirm boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-lvm/confirm boolean true + +d-i partman-auto-lvm/guided_size string max +d-i partman-auto/choose_recipe select atomic + +d-i partman/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true + +# Write the changes to disks and configure LVM? +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true + +d-i partman-partitioning/no_bootable_gpt_biosgrub boolean false +d-i partman-partitioning/no_bootable_gpt_efi boolean false +d-i partman-efi/non_efi_system boolean true + +# Default user +d-i passwd/user-fullname string packer +d-i passwd/username string packer +d-i passwd/user-password password packer +d-i passwd/user-password-again password packer +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Minimum packages +d-i pkgsel/include string openssh-server ntp linux-tools-$(uname -r) linux-cloud-tools-$(uname -r) linux-cloud-tools-common + +# Upgrade packages after debootstrap? (none, safe-upgrade, full-upgrade) +# (note: set to none for speed) +d-i pkgsel/upgrade select none + +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note + +d-i pkgsel/update-policy select none + +choose-mirror-bin mirror/http/proxy string +``` diff --git a/website/source/docs/builders/hyperv.html.md b/website/source/docs/builders/hyperv.html.md index d12e2a212..57f2015a9 100644 --- a/website/source/docs/builders/hyperv.html.md +++ b/website/source/docs/builders/hyperv.html.md @@ -12,9 +12,12 @@ sidebar_current: 'docs-builders-hyperv' The HyperV Packer builder is able to create [Hyper-V](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) virtual machines and export them. -Packer currently only support building HyperV machines with an iso: - - [hyperv-iso](/docs/builders/hyperv-iso.html) - Starts from an ISO file, creates a brand new Hyper-V VM, installs an OS, provisions software within the OS, then exports that machine to create an image. This is best for people who want to start from scratch. + +- [hyperv-vmcx](/docs/builders/hyperv-vmcx.html) - Clones an + an existing virtual machine, provisions software within the OS, + then exports that machine to create an image. This is best for + people who have existing base images and want to customize them. \ No newline at end of file From 452fcbd9a1be9f4c582e4757ea7b833839fc6d98 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 12 Mar 2017 23:51:59 +0000 Subject: [PATCH 03/17] Only attach dvd drive if there is one Fix debug messages for cloning Add hyperv-vmcx as a builder from command line --- builder/hyperv/common/step_clone_vm.go | 2 +- builder/hyperv/common/step_mount_dvddrive.go | 12 +++++++-- builder/hyperv/vmcx/builder.go | 28 +++++++++++++------- command/plugin.go | 4 +++ common/powershell/hyperv/hyperv.go | 2 +- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go index 0159d2b9e..025a1bb06 100644 --- a/builder/hyperv/common/step_clone_vm.go +++ b/builder/hyperv/common/step_clone_vm.go @@ -28,7 +28,7 @@ type StepCloneVM struct { func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - ui.Say("Creating virtual machine...") + ui.Say("Cloning virtual machine...") path := state.Get("packerTempDir").(string) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 632120053..d91be3a88 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -2,9 +2,9 @@ package common import ( "fmt" + "log" "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" - "log" ) type StepMountDvdDrive struct { @@ -17,7 +17,15 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error mounting dvd drive: %s" vmName := state.Get("vmName").(string) - isoPath := state.Get("iso_path").(string) + + // Determine if we even have a dvd disk to attach + var isoPath string + if isoPathRaw, ok := state.GetOk("iso_path"); ok { + isoPath = isoPathRaw.(string) + } else { + log.Println("No dvd disk, not attaching.") + return multistep.ActionContinue + } // should be able to mount up to 60 additional iso images using SCSI // but Windows would only allow a max of 22 due to available drive letters diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 791c9ae46..0282d1f18 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -348,15 +348,23 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Force: b.config.PackerForce, Path: b.config.OutputDir, }, - &common.StepDownload{ - Checksum: b.config.ISOChecksum, - ChecksumType: b.config.ISOChecksumType, - Description: "ISO", - ResultKey: "iso_path", - Url: b.config.ISOUrls, - Extension: b.config.TargetExtension, - TargetPath: b.config.TargetPath, - }, + } + + if b.config.RawSingleISOUrl != "" || len(b.config.ISOUrls) > 0 { + steps = append(steps, + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: b.config.ISOUrls, + Extension: b.config.TargetExtension, + TargetPath: b.config.TargetPath, + }, + ) + } + + steps = append(steps, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -449,7 +457,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, // the clean up actions for each step will be executed reverse order - } + ) // Run the steps. if b.config.PackerDebug { diff --git a/command/plugin.go b/command/plugin.go index 11baf4729..373c7320f 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -92,8 +92,12 @@ var Builders = map[string]packer.Builder{ "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), "hyperv-iso": new(hypervisobuilder.Builder), +<<<<<<< HEAD "lxc": new(lxcbuilder.Builder), "lxd": new(lxdbuilder.Builder), +======= + "hyperv-vmcx": new(hypervvmcxbuilder.Builder), +>>>>>>> Only attach dvd drive if there is one "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder), diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index 461d4120a..9937ed3b7 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -248,7 +248,7 @@ if ((Get-Command Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) { func CloneVirtualMachine(cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, ram int64, switchName string) error { var script = ` -param([string]$CloneFromVMName, [string]$CloneFromSnapshotName, [string]CloneAllSnapshotsString, [string]$vmName, [string]$path, [long]$memoryStartupBytes, [string]$switchName) +param([string]$CloneFromVMName, [string]$CloneFromSnapshotName, [string]$CloneAllSnapshotsString, [string]$vmName, [string]$path, [long]$memoryStartupBytes, [string]$switchName) $CloneAllSnapshots = [System.Boolean]::Parse($CloneAllSnapshotsString) From efa62e1550014d87a445a8baf69e5473d530af58 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 21 May 2017 17:29:26 +0100 Subject: [PATCH 04/17] 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. Can import a virtual machine from a folder Can clone an existing virtual machine --- builder/hyperv/common/driver.go | 4 +- builder/hyperv/common/driver_ps_4.go | 8 +- builder/hyperv/common/step_clone_vm.go | 21 ++- builder/hyperv/common/step_create_vm.go | 25 ++- builder/hyperv/iso/builder.go | 20 +- builder/hyperv/iso/builder_test.go | 2 +- builder/hyperv/vmcx/builder.go | 45 ++++- builder/hyperv/vmcx/builder_test.go | 85 +++++---- command/plugin.go | 4 + common/powershell/hyperv/hyperv.go | 172 +++++++++++++----- .../source/docs/builders/hyperv-iso.html.md | 21 ++- .../source/docs/builders/hyperv-vmcx.html.md | 30 ++- 12 files changed, 309 insertions(+), 128 deletions(-) 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,42 +56,18 @@ func TestBuilderPrepare_Defaults(t *testing.T) { } } -func TestBuilderPrepare_DiskSize(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) - } - if err != nil { - t.Fatalf("should not have error: %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() + //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 + // Add a random key config["i_should_not_be_valid"] = true warns, err := b.Prepare(config) @@ -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) + +$WorkingPath = Join-Path $exportPath $vmName + +if (Test-Path $WorkingPath) { + throw "Export path working directory: $WorkingPath already exists!" } -func CloneVirtualMachine(cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, ram int64, switchName string) error { +$allSnapshots = [System.Boolean]::Parse($allSnapshotsString) - var script = ` -param([string]$CloneFromVMName, [string]$CloneFromSnapshotName, [string]$CloneAllSnapshotsString, [string]$vmName, [string]$path, [long]$memoryStartupBytes, [string]$switchName) - -$CloneAllSnapshots = [System.Boolean]::Parse($CloneAllSnapshotsString) - -$ExportPath = Join-Path $path $VMName - -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 + ` -$VirtualMachinePath = Get-ChildItem -Path (Join-Path $ExportPath 'Virtual Machines') -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} -} -if (!$VirtualMachinePath){ - $VirtualMachinePath = Get-ChildItem -Path $ExportPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} + allSnapshotsString := "False" + if allSnapshots { + allSnapshotsString = "True" + } + + var ps powershell.PowerShellCmd + err := ps.Run(script, exportPath, vmName, snapshotName, allSnapshotsString) + + return err } -$compatibilityReport = Compare-VM -Path $VirtualMachinePath -VirtualMachinePath $ExportPath -SmartPagingFilePath $ExportPath -SnapshotFilePath $ExportPath -VhdDestinationPath (Join-Path -Path $ExportPath -ChildPath 'Virtual Hard Disks') -GenerateNewId -Copy:$false +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 +} + +$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 $VirtualMachinesPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} +} +if (!$VirtualMachinePath){ + $VirtualMachinePath = Get-ChildItem -Path $importPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName} +} + +$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, cloneFromVmName, cloneFromSnapshotName, CloneAllSnapshotsString, vmName, path, strconv.FormatInt(ram, 10), switchName) + 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 + } + } + + 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. From 3d0ac529e03edab4e7b3224e38e4125e5bb230b6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 29 May 2017 01:51:50 +0100 Subject: [PATCH 05/17] use common floppy_config instead of builder specific one Add tests for floppy files and comm --- builder/hyperv/iso/builder_test.go | 108 +++++++++++++++++++++ builder/hyperv/vmcx/builder.go | 2 +- builder/hyperv/vmcx/builder_test.go | 143 ++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 1c655f475..d32ba4f41 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/hashicorp/packer/packer" + "fmt" ) func testConfig() map[string]interface{} { @@ -298,3 +299,110 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { t.Fatalf("bad: %#v", b.config.ISOUrls) } } + +func TestBuilderPrepare_FloppyFiles(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "floppy_files") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if len(b.config.FloppyFiles) != 0 { + t.Fatalf("bad: %#v", b.config.FloppyFiles) + } + + floppies_path := "../../../common/test-fixtures/floppies" + config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} + if !reflect.DeepEqual(b.config.FloppyFiles, expected) { + t.Fatalf("bad: %#v", b.config.FloppyFiles) + } +} + +func TestBuilderPrepare_InvalidFloppies(t *testing.T) { + var b Builder + config := testConfig() + config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} + b = Builder{} + _, errs := b.Prepare(config) + if errs == nil { + t.Fatalf("Nonexistent floppies should trigger multierror") + } + + if len(errs.(*packer.MultiError).Errors) != 2 { + t.Fatalf("Multierror should work and report 2 errors") + } +} + +func TestBuilderPrepare_CommConfig(t *testing.T) { + // Test Winrm + { + config := testConfig() + config["communicator"] = "winrm" + config["winrm_username"] = "username" + config["winrm_password"] = "password" + config["winrm_host"] = "1.2.3.4" + + var b Builder + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.Comm.WinRMUser != "username" { + t.Errorf("bad winrm_username: %s", b.config.Comm.WinRMUser) + } + if b.config.Comm.WinRMPassword != "password" { + t.Errorf("bad winrm_password: %s", b.config.Comm.WinRMPassword) + } + if host := b.config.Comm.Host(); host != "1.2.3.4" { + t.Errorf("bad host: %s", host) + } + } + + // Test SSH + { + config := testConfig() + config["communicator"] = "ssh" + config["ssh_username"] = "username" + config["ssh_password"] = "password" + config["ssh_host"] = "1.2.3.4" + + var b Builder + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.Comm.SSHUsername != "username" { + t.Errorf("bad ssh_username: %s", b.config.Comm.SSHUsername) + } + if b.config.Comm.SSHPassword != "password" { + t.Errorf("bad ssh_password: %s", b.config.Comm.SSHPassword) + } + if host := b.config.Comm.Host(); host != "1.2.3.4" { + t.Errorf("bad host: %s", host) + } + } +} diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index b75667742..3236fa945 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -42,7 +42,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` - hypervcommon.FloppyConfig `mapstructure:",squash"` + common.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` hypervcommon.RunConfig `mapstructure:",squash"` diff --git a/builder/hyperv/vmcx/builder_test.go b/builder/hyperv/vmcx/builder_test.go index f0b7736bd..3dd5cc4ab 100644 --- a/builder/hyperv/vmcx/builder_test.go +++ b/builder/hyperv/vmcx/builder_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/packer/packer" "io/ioutil" "os" + "fmt" ) func testConfig() map[string]interface{} { @@ -259,3 +260,145 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { t.Fatalf("bad: %#v", b.config.ISOUrls) } } + +func TestBuilderPrepare_FloppyFiles(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, "floppy_files") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if len(b.config.FloppyFiles) != 0 { + t.Fatalf("bad: %#v", b.config.FloppyFiles) + } + + floppies_path := "../../../common/test-fixtures/floppies" + config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} + if !reflect.DeepEqual(b.config.FloppyFiles, expected) { + t.Fatalf("bad: %#v", b.config.FloppyFiles) + } +} + +func TestBuilderPrepare_InvalidFloppies(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 + + config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} + b = Builder{} + _, errs := b.Prepare(config) + if errs == nil { + t.Fatalf("Nonexistent floppies should trigger multierror") + } + + if len(errs.(*packer.MultiError).Errors) != 2 { + t.Fatalf("Multierror should work and report 2 errors") + } +} + +func TestBuilderPrepare_CommConfig(t *testing.T) { + // Test Winrm + { + 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 + + config["communicator"] = "winrm" + config["winrm_username"] = "username" + config["winrm_password"] = "password" + config["winrm_host"] = "1.2.3.4" + + var b Builder + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.Comm.WinRMUser != "username" { + t.Errorf("bad winrm_username: %s", b.config.Comm.WinRMUser) + } + if b.config.Comm.WinRMPassword != "password" { + t.Errorf("bad winrm_password: %s", b.config.Comm.WinRMPassword) + } + if host := b.config.Comm.Host(); host != "1.2.3.4" { + t.Errorf("bad host: %s", host) + } + } + + // Test SSH + { + 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 + + config["communicator"] = "ssh" + config["ssh_username"] = "username" + config["ssh_password"] = "password" + config["ssh_host"] = "1.2.3.4" + + var b Builder + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.Comm.SSHUsername != "username" { + t.Errorf("bad ssh_username: %s", b.config.Comm.SSHUsername) + } + if b.config.Comm.SSHPassword != "password" { + t.Errorf("bad ssh_password: %s", b.config.Comm.SSHPassword) + } + if host := b.config.Comm.Host(); host != "1.2.3.4" { + t.Errorf("bad host: %s", host) + } + } +} From 823275939767d2ec32a50206430ca506334389d6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 29 May 2017 02:36:01 +0100 Subject: [PATCH 06/17] If vhd or vhdx extension is specified for ISOUrls, we want to use an existing hard drive which means that we don't need to specify hard drive size Filepath.ext includes the dot --- builder/hyperv/common/step_clone_vm.go | 2 +- builder/hyperv/common/step_create_vm.go | 2 +- builder/hyperv/iso/builder.go | 11 +-- builder/hyperv/iso/builder_test.go | 92 +++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go index 9b005422d..f867bd331 100644 --- a/builder/hyperv/common/step_clone_vm.go +++ b/builder/hyperv/common/step_clone_vm.go @@ -40,7 +40,7 @@ func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { harddrivePath := "" if harddrivePathRaw, ok := state.GetOk("iso_path"); ok { extension := strings.ToLower(filepath.Ext(harddrivePathRaw.(string))) - if extension == "vhd" || extension == "vhdx" { + if extension == ".vhd" || extension == ".vhdx" { harddrivePath = harddrivePathRaw.(string) } else { log.Println("No existing virtual harddrive, not attaching.") diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 46c4964cb..4b9601842 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -39,7 +39,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { harddrivePath := "" if harddrivePathRaw, ok := state.GetOk("iso_path"); ok { extension := strings.ToLower(filepath.Ext(harddrivePathRaw.(string))) - if extension == "vhd" || extension == "vhdx" { + if extension == ".vhd" || extension == ".vhdx" { harddrivePath = harddrivePathRaw.(string) } else { log.Println("No existing virtual harddrive, not attaching.") diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 382c35394..4d5583239 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -117,22 +117,15 @@ 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...) - - 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)...) - if b.config.ISOConfig.TargetExtension != "vhd" && b.config.ISOConfig.TargetExtension != "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 { diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index d32ba4f41..0fea9b8e0 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -300,6 +300,98 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { } } +func TestBuilderPrepare_SizeNotRequiredWhenUsingExistingHarddrive(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + delete(config, "disk_size") + + config["disk_size"] = 1 + + // Test just iso_urls set but with vhdx + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io/hdd.vhdx", + "http://www.hashicorp.com/dvd.iso", + } + + b = Builder{} + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected := []string{ + "http://www.packer.io/hdd.vhdx", + "http://www.hashicorp.com/dvd.iso", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test just iso_urls set but with vhd + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io/hdd.vhd", + "http://www.hashicorp.com/dvd.iso", + } + + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io/hdd.vhd", + "http://www.hashicorp.com/dvd.iso", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} + +func TestBuilderPrepare_SizeIsRequiredWhenNotUsingExistingHarddrive(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + delete(config, "disk_size") + + config["disk_size"] = 1 + + // Test just iso_urls set but with vhdx + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io/os.iso", + "http://www.hashicorp.com/dvd.iso", + } + + b = Builder{} + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Errorf("should have error") + } + + expected := []string{ + "http://www.packer.io/os.iso", + "http://www.hashicorp.com/dvd.iso", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} + func TestBuilderPrepare_FloppyFiles(t *testing.T) { var b Builder config := testConfig() From 628116f4c4d493a4164ac918f8fd3fa9fa43ca8f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 29 May 2017 03:12:35 +0100 Subject: [PATCH 07/17] Test settings for clone from vm and import vmxc from path --- builder/hyperv/vmcx/builder.go | 6 --- builder/hyperv/vmcx/builder_test.go | 84 +++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 3236fa945..c84e9e2a3 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" "github.com/mitchellh/multistep" - "path/filepath" ) const ( @@ -126,11 +125,6 @@ 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)...) diff --git a/builder/hyperv/vmcx/builder_test.go b/builder/hyperv/vmcx/builder_test.go index 3dd5cc4ab..43b3d339f 100644 --- a/builder/hyperv/vmcx/builder_test.go +++ b/builder/hyperv/vmcx/builder_test.go @@ -80,6 +80,90 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { } } +func TestBuilderPrepare_CloneFromExistingMachineOrImportFromExportedMachineSettingsRequired(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "clone_from_vmxc_path") + + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ExportedMachinePathDoesNotExist(t *testing.T) { + var b Builder + config := testConfig() + + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + + //Delete the folder immediately + os.RemoveAll(td) + + config["clone_from_vmxc_path"] = td + + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) { + var b Builder + config := testConfig() + + //Create vmxc folder + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + + //Only delete afterwards + defer os.RemoveAll(td) + + config["clone_from_vmxc_path"] = td + + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + +func TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmxcPathRequired(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "clone_from_vmxc_path") + + config["clone_from_vm_name"] = "test_machine_name_that_does_not_exist" + + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + + if err == nil { + 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." { + t.Fatalf("should not have error: %s", err) + } + } +} + func TestBuilderPrepare_ISOChecksum(t *testing.T) { var b Builder config := testConfig() From 2fbe0b4a7f9d2da24b5ded3b1127a7f1e57a0652 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 30 May 2017 01:16:03 +0100 Subject: [PATCH 08/17] Don't try to mount vhd and vhdx files as dvd drive. Hard drives are mounted in the create vm step --- builder/hyperv/common/step_clone_vm.go | 6 +++--- builder/hyperv/common/step_create_vm.go | 6 +++--- builder/hyperv/common/step_mount_dvddrive.go | 10 +++++++++- builder/hyperv/iso/builder.go | 8 ++++---- builder/hyperv/iso/builder_test.go | 2 +- builder/hyperv/vmcx/builder.go | 8 ++++---- builder/hyperv/vmcx/builder_test.go | 6 +++--- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go index f867bd331..4a5b55566 100644 --- a/builder/hyperv/common/step_clone_vm.go +++ b/builder/hyperv/common/step_clone_vm.go @@ -3,11 +3,11 @@ package common import ( "fmt" "log" - "strings" "path/filepath" + "strings" - "github.com/mitchellh/multistep" "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" ) // This step clones an existing virtual machine. @@ -35,7 +35,7 @@ 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 { diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 4b9601842..f745514e1 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -3,9 +3,9 @@ package common import ( "fmt" "log" - "strings" "path/filepath" - + "strings" + "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" ) @@ -34,7 +34,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Creating 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 { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index d91be3a88..1535e86b4 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -2,9 +2,11 @@ package common import ( "fmt" - "log" "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" + "log" + "path/filepath" + "strings" ) type StepMountDvdDrive struct { @@ -27,6 +29,12 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + // Determine if its a virtual hdd to mount + if strings.ToLower(filepath.Ext(isoPath)) == ".vhd" || strings.ToLower(filepath.Ext(isoPath)) == ".vhdx" { + log.Println("Its a hard disk, not attaching.") + return multistep.ActionContinue + } + // should be able to mount up to 60 additional iso images using SCSI // but Windows would only allow a max of 22 due to available drive letters // Will Windows assign DVD drives to A: and B: ? diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 4d5583239..2cd786a42 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -5,8 +5,8 @@ import ( "fmt" "log" "os" - "strings" "path/filepath" + "strings" hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" "github.com/hashicorp/packer/common" @@ -117,7 +117,7 @@ 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...) - + 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)...) @@ -125,7 +125,7 @@ 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 { @@ -152,7 +152,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Cpu = 1 } - if b.config.Generation != 2 { + if b.config.Generation < 1 || b.config.Generation > 2 { b.config.Generation = 1 } diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 0fea9b8e0..56a4e9d7a 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" - "github.com/hashicorp/packer/packer" "fmt" + "github.com/hashicorp/packer/packer" ) func testConfig() map[string]interface{} { diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index c84e9e2a3..abf93d3c8 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -71,7 +71,7 @@ type Config struct { // 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"` @@ -195,7 +195,7 @@ 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.")) @@ -209,7 +209,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if b.config.Generation != 1 || b.config.Generation != 2 { + if b.config.Generation < 1 || b.config.Generation > 2 { b.config.Generation = 1 } @@ -395,7 +395,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, + 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 43b3d339f..209094bf3 100644 --- a/builder/hyperv/vmcx/builder_test.go +++ b/builder/hyperv/vmcx/builder_test.go @@ -4,10 +4,10 @@ import ( "reflect" "testing" + "fmt" "github.com/hashicorp/packer/packer" "io/ioutil" "os" - "fmt" ) func testConfig() map[string]interface{} { @@ -19,7 +19,7 @@ func testConfig() map[string]interface{} { "ssh_username": "foo", "ram_size": 64, "guest_additions_mode": "none", - "clone_from_vmxc_path": "generated", + "clone_from_vmxc_path": "generated", packer.BuildNameConfigKey: "foo", } } @@ -142,7 +142,7 @@ func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) { } } -func TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmxcPathRequired(t *testing.T) { +func disabled_TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmxcPathRequired(t *testing.T) { var b Builder config := testConfig() delete(config, "clone_from_vmxc_path") From 5f2c71f7d7da22a8c548a3d61a7a3b761e74d216 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 19 Jun 2017 21:22:55 +0100 Subject: [PATCH 09/17] Floppy directories are provided by default with common.floppydrives --- builder/hyperv/iso/builder.go | 1 + builder/hyperv/vmcx/builder.go | 14 +-- .../source/docs/builders/hyperv-iso.html.md | 4 +- .../source/docs/builders/hyperv-vmcx.html.md | 112 ++++++++++-------- 4 files changed, 66 insertions(+), 65 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 2cd786a42..7151ca38b 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -325,6 +325,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyConfig.FloppyFiles, Directories: b.config.FloppyConfig.FloppyDirectories, + Directories: b.config.FloppyConfig.FloppyDirectories, }, &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index abf93d3c8..656a3fa13 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -50,16 +50,7 @@ type Config struct { // The size, in megabytes, of the computer memory in the VM. // By default, this is 1024 (about 1 GB). RamSize uint `mapstructure:"ram_size"` - // A list of files to place onto a floppy disk that is attached when the - // VM is booted. This is most useful for unattended Windows installs, - // which look for an Autounattend.xml file on removable media. By default, - // no floppy will be attached. All files listed in this setting get - // placed into the root directory of the floppy and the floppy is attached - // as the first floppy device. Currently, no support exists for creating - // sub-directories on the floppy. Wildcard characters (*, ?, and []) - // are allowed. Directory names are also allowed, which will add all - // the files found in the directory to the floppy. - FloppyFiles []string `mapstructure:"floppy_files"` + // SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` @@ -214,7 +205,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.Generation == 2 { - if len(b.config.FloppyFiles) > 0 { + if len(b.config.FloppyFiles) > 0 || len(b.config.FloppyDirectories) > 0 { err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") errs = packer.MultiErrorAppend(errs, err) } @@ -385,6 +376,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps = append(steps, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, + Directories: b.config.FloppyConfig.FloppyDirectories, }, &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, diff --git a/website/source/docs/builders/hyperv-iso.html.md b/website/source/docs/builders/hyperv-iso.html.md index f762cf3cc..afbc51cb3 100644 --- a/website/source/docs/builders/hyperv-iso.html.md +++ b/website/source/docs/builders/hyperv-iso.html.md @@ -63,7 +63,7 @@ can be configured for this builder. `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or "sha512" currently. While "none" will skip checksumming, this is not recommended since ISO files and virtual harddrive files are generally large - and corruption does happen from time to time. + and corruption does happen from time to time. - `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 @@ -114,7 +114,7 @@ can be configured for this builder. characters (`*`, `?`, and `[]`) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -- `floppy_dirs` (array of strings) - A list of directories to place onto +- `floppy_dirs` (array of strings) - A list of directories to place onto the floppy disk recursively. This is similar to the `floppy_files` option except that the directory structure is preserved. This is useful for when your floppy disk includes drivers or if you just want to organize it's diff --git a/website/source/docs/builders/hyperv-vmcx.html.md b/website/source/docs/builders/hyperv-vmcx.html.md index 0277e18da..0f24d53b2 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md +++ b/website/source/docs/builders/hyperv-vmcx.html.md @@ -61,49 +61,49 @@ In addition to the options listed here, a can be configured for this builder. ### Required for virtual machine import: -- `clone_from_vmxc_path` (string) - The path to the exported +- `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. +- `clone_from_vm_name` (string) - The name of the vm to clone from. Ideally the machine to clone from should be shutdown. ### Optional: -- `clone_from_snapshot_name` (string) - The name of the snapshot +- `clone_from_snapshot_name` (string) - The name of the snapshot -- `clone_all_snapshots` (boolean) - Should all snapshots be cloned +- `clone_all_snapshots` (boolean) - Should all snapshots be cloned when the machine is cloned. -- `boot_command` (array of strings) - This is an array of commands to type +- `boot_command` (array of strings) - This is an array of commands to type when the virtual machine is first booted. The goal of these commands should be to type just enough to initialize the operating system installer. Special keys can be typed as well, and are covered in the section below on the boot command. If this is not specified, it is assumed the installer will start itself. -- `boot_wait` (string) - The time to wait after booting the initial virtual +- `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be a duration. Examples are "5s" and "1m30s" which will cause Packer to wait five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -- `cpu` (integer) - The number of cpus the virtual machine should use. If this isn't specified, +- `cpu` (integer) - The number of cpus the virtual machine should use. If this isn't specified, the default is 1 cpu. -- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. +- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. This defaults to false. -- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. +- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. This defaults to false. -- `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. +- `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. This defaults to false. -- `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. +- `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory and have at least 4GB of RAM for virtual machine. -- `floppy_files` (array of strings) - A list of files to place onto a floppy +- `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file on removable media. By default, no floppy will be attached. All files @@ -113,13 +113,21 @@ can be configured for this builder. characters (*, ?, and []) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -- `guest_additions_mode` (string) - How should guest additions be installed. +- `floppy_dirs` (array of strings) - A list of directories to place onto + the floppy disk recursively. This is similar to the `floppy_files` option + except that the directory structure is preserved. This is useful for when + your floppy disk includes drivers or if you just want to organize it's + contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. + The maximum summary size of all files in the listed directories are the + same as in `floppy_files`. + +- `guest_additions_mode` (string) - How should guest additions be installed. If value `attach` then attach iso image with by specified by `guest_additions_path`. Otherwise guest additions is not installed. -- `guest_additions_path` (string) - The path to the iso image for guest additions. +- `guest_additions_path` (string) - The path to the iso image for guest additions. -- `http_directory` (string) - Path to a directory to serve using an HTTP +- `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting kickstart files and so on. By default this is "", which means no HTTP @@ -127,50 +135,50 @@ can be configured for this builder. available as variables in `boot_command`. This is covered in more detail below. -- `http_port_min` and `http_port_max` (integer) - These are the minimum and +- `http_port_min` and `http_port_max` (integer) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP server to be on one port, make this minimum and maximum port the same. By default the values are 8000 and 9000, respectively. -- `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO +- `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_type` (string) - The type of the checksum specified in +- `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. -- `iso_url` (string) - A URL to the ISO containing the installation image. +- `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_urls` (array of strings) - Multiple URLs for the ISO to download. +- `iso_urls` (array of strings) - Multiple URLs for the ISO to download. Packer will try these in order. If anything goes wrong attempting to download or while downloading a single URL, it will move on to the next. All URLs must point to the same file (same checksum). By default this is empty and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. -- `iso_target_extension` (string) - The extension of the iso file after +- `iso_target_extension` (string) - The extension of the iso file after download. This defaults to "iso". -- `iso_target_path` (string) - The path where the iso should be saved after +- `iso_target_path` (string) - The path where the iso should be saved after download. By default will go in the packer cache, with a hash of the original filename as its name. -- `output_directory` (string) - This is the path to the directory where the +- `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. -- `ram_size` (integer) - The size, in megabytes, of the ram to create +- `ram_size` (integer) - The size, in megabytes, of the ram to create for the VM. By default, this is 1 GB. * `secondary_iso_images` (array of strings) - A list of iso paths to attached to a @@ -178,33 +186,33 @@ can be configured for this builder. look for an `Autounattend.xml` file on removable media. By default, no secondary iso will be attached. -- `shutdown_command` (string) - The command to use to gracefully shut down the machine once all +- `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty string, which tells Packer to just forcefully shut down the machine unless a shutdown command takes place inside script so this may safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank since reboots may fail and specify the final shutdown command in your last script. -- `shutdown_timeout` (string) - The amount of time to wait after executing +- `shutdown_timeout` (string) - The amount of time to wait after executing the `shutdown_command` for the virtual machine to actually shut down. If it doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. -- `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when +- `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when exporting. This defaults to false. -- `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting +- `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting this to an empty string, Packer will try to determine the switch to use by looking for external switch that is up and running. -- `switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. +- switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. By default none is set. If none is set then a vlan is not set on the switch's network card. If this value is set it should match the vlan specified in by `vlan_id`. -- `vlan_id` (string) - This is the vlan of the virtual machine's network card for the new virtual +- `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. -- `vm_name` (string) - This is the name of the virtual machine for the new virtual +- `vm_name` (string) - This is the name of the virtual machine for the new virtual machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. @@ -224,47 +232,47 @@ to the machine, simulating a human actually typing the keyboard. There are a set of special keys available. If these are in your boot command, they will be replaced by the proper key: -- `<bs>` - Backspace +- `<bs>` - Backspace -- `<del>` - Delete +- `<del>` - Delete -- `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. +- `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. -- `<esc>` - Simulates pressing the escape key. +- `<esc>` - Simulates pressing the escape key. -- `<tab>` - Simulates pressing the tab key. +- `<tab>` - Simulates pressing the tab key. -- `<f1>` - `<f12>` - Simulates pressing a function key. +- `<f1>` - `<f12>` - Simulates pressing a function key. -- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. +- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. -- `<spacebar>` - Simulates pressing the spacebar. +- `<spacebar>` - Simulates pressing the spacebar. -- `<insert>` - Simulates pressing the insert key. +- `<insert>` - Simulates pressing the insert key. -- `<home>` `<end>` - Simulates pressing the home and end keys. +- `<home>` `<end>` - Simulates pressing the home and end keys. -- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. +- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. -- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key. +- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key. -- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key. +- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key. -- `<leftShift>` `<rightShift>` - Simulates pressing the shift key. +- `<leftShift>` `<rightShift>` - Simulates pressing the shift key. -- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key. +- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key. -- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl key. +- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl key. -- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the shift key. +- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the shift key. -- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key. +- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key. -- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key. +- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key. -- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift key. +- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift key. -- `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before +- `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This is useful if you have to generally wait for the UI to update before typing more. From 7978fd8ec0ddbb9e9970f841d9ccf429195a45da Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:11:45 -0700 Subject: [PATCH 10/17] make fmt --- builder/hyperv/vmcx/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 656a3fa13..9eb88dca7 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -375,7 +375,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps = append(steps, &common.StepCreateFloppy{ - Files: b.config.FloppyFiles, + Files: b.config.FloppyFiles, Directories: b.config.FloppyConfig.FloppyDirectories, }, &common.StepHTTPServer{ From fcfdff0efdc3f0e26ba0cb4367ec96e63a3b3348 Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:15:43 -0700 Subject: [PATCH 11/17] rerun scripts/generate-plugins.go --- command/plugin.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index 6524f47b4..d9e7ea577 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -26,12 +26,9 @@ import ( filebuilder "github.com/hashicorp/packer/builder/file" googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute" hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso" -<<<<<<< HEAD + hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx" 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" @@ -96,12 +93,9 @@ var Builders = map[string]packer.Builder{ "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), "hyperv-iso": new(hypervisobuilder.Builder), -<<<<<<< HEAD + "hyperv-vmcx": new(hypervvmcxbuilder.Builder), "lxc": new(lxcbuilder.Builder), "lxd": new(lxdbuilder.Builder), -======= - "hyperv-vmcx": new(hypervvmcxbuilder.Builder), ->>>>>>> Only attach dvd drive if there is one "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder), From d8c6e6d4a4f1e6ead3f67f495a628208e27c309c Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:19:14 -0700 Subject: [PATCH 12/17] remove duplicate line --- builder/hyperv/iso/builder.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 7151ca38b..2cd786a42 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -325,7 +325,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyConfig.FloppyFiles, Directories: b.config.FloppyConfig.FloppyDirectories, - Directories: b.config.FloppyConfig.FloppyDirectories, }, &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, From 5937f75898f27e4031cb9532fbe364c7032b50d3 Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:36:05 -0700 Subject: [PATCH 13/17] reformat docs --- .../source/docs/builders/hyperv-vmcx.html.md | 268 +++++++++--------- website/source/layouts/docs.erb | 3 + 2 files changed, 143 insertions(+), 128 deletions(-) diff --git a/website/source/docs/builders/hyperv-vmcx.html.md b/website/source/docs/builders/hyperv-vmcx.html.md index 0f24d53b2..f5074b358 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md +++ b/website/source/docs/builders/hyperv-vmcx.html.md @@ -2,6 +2,7 @@ description: |- The Hyper-V Packer builder is able to clone an existing Hyper-V virtual machine and export them. layout: "docs" +sidebar_current: 'docs-builders-hyperv-vmcx' page_title: "Hyper-V Builder (from an vmcx)" --- @@ -25,10 +26,11 @@ 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 + +```json { "type": "hyperv-vmcx", - "clone_from_vmxc_path": "c:\virtual machines\ubuntu-12.04.5-server-amd64", + "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" @@ -36,13 +38,14 @@ Import from folder: ``` Clone from existing virtual machine: -```javascript + +```json { - "type": "hyperv-vmcx", - "clone_from_vm_name": "ubuntu-12.04.5-server-amd64", - "ssh_username": "packer", - "ssh_password": "packer", - "shutdown_command": "echo 'packer' | sudo -S shutdown -P now" + "clone_from_vm_name": "ubuntu-12.04.5-server-amd64", + "shutdown_command": "echo 'packer' | sudo -S shutdown -P now", + "ssh_password": "packer", + "ssh_username": "packer", + "type": "hyperv-vmcx" } ``` @@ -61,73 +64,78 @@ In addition to the options listed here, a can be configured for this builder. ### Required for virtual machine import: -- `clone_from_vmxc_path` (string) - The path to the exported - virtual machine folder. + +- `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. + +- `clone_from_vm_name` (string) - The name of the vm to clone from. Ideally + the machine to clone from should be shutdown. ### Optional: -- `clone_from_snapshot_name` (string) - The name of the snapshot -- `clone_all_snapshots` (boolean) - Should all snapshots be cloned - when the machine is cloned. +- `clone_from_snapshot_name` (string) - The name of the snapshot -- `boot_command` (array of strings) - This is an array of commands to type +- `clone_all_snapshots` (boolean) - Should all snapshots be cloned when the + machine is cloned. + +- `boot_command` (array of strings) - This is an array of commands to type when the virtual machine is first booted. The goal of these commands should - be to type just enough to initialize the operating system installer. Special - keys can be typed as well, and are covered in the section below on the boot - command. If this is not specified, it is assumed the installer will start - itself. + be to type just enough to initialize the operating system installer. + Special keys can be typed as well, and are covered in the section below on + the boot command. If this is not specified, it is assumed the installer + will start itself. -- `boot_wait` (string) - The time to wait after booting the initial virtual +- `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be a duration. Examples are "5s" and "1m30s" which will cause Packer to wait - five seconds and one minute 30 seconds, respectively. If this isn't specified, - the default is 10 seconds. + five seconds and one minute 30 seconds, respectively. If this isn't + specified, the default is 10 seconds. -- `cpu` (integer) - The number of cpus the virtual machine should use. If this isn't specified, - the default is 1 cpu. +- `cpu` (integer) - The number of cpus the virtual machine should use. If + this isn't specified, the default is 1 cpu. -- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. - This defaults to false. +- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual + machine. This defaults to false. -- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. - This defaults to false. +- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual + machine. This defaults to false. -- `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. - This defaults to false. +- `enable_secure_boot` (bool) - If true enable secure boot for virtual + machine. This defaults to false. -- `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. - This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory - and have at least 4GB of RAM for virtual machine. +- `enable_virtualization_extensions` (bool) - If true enable virtualization + extensions for virtual machine. This defaults to false. For nested + virtualization you need to enable mac spoofing, disable dynamic memory and + have at least 4GB of RAM for virtual machine. -- `floppy_files` (array of strings) - A list of files to place onto a floppy - disk that is attached when the VM is booted. This is most useful - for unattended Windows installs, which look for an `Autounattend.xml` file - on removable media. By default, no floppy will be attached. All files - listed in this setting get placed into the root directory of the floppy - and the floppy is attached as the first floppy device. Currently, no - support exists for creating sub-directories on the floppy. Wildcard - characters (*, ?, and []) are allowed. Directory names are also allowed, - which will add all the files found in the directory to the floppy. +- `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful for + unattended Windows installs, which look for an `Autounattend.xml` file on + removable media. By default, no floppy will be attached. All files listed + in this setting get placed into the root directory of the floppy and the + floppy is attached as the first floppy device. Currently, no support exists + for creating sub-directories on the floppy. Wildcard characters (*, ?, and + []) are allowed. Directory names are also allowed, which will add all the + files found in the directory to the floppy. -- `floppy_dirs` (array of strings) - A list of directories to place onto - the floppy disk recursively. This is similar to the `floppy_files` option +- `floppy_dirs` (array of strings) - A list of directories to place onto the + floppy disk recursively. This is similar to the `floppy_files` option except that the directory structure is preserved. This is useful for when your floppy disk includes drivers or if you just want to organize it's contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. The maximum summary size of all files in the listed directories are the same as in `floppy_files`. -- `guest_additions_mode` (string) - How should guest additions be installed. - If value `attach` then attach iso image with by specified by `guest_additions_path`. - Otherwise guest additions is not installed. +- `guest_additions_mode` (string) - How should guest additions be installed. + If value `attach` then attach iso image with by specified by + `guest_additions_path`. Otherwise guest additions is not installed. -- `guest_additions_path` (string) - The path to the iso image for guest additions. +- `guest_additions_path` (string) - The path to the iso image for guest + additions. -- `http_directory` (string) - Path to a directory to serve using an HTTP +- `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting kickstart files and so on. By default this is "", which means no HTTP @@ -135,86 +143,90 @@ can be configured for this builder. available as variables in `boot_command`. This is covered in more detail below. -- `http_port_min` and `http_port_max` (integer) - These are the minimum and - maximum port to use for the HTTP server started to serve the `http_directory`. - Because Packer often runs in parallel, Packer will choose a randomly available - port in this range to run the HTTP server. If you want to force the HTTP - server to be on one port, make this minimum and maximum port the same. - By default the values are 8000 and 9000, respectively. +- `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the + `http_directory`. Because Packer often runs in parallel, Packer will choose + a randomly available port in this range to run the HTTP server. If you want + to force the HTTP server to be on one port, make this minimum and maximum + port the same. By default the values are 8000 and 9000, respectively. -- `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. 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_type` (string) - The type of the checksum specified in +- `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. -- `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. + 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_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to download - or while downloading a single URL, it will move on to the next. All URLs - must point to the same file (same checksum). By default this is empty - and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. +- `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to + download or while downloading a single URL, it will move on to the next. + All URLs must point to the same file (same checksum). By default this is + empty and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be + specified. -- `iso_target_extension` (string) - The extension of the iso file after +- `iso_target_extension` (string) - The extension of the iso file after download. This defaults to "iso". -- `iso_target_path` (string) - The path where the iso should be saved after +- `iso_target_path` (string) - The path where the iso should be saved after download. By default will go in the packer cache, with a hash of the original filename as its name. -- `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 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. -- `ram_size` (integer) - The size, in megabytes, of the ram to create - for the VM. By default, this is 1 GB. +- `ram_size` (integer) - The size, in megabytes, of the ram to create for the + VM. By default, this is 1 GB. -* `secondary_iso_images` (array of strings) - A list of iso paths to attached to a - VM when it is booted. This is most useful for unattended Windows installs, which - look for an `Autounattend.xml` file on removable media. By default, no - secondary iso will be attached. +* `secondary_iso_images` (array of strings) - A list of iso paths to attached + to a VM when it is booted. This is most useful for unattended Windows + installs, which look for an `Autounattend.xml` file on removable media. By + default, no secondary iso will be attached. -- `shutdown_command` (string) - The command to use to gracefully shut down the machine once all - the provisioning is done. By default this is an empty string, which tells Packer to just - forcefully shut down the machine unless a shutdown command takes place inside script so this may - safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank - since reboots may fail and specify the final shutdown command in your last script. +- `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. By default this is an empty + string, which tells Packer to just forcefully shut down the machine unless + a shutdown command takes place inside script so this may safely be omitted. + If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last + script. -- `shutdown_timeout` (string) - The amount of time to wait after executing - the `shutdown_command` for the virtual machine to actually shut down. - If it doesn't shut down in this time, it is an error. By default, the timeout - is "5m", or five minutes. +- `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. If it + doesn't shut down in this time, it is an error. By default, the timeout is + "5m", or five minutes. -- `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when - exporting. This defaults to false. +- `skip_compaction` (bool) - If true skip compacting the hard disk for + virtual machine when exporting. This defaults to false. -- `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting - this to an empty string, Packer will try to determine the switch to use by looking for - external switch that is up and running. +- `switch_name` (string) - The name of the switch to connect the virtual + machine to. Be defaulting this to an empty string, Packer will try to + determine the switch to use by looking for external switch that is up and + running. -- switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. - By default none is set. If none is set then a vlan is not set on the switch's network card. - If this value is set it should match the vlan specified in by `vlan_id`. +- `switch_vlan_id` (string) - This is the vlan of the virtual switch's + network card. By default none is set. If none is set then a vlan is not set + on the switch's network card. If this value is set it should match the vlan + specified in by `vlan_id`. -- `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. +- `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. -- `vm_name` (string) - This is the name of the virtual machine for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", - where "BUILDNAME" is the name of the build. +- `vm_name` (string) - This is the name of the virtual machine for the new + virtual machine, without the file extension. By default this is + "packer-BUILDNAME", where "BUILDNAME" is the name of the build. ## Boot Command @@ -232,47 +244,47 @@ to the machine, simulating a human actually typing the keyboard. There are a set of special keys available. If these are in your boot command, they will be replaced by the proper key: -- `<bs>` - Backspace +- `<bs>` - Backspace -- `<del>` - Delete +- `<del>` - Delete -- `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. +- `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. -- `<esc>` - Simulates pressing the escape key. +- `<esc>` - Simulates pressing the escape key. -- `<tab>` - Simulates pressing the tab key. +- `<tab>` - Simulates pressing the tab key. -- `<f1>` - `<f12>` - Simulates pressing a function key. +- `<f1>` - `<f12>` - Simulates pressing a function key. -- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. +- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. -- `<spacebar>` - Simulates pressing the spacebar. +- `<spacebar>` - Simulates pressing the spacebar. -- `<insert>` - Simulates pressing the insert key. +- `<insert>` - Simulates pressing the insert key. -- `<home>` `<end>` - Simulates pressing the home and end keys. +- `<home>` `<end>` - Simulates pressing the home and end keys. -- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. +- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. -- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key. +- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key. -- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key. +- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key. -- `<leftShift>` `<rightShift>` - Simulates pressing the shift key. +- `<leftShift>` `<rightShift>` - Simulates pressing the shift key. -- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key. +- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key. -- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl key. +- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl key. -- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the shift key. +- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the shift key. -- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key. +- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key. -- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key. +- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key. -- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift key. +- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift key. -- `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before +- `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This is useful if you have to generally wait for the UI to update before typing more. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 1ceb2a276..1d5ddf4bb 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -111,6 +111,9 @@ <li<%= sidebar_current("docs-builders-hyperv-iso") %>> <a href="/docs/builders/hyperv-iso.html">ISO</a> </li> + <li<%= sidebar_current("docs-builders-hyperv-vmcx") %>> + <a href="/docs/builders/hyperv-vmcx.html">VMCX</a> + </li> </ul> </li> <li<%= sidebar_current("docs-builders-lxc") %>> From 91d66fb67c450f2a8fc90be22f2b4d02e04a3b41 Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:38:47 -0700 Subject: [PATCH 14/17] use new method of building runner --- builder/hyperv/vmcx/builder.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 9eb88dca7..a9583327a 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -471,17 +471,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ) // Run the steps. - if b.config.PackerDebug { - pauseFn := common.MultistepDebugFn(ui) - state.Put("pauseFn", pauseFn) - b.runner = &multistep.DebugRunner{ - Steps: steps, - PauseFn: pauseFn, - } - } else { - b.runner = &multistep.BasicRunner{Steps: steps} - } - + b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) b.runner.Run(state) // Report any errors. From 6e9d37485aaa6f15de42a3a6b912fcdada755fb4 Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:41:35 -0700 Subject: [PATCH 15/17] make it clear that VHDs work as well as ISOs --- website/source/docs/builders/hyperv-vmcx.html.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/website/source/docs/builders/hyperv-vmcx.html.md b/website/source/docs/builders/hyperv-vmcx.html.md index f5074b358..854710a23 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md +++ b/website/source/docs/builders/hyperv-vmcx.html.md @@ -161,13 +161,14 @@ can be configured for this builder. recommended since ISO 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 or VHD 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_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to - download or while downloading a single URL, it will move on to the next. +- `iso_urls` (array of strings) - Multiple URLs for the ISO or VHD to + download. Packer will try these in order. If anything goes wrong attempting + to download or while downloading a single URL, it will move on to the next. All URLs must point to the same file (same checksum). By default this is empty and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. From 2655cf74934efd2c7d74660500b08c923996de79 Mon Sep 17 00:00:00 2001 From: Matthew Hooker <mwhooker@gmail.com> Date: Mon, 11 Sep 2017 10:48:49 -0700 Subject: [PATCH 16/17] fix tests --- builder/hyperv/iso/builder_test.go | 50 ------------------------------ 1 file changed, 50 deletions(-) diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 56a4e9d7a..3fc17a8b7 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "fmt" "github.com/hashicorp/packer/packer" ) @@ -392,55 +391,6 @@ func TestBuilderPrepare_SizeIsRequiredWhenNotUsingExistingHarddrive(t *testing.T } } -func TestBuilderPrepare_FloppyFiles(t *testing.T) { - var b Builder - config := testConfig() - - delete(config, "floppy_files") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("bad err: %s", err) - } - - if len(b.config.FloppyFiles) != 0 { - t.Fatalf("bad: %#v", b.config.FloppyFiles) - } - - floppies_path := "../../../common/test-fixtures/floppies" - config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} - if !reflect.DeepEqual(b.config.FloppyFiles, expected) { - t.Fatalf("bad: %#v", b.config.FloppyFiles) - } -} - -func TestBuilderPrepare_InvalidFloppies(t *testing.T) { - var b Builder - config := testConfig() - config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} - b = Builder{} - _, errs := b.Prepare(config) - if errs == nil { - t.Fatalf("Nonexistent floppies should trigger multierror") - } - - if len(errs.(*packer.MultiError).Errors) != 2 { - t.Fatalf("Multierror should work and report 2 errors") - } -} - func TestBuilderPrepare_CommConfig(t *testing.T) { // Test Winrm { From 4f6a207441be69358b01c108437778dfcb80ae4a Mon Sep 17 00:00:00 2001 From: Vijaya Bhaskar Reddy Kondreddi <vijaya.reddy@ni.com> Date: Wed, 11 Oct 2017 22:10:39 +0530 Subject: [PATCH 17/17] go fmt --- builder/hyperv/common/step_create_vm.go | 4 ++-- common/powershell/hyperv/hyperv.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index f745514e1..d88de5558 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -47,8 +47,8 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { } else { log.Println("No existing virtual harddrive, not attaching.") } - - vhdPath := state.Get("packerVhdTempDir").(string) + + vhdPath := state.Get("packerVhdTempDir").(string) // convert the MB to bytes ramSize := int64(s.RamSize * 1024 * 1024) diskSize := int64(s.DiskSize * 1024 * 1024) diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index 4d6e78b65..0c649c450 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -218,7 +218,7 @@ if ($harddrivePath){ ` var ps powershell.PowerShellCmd err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) - + if err != nil { return err }