Added support for sound, serial ports, parallel ports, usb, and specifying a default network to the vmware builder.

builder/vmware/{iso,vmx}:
    Added the specific configuration options that get parsed.
    Normalize paths when pulling them from the json template so that they'll work on Windows too.
    Added some improved error checking when parsing these options.
    Stash the vm's network connection type so that other steps can figure out addressing information
    Modified the esx5 driver to support the new addressing logic.
    Modified the template in step_create_vmx to include the new options.

builder/vmware/common:
    Implemented a parser for vmware's configuration files to the vmware builder.
    Modified the driver's interface to include support for resolving both guest/host hw and ip addresses
    Implemented a base structure with some methods that implement these features.
    Rewrote all ip and mac address dependent code to utilize these new methods.
    Removed host_ip and guest_ip due to their logic being moved directly into a
        base-structure used by each driver. The code was explicitly checking runtime.GOOS
        instead of portably using net.Interfaces() anyways.
    Updated driver_mock to support the new addressing methods
pull/3417/head
Ali Rizvi-Santiago 11 years ago
parent ff1ffd90e0
commit 75d3ea7cee

@ -1,14 +1,19 @@
package common
import (
"errors"
"bytes"
"fmt"
"log"
"os"
"os/exec"
"io/ioutil"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"net"
"github.com/hashicorp/packer/helper/multistep"
)
@ -29,10 +34,6 @@ type Driver interface {
// 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,32 @@ 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.
// 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 +211,218 @@ func compareVersions(versionFound string, versionWanted string, product string)
return nil
}
// helper functions that read configuration information from a file
func readNetmapConfig(path string) (NetworkMap,error) {
fd,err := os.Open(path)
if err != nil { return nil, err }
defer fd.Close()
return ReadNetworkMap(fd)
}
func readDhcpConfig(path string) (DhcpConfiguration,error) {
fd,err := os.Open(path)
if err != nil { return nil, err }
defer fd.Close()
return ReadDhcpConfiguration(fd)
}
// 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
VmnetnatConfPath func() string
DhcpConfPath func() string
NetmapConfPath func() string
}
func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string,error) {
vmxPath := state.Get("vmx_path").(string)
log.Println("Lookup up IP information...")
f, err := os.Open(vmxPath)
if err != nil {
return "", err
}
defer f.Close()
vmxBytes, err := ioutil.ReadAll(f)
if err != nil {
return "", err
}
vmxData := ParseVMX(string(vmxBytes))
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) {
// read netmap config
pathNetmap := d.NetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
netmap,err := readNetmapConfig(pathNetmap)
if err != nil { return "",err }
// convert the stashed network to a device
network := state.Get("vmnetwork").(string)
device,err := netmap.NameIntoDevice(network)
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) {
// parse network<->device mapping
pathNetmap := d.NetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
netmap,err := readNetmapConfig(pathNetmap)
if err != nil { return "",err }
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath()
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 }
// convert network to name
network := state.Get("vmnetwork").(string)
device,err := netmap.NameIntoDevice(network)
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) {
// parse network<->device mapping
pathNetmap := d.NetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
netmap,err := readNetmapConfig(pathNetmap)
if err != nil { return "",err }
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath()
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 }
// convert network to name
network := state.Get("vmnetwork").(string)
device,err := netmap.NameIntoDevice(network)
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
@ -139,6 +141,11 @@ func (d *Fusion5Driver) Verify() error {
return err
}
// default paths
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
}
return nil
}
@ -158,10 +165,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">

@ -34,6 +34,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,6 +78,12 @@ type DriverMock struct {
DhcpLeasesPathDevice string
DhcpLeasesPathResult string
NetmapPathCalled bool
NetmapPathResult string
DhcpConfPathCalled bool
DhcpConfPathResult string
VerifyCalled bool
VerifyErr error
}
@ -98,6 +124,30 @@ func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) {
return d.CommHostResult, d.CommHostErr
}
func (d *DriverMock) HostAddress(state multistep.StateBag) (string, error) {
d.HostAddressCalled = true
d.HostAddressState = state
return d.HostAddressResult, d.HostAddressErr
}
func (d *DriverMock) HostIP(state multistep.StateBag) (string, error) {
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,6 +184,16 @@ func (d *DriverMock) DhcpLeasesPath(device string) string {
return d.DhcpLeasesPathResult
}
func (d *DriverMock) NetmapPath(device string) string {
d.NetmapPathCalled = true
return d.NetmapPathResult
}
func (d *DriverMock) DhcpConfPath(device string) string {
d.DhcpConfPathCalled = true
return d.DhcpConfPathResult
}
func (d *DriverMock) Verify() error {
d.VerifyCalled = true
return d.VerifyErr

@ -0,0 +1,974 @@
package common
import (
"fmt"
"os"
"io"
"strings"
"strconv"
"net"
"sort"
)
type sentinelSignaller chan struct{}
/** low-level parsing */
// strip the comments and extraneous newlines from a byte channel
func uncomment(eof sentinelSignaller, in <-chan byte) chan byte {
out := make(chan byte)
go func(in <-chan byte, out chan byte) {
var endofline bool
for stillReading := true; stillReading; {
select {
case <-eof:
stillReading = false
case ch := <-in:
switch ch {
case '#':
endofline = true
case '\n':
if endofline { endofline = false }
}
if !endofline { out <- ch }
}
}
}(in, out)
return out
}
// convert a byte channel into a channel of pseudo-tokens
func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) chan string {
var ch byte
var state string
var quote bool
out := make(chan string)
go func(out chan string) {
for stillReading := true; stillReading; {
select {
case <-eof:
stillReading = false
case ch = <-in:
if quote {
if ch == '"' {
out <- state + string(ch)
state,quote = "",false
continue
}
state += string(ch)
continue
}
switch ch {
case '"':
quote = true
state += string(ch)
continue
case '\r':
fallthrough
case '\n':
fallthrough
case '\t':
fallthrough
case ' ':
if len(state) == 0 { continue }
out <- state
state = ""
case '{': fallthrough
case '}': fallthrough
case ';':
if len(state) > 0 { out <- state }
out <- string(ch)
state = ""
default:
state += string(ch)
}
}
}
if len(state) > 0 { out <- state }
}(out)
return out
}
/** mid-level parsing */
type tkParameter struct {
name string
operand []string
}
func (e *tkParameter) String() string {
var values []string
for _,val := range e.operand {
values = append(values, val)
}
return fmt.Sprintf("%s [%s]", e.name, strings.Join(values, ","))
}
type tkGroup struct {
parent *tkGroup
id tkParameter
groups []*tkGroup
params []tkParameter
}
func (e *tkGroup) String() string {
var id []string
id = append(id, e.id.name)
for _,val := range e.id.operand {
id = append(id, val)
}
var config []string
for _,val := range e.params {
config = append(config, val.String())
}
return fmt.Sprintf("%s {\n%s\n}", strings.Join(id, " "), strings.Join(config, "\n"))
}
// convert a channel of pseudo-tokens into an tkParameter struct
func parseTokenParameter(in chan string) tkParameter {
var result tkParameter
for {
token := <-in
if result.name == "" {
result.name = token
continue
}
switch token {
case "{": fallthrough
case "}": fallthrough
case ";": goto leave
default:
result.operand = append(result.operand, token)
}
}
leave:
return result
}
// convert a channel of pseudo-tokens into an tkGroup tree */
func parseDhcpConfig(eof sentinelSignaller, in chan string) (tkGroup,error) {
var tokens []string
var result tkGroup
toParameter := func(tokens []string) tkParameter {
out := make(chan string)
go func(out chan string){
for _,v := range tokens { out <- v }
out <- ";"
}(out)
return parseTokenParameter(out)
}
for stillReading,currentgroup := true,&result; stillReading; {
select {
case <-eof:
stillReading = false
case tk := <-in:
switch tk {
case "{":
grp := &tkGroup{parent:currentgroup}
grp.id = toParameter(tokens)
currentgroup.groups = append(currentgroup.groups, grp)
currentgroup = grp
case "}":
if currentgroup.parent == nil {
return tkGroup{}, fmt.Errorf("Unable to close the global declaration")
}
if len(tokens) > 0 {
return tkGroup{}, fmt.Errorf("List of tokens was left unterminated : %v", tokens)
}
currentgroup = currentgroup.parent
case ";":
arg := toParameter(tokens)
currentgroup.params = append(currentgroup.params, arg)
default:
tokens = append(tokens, tk)
continue
}
tokens = []string{}
}
}
return result,nil
}
func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) chan string {
var ch byte
var state string
var quote bool
var lastnewline bool
out := make(chan string)
go func(out chan string) {
for stillReading := true; stillReading; {
select {
case <-eof:
stillReading = false
case ch = <-in:
if quote {
if ch == '"' {
out <- state + string(ch)
state,quote = "",false
continue
}
state += string(ch)
continue
}
switch ch {
case '"':
quote = true
state += string(ch)
continue
case '\r':
fallthrough
case '\t':
fallthrough
case ' ':
if len(state) == 0 { continue }
out <- state
state = ""
case '\n':
if lastnewline { continue }
if len(state) > 0 { out <- state }
out <- string(ch)
state = ""
lastnewline = true
continue
case '.': fallthrough
case '=':
if len(state) > 0 { out <- state }
out <- string(ch)
state = ""
default:
state += string(ch)
}
lastnewline = false
}
}
if len(state) > 0 { out <- state }
}(out)
return out
}
func parseNetworkMapConfig(eof sentinelSignaller, in chan string) (NetworkMap,error) {
var unsorted map[string]map[string]string
var state []string
addResult := func(network string, attribute string, value string) error {
_,ok := unsorted[network]
if !ok { unsorted[network] = make(map[string]string) }
val,err := strconv.Unquote(value)
if err != nil { return err }
current := unsorted[network]
current[attribute] = val
return nil
}
stillReading := true
for unsorted = make(map[string]map[string]string); stillReading; {
select {
case <-eof:
if len(state) == 3 {
err := addResult(state[0], state[1], state[2])
if err != nil { return nil,err }
}
stillReading = false
case tk := <-in:
switch tk {
case ".":
if len(state) != 1 { return nil,fmt.Errorf("Missing network index") }
case "=":
if len(state) != 2 { return nil,fmt.Errorf("Assignment to empty attribute") }
case "\n":
if len(state) == 0 { continue }
if len(state) != 3 { return nil,fmt.Errorf("Invalid attribute assignment : %v", state) }
err := addResult(state[0], state[1], state[2])
if err != nil { return nil,err }
state = make([]string, 0)
default:
state = append(state, tk)
}
}
}
result := make([]map[string]string, 0)
var keys []string
for k := range unsorted { keys = append(keys, k) }
sort.Strings(keys)
for _,k := range keys {
result = append(result, unsorted[k])
}
return result,nil
}
/** higher-level parsing */
/// parameters
type pParameter interface { repr() string }
type pParameterInclude struct {
filename string
}
func (e pParameterInclude) repr() string { return fmt.Sprintf("include-file:filename=%s",e.filename) }
type pParameterOption struct {
name string
value string
}
func (e pParameterOption) repr() string { return fmt.Sprintf("option:%s=%s",e.name,e.value) }
// allow some-kind-of-something
type pParameterGrant struct {
verb string // allow,deny,ignore
attribute string
}
func (e pParameterGrant) repr() string { return fmt.Sprintf("grant:%s,%s",e.verb,e.attribute) }
type pParameterAddress4 []string
func (e pParameterAddress4) repr() string {
return fmt.Sprintf("fixed-address4:%s",strings.Join(e,","))
}
type pParameterAddress6 []string
func (e pParameterAddress6) repr() string {
return fmt.Sprintf("fixed-address6:%s",strings.Join(e,","))
}
// hardware address 00:00:00:00:00:00
type pParameterHardware struct {
class string
address []byte
}
func (e pParameterHardware) repr() string {
res := make([]string, 0)
for _,v := range e.address {
res = append(res, fmt.Sprintf("%02x",v))
}
return fmt.Sprintf("hardware-address:%s[%s]",e.class,strings.Join(res,":"))
}
type pParameterBoolean struct {
parameter string
truancy bool
}
func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%s",e.parameter,e.truancy) }
type pParameterClientMatch struct {
name string
data string
}
func (e pParameterClientMatch) repr() string { return fmt.Sprintf("match-client:%s=%s",e.name,e.data) }
// range 127.0.0.1 127.0.0.255
type pParameterRange4 struct {
min net.IP
max net.IP
}
func (e pParameterRange4) repr() string { return fmt.Sprintf("range4:%s-%s",e.min.String(),e.max.String()) }
type pParameterRange6 struct {
min net.IP
max net.IP
}
func (e pParameterRange6) repr() string { return fmt.Sprintf("range6:%s-%s",e.min.String(),e.max.String()) }
type pParameterPrefix6 struct {
min net.IP
max net.IP
bits int
}
func (e pParameterPrefix6) repr() string { return fmt.Sprintf("prefix6:/%d:%s-%s",e.bits,e.min.String(),e.max.String()) }
// some-kind-of-parameter 1024
type pParameterOther struct {
parameter string
value string
}
func (e pParameterOther) repr() string { return fmt.Sprintf("parameter:%s=%s",e.parameter,e.value) }
type pParameterExpression struct {
parameter string
expression string
}
func (e pParameterExpression) repr() string { return fmt.Sprintf("parameter-expression:%s=\"%s\"",e.parameter,e.expression) }
type pDeclarationIdentifier interface { repr() string }
type pDeclaration struct {
id pDeclarationIdentifier
parent *pDeclaration
parameters []pParameter
declarations []pDeclaration
}
func (e *pDeclaration) short() string {
return e.id.repr()
}
func (e *pDeclaration) repr() string {
res := e.short()
var parameters []string
for _,v := range e.parameters {
parameters = append(parameters, v.repr())
}
var groups []string
for _,v := range e.declarations {
groups = append(groups, fmt.Sprintf("-> %s",v.short()))
}
if e.parent != nil {
res = fmt.Sprintf("%s parent:%s",res,e.parent.short())
}
return fmt.Sprintf("%s\n%s\n%s\n", res, strings.Join(parameters,"\n"), strings.Join(groups,"\n"))
}
type pDeclarationGlobal struct {}
func (e pDeclarationGlobal) repr() string { return fmt.Sprintf("{global}") }
type pDeclarationShared struct { name string }
func (e pDeclarationShared) repr() string { return fmt.Sprintf("{shared-network %s}", e.name) }
type pDeclarationSubnet4 struct { net.IPNet }
func (e pDeclarationSubnet4) repr() string { return fmt.Sprintf("{subnet4 %s}", e.String()) }
type pDeclarationSubnet6 struct { net.IPNet }
func (e pDeclarationSubnet6) repr() string { return fmt.Sprintf("{subnet6 %s}", e.String()) }
type pDeclarationHost struct { name string }
func (e pDeclarationHost) repr() string { return fmt.Sprintf("{host name:%s}", e.name) }
type pDeclarationPool struct {}
func (e pDeclarationPool) repr() string { return fmt.Sprintf("{pool}") }
type pDeclarationGroup struct {}
func (e pDeclarationGroup) repr() string { return fmt.Sprintf("{group}") }
type pDeclarationClass struct { name string }
func (e pDeclarationClass) repr() string { return fmt.Sprintf("{class}") }
/** parsers */
func parseParameter(val tkParameter) (pParameter,error) {
switch val.name {
case "include":
if len(val.operand) != 2 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterInclude : %v",val.operand)
}
name := val.operand[0]
return pParameterInclude{filename: name},nil
case "option":
if len(val.operand) != 2 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterOption : %v",val.operand)
}
name, value := val.operand[0], val.operand[1]
return pParameterOption{name: name, value: value},nil
case "allow": fallthrough
case "deny": fallthrough
case "ignore":
if len(val.operand) < 1 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterGrant : %v",val.operand)
}
attribute := strings.Join(val.operand," ")
return pParameterGrant{verb: strings.ToLower(val.name), attribute: attribute},nil
case "range":
if len(val.operand) < 1 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange4 : %v",val.operand)
}
idxAddress := map[bool]int{true:1,false:0}[strings.ToLower(val.operand[0]) == "bootp"]
if len(val.operand) > 2 + idxAddress {
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange : %v",val.operand)
}
if idxAddress + 1 > len(val.operand) {
res := net.ParseIP(val.operand[idxAddress])
return pParameterRange4{min: res, max: res},nil
}
addr1 := net.ParseIP(val.operand[idxAddress])
addr2 := net.ParseIP(val.operand[idxAddress+1])
return pParameterRange4{min: addr1, max: addr2},nil
case "range6":
if len(val.operand) == 1 {
address := val.operand[0]
if (strings.Contains(address, "/")) {
cidr := strings.SplitN(address, "/", 2)
if len(cidr) != 2 { return nil,fmt.Errorf("Unknown ipv6 format : %v", address) }
address := net.ParseIP(cidr[0])
bits,err := strconv.Atoi(cidr[1])
if err != nil { return nil,err }
mask := net.CIDRMask(bits, net.IPv6len*8)
// figure out the network address
network := address.Mask(mask)
// make a broadcast address
broadcast := network
networkSize,totalSize := mask.Size()
hostSize := totalSize-networkSize
for i := networkSize / 8; i < totalSize / 8; i++ {
broadcast[i] = byte(0xff)
}
octetIndex := network[networkSize / 8]
bitsLeft := (uint32)(hostSize%8)
broadcast[octetIndex] = network[octetIndex] | ((1<<bitsLeft)-1)
// FIXME: check that the broadcast address was made correctly
return pParameterRange6{min: network, max: broadcast},nil
}
res := net.ParseIP(address)
return pParameterRange6{min: res, max:res},nil
}
if len(val.operand) == 2 {
addr := net.ParseIP(val.operand[0])
if strings.ToLower(val.operand[1]) == "temporary" {
return pParameterRange6{min: addr, max: addr},nil
}
other := net.ParseIP(val.operand[1])
return pParameterRange6{min: addr, max: other},nil
}
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v",val.operand)
case "prefix6":
if len(val.operand) != 3 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v",val.operand)
}
bits,err := strconv.Atoi(val.operand[2])
if err != nil {
return nil,fmt.Errorf("Invalid bits for pParameterPrefix6 : %v",val.operand[2])
}
minaddr := net.ParseIP(val.operand[0])
maxaddr := net.ParseIP(val.operand[1])
return pParameterPrefix6{min: minaddr, max: maxaddr, bits:bits},nil
case "hardware":
if len(val.operand) != 2 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterHardware : %v",val.operand)
}
class := val.operand[0]
octets := strings.Split(val.operand[1], ":")
address := make([]byte, 0)
for _,v := range octets {
b,err := strconv.ParseInt(v, 16, 0)
if err != nil { return nil,err }
address = append(address, byte(b))
}
return pParameterHardware{class: class, address: address},nil
case "fixed-address":
ip4addrs := make(pParameterAddress4,len(val.operand))
copy(ip4addrs, val.operand)
return ip4addrs,nil
case "fixed-address6":
ip6addrs := make(pParameterAddress6,len(val.operand))
copy(ip6addrs, val.operand)
return ip6addrs,nil
case "host-identifier":
if len(val.operand) != 3 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterClientMatch : %v",val.operand)
}
if val.operand[0] != "option" {
return nil,fmt.Errorf("Invalid match parameter : %v",val.operand[0])
}
optionName := val.operand[1]
optionData := val.operand[2]
return pParameterClientMatch{name: optionName, data: optionData},nil
default:
length := len(val.operand)
if length < 1 {
return pParameterBoolean{parameter: val.name, truancy: true},nil
} else if length > 1 {
if val.operand[0] == "=" {
return pParameterExpression{parameter: val.name, expression: strings.Join(val.operand[1:],"")},nil
}
}
if length != 1 {
return nil,fmt.Errorf("Invalid number of parameters for pParameterOther : %v",val.operand)
}
if strings.ToLower(val.name) == "not" {
return pParameterBoolean{parameter: val.operand[0], truancy: false},nil
}
return pParameterOther{parameter: val.name, value: val.operand[0]}, nil
}
}
func parseTokenGroup(val tkGroup) (*pDeclaration,error) {
params := val.id.operand
switch val.id.name {
case "group":
return &pDeclaration{id:pDeclarationGroup{}},nil
case "pool":
return &pDeclaration{id:pDeclarationPool{}},nil
case "host":
if len(params) == 1 {
return &pDeclaration{id:pDeclarationHost{name: params[0]}},nil
}
case "subnet":
if len(params) == 3 && strings.ToLower(params[1]) == "netmask" {
addr := make([]byte, 4)
for i,v := range strings.SplitN(params[2], ".", 4) {
res,err := strconv.ParseInt(v, 10, 0)
if err != nil { return nil,err }
addr[i] = byte(res)
}
oc1,oc2,oc3,oc4 := addr[0],addr[1],addr[2],addr[3]
if subnet,mask := net.ParseIP(params[0]),net.IPv4Mask(oc1,oc2,oc3,oc4); subnet != nil && mask != nil {
return &pDeclaration{id:pDeclarationSubnet4{net.IPNet{IP:subnet,Mask:mask}}},nil
}
}
case "subnet6":
if len(params) == 1 {
ip6 := strings.SplitN(params[0], "/", 2)
if len(ip6) == 2 && strings.Contains(ip6[0], ":") {
address := net.ParseIP(ip6[0])
prefix,err := strconv.Atoi(ip6[1])
if err != nil { return nil, err }
return &pDeclaration{id:pDeclarationSubnet6{net.IPNet{IP:address,Mask:net.CIDRMask(prefix, net.IPv6len*8)}}},nil
}
}
case "shared-network":
if len(params) == 1 {
return &pDeclaration{id:pDeclarationShared{name: params[0]}},nil
}
case "":
return &pDeclaration{id:pDeclarationGlobal{}},nil
}
return nil,fmt.Errorf("Invalid pDeclaration : %v : %v", val.id.name, params)
}
func flattenDhcpConfig(root tkGroup) (*pDeclaration,error) {
var result *pDeclaration
result,err := parseTokenGroup(root)
if err != nil { return nil,err }
for _,p := range root.params {
param,err := parseParameter(p)
if err != nil { return nil,err }
result.parameters = append(result.parameters, param)
}
for _,p := range root.groups {
group,err := flattenDhcpConfig(*p)
if err != nil { return nil,err }
group.parent = result
result.declarations = append(result.declarations, *group)
}
return result,nil
}
/** reduce the tree into the things that we care about */
type grant uint
const (
ALLOW grant = iota
IGNORE grant = iota
DENY grant = iota
)
type configDeclaration struct {
id []pDeclarationIdentifier
composites []pDeclaration
address []pParameter
options map[string]string
grants map[string]grant
attributes map[string]bool
parameters map[string]string
expressions map[string]string
hostid []pParameterClientMatch
}
func createDeclaration(node pDeclaration) configDeclaration {
var hierarchy []pDeclaration
for n := &node; n != nil; n = n.parent {
hierarchy = append(hierarchy, *n)
}
var result configDeclaration
result.address = make([]pParameter, 0)
result.options = make(map[string]string)
result.grants = make(map[string]grant)
result.attributes = make(map[string]bool)
result.parameters = make(map[string]string)
result.expressions = make(map[string]string)
result.hostid = make([]pParameterClientMatch, 0)
// walk from globals to pDeclaration collecting all parameters
for i := len(hierarchy)-1; i >= 0; i-- {
result.composites = append(result.composites, hierarchy[(len(hierarchy)-1) - i])
result.id = append(result.id, hierarchy[(len(hierarchy)-1) - i].id)
// update configDeclaration parameters
for _,p := range hierarchy[i].parameters {
switch p.(type) {
case pParameterOption:
result.options[p.(pParameterOption).name] = p.(pParameterOption).value
case pParameterGrant:
Grant := map[string]grant{"ignore":IGNORE, "allow":ALLOW, "deny":DENY}
result.grants[p.(pParameterGrant).attribute] = Grant[p.(pParameterGrant).verb]
case pParameterBoolean:
result.attributes[p.(pParameterBoolean).parameter] = p.(pParameterBoolean).truancy
case pParameterClientMatch:
result.hostid = append(result.hostid, p.(pParameterClientMatch))
case pParameterExpression:
result.expressions[p.(pParameterExpression).parameter] = p.(pParameterExpression).expression
case pParameterOther:
result.parameters[p.(pParameterOther).parameter] = p.(pParameterOther).value
default:
result.address = append(result.address, p)
}
}
}
return result
}
func (e *configDeclaration) repr() string {
var result []string
var res []string
res = make([]string, 0)
for _,v := range e.id { res = append(res, v.repr()) }
result = append(result, strings.Join(res, ","))
if len(e.address) > 0 {
res = make([]string, 0)
for _,v := range e.address { res = append(res, v.repr()) }
result = append(result, fmt.Sprintf("address : %v", strings.Join(res, ",")))
}
if len(e.options) > 0 { result = append(result, fmt.Sprintf("options : %v", e.options)) }
if len(e.grants) > 0 { result = append(result, fmt.Sprintf("grants : %v", e.grants)) }
if len(e.attributes) > 0 { result = append(result, fmt.Sprintf("attributes : %v", e.attributes)) }
if len(e.parameters) > 0 { result = append(result, fmt.Sprintf("parameters : %v", e.parameters)) }
if len(e.expressions) > 0 { result = append(result, fmt.Sprintf("parameter-expressions : %v", e.expressions)) }
if len(e.hostid) > 0 {
res = make([]string, 0)
for _,v := range e.hostid { res = append(res, v.repr()) }
result = append(result, fmt.Sprintf("hostid : %v", strings.Join(res, " ")))
}
return strings.Join(result, "\n") + "\n"
}
func (e *configDeclaration) IP4() (net.IP,error) {
var result []string
for _,entry := range e.address {
switch entry.(type) {
case pParameterAddress4:
for _,s := range entry.(pParameterAddress4) {
result = append(result, s)
}
}
}
if len(result) > 1 {
return nil,fmt.Errorf("More than one address4 returned : %v", result)
} else if len(result) == 0 {
return nil,fmt.Errorf("No IP4 addresses found")
}
if res := net.ParseIP(result[0]); res != nil { return res,nil }
res,err := net.ResolveIPAddr("ip4", result[0])
if err != nil { return nil,err }
return res.IP,nil
}
func (e *configDeclaration) IP6() (net.IP,error) {
var result []string
for _,entry := range e.address {
switch entry.(type) {
case pParameterAddress6:
for _,s := range entry.(pParameterAddress6) {
result = append(result, s)
}
}
}
if len(result) > 1 {
return nil,fmt.Errorf("More than one address6 returned : %v", result)
} else if len(result) == 0 {
return nil,fmt.Errorf("No IP6 addresses found")
}
if res := net.ParseIP(result[0]); res != nil { return res,nil }
res,err := net.ResolveIPAddr("ip6", result[0])
if err != nil { return nil,err }
return res.IP,nil
}
func (e *configDeclaration) Hardware() (net.HardwareAddr,error) {
var result []pParameterHardware
for _,addr := range e.address {
switch addr.(type) {
case pParameterHardware:
result = append(result, addr.(pParameterHardware))
}
}
if len(result) > 0 {
return nil,fmt.Errorf("More than one hardware address returned : %v", result)
}
res := make(net.HardwareAddr, 0)
for _,by := range result[0].address {
res = append(res, by)
}
return res,nil
}
/*** Dhcp Configuration */
type DhcpConfiguration []configDeclaration
func ReadDhcpConfiguration(fd *os.File) (DhcpConfiguration,error) {
fromfile,eof := consumeFile(fd)
uncommented := uncomment(eof, fromfile)
tokenized := tokenizeDhcpConfig(eof, uncommented)
parsetree,err := parseDhcpConfig(eof, tokenized)
if err != nil { return nil,err }
global,err := flattenDhcpConfig(parsetree)
if err != nil { return nil,err }
var walkDeclarations func(root pDeclaration, out chan*configDeclaration);
walkDeclarations = func(root pDeclaration, out chan*configDeclaration) {
res := createDeclaration(root)
out <- &res
for _,p := range root.declarations {
walkDeclarations(p, out)
}
}
each := make(chan*configDeclaration)
go func(out chan*configDeclaration) {
walkDeclarations(*global, out)
out <- nil
}(each)
var result DhcpConfiguration
for decl := <-each; decl != nil; decl = <-each {
result = append(result, *decl)
}
return result,nil
}
func (e *DhcpConfiguration) Global() configDeclaration {
result := (*e)[0]
if len(result.id) != 1 {
panic(fmt.Errorf("Something that can't happen happened"))
}
return result
}
func (e *DhcpConfiguration) SubnetByAddress(address net.IP) (configDeclaration,error) {
var result []configDeclaration
for _,entry := range *e {
switch entry.id[0].(type) {
case pDeclarationSubnet4:
id := entry.id[0].(pDeclarationSubnet4)
if id.Contains(address) {
result = append(result, entry)
}
case pDeclarationSubnet6:
id := entry.id[0].(pDeclarationSubnet6)
if id.Contains(address) {
result = append(result, entry)
}
}
}
if len(result) == 0 {
return configDeclaration{},fmt.Errorf("No network declarations containing %s found", address.String())
}
if len(result) > 1 {
return configDeclaration{},fmt.Errorf("More than 1 network declaration found : %v", result)
}
return result[0],nil
}
func (e *DhcpConfiguration) HostByName(host string) (configDeclaration,error) {
var result []configDeclaration
for _,entry := range *e {
switch entry.id[0].(type) {
case pDeclarationHost:
id := entry.id[0].(pDeclarationHost)
if strings.ToLower(id.name) == strings.ToLower(host) {
result = append(result, entry)
}
}
}
if len(result) == 0 {
return configDeclaration{},fmt.Errorf("No host declarations containing %s found", host)
}
if len(result) > 1 {
return configDeclaration{},fmt.Errorf("More than 1 host declaration found : %v", result)
}
return result[0],nil
}
/*** Network Map */
type NetworkMap []map[string]string
func ReadNetworkMap(fd *os.File) (NetworkMap,error) {
fromfile,eof := consumeFile(fd)
uncommented := uncomment(eof,fromfile)
tokenized := tokenizeNetworkMapConfig(eof, uncommented)
result,err := parseNetworkMapConfig(eof, tokenized)
if err != nil { return nil,err }
return result,nil
}
func (e *NetworkMap) NameIntoDevice(name string) (string,error) {
for _,val := range *e {
if strings.ToLower(val["name"]) == strings.ToLower(name) {
return val["device"],nil
}
}
return "",fmt.Errorf("Network name not found : %v", name)
}
func (e *NetworkMap) DeviceIntoName(device string) (string,error) {
for _,val := range *e {
if strings.ToLower(val["device"]) == strings.ToLower(device) {
return val["name"],nil
}
}
return "",fmt.Errorf("Device name not found : %v", device)
}
func (e *NetworkMap) repr() string {
var result []string
for idx,val := range *e {
result = append(result, fmt.Sprintf("network%d.name = \"%s\"", idx, val["name"]))
result = append(result, fmt.Sprintf("network%d.device = \"%s\"", idx, val["device"]))
}
return strings.Join(result, "\n")
}
/** main */
func consumeFile(fd *os.File) (chan byte,sentinelSignaller) {
fromfile := make(chan byte)
eof := make(sentinelSignaller)
go func() {
b := make([]byte, 1)
for {
_,err := fd.Read(b)
if err == io.EOF { break }
fromfile <- b[0]
}
close(eof)
}()
return fromfile,eof
}

@ -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
@ -181,6 +183,20 @@ 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.VmnetnatConfPath = func() string {
return playerVmnetnatConfPath()
}
d.VmwareDriver.DhcpConfPath = func() string {
return playerVmDhcpConfPath()
}
d.VmwareDriver.NetmapConfPath = func() string {
return playerNetmapConfPath()
}
return nil
}
@ -191,11 +207,3 @@ func (d *Player5Driver) ToolsIsoPath(flavor string) string {
func (d *Player5Driver) ToolsInstall() error {
return nil
}
func (d *Player5Driver) DhcpLeasesPath(device string) string {
return playerDhcpLeasesPath(device)
}
func (d *Player5Driver) VmnetnatConfPath() string {
return playerVmnetnatConfPath()
}

@ -65,6 +65,20 @@ func playerVmnetnatConfPath() string {
return findFile("vmnetnat.conf", playerDataFilePaths())
}
func playerVmDhcpConfPath() string {
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 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 +101,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
}

@ -33,3 +33,4 @@ func (d *Workstation10Driver) Verify() error {
return workstationVerifyVersion(VMWARE_WS_VERSION)
}

@ -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
@ -142,6 +144,23 @@ 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.VmnetnatConfPath = func() string {
return workstationVmnetnatConfPath()
}
d.VmwareDriver.DhcpConfPath = func() string {
return workstationDhcpConfPath()
}
d.VmwareDriver.NetmapConfPath = func() string {
return workstationNetmapConfPath()
}
return nil
}
@ -152,11 +171,3 @@ func (d *Workstation9Driver) ToolsIsoPath(flavor string) string {
func (d *Workstation9Driver) ToolsInstall() error {
return nil
}
func (d *Workstation9Driver) DhcpLeasesPath(device string) string {
return workstationDhcpLeasesPath(device)
}
func (d *Workstation9Driver) VmnetnatConfPath() string {
return workstationVmnetnatConfPath()
}

@ -63,6 +63,14 @@ func workstationVmnetnatConfPath() string {
return findFile("vmnetnat.conf", workstationDataFilePaths())
}
func workstationNetmapConfPath() string {
return findFile("netmap.conf", workstationDataFilePaths())
}
func workstationDhcpConfPath() string {
return findFile("vmnetdhcp.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.

@ -51,6 +51,14 @@ func workstationVmnetnatConfPath() string {
return ""
}
func workstationNetmapConfPath(device string) string {
return "" // FIXME
}
func workstationDhcpConfPath(device string) string {
return "/etc/vmware/" + device + "/dhcpd/dhcpd.conf"
}
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)

@ -64,7 +64,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 +94,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,12 +38,31 @@ type Config struct {
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
// disk drives
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
Format string `mapstructure:"format"`
// platform information
GuestOSType string `mapstructure:"guest_os_type"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"`
// Network 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
BootCommand []string `mapstructure:"boot_command"`
KeepRegistered bool `mapstructure:"keep_registered"`
OVFToolOptions []string `mapstructure:"ovftool_options"`
SkipCompaction bool `mapstructure:"skip_compaction"`
@ -53,6 +72,7 @@ type Config struct {
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"`
@ -158,6 +178,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 == "" {

@ -18,12 +18,16 @@ import (
"github.com/hashicorp/packer/communicator/ssh"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
gossh "golang.org/x/crypto/ssh"
)
// 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 {
vmwcommon.VmwareDriver
Host string
Port uint
Username string
@ -143,10 +147,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 +159,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

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/helper/multistep"
@ -19,6 +20,21 @@ type vmxTemplateData struct {
DiskName string
ISOPath string
Version string
Network 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
Parallel_Present string
Parallel_Bidirectional string
Parallel_Filename string
}
type additionalDiskTemplateData struct {
@ -39,6 +55,121 @@ type stepCreateVMX struct {
tempDir string
}
/* serial conversions */
type serialConfigPipe struct {
filename string
endpoint string
host string
yield string
}
type serialConfigFile struct {
filename string
}
type serialConfigDevice struct {
devicename string
}
type serialUnion struct {
serialType interface{}
pipe *serialConfigPipe
file *serialConfigFile
device *serialConfigDevice
}
func unformat_serial(config string) (*serialUnion,error) {
comptype := strings.SplitN(config, ":", 2)
if len(comptype) < 1 {
return nil,fmt.Errorf("Unexpected format for serial port: %s", config)
}
switch strings.ToUpper(comptype[0]) {
case "PIPE":
comp := strings.Split(comptype[1], ",")
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":
res := &serialConfigFile{ filename : comptype[1] }
return &serialUnion{serialType:res, file:res},nil
case "DEVICE":
res := new(serialConfigDevice)
res.devicename = map[bool]string{true:strings.ToUpper(comptype[1]), false:"COM1"}[len(comptype[1]) > 0]
return &serialUnion{serialType:res, device:res},nil
default:
return nil,fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(comptype[0]), config)
}
}
/* parallel port */
type parallelUnion struct {
parallelType interface{}
file *parallelPortFile
device *parallelPortDevice
}
type parallelPortFile struct {
filename string
}
type parallelPortDevice struct {
bidirectional string
devicename string
}
func unformat_parallel(config string) (*parallelUnion,error) {
comptype := strings.SplitN(config, ":", 2)
if len(comptype) < 1 {
return nil,fmt.Errorf("Unexpected format for parallel port: %s", config)
}
switch strings.ToUpper(comptype[0]) {
case "FILE":
res := &parallelPortFile{ filename: comptype[1] }
return &parallelUnion{ parallelType:res, file: res},nil
case "DEVICE":
comp := strings.Split(comptype[1], ",")
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 = strings.ToUpper(comp[0])
if len(comp) > 1 {
switch strings.ToUpper(comp[1]) {
case "BI":
res.bidirectional = "TRUE"
case "UNI":
res.bidirectional = "FALSE"
default:
return nil,fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(comp[0]), config)
}
}
return &parallelUnion{ parallelType:res, device: res},nil
}
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)
@ -111,14 +242,90 @@ 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,
Network: config.Network,
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",
}
// store the network so that we can later figure out what ip address to bind to
state.Put("vmnetwork", config.Network)
// check if serial port has been configured
if config.Serial != "" {
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 = ""
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)
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 != "" {
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
}
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)
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,7 +383,7 @@ ehci.pciSlotNumber = "34"
ehci.present = "TRUE"
ethernet0.addressType = "generated"
ethernet0.bsdName = "en0"
ethernet0.connectionType = "nat"
ethernet0.connectionType = "{{ .Network }}"
ethernet0.displayName = "Ethernet"
ethernet0.linkStatePropagation.enable = "FALSE"
ethernet0.pciSlotNumber = "33"
@ -227,11 +434,38 @@ 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 = "TRUE"
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 = "TRUE"
parallel0.bidirectional = "{{ .Parallel_Bidirectional }}"
virtualHW.productCompatibility = "hosted"
virtualHW.version = "{{ .Version }}"
vmci0.id = "1861462627"

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

Loading…
Cancel
Save