Merge pull request #5444 from vijayinvites/packer-vhdx

Hyperv vmcx builder and allow vhd/vhdx instead of ISO
This commit is contained in:
SwampDragons 2017-10-13 11:53:27 -07:00 committed by GitHub
commit f3c33240fa
16 changed files with 2713 additions and 31 deletions

View File

@ -64,10 +64,14 @@ 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, string, bool, string, string, string, int64, string) error
DeleteVirtualMachine(string) error
GetVirtualMachineGeneration(string) (uint, error)
SetVirtualMachineCpuCount(string, uint) error
SetVirtualMachineMacSpoofing(string, bool) error

View File

@ -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)
@ -166,8 +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(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 {

View File

@ -0,0 +1,139 @@
package common
import (
"fmt"
"log"
"path/filepath"
"strings"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
)
// This step clones an existing virtual machine.
//
// Produces:
// VMName string - The name of the VM
type StepCloneVM struct {
CloneFromVMXCPath string
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("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.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)
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))
}
}

View File

@ -2,6 +2,9 @@ package common
import (
"fmt"
"log"
"path/filepath"
"strings"
"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)

View File

@ -5,6 +5,8 @@ import (
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"log"
"path/filepath"
"strings"
)
type StepMountDvdDrive struct {
@ -17,7 +19,21 @@ 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
}
// 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

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
hypervcommon "github.com/hashicorp/packer/builder/hyperv/common"
@ -124,9 +125,12 @@ 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)...)
err = b.checkDiskSize()
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
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 {
errs = packer.MultiErrorAppend(errs, err)
}
}
err = b.checkRamSize()
@ -148,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
}
@ -163,6 +167,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"

View File

@ -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)
@ -298,3 +298,153 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
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_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)
}
}
}

View File

@ -0,0 +1,558 @@
package vmcx
import (
"errors"
"fmt"
"log"
"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"
)
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"`
common.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"`
//
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 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"`
// 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
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)
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)...)
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
}
b.config.Generation = 1
if b.config.CloneFromVMName == "" {
if b.config.CloneFromVMXCPath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vm_name must be specified if clone_from_vmxc_path is not specified."))
}
} 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.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 < 1 || b.config.Generation > 2 {
b.config.Generation = 1
}
if b.config.Generation == 2 {
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)
}
}
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."))
}
}
}
// 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,
},
}
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,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&hypervcommon.StepCreateSwitch{
SwitchName: b.config.SwitchName,
},
&hypervcommon.StepCloneVM{
CloneFromVMXCPath: b.config.CloneFromVMXCPath,
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.
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
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)
}

View File

@ -0,0 +1,488 @@
package vmcx
import (
"reflect"
"testing"
"fmt"
"github.com/hashicorp/packer/packer"
"io/ioutil"
"os"
)
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,
"guest_additions_mode": "none",
"clone_from_vmxc_path": "generated",
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()
//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)
}
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_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)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
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 disabled_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()
//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)
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()
//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)
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()
//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 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 not have an 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)
}
}
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)
}
}
}

View File

@ -26,6 +26,7 @@ import (
filebuilder "github.com/hashicorp/packer/builder/file"
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
lxdbuilder "github.com/hashicorp/packer/builder/lxd"
nullbuilder "github.com/hashicorp/packer/builder/null"
@ -92,6 +93,7 @@ var Builders = map[string]packer.Builder{
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
"lxc": new(lxcbuilder.Builder),
"lxd": new(lxdbuilder.Builder),
"null": new(nullbuilder.Builder),

View File

@ -187,27 +187,37 @@ 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,6 +244,178 @@ if ((Get-Command Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
return err
}
func ExportVmxcVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error {
var script = `
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!"
}
$allSnapshots = [System.Boolean]::Parse($allSnapshotsString)
if ($snapshotName) {
$snapshot = Get-VMSnapshot -VMName $vmName -Name $snapshotName
Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
} else {
if (!$allSnapshots) {
#Use last snapshot if one was not specified
$snapshot = Get-VMSnapshot -VMName $vmName | Select -Last 1
} else {
$snapshot = $null
}
if (!$snapshot) {
#No snapshot clone
Export-VM -Name $vmName -Path $exportPath -ErrorAction Stop
} else {
#Snapshot clone
Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
}
}
$result = Get-ChildItem -Path $WorkingPath | Move-Item -Destination $exportPath -Force
$result = Remove-Item -Path $WorkingPath
`
allSnapshotsString := "False"
if allSnapshots {
allSnapshotsString = "True"
}
var ps powershell.PowerShellCmd
err := ps.Run(script, exportPath, vmName, snapshotName, allSnapshotsString)
return err
}
func CopyVmxcVirtualMachine(exportPath string, cloneFromVmxcPath string) error {
var script = `
param([string]$exportPath, [string]$cloneFromVmxcPath)
if (!(Test-Path $cloneFromVmxcPath)){
throw "Clone from vmxc directory: $cloneFromVmxcPath does not exist!"
}
if (!(Test-Path $exportPath)){
New-Item -ItemType Directory -Force -Path $exportPath
}
$cloneFromVmxcPath = Join-Path $cloneFromVmxcPath '\*'
Copy-Item $cloneFromVmxcPath $exportPath -Recurse -Force
`
var ps powershell.PowerShellCmd
err := ps.Run(script, exportPath, cloneFromVmxcPath)
return err
}
func ImportVmxcVirtualMachine(importPath string, vmName string, harddrivePath string, ram int64, switchName string) error {
var script = `
param([string]$importPath, [string]$vmName, [string]$harddrivePath, [long]$memoryStartupBytes, [string]$switchName)
$VirtualHarddisksPath = Join-Path -Path $importPath -ChildPath 'Virtual Hard Disks'
if (!(Test-Path $VirtualHarddisksPath)) {
New-Item -ItemType Directory -Force -Path $VirtualHarddisksPath
}
$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
Connect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor -SwitchName $switchName
$vm = Import-VM -CompatibilityReport $compatibilityReport
if ($vm) {
$result = Rename-VM -VM $vm -NewName $VMName
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, importPath, vmName, harddrivePath, strconv.FormatInt(ram, 10), switchName)
return err
}
func CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
if cloneFromVmName != "" {
err := ExportVmxcVirtualMachine(path, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots)
if err != nil {
return err
}
}
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) {
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 = `

View File

@ -246,6 +246,88 @@ 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 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 = `

View File

@ -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:
@ -113,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

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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") %>>