mirror of
https://github.com/hashicorp/packer.git
synced 2026-06-10 09:10:27 -04:00
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
This commit is contained in:
parent
ff1ffd90e0
commit
75d3ea7cee
22 changed files with 1648 additions and 338 deletions
|
|
@ -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
|
||||
|
|
|
|||
974
builder/vmware/common/driver_parser.go
Normal file
974
builder/vmware/common/driver_parser.go
Normal file
|
|
@ -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 := ¶llelPortFile{ filename: comptype[1] }
|
||||
return ¶llelUnion{ 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 ¶llelUnion{ 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…
Reference in a new issue