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:
commit
4be86e2e18
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -35,3 +35,7 @@ func (d *Player6Driver) Verify() error {
|
|||
|
||||
return playerVerifyVersion(VMWARE_PLAYER_VERSION)
|
||||
}
|
||||
|
||||
func (d *Player6Driver) GetVmwareDriver() VmwareDriver {
|
||||
return d.Player5Driver.VmwareDriver
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
@ -28,16 +29,49 @@ 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 {
|
||||
|
|
|
@ -33,3 +33,7 @@ func (d *Workstation10Driver) Verify() error {
|
|||
|
||||
return workstationVerifyVersion(VMWARE_WS_VERSION)
|
||||
}
|
||||
|
||||
func (d *Workstation10Driver) GetVmwareDriver() VmwareDriver {
|
||||
return d.Workstation9Driver.VmwareDriver
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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";
|
||||
|
||||
`
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 == "" {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 := ¶llelPortFile{filename: filepath.FromSlash(formatOptions)}
|
||||
return ¶llelUnion{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 ¶llelUnion{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 ¶llelUnion{parallelType: res, auto: res}, nil
|
||||
|
||||
case "NONE":
|
||||
return ¶llelUnion{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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue