Merge pull request #3417 from arizvisa/vmware-iso-extraconfig

Added support for sound, serial/parallel/usb ports, and mapping the correct network (via a couple parsers) for the VMware builder.
This commit is contained in:
Matthew Hooker 2018-02-09 13:01:01 -08:00 committed by GitHub
commit 4be86e2e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 3857 additions and 391 deletions

View File

@ -2,13 +2,18 @@ package common
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/hashicorp/packer/helper/multistep"
)
@ -24,15 +29,11 @@ type Driver interface {
CompactDisk(string) error
// CreateDisk creates a virtual disk with the given size.
CreateDisk(string, string, string) error
CreateDisk(string, string, string, string) error
// Checks if the VMX file at the given path is running.
IsRunning(string) (bool, error)
// CommHost returns the host address for the VM that is being
// managed by this driver.
CommHost(multistep.StateBag) (string, error)
// Start starts a VM specified by the path to the VMX given.
Start(string, bool) error
@ -49,14 +50,33 @@ type Driver interface {
// Attach the VMware tools ISO
ToolsInstall() error
// Get the path to the DHCP leases file for the given device.
DhcpLeasesPath(string) string
// Verify checks to make sure that this driver should function
// properly. This should check that all the files it will use
// appear to exist and so on. If everything is okay, this doesn't
// return an error. Otherwise, this returns an error.
// return an error. Otherwise, this returns an error. Each vmware
// driver should assign the VmwareMachine callback functions for locating
// paths within this function.
Verify() error
/// This is to establish a connection to the guest
CommHost(multistep.StateBag) (string, error)
/// These methods are generally implemented by the VmwareDriver
/// structure within this file. A driver implementation can
/// reimplement these, though, if it wants.
GetVmwareDriver() VmwareDriver
// Get the guest hw address for the vm
GuestAddress(multistep.StateBag) (string, error)
// Get the guest ip address for the vm
GuestIP(multistep.StateBag) (string, error)
// Get the host hw address for the vm
HostAddress(multistep.StateBag) (string, error)
// Get the host ip address for the vm
HostIP(multistep.StateBag) (string, error)
}
// NewDriver returns a new driver implementation for this operating
@ -192,3 +212,305 @@ func compareVersions(versionFound string, versionWanted string, product string)
return nil
}
/// helper functions that read configuration information from a file
// read the network<->device configuration out of the specified path
func ReadNetmapConfig(path string) (NetworkMap, error) {
fd, err := os.Open(path)
if err != nil {
return nil, err
}
defer fd.Close()
return ReadNetworkMap(fd)
}
// read the dhcp configuration out of the specified path
func ReadDhcpConfig(path string) (DhcpConfiguration, error) {
fd, err := os.Open(path)
if err != nil {
return nil, err
}
defer fd.Close()
return ReadDhcpConfiguration(fd)
}
// read the VMX configuration from the specified path
func readVMXConfig(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return map[string]string{}, err
}
defer f.Close()
vmxBytes, err := ioutil.ReadAll(f)
if err != nil {
return map[string]string{}, err
}
return ParseVMX(string(vmxBytes)), nil
}
// read the connection type out of a vmx configuration
func readCustomDeviceName(vmxData map[string]string) (string, error) {
connectionType, ok := vmxData["ethernet0.connectiontype"]
if !ok || connectionType != "custom" {
return "", fmt.Errorf("Unable to determine the device name for the connection type : %s", connectionType)
}
device, ok := vmxData["ethernet0.vnet"]
if !ok || device == "" {
return "", fmt.Errorf("Unable to determine the device name for the connection type \"%s\" : %s", connectionType, device)
}
return device, nil
}
// This VmwareDriver is a base class that contains default methods
// that a Driver can use or implement themselves.
type VmwareDriver struct {
/// These methods define paths that are utilized by the driver
/// A driver must overload these in order to point to the correct
/// files so that the address detection (ip and ethernet) machinery
/// works.
DhcpLeasesPath func(string) string
DhcpConfPath func(string) string
VmnetnatConfPath func(string) string
/// This method returns an object with the NetworkNameMapper interface
/// that maps network to device and vice-versa.
NetworkMapper func() (NetworkNameMapper, error)
}
func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) {
vmxPath := state.Get("vmx_path").(string)
log.Println("Lookup up IP information...")
vmxData, err := readVMXConfig(vmxPath)
if err != nil {
return "", err
}
var ok bool
macAddress := ""
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
return "", errors.New("couldn't find MAC address in VMX")
}
}
res, err := net.ParseMAC(macAddress)
if err != nil {
return "", err
}
return res.String(), nil
}
func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
// grab network mapper
netmap, err := d.NetworkMapper()
if err != nil {
return "", err
}
// convert the stashed network to a device
network := state.Get("vmnetwork").(string)
device, err := netmap.NameIntoDevice(network)
// we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration
if err != nil || network == "custom" {
vmxPath := state.Get("vmx_path").(string)
vmxData, err := readVMXConfig(vmxPath)
if err != nil {
return "", err
}
device, err = readCustomDeviceName(vmxData)
if err != nil {
return "", err
}
}
// figure out our MAC address for looking up the guest address
MACAddress, err := d.GuestAddress(state)
if err != nil {
return "", err
}
// figure out the correct dhcp leases
dhcpLeasesPath := d.DhcpLeasesPath(device)
log.Printf("DHCP leases path: %s", dhcpLeasesPath)
if dhcpLeasesPath == "" {
return "", errors.New("no DHCP leases path found.")
}
// open up the lease and read its contents
fh, err := os.Open(dhcpLeasesPath)
if err != nil {
return "", err
}
defer fh.Close()
dhcpBytes, err := ioutil.ReadAll(fh)
if err != nil {
return "", err
}
// start grepping through the file looking for fields that we care about
var lastIp string
var lastLeaseEnd time.Time
var curIp string
var curLeaseEnd time.Time
ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
for _, line := range strings.Split(string(dhcpBytes), "\n") {
// Need to trim off CR character when running in windows
line = strings.TrimRight(line, "\r")
matches := ipLineRe.FindStringSubmatch(line)
if matches != nil {
lastIp = matches[1]
continue
}
matches = endTimeLineRe.FindStringSubmatch(line)
if matches != nil {
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
continue
}
// If the mac address matches and this lease ends farther in the
// future than the last match we might have, then choose it.
matches = macLineRe.FindStringSubmatch(line)
if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
curIp = lastIp
curLeaseEnd = lastLeaseEnd
}
}
if curIp == "" {
return "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", MACAddress, dhcpLeasesPath)
}
return curIp, nil
}
func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
// grab mapper for converting network<->device
netmap, err := d.NetworkMapper()
if err != nil {
return "", err
}
// convert network to name
network := state.Get("vmnetwork").(string)
device, err := netmap.NameIntoDevice(network)
// we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration
if err != nil || network == "custom" {
vmxPath := state.Get("vmx_path").(string)
vmxData, err := readVMXConfig(vmxPath)
if err != nil {
return "", err
}
device, err = readCustomDeviceName(vmxData)
if err != nil {
return "", err
}
}
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath(device)
if _, err := os.Stat(pathDhcpConfig); err != nil {
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
}
config, err := ReadDhcpConfig(pathDhcpConfig)
if err != nil {
return "", err
}
// find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device)
if err != nil {
return "", err
}
// finally grab the hardware address
address, err := interfaceConfig.Hardware()
if err == nil {
return address.String(), nil
}
// we didn't find it, so search through our interfaces for the device name
interfaceList, err := net.Interfaces()
if err == nil {
return "", err
}
names := make([]string, 0)
for _, intf := range interfaceList {
if strings.HasSuffix(strings.ToLower(intf.Name), device) {
return intf.HardwareAddr.String(), nil
}
names = append(names, intf.Name)
}
return "", fmt.Errorf("Unable to find device %s : %v", device, names)
}
func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
// grab mapper for converting network<->device
netmap, err := d.NetworkMapper()
if err != nil {
return "", err
}
// convert network to name
network := state.Get("vmnetwork").(string)
device, err := netmap.NameIntoDevice(network)
// we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration
if err != nil || network == "custom" {
vmxPath := state.Get("vmx_path").(string)
vmxData, err := readVMXConfig(vmxPath)
if err != nil {
return "", err
}
device, err = readCustomDeviceName(vmxData)
if err != nil {
return "", err
}
}
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath(device)
if _, err := os.Stat(pathDhcpConfig); err != nil {
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
}
config, err := ReadDhcpConfig(pathDhcpConfig)
if err != nil {
return "", err
}
// find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device)
if err != nil {
return "", err
}
address, err := interfaceConfig.IP4()
if err != nil {
return "", err
}
return address.String(), nil
}

View File

@ -14,6 +14,8 @@ import (
// Fusion5Driver is a driver that can run VMware Fusion 5.
type Fusion5Driver struct {
VmwareDriver
// This is the path to the "VMware Fusion.app"
AppPath string
@ -39,8 +41,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
return nil
}
func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
func (d *Fusion5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
@ -139,6 +141,32 @@ func (d *Fusion5Driver) Verify() error {
return err
}
libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion")
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
}
d.VmwareDriver.DhcpConfPath = func(device string) string {
return filepath.Join(libpath, device, "dhcpd.conf")
}
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
return filepath.Join(libpath, device, "nat.conf")
}
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
pathNetworking := filepath.Join(libpath, "networking")
if _, err := os.Stat(pathNetworking); err != nil {
return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking)
}
fd, err := os.Open(pathNetworking)
if err != nil {
return nil, err
}
defer fd.Close()
return ReadNetworkingConfig(fd)
}
return nil
}
@ -158,10 +186,6 @@ func (d *Fusion5Driver) ToolsInstall() error {
return nil
}
func (d *Fusion5Driver) DhcpLeasesPath(device string) string {
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
}
const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
@ -170,3 +194,7 @@ const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
<true/>
</dict>
</plist>`
func (d *Fusion5Driver) GetVmwareDriver() VmwareDriver {
return d.VmwareDriver
}

View File

@ -66,5 +66,36 @@ func (d *Fusion6Driver) Verify() error {
}
log.Printf("Detected VMware version: %s", matches[1])
libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion")
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
}
d.VmwareDriver.DhcpConfPath = func(device string) string {
return filepath.Join(libpath, device, "dhcpd.conf")
}
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
return filepath.Join(libpath, device, "nat.conf")
}
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
pathNetworking := filepath.Join(libpath, "networking")
if _, err := os.Stat(pathNetworking); err != nil {
return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking)
}
fd, err := os.Open(pathNetworking)
if err != nil {
return nil, err
}
defer fd.Close()
return ReadNetworkingConfig(fd)
}
return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional")
}
func (d *Fusion6Driver) GetVmwareDriver() VmwareDriver {
return d.Fusion5Driver.VmwareDriver
}

View File

@ -1,6 +1,7 @@
package common
import (
"net"
"sync"
"github.com/hashicorp/packer/helper/multistep"
@ -21,6 +22,7 @@ type DriverMock struct {
CreateDiskCalled bool
CreateDiskOutput string
CreateDiskSize string
CreateDiskAdapterType string
CreateDiskTypeId string
CreateDiskErr error
@ -34,6 +36,26 @@ type DriverMock struct {
CommHostResult string
CommHostErr error
HostAddressCalled bool
HostAddressState multistep.StateBag
HostAddressResult string
HostAddressErr error
HostIPCalled bool
HostIPState multistep.StateBag
HostIPResult string
HostIPErr error
GuestAddressCalled bool
GuestAddressState multistep.StateBag
GuestAddressResult string
GuestAddressErr error
GuestIPCalled bool
GuestIPState multistep.StateBag
GuestIPResult string
GuestIPErr error
StartCalled bool
StartPath string
StartHeadless bool
@ -58,10 +80,33 @@ type DriverMock struct {
DhcpLeasesPathDevice string
DhcpLeasesPathResult string
DhcpConfPathCalled bool
DhcpConfPathResult string
VmnetnatConfPathCalled bool
VmnetnatConfPathResult string
NetmapConfPathCalled bool
NetmapConfPathResult string
VerifyCalled bool
VerifyErr error
}
type NetworkMapperMock struct {
NameIntoDeviceCalled int
DeviceIntoNameCalled int
}
func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) {
m.NameIntoDeviceCalled += 1
return "", nil
}
func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) {
m.DeviceIntoNameCalled += 1
return "", nil
}
func (d *DriverMock) Clone(dst string, src string) error {
d.CloneCalled = true
d.CloneDst = dst
@ -75,10 +120,11 @@ func (d *DriverMock) CompactDisk(path string) error {
return d.CompactDiskErr
}
func (d *DriverMock) CreateDisk(output string, size string, typeId string) error {
func (d *DriverMock) CreateDisk(output string, size string, adapterType string, typeId string) error {
d.CreateDiskCalled = true
d.CreateDiskOutput = output
d.CreateDiskSize = size
d.CreateDiskAdapterType = adapterType
d.CreateDiskTypeId = typeId
return d.CreateDiskErr
}
@ -98,6 +144,58 @@ func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) {
return d.CommHostResult, d.CommHostErr
}
func MockInterface() net.Interface {
interfaces, err := net.Interfaces()
// Build a dummy interface due to being unable to enumerate interfaces
if err != nil || len(interfaces) == 0 {
return net.Interface{
Index: 0,
MTU: -1,
Name: "dummy",
HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0},
Flags: net.FlagLoopback,
}
}
// Find the first loopback interface
for _, intf := range interfaces {
if intf.Flags&net.FlagLoopback == net.FlagLoopback {
return intf
}
}
// Fall-back to just the first one
return interfaces[0]
}
func (d *DriverMock) HostAddress(state multistep.StateBag) (string, error) {
intf := MockInterface()
d.HostAddressResult = intf.HardwareAddr.String()
d.HostAddressCalled = true
d.HostAddressState = state
return d.HostAddressResult, d.HostAddressErr
}
func (d *DriverMock) HostIP(state multistep.StateBag) (string, error) {
d.HostIPResult = "127.0.0.1"
d.HostIPCalled = true
d.HostIPState = state
return d.HostIPResult, d.HostIPErr
}
func (d *DriverMock) GuestAddress(state multistep.StateBag) (string, error) {
d.GuestAddressCalled = true
d.GuestAddressState = state
return d.GuestAddressResult, d.GuestAddressErr
}
func (d *DriverMock) GuestIP(state multistep.StateBag) (string, error) {
d.GuestIPCalled = true
d.GuestIPState = state
return d.GuestIPResult, d.GuestIPErr
}
func (d *DriverMock) Start(path string, headless bool) error {
d.StartCalled = true
d.StartPath = path
@ -134,7 +232,39 @@ func (d *DriverMock) DhcpLeasesPath(device string) string {
return d.DhcpLeasesPathResult
}
func (d *DriverMock) DhcpConfPath(device string) string {
d.DhcpConfPathCalled = true
return d.DhcpConfPathResult
}
func (d *DriverMock) VmnetnatConfPath(device string) string {
d.VmnetnatConfPathCalled = true
return d.VmnetnatConfPathResult
}
func (d *DriverMock) NetmapConfPath() string {
d.NetmapConfPathCalled = true
return d.NetmapConfPathResult
}
func (d *DriverMock) Verify() error {
d.VerifyCalled = true
return d.VerifyErr
}
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
var state VmwareDriver
state.DhcpLeasesPath = func(string) string {
return "/path/to/dhcp.leases"
}
state.DhcpConfPath = func(string) string {
return "/path/to/dhcp.conf"
}
state.VmnetnatConfPath = func(string) string {
return "/path/to/vmnetnat.conf"
}
state.NetworkMapper = func() (NetworkNameMapper, error) {
return NetworkMapperMock{}, nil
}
return state
}

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,8 @@ import (
// Player5Driver is a driver that can run VMware Player 5 on Linux.
type Player5Driver struct {
VmwareDriver
AppPath string
VdiskManagerPath string
QemuImgPath string
@ -62,12 +64,12 @@ func (d *Player5Driver) qemuCompactDisk(diskPath string) error {
return nil
}
func (d *Player5Driver) CreateDisk(output string, size string, type_id string) error {
func (d *Player5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
var cmd *exec.Cmd
if d.QemuImgPath != "" {
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
} else {
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
}
if _, _, err := runAndLog(cmd); err != nil {
return err
@ -181,6 +183,33 @@ func (d *Player5Driver) Verify() error {
"One of these is required to configure disks for VMware Player.")
}
// Assigning the path callbacks to VmwareDriver
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
return playerDhcpLeasesPath(device)
}
d.VmwareDriver.DhcpConfPath = func(device string) string {
return playerVmDhcpConfPath(device)
}
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
return playerVmnetnatConfPath(device)
}
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
pathNetmap := playerNetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
fd, err := os.Open(pathNetmap)
if err != nil {
return nil, err
}
defer fd.Close()
return ReadNetworkMap(fd)
}
return nil
}
@ -192,10 +221,6 @@ func (d *Player5Driver) ToolsInstall() error {
return nil
}
func (d *Player5Driver) DhcpLeasesPath(device string) string {
return playerDhcpLeasesPath(device)
}
func (d *Player5Driver) VmnetnatConfPath() string {
return playerVmnetnatConfPath()
func (d *Player5Driver) GetVmwareDriver() VmwareDriver {
return d.VmwareDriver
}

View File

@ -57,14 +57,29 @@ func playerDhcpLeasesPath(device string) string {
} else if _, err := os.Stat(path); err == nil {
return path
}
return findFile("vmnetdhcp.leases", playerDataFilePaths())
}
func playerVmnetnatConfPath() string {
func playerVmDhcpConfPath(device string) string {
// the device isn't actually used on windows hosts
path, err := playerDhcpConfigPathRegistry()
if err != nil {
log.Printf("Error finding configuration in registry: %s", err)
} else if _, err := os.Stat(path); err == nil {
return path
}
return findFile("vmnetdhcp.conf", playerDataFilePaths())
}
func playerVmnetnatConfPath(device string) string {
// the device isn't actually used on windows hosts
return findFile("vmnetnat.conf", playerDataFilePaths())
}
func playerNetmapConfPath() string {
return findFile("netmap.conf", playerDataFilePaths())
}
// This reads the VMware installation path from the Windows registry.
func playerVMwareRoot() (s string, err error) {
key := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmplayer.exe`
@ -87,7 +102,18 @@ func playerDhcpLeasesPathRegistry() (s string, err error) {
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
return
}
return normalizePath(s), nil
}
// This reads the VMware DHCP configuration path from the Windows registry.
func playerDhcpConfigPathRegistry() (s string, err error) {
key := "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters"
subkey := "ConfFile"
s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey)
if err != nil {
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
return
}
return normalizePath(s), nil
}

View File

@ -35,3 +35,7 @@ func (d *Player6Driver) Verify() error {
return playerVerifyVersion(VMWARE_PLAYER_VERSION)
}
func (d *Player6Driver) GetVmwareDriver() VmwareDriver {
return d.Player5Driver.VmwareDriver
}

View File

@ -8,6 +8,7 @@ import (
"fmt"
"log"
"os/exec"
"path/filepath"
"regexp"
"runtime"
)
@ -28,17 +29,50 @@ func playerFindVmrun() (string, error) {
return exec.LookPath("vmrun")
}
func playerDhcpLeasesPath(device string) string {
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
}
func playerToolsIsoPath(flavor string) string {
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
}
func playerVmnetnatConfPath() string {
// return the base path to vmware's config on the host
func playerVMwareRoot() (s string, err error) {
return "/etc/vmware", nil
}
func playerDhcpLeasesPath(device string) string {
base, err := playerVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, device, "dhcpd/dhcpd.leases")
}
func playerVmDhcpConfPath(device string) string {
base, err := playerVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, device, "dhcp/dhcp.conf")
}
func playerVmnetnatConfPath(device string) string {
base, err := playerVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, device, "nat/nat.conf")
}
func playerNetmapConfPath() string {
base, err := playerVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, "netmap.conf")
}
func playerVerifyVersion(version string) error {
if runtime.GOOS != "linux" {

View File

@ -33,3 +33,7 @@ func (d *Workstation10Driver) Verify() error {
return workstationVerifyVersion(VMWARE_WS_VERSION)
}
func (d *Workstation10Driver) GetVmwareDriver() VmwareDriver {
return d.Workstation9Driver.VmwareDriver
}

View File

@ -14,6 +14,8 @@ import (
// Workstation9Driver is a driver that can run VMware Workstation 9
type Workstation9Driver struct {
VmwareDriver
AppPath string
VdiskManagerPath string
VmrunPath string
@ -40,8 +42,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error {
return nil
}
func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error {
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
func (d *Workstation9Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
@ -142,6 +144,33 @@ func (d *Workstation9Driver) Verify() error {
return err
}
// Assigning the path callbacks to VmwareDriver
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
return workstationDhcpLeasesPath(device)
}
d.VmwareDriver.DhcpConfPath = func(device string) string {
return workstationDhcpConfPath(device)
}
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
return workstationVmnetnatConfPath(device)
}
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
pathNetmap := workstationNetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
fd, err := os.Open(pathNetmap)
if err != nil {
return nil, err
}
defer fd.Close()
return ReadNetworkMap(fd)
}
return nil
}
@ -153,10 +182,6 @@ func (d *Workstation9Driver) ToolsInstall() error {
return nil
}
func (d *Workstation9Driver) DhcpLeasesPath(device string) string {
return workstationDhcpLeasesPath(device)
}
func (d *Workstation9Driver) VmnetnatConfPath() string {
return workstationVmnetnatConfPath()
func (d *Workstation9Driver) GetVmwareDriver() VmwareDriver {
return d.VmwareDriver
}

View File

@ -59,10 +59,20 @@ func workstationDhcpLeasesPath(device string) string {
return findFile("vmnetdhcp.leases", workstationDataFilePaths())
}
func workstationVmnetnatConfPath() string {
func workstationDhcpConfPath(device string) string {
// device isn't used on a windows host
return findFile("vmnetdhcp.conf", workstationDataFilePaths())
}
func workstationVmnetnatConfPath(device string) string {
// device isn't used on a windows host
return findFile("vmnetnat.conf", workstationDataFilePaths())
}
func workstationNetmapConfPath() string {
return findFile("netmap.conf", workstationDataFilePaths())
}
// See http://blog.natefinch.com/2012/11/go-win-stuff.html
//
// This is used by workstationVMwareRoot in order to read some registry data.

View File

@ -39,18 +39,51 @@ func workstationFindVmrun() (string, error) {
return exec.LookPath("vmrun")
}
// return the base path to vmware's config on the host
func workstationVMwareRoot() (s string, err error) {
return "/etc/vmware", nil
}
func workstationDhcpLeasesPath(device string) string {
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
base, err := workstationVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, device, "dhcpd/dhcpd.leases")
}
func workstationDhcpConfPath(device string) string {
base, err := workstationVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, device, "dhcp/dhcpd.conf")
}
func workstationVmnetnatConfPath(device string) string {
base, err := workstationVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, device, "nat/nat.conf")
}
func workstationNetmapConfPath() string {
base, err := workstationVMwareRoot()
if err != nil {
log.Printf("Error finding VMware root: %s", err)
return ""
}
return filepath.Join(base, "netmap.conf")
}
func workstationToolsIsoPath(flavor string) string {
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
}
func workstationVmnetnatConfPath() string {
return ""
}
func workstationVerifyVersion(version string) error {
if runtime.GOOS != "linux" {
return fmt.Errorf("The VMware WS version %s driver is only supported on Linux, and Windows, at the moment. Your OS: %s", version, runtime.GOOS)

View File

@ -1,90 +0,0 @@
package common
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"time"
)
// Interface to help find the IP address of a running virtual machine.
type GuestIPFinder interface {
GuestIP() (string, error)
}
// DHCPLeaseGuestLookup looks up the IP address of a guest using DHCP
// lease information from the VMware network devices.
type DHCPLeaseGuestLookup struct {
// Driver that is being used (to find leases path)
Driver Driver
// Device that the guest is connected to.
Device string
// MAC address of the guest.
MACAddress string
}
func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) {
dhcpLeasesPath := f.Driver.DhcpLeasesPath(f.Device)
log.Printf("DHCP leases path: %s", dhcpLeasesPath)
if dhcpLeasesPath == "" {
return "", errors.New("no DHCP leases path found.")
}
fh, err := os.Open(dhcpLeasesPath)
if err != nil {
return "", err
}
defer fh.Close()
dhcpBytes, err := ioutil.ReadAll(fh)
if err != nil {
return "", err
}
var lastIp string
var lastLeaseEnd time.Time
var curIp string
var curLeaseEnd time.Time
ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
for _, line := range strings.Split(string(dhcpBytes), "\n") {
// Need to trim off CR character when running in windows
line = strings.TrimRight(line, "\r")
matches := ipLineRe.FindStringSubmatch(line)
if matches != nil {
lastIp = matches[1]
continue
}
matches = endTimeLineRe.FindStringSubmatch(line)
if matches != nil {
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
continue
}
// If the mac address matches and this lease ends farther in the
// future than the last match we might have, then choose it.
matches = macLineRe.FindStringSubmatch(line)
if matches != nil && strings.EqualFold(matches[1], f.MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
curIp = lastIp
curLeaseEnd = lastLeaseEnd
}
}
if curIp == "" {
return "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", f.MACAddress, dhcpLeasesPath)
}
return curIp, nil
}

View File

@ -1,82 +0,0 @@
package common
import (
"io/ioutil"
"os"
"testing"
)
func TestDHCPLeaseGuestLookup_impl(t *testing.T) {
var _ GuestIPFinder = new(DHCPLeaseGuestLookup)
}
func TestDHCPLeaseGuestLookup(t *testing.T) {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
if _, err := tf.Write([]byte(testLeaseContents)); err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
driver := new(DriverMock)
driver.DhcpLeasesPathResult = tf.Name()
finder := &DHCPLeaseGuestLookup{
Driver: driver,
Device: "vmnet8",
MACAddress: "00:0c:29:59:91:02",
}
ip, err := finder.GuestIP()
if err != nil {
t.Fatalf("err: %s", err)
}
if !driver.DhcpLeasesPathCalled {
t.Fatal("should ask for DHCP leases path")
}
if driver.DhcpLeasesPathDevice != "vmnet8" {
t.Fatal("should be vmnet8")
}
if ip != "192.168.126.130" {
t.Fatalf("bad: %#v", ip)
}
}
const testLeaseContents = `
# All times in this file are in UTC (GMT), not your local timezone. This is
# not a bug, so please don't ask about it. There is no portable way to
# store leases in the local timezone, so please don't request this as a
# feature. If this is inconvenient or confusing to you, we sincerely
# apologize. Seriously, though - don't ask.
# The format of this file is documented in the dhcpd.leases(5) manual page.
lease 192.168.126.129 {
starts 0 2013/09/15 23:58:51;
ends 1 2013/09/16 00:28:51;
hardware ethernet 00:0c:29:59:91:02;
client-hostname "precise64";
}
lease 192.168.126.130 {
starts 2 2013/09/17 21:39:07;
ends 2 2013/09/17 22:09:07;
hardware ethernet 00:0c:29:59:91:02;
client-hostname "precise64";
}
lease 192.168.126.128 {
starts 0 2013/09/15 20:09:59;
ends 0 2013/09/15 20:21:58;
hardware ethernet 00:0c:29:59:91:02;
client-hostname "precise64";
}
lease 192.168.126.127 {
starts 0 2013/09/15 20:09:59;
ends 0 2013/09/15 20:21:58;
hardware ethernet 01:0c:29:59:91:02;
client-hostname "precise64";
`

View File

@ -1,7 +0,0 @@
package common
// Interface to help find the host IP that is available from within
// the VMware virtual machines.
type HostIPFinder interface {
HostIP() (string, error)
}

View File

@ -1,11 +0,0 @@
package common
import "testing"
func TestIfconfigIPFinder_Impl(t *testing.T) {
var raw interface{}
raw = &IfconfigIPFinder{}
if _, ok := raw.(HostIPFinder); !ok {
t.Fatalf("IfconfigIPFinder is not a host IP finder")
}
}

View File

@ -1,65 +0,0 @@
package common
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"regexp"
"strings"
)
// VMnetNatConfIPFinder finds the IP address of the host machine by
// retrieving the IP from the vmnetnat.conf. This isn't a full proof
// technique but so far it has not failed.
type VMnetNatConfIPFinder struct{}
func (*VMnetNatConfIPFinder) HostIP() (string, error) {
driver := &Workstation9Driver{}
vmnetnat := driver.VmnetnatConfPath()
if vmnetnat == "" {
return "", errors.New("Could not find NAT vmnet conf file")
}
if _, err := os.Stat(vmnetnat); err != nil {
return "", fmt.Errorf("Could not find NAT vmnet conf file: %s", vmnetnat)
}
f, err := os.Open(vmnetnat)
if err != nil {
return "", err
}
defer f.Close()
ipRe := regexp.MustCompile(`^\s*ip\s*=\s*(.+?)\s*$`)
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if line != "" {
matches := ipRe.FindStringSubmatch(line)
if matches != nil {
ip := matches[1]
dotIndex := strings.LastIndex(ip, ".")
if dotIndex == -1 {
continue
}
ip = ip[0:dotIndex] + ".1"
return ip, nil
}
}
if err == io.EOF {
break
}
if err != nil {
return "", err
}
}
return "", errors.New("host IP not found in " + vmnetnat)
}

View File

@ -1,11 +0,0 @@
package common
import "testing"
func TestVMnetNatConfIPFinder_Impl(t *testing.T) {
var raw interface{}
raw = &VMnetNatConfIPFinder{}
if _, ok := raw.(HostIPFinder); !ok {
t.Fatalf("VMnetNatConfIPFinder is not a host IP finder")
}
}

View File

@ -14,34 +14,12 @@ import (
func CommHost(config *SSHConfig) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
driver := state.Get("driver").(Driver)
vmxPath := state.Get("vmx_path").(string)
if config.Comm.SSHHost != "" {
return config.Comm.SSHHost, nil
}
log.Println("Lookup up IP information...")
vmxData, err := ReadVMX(vmxPath)
if err != nil {
return "", err
}
var ok bool
macAddress := ""
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
return "", errors.New("couldn't find MAC address in VMX")
}
}
ipLookup := &DHCPLeaseGuestLookup{
Driver: driver,
Device: "vmnet8",
MACAddress: macAddress,
}
ipAddress, err := ipLookup.GuestIP()
ipAddress, err := driver.GuestIP(state)
if err != nil {
log.Printf("IP lookup failed: %s", err)
return "", fmt.Errorf("IP lookup failed: %s", err)

View File

@ -116,6 +116,10 @@ func TestStepShutdown_noCommand(t *testing.T) {
}
func TestStepShutdown_locks(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.")
}
state := testStepShutdownState(t)
step := new(StepShutdown)
step.Testing = true

View File

@ -7,7 +7,6 @@ import (
"net"
"os"
"regexp"
"runtime"
"strings"
"time"
"unicode"
@ -64,7 +63,7 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
}
// Connect to VNC
ui.Say("Connecting to VM via VNC")
ui.Say(fmt.Sprintf("Connecting to VM via VNC (%s:%d)", vncIp, vncPort))
nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIp, vncPort))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)
@ -94,16 +93,7 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
// Determine the host IP
var ipFinder HostIPFinder
if finder, ok := driver.(HostIPFinder); ok {
ipFinder = finder
} else if runtime.GOOS == "windows" {
ipFinder = new(VMnetNatConfIPFinder)
} else {
ipFinder = &IfconfigIPFinder{Device: "vmnet8"}
}
hostIP, err := ipFinder.HostIP()
hostIP, err := driver.HostIP(state)
if err != nil {
err := fmt.Errorf("Error detecting host IP: %s", err)
state.Put("error", err)

View File

@ -38,21 +38,43 @@ type Config struct {
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
// disk drives
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
DiskAdapterType string `mapstructure:"disk_adapter_type"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
Format string `mapstructure:"format"`
// cdrom drive
CdromAdapterType string `mapstructure:"cdrom_adapter_type"`
// platform information
GuestOSType string `mapstructure:"guest_os_type"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"`
// Network adapter and type
NetworkAdapterType string `mapstructure:"network_adapter_type"`
Network string `mapstructure:"network"`
// device presence
Sound bool `mapstructure:"sound"`
USB bool `mapstructure:"usb"`
// communication ports
Serial string `mapstructure:"serial"`
Parallel string `mapstructure:"parallel"`
// booting a guest
KeepRegistered bool `mapstructure:"keep_registered"`
OVFToolOptions []string `mapstructure:"ovftool_options"`
SkipCompaction bool `mapstructure:"skip_compaction"`
SkipExport bool `mapstructure:"skip_export"`
VMName string `mapstructure:"vm_name"`
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
Version string `mapstructure:"version"`
// remote vsphere
RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
@ -109,6 +131,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.DiskSize = 40000
}
if b.config.DiskAdapterType == "" {
// Default is lsilogic
b.config.DiskAdapterType = "lsilogic"
}
if b.config.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
b.config.DiskTypeId = "1"
@ -158,6 +185,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
if b.config.Network == "" {
b.config.Network = "nat"
}
if !b.config.Sound {
b.config.Sound = false
}
if !b.config.USB {
b.config.USB = false
}
// Remote configuration validation
if b.config.RemoteType != "" {
if b.config.RemoteHost == "" {

View File

@ -14,6 +14,7 @@ import (
"strings"
"time"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
commonssh "github.com/hashicorp/packer/common/ssh"
"github.com/hashicorp/packer/communicator/ssh"
"github.com/hashicorp/packer/helper/multistep"
@ -24,6 +25,8 @@ import (
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
// virtual machines. This driver can only manage one machine at a time.
type ESX5Driver struct {
base vmwcommon.VmwareDriver
Host string
Port uint
Username string
@ -46,9 +49,9 @@ func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
return nil
}
func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string) error {
func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, adapter_type string, typeId string) error {
diskPath := d.datastorePath(diskPathLocal)
return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", "lsilogic", diskPath)
return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", adapter_type, diskPath)
}
func (d *ESX5Driver) IsRunning(string) (bool, error) {
@ -143,10 +146,6 @@ func (d *ESX5Driver) ToolsInstall() error {
return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
}
func (d *ESX5Driver) DhcpLeasesPath(string) string {
return ""
}
func (d *ESX5Driver) Verify() error {
checks := []func() error{
d.connect,
@ -159,11 +158,10 @@ func (d *ESX5Driver) Verify() error {
return err
}
}
return nil
}
func (d *ESX5Driver) HostIP() (string, error) {
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
if err != nil {
return "", err
@ -174,6 +172,101 @@ func (d *ESX5Driver) HostIP() (string, error) {
return host, err
}
func (d *ESX5Driver) GuestIP(multistep.StateBag) (string, error) {
// GuestIP is defined by the user as d.Host..but let's validate it just to be sure
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
defer conn.Close()
if err != nil {
return "", err
}
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
return host, err
}
func (d *ESX5Driver) HostAddress(multistep.StateBag) (string, error) {
// make a connection
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
defer conn.Close()
if err != nil {
return "", err
}
// get the local address (the host)
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil {
return "", fmt.Errorf("Unable to determine host address for ESXi: %v", err)
}
// iterate through all the interfaces..
interfaces, err := net.Interfaces()
if err != nil {
return "", fmt.Errorf("Unable to enumerate host interfaces : %v", err)
}
for _, intf := range interfaces {
addrs, err := intf.Addrs()
if err != nil {
continue
}
// ..checking to see if any if it's addrs match the host address
for _, addr := range addrs {
if addr.String() == host { // FIXME: Is this the proper way to compare two HardwareAddrs?
return intf.HardwareAddr.String(), nil
}
}
}
// ..unfortunately nothing was found
return "", fmt.Errorf("Unable to locate interface matching host address in ESXi: %v", host)
}
func (d *ESX5Driver) GuestAddress(multistep.StateBag) (string, error) {
// list all the interfaces on the esx host
r, err := d.esxcli("network", "ip", "interface", "list")
if err != nil {
return "", fmt.Errorf("Could not retrieve network interfaces for ESXi: %v", err)
}
// rip out the interface name and the MAC address from the csv output
addrs := make(map[string]string)
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
if strings.ToUpper(record["Enabled"]) != "TRUE" {
continue
}
addrs[record["Name"]] = record["MAC Address"]
}
// list all the addresses on the esx host
r, err = d.esxcli("network", "ip", "interface", "ipv4", "get")
if err != nil {
return "", fmt.Errorf("Could not retrieve network addresses for ESXi: %v", err)
}
// figure out the interface name that matches the specified d.Host address
var intf string
intf = ""
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
if record["IPv4 Address"] == d.Host && record["Name"] != "" {
intf = record["Name"]
break
}
}
if intf == "" {
return "", fmt.Errorf("Unable to find matching address for ESXi guest")
}
// find the MAC address according to the interface name
result, ok := addrs[intf]
if !ok {
return "", fmt.Errorf("Unable to find address for ESXi guest interface")
}
// ..and we're good
return result, nil
}
func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint, error) {
var vncPort uint
@ -531,6 +624,10 @@ func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
return &esxcliReader{r, header}, nil
}
func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver {
return d.base
}
type esxcliReader struct {
cr *csv.Reader
header []string

View File

@ -50,8 +50,9 @@ func TestESX5Driver_HostIP(t *testing.T) {
defer listen.Close()
driver := ESX5Driver{Host: "localhost", Port: uint(port)}
state := new(multistep.BasicStateBag)
if host, _ := driver.HostIP(); host != expected_host {
if host, _ := driver.HostIP(state); host != expected_host {
t.Error(fmt.Sprintf("Expected string, %s but got %s", expected_host, host))
}
}

View File

@ -28,7 +28,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep
ui.Say("Creating virtual machine disk")
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskTypeId); err != nil {
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskAdapterType, config.DiskTypeId); err != nil {
err := fmt.Errorf("Error creating disk: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@ -46,7 +46,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep
additionalpath := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.vmdk", config.DiskName, i+1))
size := fmt.Sprintf("%dM", uint64(additionalsize))
if err := driver.CreateDisk(additionalpath, size, config.DiskTypeId); err != nil {
if err := driver.CreateDisk(additionalpath, size, config.DiskAdapterType, config.DiskTypeId); err != nil {
err := fmt.Errorf("Error creating additional disk: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -6,6 +6,8 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/helper/multistep"
@ -16,9 +18,38 @@ import (
type vmxTemplateData struct {
Name string
GuestOS string
DiskName string
ISOPath string
Version string
SCSI_Present string
SCSI_diskAdapterType string
SATA_Present string
NVME_Present string
DiskName string
DiskType string
CDROMType string
CDROMType_MasterSlave string
Network_Type string
Network_Device string
Network_Adapter string
Sound_Present string
Usb_Present string
Serial_Present string
Serial_Type string
Serial_Endpoint string
Serial_Host string
Serial_Yield string
Serial_Filename string
Serial_Auto string
Parallel_Present string
Parallel_Bidirectional string
Parallel_Filename string
Parallel_Auto string
}
type additionalDiskTemplateData struct {
@ -39,11 +70,238 @@ type stepCreateVMX struct {
tempDir string
}
/* serial conversions */
type serialConfigPipe struct {
filename string
endpoint string
host string
yield string
}
type serialConfigFile struct {
filename string
yield string
}
type serialConfigDevice struct {
devicename string
yield string
}
type serialConfigAuto struct {
devicename string
yield string
}
type serialUnion struct {
serialType interface{}
pipe *serialConfigPipe
file *serialConfigFile
device *serialConfigDevice
auto *serialConfigAuto
}
func unformat_serial(config string) (*serialUnion, error) {
var defaultSerialPort string
if runtime.GOOS == "windows" {
defaultSerialPort = "COM1"
} else {
defaultSerialPort = "/dev/ttyS0"
}
input := strings.SplitN(config, ":", 2)
if len(input) < 1 {
return nil, fmt.Errorf("Unexpected format for serial port: %s", config)
}
var formatType, formatOptions string
formatType = input[0]
if len(input) == 2 {
formatOptions = input[1]
} else {
formatOptions = ""
}
switch strings.ToUpper(formatType) {
case "PIPE":
comp := strings.Split(formatOptions, ",")
if len(comp) < 3 || len(comp) > 4 {
return nil, fmt.Errorf("Unexpected format for serial port : pipe : %s", config)
}
if res := strings.ToLower(comp[1]); res != "client" && res != "server" {
return nil, fmt.Errorf("Unexpected format for serial port : pipe : endpoint : %s : %s", res, config)
}
if res := strings.ToLower(comp[2]); res != "app" && res != "vm" {
return nil, fmt.Errorf("Unexpected format for serial port : pipe : host : %s : %s", res, config)
}
res := &serialConfigPipe{
filename: comp[0],
endpoint: comp[1],
host: map[string]string{"app": "TRUE", "vm": "FALSE"}[strings.ToLower(comp[2])],
yield: "FALSE",
}
if len(comp) == 4 {
res.yield = strings.ToUpper(comp[3])
}
if res.yield != "TRUE" && res.yield != "FALSE" {
return nil, fmt.Errorf("Unexpected format for serial port : pipe : yield : %s : %s", res.yield, config)
}
return &serialUnion{serialType: res, pipe: res}, nil
case "FILE":
comp := strings.Split(formatOptions, ",")
if len(comp) > 2 {
return nil, fmt.Errorf("Unexpected format for serial port : file : %s", config)
}
res := &serialConfigFile{yield: "FALSE"}
res.filename = filepath.FromSlash(comp[0])
res.yield = map[bool]string{true: strings.ToUpper(comp[0]), false: "FALSE"}[len(comp) > 1]
if res.yield != "TRUE" && res.yield != "FALSE" {
return nil, fmt.Errorf("Unexpected format for serial port : file : yield : %s : %s", res.yield, config)
}
return &serialUnion{serialType: res, file: res}, nil
case "DEVICE":
comp := strings.Split(formatOptions, ",")
if len(comp) > 2 {
return nil, fmt.Errorf("Unexpected format for serial port : device : %s", config)
}
res := new(serialConfigDevice)
if len(comp) == 2 {
res.devicename = map[bool]string{true: filepath.FromSlash(comp[0]), false: defaultSerialPort}[len(comp[0]) > 0]
res.yield = strings.ToUpper(comp[1])
} else if len(comp) == 1 {
res.devicename = map[bool]string{true: filepath.FromSlash(comp[0]), false: defaultSerialPort}[len(comp[0]) > 0]
res.yield = "FALSE"
} else if len(comp) == 0 {
res.devicename = defaultSerialPort
res.yield = "FALSE"
}
if res.yield != "TRUE" && res.yield != "FALSE" {
return nil, fmt.Errorf("Unexpected format for serial port : device : yield : %s : %s", res.yield, config)
}
return &serialUnion{serialType: res, device: res}, nil
case "AUTO":
res := new(serialConfigAuto)
res.devicename = defaultSerialPort
if len(formatOptions) > 0 {
res.yield = strings.ToUpper(formatOptions)
} else {
res.yield = "FALSE"
}
if res.yield != "TRUE" && res.yield != "FALSE" {
return nil, fmt.Errorf("Unexpected format for serial port : auto : yield : %s : %s", res.yield, config)
}
return &serialUnion{serialType: res, auto: res}, nil
case "NONE":
return &serialUnion{serialType: nil}, nil
default:
return nil, fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(formatType), config)
}
}
/* parallel port */
type parallelUnion struct {
parallelType interface{}
file *parallelPortFile
device *parallelPortDevice
auto *parallelPortAuto
}
type parallelPortFile struct {
filename string
}
type parallelPortDevice struct {
bidirectional string
devicename string
}
type parallelPortAuto struct {
bidirectional string
}
func unformat_parallel(config string) (*parallelUnion, error) {
input := strings.SplitN(config, ":", 2)
if len(input) < 1 {
return nil, fmt.Errorf("Unexpected format for parallel port: %s", config)
}
var formatType, formatOptions string
formatType = input[0]
if len(input) == 2 {
formatOptions = input[1]
} else {
formatOptions = ""
}
switch strings.ToUpper(formatType) {
case "FILE":
res := &parallelPortFile{filename: filepath.FromSlash(formatOptions)}
return &parallelUnion{parallelType: res, file: res}, nil
case "DEVICE":
comp := strings.Split(formatOptions, ",")
if len(comp) < 1 || len(comp) > 2 {
return nil, fmt.Errorf("Unexpected format for parallel port: %s", config)
}
res := new(parallelPortDevice)
res.bidirectional = "FALSE"
res.devicename = filepath.FromSlash(comp[0])
if len(comp) > 1 {
switch strings.ToUpper(comp[1]) {
case "BI":
res.bidirectional = "TRUE"
case "UNI":
res.bidirectional = "FALSE"
default:
return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(comp[0]), config)
}
}
return &parallelUnion{parallelType: res, device: res}, nil
case "AUTO":
res := new(parallelPortAuto)
switch strings.ToUpper(formatOptions) {
case "":
fallthrough
case "UNI":
res.bidirectional = "FALSE"
case "BI":
res.bidirectional = "TRUE"
default:
return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(formatOptions), config)
}
return &parallelUnion{parallelType: res, auto: res}, nil
case "NONE":
return &parallelUnion{parallelType: nil}, nil
}
return nil, fmt.Errorf("Unexpected format for parallel port: %s", config)
}
/* regular steps */
func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
// Convert the iso_path into a path relative to the .vmx file if possible
if relativeIsoPath, err := filepath.Rel(config.VMXTemplatePath, filepath.FromSlash(isoPath)); err == nil {
isoPath = relativeIsoPath
}
ui.Say("Building and writing VMX file")
vmxTemplate := DefaultVMXTemplate
@ -111,14 +369,211 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist
}
}
ctx.Data = &vmxTemplateData{
templateData := vmxTemplateData{
Name: config.VMName,
GuestOS: config.GuestOSType,
DiskName: config.DiskName,
Version: config.Version,
ISOPath: isoPath,
SCSI_Present: "FALSE",
SCSI_diskAdapterType: "lsilogic",
SATA_Present: "FALSE",
NVME_Present: "FALSE",
DiskType: "scsi",
CDROMType: "ide",
CDROMType_MasterSlave: "0",
Network_Adapter: "e1000",
Sound_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.Sound)],
Usb_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.USB)],
Serial_Present: "FALSE",
Parallel_Present: "FALSE",
}
/// Use the disk adapter type that the user specified to tweak the .vmx
// Also sync the cdrom adapter type according to what's common for that disk type.
diskAdapterType := strings.ToLower(config.DiskAdapterType)
switch diskAdapterType {
case "ide":
templateData.DiskType = "ide"
templateData.CDROMType = "ide"
templateData.CDROMType_MasterSlave = "1"
case "sata":
templateData.SATA_Present = "TRUE"
templateData.DiskType = "sata"
templateData.CDROMType = "sata"
templateData.CDROMType_MasterSlave = "1"
case "nvme":
templateData.NVME_Present = "TRUE"
templateData.DiskType = "nvme"
templateData.SATA_Present = "TRUE"
templateData.CDROMType = "sata"
templateData.CDROMType_MasterSlave = "0"
case "scsi":
diskAdapterType = "lsilogic"
fallthrough
default:
templateData.SCSI_Present = "TRUE"
templateData.SCSI_diskAdapterType = diskAdapterType
templateData.DiskType = "scsi"
templateData.CDROMType = "ide"
templateData.CDROMType_MasterSlave = "0"
}
/// Handle the cdrom adapter type. If the disk adapter type and the
// cdrom adapter type are the same, then ensure that the cdrom is the
// slave device on whatever bus the disk adapter is on.
cdromAdapterType := strings.ToLower(config.CdromAdapterType)
if cdromAdapterType == "" {
cdromAdapterType = templateData.CDROMType
} else if cdromAdapterType == diskAdapterType {
templateData.CDROMType_MasterSlave = "1"
} else {
templateData.CDROMType_MasterSlave = "0"
}
switch cdromAdapterType {
case "ide":
templateData.CDROMType = "ide"
case "sata":
templateData.SATA_Present = "TRUE"
templateData.CDROMType = "sata"
case "scsi":
templateData.SCSI_Present = "TRUE"
templateData.CDROMType = "scsi"
default:
err := fmt.Errorf("Error procesing VMX template: %s", cdromAdapterType)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
/// Assign the network adapter type into the template if one was specified.
network_adapter := strings.ToLower(config.NetworkAdapterType)
if network_adapter != "" {
templateData.Network_Adapter = network_adapter
}
/// Check the network type that the user specified
network := config.Network
driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver()
// read netmap config
netmap, err := driver.NetworkMapper()
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// try and convert the specified network to a device
device, err := netmap.NameIntoDevice(network)
// success. so we know that it's an actual network type inside netmap.conf
if err == nil {
templateData.Network_Type = network
templateData.Network_Device = device
// we were unable to find the type, so assume it's a custom network device.
} else {
templateData.Network_Type = "custom"
templateData.Network_Device = network
}
// store the network so that we can later figure out what ip address to bind to
state.Put("vmnetwork", network)
/// check if serial port has been configured
if config.Serial == "" {
templateData.Serial_Present = "FALSE"
} else {
serial, err := unformat_serial(config.Serial)
if err != nil {
err := fmt.Errorf("Error procesing VMX template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
templateData.Serial_Present = "TRUE"
templateData.Serial_Filename = ""
templateData.Serial_Yield = ""
templateData.Serial_Endpoint = ""
templateData.Serial_Host = ""
templateData.Serial_Auto = "FALSE"
switch serial.serialType.(type) {
case *serialConfigPipe:
templateData.Serial_Type = "pipe"
templateData.Serial_Endpoint = serial.pipe.endpoint
templateData.Serial_Host = serial.pipe.host
templateData.Serial_Yield = serial.pipe.yield
templateData.Serial_Filename = filepath.FromSlash(serial.pipe.filename)
case *serialConfigFile:
templateData.Serial_Type = "file"
templateData.Serial_Filename = filepath.FromSlash(serial.file.filename)
case *serialConfigDevice:
templateData.Serial_Type = "device"
templateData.Serial_Filename = filepath.FromSlash(serial.device.devicename)
case *serialConfigAuto:
templateData.Serial_Type = "device"
templateData.Serial_Filename = filepath.FromSlash(serial.auto.devicename)
templateData.Serial_Yield = serial.auto.yield
templateData.Serial_Auto = "TRUE"
case nil:
templateData.Serial_Present = "FALSE"
break
default:
err := fmt.Errorf("Error procesing VMX template: %v", serial)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
/// check if parallel port has been configured
if config.Parallel == "" {
templateData.Parallel_Present = "FALSE"
} else {
parallel, err := unformat_parallel(config.Parallel)
if err != nil {
err := fmt.Errorf("Error procesing VMX template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
templateData.Parallel_Auto = "FALSE"
switch parallel.parallelType.(type) {
case *parallelPortFile:
templateData.Parallel_Present = "TRUE"
templateData.Parallel_Filename = filepath.FromSlash(parallel.file.filename)
case *parallelPortDevice:
templateData.Parallel_Present = "TRUE"
templateData.Parallel_Bidirectional = parallel.device.bidirectional
templateData.Parallel_Filename = filepath.FromSlash(parallel.device.devicename)
case *parallelPortAuto:
templateData.Parallel_Present = "TRUE"
templateData.Parallel_Auto = "TRUE"
templateData.Parallel_Bidirectional = parallel.auto.bidirectional
case nil:
templateData.Parallel_Present = "FALSE"
break
default:
err := fmt.Errorf("Error procesing VMX template: %v", parallel)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ctx.Data = &templateData
/// render the .vmx template
vmxContents, err := interpolate.Render(vmxTemplate, &ctx)
if err != nil {
err := fmt.Errorf("Error procesing VMX template: %s", err)
@ -176,12 +631,13 @@ ehci.pciSlotNumber = "34"
ehci.present = "TRUE"
ethernet0.addressType = "generated"
ethernet0.bsdName = "en0"
ethernet0.connectionType = "nat"
ethernet0.connectionType = "{{ .Network_Type }}"
ethernet0.vnet = "{{ .Network_Device }}"
ethernet0.displayName = "Ethernet"
ethernet0.linkStatePropagation.enable = "FALSE"
ethernet0.pciSlotNumber = "33"
ethernet0.present = "TRUE"
ethernet0.virtualDev = "e1000"
ethernet0.virtualDev = "{{ .Network_Adapter }}"
ethernet0.wakeOnPcktRcv = "FALSE"
extendedConfigFile = "{{ .Name }}.vmxf"
floppy0.present = "FALSE"
@ -190,9 +646,21 @@ gui.fullScreenAtPowerOn = "FALSE"
gui.viewModeAtPowerOn = "windowed"
hgfs.linkRootShare = "TRUE"
hgfs.mapRootShare = "TRUE"
ide1:0.present = "TRUE"
ide1:0.fileName = "{{ .ISOPath }}"
ide1:0.deviceType = "cdrom-image"
scsi0.present = "{{ .SCSI_Present }}"
scsi0.virtualDev = "{{ .SCSI_diskAdapterType }}"
scsi0.pciSlotNumber = "16"
scsi0:0.redo = ""
sata0.present = "{{ .SATA_Present }}"
nvme0.present = "{{ .NVME_Present }}"
{{ .DiskType }}0:0.present = "TRUE"
{{ .DiskType }}0:0.fileName = "{{ .DiskName }}.vmdk"
{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.present = "TRUE"
{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.fileName = "{{ .ISOPath }}"
{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.deviceType = "cdrom-image"
isolation.tools.hgfs.disable = "FALSE"
memsize = "512"
nvram = "{{ .Name }}.nvram"
@ -221,17 +689,38 @@ powerType.suspend = "soft"
proxyApps.publishToHost = "FALSE"
replay.filename = ""
replay.supported = "FALSE"
scsi0.pciSlotNumber = "16"
scsi0.present = "TRUE"
scsi0.virtualDev = "lsilogic"
scsi0:0.fileName = "{{ .DiskName }}.vmdk"
scsi0:0.present = "TRUE"
scsi0:0.redo = ""
sound.startConnected = "FALSE"
// Sound
sound.startConnected = "{{ .Sound_Present }}"
sound.present = "{{ .Sound_Present }}"
sound.fileName = "-1"
sound.autodetect = "TRUE"
tools.syncTime = "TRUE"
tools.upgrade.policy = "upgradeAtPowerCycle"
// USB
usb.pciSlotNumber = "32"
usb.present = "FALSE"
usb.present = "{{ .Usb_Present }}"
usb_xhci.present = "TRUE"
// Serial
serial0.present = "{{ .Serial_Present }}"
serial0.startConnected = "{{ .Serial_Present }}"
serial0.fileName = "{{ .Serial_Filename }}"
serial0.autodetect = "{{ .Serial_Auto }}"
serial0.fileType = "{{ .Serial_Type }}"
serial0.yieldOnMsrRead = "{{ .Serial_Yield }}"
serial0.pipe.endPoint = "{{ .Serial_Endpoint }}"
serial0.tryNoRxLoss = "{{ .Serial_Host }}"
// Parallel
parallel0.present = "{{ .Parallel_Present }}"
parallel0.startConnected = "{{ .Parallel_Present }}"
parallel0.fileName = "{{ .Parallel_Filename }}"
parallel0.autodetect = "{{ .Parallel_Auto }}"
parallel0.bidirectional = "{{ .Parallel_Bidirectional }}"
virtualHW.productCompatibility = "hosted"
virtualHW.version = "{{ .Version }}"
vmci0.id = "1861462627"

View File

@ -0,0 +1,409 @@
package iso
import (
"bytes"
"fmt"
"io/ioutil"
"math"
"math/rand"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner/shell"
"github.com/hashicorp/packer/template"
"testing"
)
var vmxTestBuilderConfig = map[string]string{
"type": `"vmware-iso"`,
"iso_url": `"https://archive.org/download/ut-ttylinux-i686-12.6/ut-ttylinux-i686-12.6.iso"`,
"iso_checksum_type": `"md5"`,
"iso_checksum": `"43c1feeae55a44c6ef694b8eb18408a6"`,
"ssh_username": `"root"`,
"ssh_password": `"password"`,
"ssh_wait_timeout": `"45s"`,
"boot_command": `["<enter><wait5><wait10>","root<enter><wait>password<enter><wait>","udhcpc<enter><wait>"]`,
"shutdown_command": `"/sbin/shutdown -h; exit 0"`,
}
var vmxTestProvisionerConfig = map[string]string{
"type": `"shell"`,
"inline": `["echo hola mundo"]`,
}
const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}`
func tmpnam(prefix string) string {
var path string
var err error
const length = 16
dir := os.TempDir()
max := int(math.Pow(2, float64(length)))
n, err := rand.Intn(max), nil
for path = filepath.Join(dir, prefix+strconv.Itoa(n)); err == nil; _, err = os.Stat(path) {
n = rand.Intn(max)
path = filepath.Join(dir, prefix+strconv.Itoa(n))
}
return path
}
func createFloppyOutput(prefix string) (string, string, error) {
output := tmpnam(prefix)
f, err := os.Create(output)
if err != nil {
return "", "", fmt.Errorf("Unable to create empty %s: %s", output, err)
}
f.Close()
vmxData := []string{
`"floppy0.present":"TRUE"`,
`"floppy0.fileType":"file"`,
`"floppy0.clientDevice":"FALSE"`,
`"floppy0.fileName":"%s"`,
`"floppy0.startConnected":"TRUE"`,
}
outputFile := strings.Replace(output, "\\", "\\\\", -1)
vmxString := fmt.Sprintf("{"+strings.Join(vmxData, ",")+"}", outputFile)
return output, vmxString, nil
}
func readFloppyOutput(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("Unable to open file %s", path)
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return "", fmt.Errorf("Unable to read file: %s", err)
}
if len(data) == 0 {
return "", nil
}
return string(data[:bytes.IndexByte(data, 0)]), nil
}
func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisionerConfig map[string]string) error {
ui := packer.TestUi(t)
// create builder config and update with user-supplied options
cfgBuilder := map[string]string{}
for k, v := range vmxTestBuilderConfig {
cfgBuilder[k] = v
}
for k, v := range builderConfig {
cfgBuilder[k] = v
}
// convert our builder config into a single sprintfable string
builderLines := []string{}
for k, v := range cfgBuilder {
builderLines = append(builderLines, fmt.Sprintf(`"%s":%s`, k, v))
}
// create provisioner config and update with user-supplied options
cfgProvisioner := map[string]string{}
for k, v := range vmxTestProvisionerConfig {
cfgProvisioner[k] = v
}
for k, v := range provisionerConfig {
cfgProvisioner[k] = v
}
// convert our provisioner config into a single sprintfable string
provisionerLines := []string{}
for k, v := range cfgProvisioner {
provisionerLines = append(provisionerLines, fmt.Sprintf(`"%s":%s`, k, v))
}
// and now parse them into a template
configString := fmt.Sprintf(vmxTestTemplate, strings.Join(builderLines, `,`), strings.Join(provisionerLines, `,`))
tpl, err := template.Parse(strings.NewReader(configString))
if err != nil {
t.Fatalf("Unable to parse test config: %s", err)
}
// create our config to test the vmware-iso builder
components := packer.ComponentFinder{
Builder: func(n string) (packer.Builder, error) {
return &Builder{}, nil
},
Hook: func(n string) (packer.Hook, error) {
return &packer.DispatchHook{}, nil
},
PostProcessor: func(n string) (packer.PostProcessor, error) {
return &packer.MockPostProcessor{}, nil
},
Provisioner: func(n string) (packer.Provisioner, error) {
return &shell.Provisioner{}, nil
},
}
config := packer.CoreConfig{
Template: tpl,
Components: components,
}
// create a core using our template
core, err := packer.NewCore(&config)
if err != nil {
t.Fatalf("Unable to create core: %s", err)
}
// now we can prepare our build
b, err := core.Build("vmware-iso")
if err != nil {
t.Fatalf("Unable to create build: %s", err)
}
warn, err := b.Prepare()
if len(warn) > 0 {
for _, w := range warn {
t.Logf("Configuration warning: %s", w)
}
}
// and then finally build it
cache := &packer.FileCache{CacheDir: os.TempDir()}
artifacts, err := b.Run(ui, cache)
if err != nil {
t.Fatalf("Failed to build artifact: %s", err)
}
// check to see that we only got one artifact back
if len(artifacts) == 1 {
return artifacts[0].Destroy()
}
// otherwise some number of errors happened
t.Logf("Unexpected number of artifacts returned: %d", len(artifacts))
errors := make([]error, 0)
for _, artifact := range artifacts {
if err := artifact.Destroy(); err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
t.Errorf("%d Errors returned while trying to destroy artifacts", len(errors))
return fmt.Errorf("Error while trying to destroy artifacts: %v", errors)
}
return nil
}
func TestStepCreateVmx_SerialFile(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.")
}
tmpfile := tmpnam("SerialFileInput.")
serialConfig := map[string]string{
"serial": fmt.Sprintf(`"file:%s"`, filepath.ToSlash(tmpfile)),
}
error := setupVMwareBuild(t, serialConfig, map[string]string{})
if error != nil {
t.Errorf("Unable to read file: %s", error)
}
f, err := os.Stat(tmpfile)
if err != nil {
t.Errorf("VMware builder did not create a file for serial port: %s", err)
}
if f != nil {
if err := os.Remove(tmpfile); err != nil {
t.Fatalf("Unable to remove file %s: %s", tmpfile, err)
}
}
}
func TestStepCreateVmx_SerialPort(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.")
}
var defaultSerial string
if runtime.GOOS == "windows" {
defaultSerial = "COM1"
} else {
defaultSerial = "/dev/ttyS0"
}
config := map[string]string{
"serial": fmt.Sprintf(`"device:%s"`, filepath.ToSlash(defaultSerial)),
}
provision := map[string]string{
"inline": `"dmesg | egrep -o '^serial8250: ttyS1 at' > /dev/fd0"`,
}
// where to write output
output, vmxData, err := createFloppyOutput("SerialPortOutput.")
if err != nil {
t.Fatalf("Error creating output: %s", err)
}
defer func() {
if _, err := os.Stat(output); err == nil {
os.Remove(output)
}
}()
config["vmx_data"] = vmxData
t.Logf("Preparing to write output to %s", output)
// whee
err = setupVMwareBuild(t, config, provision)
if err != nil {
t.Errorf("%s", err)
}
// check the output
data, err := readFloppyOutput(output)
if err != nil {
t.Errorf("%s", err)
}
if data != "serial8250: ttyS1 at\n" {
t.Errorf("Serial port not detected : %v", data)
}
}
func TestStepCreateVmx_ParallelPort(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.")
}
var defaultParallel string
if runtime.GOOS == "windows" {
defaultParallel = "LPT1"
} else {
defaultParallel = "/dev/lp0"
}
config := map[string]string{
"parallel": fmt.Sprintf(`"device:%s,uni"`, filepath.ToSlash(defaultParallel)),
}
provision := map[string]string{
"inline": `"cat /proc/modules | egrep -o '^parport ' > /dev/fd0"`,
}
// where to write output
output, vmxData, err := createFloppyOutput("ParallelPortOutput.")
if err != nil {
t.Fatalf("Error creating output: %s", err)
}
defer func() {
if _, err := os.Stat(output); err == nil {
os.Remove(output)
}
}()
config["vmx_data"] = vmxData
t.Logf("Preparing to write output to %s", output)
// whee
error := setupVMwareBuild(t, config, provision)
if error != nil {
t.Errorf("%s", error)
}
// check the output
data, err := readFloppyOutput(output)
if err != nil {
t.Errorf("%s", err)
}
if data != "parport \n" {
t.Errorf("Parallel port not detected : %v", data)
}
}
func TestStepCreateVmx_Usb(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.")
}
config := map[string]string{
"usb": `"TRUE"`,
}
provision := map[string]string{
"inline": `"dmesg | egrep -m1 -o 'USB hub found$' > /dev/fd0"`,
}
// where to write output
output, vmxData, err := createFloppyOutput("UsbOutput.")
if err != nil {
t.Fatalf("Error creating output: %s", err)
}
defer func() {
if _, err := os.Stat(output); err == nil {
os.Remove(output)
}
}()
config["vmx_data"] = vmxData
t.Logf("Preparing to write output to %s", output)
// whee
error := setupVMwareBuild(t, config, provision)
if error != nil {
t.Errorf("%s", error)
}
// check the output
data, err := readFloppyOutput(output)
if err != nil {
t.Errorf("%s", err)
}
if data != "USB hub found\n" {
t.Errorf("USB support not detected : %v", data)
}
}
func TestStepCreateVmx_Sound(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.")
}
config := map[string]string{
"sound": `"TRUE"`,
}
provision := map[string]string{
"inline": `"cat /proc/modules | egrep -o '^soundcore' > /dev/fd0"`,
}
// where to write output
output, vmxData, err := createFloppyOutput("SoundOutput.")
if err != nil {
t.Fatalf("Error creating output: %s", err)
}
defer func() {
if _, err := os.Stat(output); err == nil {
os.Remove(output)
}
}()
config["vmx_data"] = vmxData
t.Logf("Preparing to write output to %s", output)
// whee
error := setupVMwareBuild(t, config, provision)
if error != nil {
t.Errorf("Unable to read file: %s", error)
}
// check the output
data, err := readFloppyOutput(output)
if err != nil {
t.Errorf("%s", err)
}
if data != "soundcore\n" {
t.Errorf("Soundcard not detected : %v", data)
}
}

View File

@ -54,6 +54,16 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste
return multistep.ActionHalt
}
var networkType string
if _, ok := vmxData["ethernet0.connectionType"]; ok {
networkType = vmxData["ethernet0.connectionType"]
}
if networkType == "" {
networkType = "nat"
log.Printf("Defaulting to network type : nat")
}
state.Put("vmnetwork", networkType)
state.Put("full_disk_path", filepath.Join(s.OutputDir, diskName))
state.Put("vmx_path", vmxPath)
return multistep.ActionContinue

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -131,7 +131,22 @@ builder.
Guide](https://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop
VMware clients. For ESXi, refer to the proper ESXi documentation.
* `disable_vnc` (boolean) - Whether to create a VNC connection or not.
- `disk_adapter_type` (string) - The adapter type of the VMware virtual disk
to create. This option is for advanced usage, modify only if you know what
you're doing. Some of the options you can specify are "ide", "sata", "nvme"
or "scsi" (which uses the "lsilogic" scsi interface by default). If you
specify another option, Packer will assume that you're specifying a "scsi"
interface of that specified type. For more information, please consult the
<a href="http://www.vmware.com/pdf/VirtualDiskManager.pdf" target="_blank"><img src="../../assets/images/Adobe_PDF_file_icon_24x24.png"/> Virtual Disk Manager User's Guide</a> for desktop VMware clients.
For ESXi, refer to the proper ESXi documentation.
- `cdrom_adapter_type` (string) - The adapter type (or bus) that will be used
by the cdrom device. This is chosen by default based on the disk adapter
type. VMware tends to lean towards "ide" for the cdrom device unless
"sata" is chosen for the disk adapter and so Packer attempts to mirror
this logic. This field can be specified as either "ide", "sata", or "scsi".
- `disable_vnc` (boolean) - Whether to create a VNC connection or not.
A `boot_command` cannot be used when this is `false`. Defaults to `false`.
- `floppy_files` (array of strings) - A list of files to place onto a floppy
@ -193,6 +208,17 @@ builder.
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.
- `network` (string) - This is the network type that the virtual machine will
be created with. This can be one of the generic values that map to a device
such as "hostonly", "nat", or "bridged". If the network is not one of these
values, then it is assumed to be a VMware network device. (VMnet0..x)
- `network_adapter_type` (string) - This is the ethernet adapter type the the
virtual machine will be created with. By default the "e1000" network adapter
type will be used by Packer. For more information, please consult the
<a href="https://kb.vmware.com/s/article/1001805" target="_blank">Choosing a network adapter for your virtual machine</a> for desktop VMware
clients. For ESXi, refer to the proper ESXi documentation.
- `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`
@ -200,6 +226,20 @@ builder.
the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the
name of the build.
- `parallel` (string) - This specifies a parallel port to add to the VM. It
has the format of `Type:option1,option2,...`. Type can be one of the
following values: "FILE", "DEVICE", "AUTO", or "NONE".
* `FILE:path` - Specifies the path to the local file to be used for the
parallel port.
* `DEVICE:path` - Specifies the path to the local device to be used for the
parallel port.
* `AUTO:direction` - Specifies to use auto-detection to determine the
parallel port. Direction can be `BI` to specify
bidirectional communication or `UNI` to specify
unidirectional communication.
* `NONE` - Specifies to not use a parallel port. (default)
- `remote_cache_datastore` (string) - The path to the datastore where
supporting files will be stored during the build on the remote machine. By
default this is the same as the `remote_datastore` option. This only has an
@ -234,6 +274,41 @@ builder.
- `remote_username` (string) - The username for the SSH user that will access
the remote machine. This is required if `remote_type` is enabled.
- `serial` (string) - This specifies a serial port to add to the VM.
It has a format of `Type:option1,option2,...`. The field `Type` can be one
of the following values: `FILE`, `DEVICE`, `PIPE`, `AUTO`, or `NONE`.
* `FILE:path(,yield)` - Specifies the path to the local file to be used as the
serial port.
* `yield` (bool) - This is an optional boolean that specifies whether
the vm should yield the cpu when polling the port.
By default, the builder will assume this as `FALSE`.
* `DEVICE:path(,yield)` - Specifies the path to the local device to be used
as the serial port. If `path` is empty, then
default to the first serial port.
* `yield` (bool) - This is an optional boolean that specifies whether
the vm should yield the cpu when polling the port.
By default, the builder will assume this as `FALSE`.
* `PIPE:path,endpoint,host(,yield)` - Specifies to use the named-pipe "path"
as a serial port. This has a few
options that determine how the VM
should use the named-pipe.
* `endpoint` (string) - Chooses the type of the VM-end, which can be
either a `client` or `server`.
* `host` (string) - Chooses the type of the host-end, which can be either
an `app` (application) or `vm` (another virtual-machine).
* `yield` (bool) - This is an optional boolean that specifies whether
the vm should yield the cpu when polling the port.
By default, the builder will assume this as `FALSE`.
* `AUTO:(yield)` - Specifies to use auto-detection to determine the serial
port to use. This has one option to determine how the VM
should support the serial port.
* `yield` (bool) - This is an optional boolean that specifies whether
the vm should yield the cpu when polling the port.
By default, the builder will assume this as `FALSE`.
* `NONE` - Specifies to not use a serial port. (default)
- `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.
@ -264,6 +339,8 @@ builder.
`--noSSLVerify`, `--skipManifestCheck`, and `--targetType` are reserved,
and should not be passed to this argument.
- `sound` (boolean) - Enable VMware's virtual soundcard device for the VM.
- `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to
upload into the VM. Valid values are "darwin", "linux", and "windows". By
default, this is empty, which means VMware tools won't be uploaded.
@ -276,6 +353,8 @@ builder.
By default the upload path is set to `{{.Flavor}}.iso`. This setting is not
used when `remote_type` is "esx5".
- `usb` (boolean) - Enable VMware's USB bus for the VM.
- `version` (string) - The [vmx hardware
version](http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003746)
for the new virtual machine. Only the default value has been tested, any