From 75d3ea7cee0ee2b7a740dcfd62f8a890a0c0dbc8 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Wed, 11 Nov 2015 06:39:06 -0600 Subject: [PATCH 01/23] 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 --- builder/vmware/common/driver.go | 250 ++++- builder/vmware/common/driver_fusion5.go | 11 +- builder/vmware/common/driver_mock.go | 60 ++ builder/vmware/common/driver_parser.go | 974 ++++++++++++++++++ builder/vmware/common/driver_player5.go | 24 +- .../vmware/common/driver_player5_windows.go | 25 + builder/vmware/common/driver_workstation10.go | 1 + builder/vmware/common/driver_workstation9.go | 27 +- .../common/driver_workstation9_windows.go | 8 + .../vmware/common/driver_workstation_unix.go | 8 + builder/vmware/common/guest_ip.go | 90 -- builder/vmware/common/guest_ip_test.go | 82 -- builder/vmware/common/host_ip.go | 7 - .../vmware/common/host_ip_ifconfig_test.go | 11 - builder/vmware/common/host_ip_vmnetnatconf.go | 65 -- .../common/host_ip_vmnetnatconf_test.go | 11 - builder/vmware/common/ssh.go | 24 +- .../vmware/common/step_type_boot_command.go | 13 +- builder/vmware/iso/builder.go | 32 + builder/vmware/iso/driver_esx5.go | 11 +- builder/vmware/iso/step_create_vmx.go | 242 ++++- builder/vmware/vmx/step_clone_vmx.go | 10 + 22 files changed, 1648 insertions(+), 338 deletions(-) create mode 100644 builder/vmware/common/driver_parser.go delete mode 100644 builder/vmware/common/guest_ip.go delete mode 100644 builder/vmware/common/guest_ip_test.go delete mode 100644 builder/vmware/common/host_ip.go delete mode 100644 builder/vmware/common/host_ip_ifconfig_test.go delete mode 100644 builder/vmware/common/host_ip_vmnetnatconf.go delete mode 100644 builder/vmware/common/host_ip_vmnetnatconf_test.go diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index d5f8b44f4..1098e591f 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -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 +} diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index 9545d3e81..4bc5ae301 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -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 = ` diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 9291be976..15e006403 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -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 diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go new file mode 100644 index 000000000..38470eb5b --- /dev/null +++ b/builder/vmware/common/driver_parser.go @@ -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< 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 +} diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 311e5019c..9dce9540c 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -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() -} diff --git a/builder/vmware/common/driver_player5_windows.go b/builder/vmware/common/driver_player5_windows.go index e16d275db..6ca065792 100644 --- a/builder/vmware/common/driver_player5_windows.go +++ b/builder/vmware/common/driver_player5_windows.go @@ -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 } diff --git a/builder/vmware/common/driver_workstation10.go b/builder/vmware/common/driver_workstation10.go index 7e6ac8b8f..716717e39 100644 --- a/builder/vmware/common/driver_workstation10.go +++ b/builder/vmware/common/driver_workstation10.go @@ -33,3 +33,4 @@ func (d *Workstation10Driver) Verify() error { return workstationVerifyVersion(VMWARE_WS_VERSION) } + diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index d870a4cda..0c463a249 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -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() -} diff --git a/builder/vmware/common/driver_workstation9_windows.go b/builder/vmware/common/driver_workstation9_windows.go index 17095badc..e69d5069f 100644 --- a/builder/vmware/common/driver_workstation9_windows.go +++ b/builder/vmware/common/driver_workstation9_windows.go @@ -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. diff --git a/builder/vmware/common/driver_workstation_unix.go b/builder/vmware/common/driver_workstation_unix.go index 2931396a4..2ff94fd37 100644 --- a/builder/vmware/common/driver_workstation_unix.go +++ b/builder/vmware/common/driver_workstation_unix.go @@ -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) diff --git a/builder/vmware/common/guest_ip.go b/builder/vmware/common/guest_ip.go deleted file mode 100644 index 25ca3b795..000000000 --- a/builder/vmware/common/guest_ip.go +++ /dev/null @@ -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 -} diff --git a/builder/vmware/common/guest_ip_test.go b/builder/vmware/common/guest_ip_test.go deleted file mode 100644 index fdd6b4c9c..000000000 --- a/builder/vmware/common/guest_ip_test.go +++ /dev/null @@ -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"; - -` diff --git a/builder/vmware/common/host_ip.go b/builder/vmware/common/host_ip.go deleted file mode 100644 index f920043e7..000000000 --- a/builder/vmware/common/host_ip.go +++ /dev/null @@ -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) -} diff --git a/builder/vmware/common/host_ip_ifconfig_test.go b/builder/vmware/common/host_ip_ifconfig_test.go deleted file mode 100644 index a69104e69..000000000 --- a/builder/vmware/common/host_ip_ifconfig_test.go +++ /dev/null @@ -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") - } -} diff --git a/builder/vmware/common/host_ip_vmnetnatconf.go b/builder/vmware/common/host_ip_vmnetnatconf.go deleted file mode 100644 index e07227a5e..000000000 --- a/builder/vmware/common/host_ip_vmnetnatconf.go +++ /dev/null @@ -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) -} diff --git a/builder/vmware/common/host_ip_vmnetnatconf_test.go b/builder/vmware/common/host_ip_vmnetnatconf_test.go deleted file mode 100644 index 9f3114a92..000000000 --- a/builder/vmware/common/host_ip_vmnetnatconf_test.go +++ /dev/null @@ -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") - } -} diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go index 4e339edb9..e03f9b6bf 100644 --- a/builder/vmware/common/ssh.go +++ b/builder/vmware/common/ssh.go @@ -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) diff --git a/builder/vmware/common/step_type_boot_command.go b/builder/vmware/common/step_type_boot_command.go index 35e30c624..c169c59eb 100644 --- a/builder/vmware/common/step_type_boot_command.go +++ b/builder/vmware/common/step_type_boot_command.go @@ -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) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 1d2a29ac5..2cce8fd3c 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -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 == "" { diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index f30451c65..6df8271f6 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -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 diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 029f2a31e..8e9f6cda0 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -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" diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go index f7874067c..bd644ca30 100644 --- a/builder/vmware/vmx/step_clone_vmx.go +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -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 From 4225b3568ee87376272dda4fdcf3325eea49cc9e Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 5 Apr 2016 18:45:36 -0500 Subject: [PATCH 02/23] Fixed bad ORs and a bad fmtstring. --- builder/vmware/common/driver_parser.go | 2 +- builder/vmware/iso/step_create_vmx.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go index 38470eb5b..7071e424d 100644 --- a/builder/vmware/common/driver_parser.go +++ b/builder/vmware/common/driver_parser.go @@ -362,7 +362,7 @@ type pParameterBoolean struct { parameter string truancy bool } -func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%s",e.parameter,e.truancy) } +func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%v",e.parameter,e.truancy) } type pParameterClientMatch struct { name string diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 8e9f6cda0..c1f4ea8f3 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -89,10 +89,10 @@ func unformat_serial(config string) (*serialUnion,error) { 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" { + 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" { + 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{ @@ -104,7 +104,7 @@ func unformat_serial(config string) (*serialUnion,error) { if len(comp) == 4 { res.yield = strings.ToUpper(comp[3]) } - if res.yield != "TRUE" || res.yield != "FALSE" { + 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 From 9b95ce0bc6ff4da69fdf91031e2e463fb049c455 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 5 Apr 2016 20:45:58 -0500 Subject: [PATCH 03/23] Completely forgot to support the unix paths in each of the drivers for the VMware builder. Fixed. --- builder/vmware/common/driver.go | 33 ++++++------ builder/vmware/common/driver_mock.go | 24 ++++++--- builder/vmware/common/driver_player5.go | 12 +++-- .../vmware/common/driver_player5_windows.go | 13 ++--- builder/vmware/common/driver_player_unix.go | 46 ++++++++++++++--- builder/vmware/common/driver_workstation9.go | 9 ++-- .../common/driver_workstation9_windows.go | 12 +++-- .../vmware/common/driver_workstation_unix.go | 51 ++++++++++++++----- 8 files changed, 136 insertions(+), 64 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 1098e591f..fa2cc0c03 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -235,8 +235,8 @@ type VmwareDriver struct { /// files so that the address detection (ip and ethernet) machinery /// works. DhcpLeasesPath func(string) string - VmnetnatConfPath func() string - DhcpConfPath func() string + DhcpConfPath func(string) string + VmnetnatConfPath func(string) string NetmapConfPath func() string } @@ -359,19 +359,20 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) { 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 } + // parse dhcpd configuration + pathDhcpConfig := d.DhcpConfPath(device) + if _, err := os.Stat(pathDhcpConfig); err != nil { + return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) + } + + config,err := readDhcpConfig(pathDhcpConfig) + if err != nil { return "",err } + // find the entry configured in the dhcpd interfaceConfig,err := config.HostByName(device) if err != nil { return "", err } @@ -404,19 +405,19 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) { netmap,err := readNetmapConfig(pathNetmap) if err != nil { return "",err } + // convert network to name + network := state.Get("vmnetwork").(string) + device,err := netmap.NameIntoDevice(network) + if err != nil { return "", err } + // parse dhcpd configuration - pathDhcpConfig := d.DhcpConfPath() + pathDhcpConfig := d.DhcpConfPath(device) if _, err := os.Stat(pathDhcpConfig); err != nil { return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) } config,err := readDhcpConfig(pathDhcpConfig) if err != nil { return "",err } - // 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 } diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 15e006403..da28cd474 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -78,12 +78,15 @@ type DriverMock struct { DhcpLeasesPathDevice string DhcpLeasesPathResult string - NetmapPathCalled bool - NetmapPathResult string - DhcpConfPathCalled bool DhcpConfPathResult string + VmnetnatConfPathCalled bool + VmnetnatConfPathResult string + + NetmapConfPathCalled bool + NetmapConfPathResult string + VerifyCalled bool VerifyErr error } @@ -184,16 +187,21 @@ 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) VmnetnatConfPath(device string) string { + d.VmnetnatConfPathCalled = true + return d.VmnetnatConfPathResult +} + +func (d *DriverMock) NetmapConfPath() string { + d.NetmapConfPathCalled = true + return d.NetmapConfPathResult +} + func (d *DriverMock) Verify() error { d.VerifyCalled = true return d.VerifyErr diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 9dce9540c..240b44a11 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -187,16 +187,18 @@ func (d *Player5Driver) Verify() error { d.VmwareDriver.DhcpLeasesPath = func(device string) string { return playerDhcpLeasesPath(device) } - d.VmwareDriver.VmnetnatConfPath = func() string { - return playerVmnetnatConfPath() + + d.VmwareDriver.DhcpConfPath = func(device string) string { + return playerVmDhcpConfPath(device) } - d.VmwareDriver.DhcpConfPath = func() string { - return playerVmDhcpConfPath() + + d.VmwareDriver.VmnetnatConfPath = func(device string) string { + return playerVmnetnatConfPath(device) } + d.VmwareDriver.NetmapConfPath = func() string { return playerNetmapConfPath() } - return nil } diff --git a/builder/vmware/common/driver_player5_windows.go b/builder/vmware/common/driver_player5_windows.go index 6ca065792..790a4c46c 100644 --- a/builder/vmware/common/driver_player5_windows.go +++ b/builder/vmware/common/driver_player5_windows.go @@ -57,15 +57,11 @@ func playerDhcpLeasesPath(device string) string { } else if _, err := os.Stat(path); err == nil { return path } - return findFile("vmnetdhcp.leases", playerDataFilePaths()) } -func playerVmnetnatConfPath() string { - return findFile("vmnetnat.conf", playerDataFilePaths()) -} - -func playerVmDhcpConfPath() string { +func playerVmDhcpConfPath(device string) string { + // the device isn't actually used on windows hosts path, err := playerDhcpConfigPathRegistry() if err != nil { log.Printf("Error finding configuration in registry: %s", err) @@ -75,6 +71,11 @@ func playerVmDhcpConfPath() string { return findFile("vmnetdhcp.conf", playerDataFilePaths()) } +func playerVmnetnatConfPath(device string) string { + // the device isn't actually used on windows hosts + return findFile("vmnetnat.conf", playerDataFilePaths()) +} + func playerNetmapConfPath() string { return findFile("netmap.conf", playerDataFilePaths()) } diff --git a/builder/vmware/common/driver_player_unix.go b/builder/vmware/common/driver_player_unix.go index 0c2cc8635..01b877d92 100644 --- a/builder/vmware/common/driver_player_unix.go +++ b/builder/vmware/common/driver_player_unix.go @@ -10,6 +10,7 @@ import ( "os/exec" "regexp" "runtime" + "path/filepath" ) func playerFindVdiskManager() (string, error) { @@ -28,16 +29,49 @@ func playerFindVmrun() (string, error) { return exec.LookPath("vmrun") } -func playerDhcpLeasesPath(device string) string { - return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases" -} - func playerToolsIsoPath(flavor string) string { return "/usr/lib/vmware/isoimages/" + flavor + ".iso" } -func playerVmnetnatConfPath() string { - return "" +// return the base path to vmware's config on the host +func playerVMwareRoot() (s string, err error) { + return "/etc/vmware", nil +} + +func playerDhcpLeasesPath(device string) string { + base, err := playerVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, device, "dhcpd/dhcpd.leases") +} + +func playerVmDhcpConfPath(device string) string { + base, err := playerVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, device, "dhcp/dhcp.conf") +} + +func playerVmnetnatConfPath(device string) string { + base, err := playerVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, device, "nat/nat.conf") +} + +func playerNetmapConfPath() string { + base, err := playerVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, "netmap.conf") } func playerVerifyVersion(version string) error { diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index 0c463a249..8a6eb5556 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -149,18 +149,17 @@ func (d *Workstation9Driver) Verify() error { return workstationDhcpLeasesPath(device) } - d.VmwareDriver.VmnetnatConfPath = func() string { - return workstationVmnetnatConfPath() + d.VmwareDriver.DhcpConfPath = func(device string) string { + return workstationDhcpConfPath(device) } - d.VmwareDriver.DhcpConfPath = func() string { - return workstationDhcpConfPath() + d.VmwareDriver.VmnetnatConfPath = func(device string) string { + return workstationVmnetnatConfPath(device) } d.VmwareDriver.NetmapConfPath = func() string { return workstationNetmapConfPath() } - return nil } diff --git a/builder/vmware/common/driver_workstation9_windows.go b/builder/vmware/common/driver_workstation9_windows.go index e69d5069f..b8d6d2b61 100644 --- a/builder/vmware/common/driver_workstation9_windows.go +++ b/builder/vmware/common/driver_workstation9_windows.go @@ -59,7 +59,13 @@ func workstationDhcpLeasesPath(device string) string { return findFile("vmnetdhcp.leases", workstationDataFilePaths()) } -func workstationVmnetnatConfPath() string { +func workstationDhcpConfPath(device string) string { + // device isn't used on a windows host + return findFile("vmnetdhcp.conf", workstationDataFilePaths()) +} + +func workstationVmnetnatConfPath(device string) string { + // device isn't used on a windows host return findFile("vmnetnat.conf", workstationDataFilePaths()) } @@ -67,10 +73,6 @@ 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. diff --git a/builder/vmware/common/driver_workstation_unix.go b/builder/vmware/common/driver_workstation_unix.go index 2ff94fd37..45e94d21e 100644 --- a/builder/vmware/common/driver_workstation_unix.go +++ b/builder/vmware/common/driver_workstation_unix.go @@ -39,26 +39,51 @@ func workstationFindVmrun() (string, error) { return exec.LookPath("vmrun") } +// return the base path to vmware's config on the host +func workstationVMwareRoot() (s string, err error) { + return "/etc/vmware", nil +} + func workstationDhcpLeasesPath(device string) string { - return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases" + base, err := workstationVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, device, "dhcpd/dhcpd.leases") +} + +func workstationDhcpConfPath(device string) string { + base, err := workstationVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, device, "dhcp/dhcpd.conf") +} + +func workstationVmnetnatConfPath(device string) string { + base, err := workstationVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, device, "nat/nat.conf") +} + +func workstationNetmapConfPath() string { + base, err := workstationVMwareRoot() + if err != nil { + log.Printf("Error finding VMware root: %s", err) + return "" + } + return filepath.Join(base, "netmap.conf") } func workstationToolsIsoPath(flavor string) string { return "/usr/lib/vmware/isoimages/" + flavor + ".iso" } -func workstationVmnetnatConfPath() string { - return "" -} - -func 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) From 15cb6a833a32a1f44c00625c00fe36ac52084a7d Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 5 Apr 2016 20:56:47 -0500 Subject: [PATCH 04/23] Ugh..missing argument in VMware builder's driver_esx5 unit-test due to api change for .HostIP(). Fixed. --- builder/vmware/iso/driver_esx5_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builder/vmware/iso/driver_esx5_test.go b/builder/vmware/iso/driver_esx5_test.go index 0c0ef757e..097d6e186 100644 --- a/builder/vmware/iso/driver_esx5_test.go +++ b/builder/vmware/iso/driver_esx5_test.go @@ -50,8 +50,9 @@ func TestESX5Driver_HostIP(t *testing.T) { defer listen.Close() driver := ESX5Driver{Host: "localhost", Port: uint(port)} + state := new(multistep.BasicStateBag) - if host, _ := driver.HostIP(); host != expected_host { + if host, _ := driver.HostIP(state); host != expected_host { t.Error(fmt.Sprintf("Expected string, %s but got %s", expected_host, host)) } } From e389d30a1b15de3042f35faa70b7fc4cad7b5c06 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Sun, 24 Apr 2016 22:22:13 -0500 Subject: [PATCH 05/23] Implemented the unit-tests for builder/vmware/iso/step_create_vmx.go --- builder/vmware/iso/step_create_vmx_test.go | 327 +++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 builder/vmware/iso/step_create_vmx_test.go diff --git a/builder/vmware/iso/step_create_vmx_test.go b/builder/vmware/iso/step_create_vmx_test.go new file mode 100644 index 000000000..e540eee8a --- /dev/null +++ b/builder/vmware/iso/step_create_vmx_test.go @@ -0,0 +1,327 @@ +package iso + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "math" + "math/rand" + "strconv" + "io/ioutil" + "bytes" + "runtime" + + "testing" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" + "github.com/mitchellh/packer/provisioner/shell" +) + +var vmxTestBuilderConfig = map[string]string{ + "type": `"vmware-iso"`, + "iso_url": `"https://archive.org/download/ut-ttylinux-i686-12.6/ut-ttylinux-i686-12.6.iso"`, + "iso_checksum_type": `"md5"`, + "iso_checksum": `"43c1feeae55a44c6ef694b8eb18408a6"`, + "ssh_username": `"root"`, + "ssh_password": `"password"`, + "ssh_wait_timeout": `"45s"`, + "boot_command": `["","rootpassword","udhcpc"]`, + "shutdown_command": `"/sbin/shutdown -h; exit 0"`, +} + +var vmxTestProvisionerConfig = map[string]string{ + "type": `"shell"`, + "inline": `["echo hola mundo"]`, +} + +const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}` + +func tmpnam(prefix string) string { + var path string + var err error + + const length = 16 + + dir := os.TempDir() + max := int(math.Pow(2, float64(length))) + + n,err := rand.Intn(max),nil + for path = filepath.Join(dir, prefix + strconv.Itoa(n)); err == nil; _,err = os.Stat(path) { + n = rand.Intn(max) + path = filepath.Join(dir, prefix + strconv.Itoa(n)) + } + return path +} + +func createFloppyOutput(prefix string) (string,string,error) { + output := tmpnam(prefix) + f,err := os.Create(output) + if err != nil { return "","",fmt.Errorf("Unable to create empty %s: %s", output, err) } + f.Close() + + vmxData := []string{ + `"floppy0.present":"TRUE"`, + `"floppy0.fileType":"file"`, + `"floppy0.clientDevice":"FALSE"`, + `"floppy0.fileName":"%s"`, + `"floppy0.startConnected":"TRUE"`, + } + + outputFile := strings.Replace(output, "\\", "\\\\", -1) + vmxString := fmt.Sprintf("{" + strings.Join(vmxData, ",") + "}", outputFile) + return output,vmxString,nil +} + +func readFloppyOutput(path string) (string,error) { + f,err := os.Open(path) + if err != nil { return "",fmt.Errorf("Unable to open file %s", path) } + defer f.Close() + data,err := ioutil.ReadAll(f) + if err != nil { return "",fmt.Errorf("Unable to read file: %s", err) } + if len(data) == 0 { return "", nil } + return string(data[:bytes.IndexByte(data,0)]),nil +} + +func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisionerConfig map[string]string) error { + ui := packer.TestUi(t) + + // create builder config and update with user-supplied options + cfgBuilder := map[string]string{} + for k,v := range vmxTestBuilderConfig { cfgBuilder[k] = v } + for k,v := range builderConfig { cfgBuilder[k] = v } + + // convert our builder config into a single sprintfable string + builderLines := []string{} + for k,v := range cfgBuilder { + builderLines = append(builderLines, fmt.Sprintf(`"%s":%s`, k, v)) + } + + // create provisioner config and update with user-supplied options + cfgProvisioner := map[string]string{} + for k,v := range vmxTestProvisionerConfig { cfgProvisioner[k] = v } + for k,v := range provisionerConfig { cfgProvisioner[k] = v } + + // convert our provisioner config into a single sprintfable string + provisionerLines := []string{} + for k,v := range cfgProvisioner { + provisionerLines = append(provisionerLines, fmt.Sprintf(`"%s":%s`, k, v)) + } + + // and now parse them into a template + configString := fmt.Sprintf(vmxTestTemplate, strings.Join(builderLines,`,`), strings.Join(provisionerLines,`,`)) + + tpl,err := template.Parse(strings.NewReader(configString)) + if err != nil { + t.Fatalf("Unable to parse test config: %s", err) + } + + // create our config to test the vmware-iso builder + components := packer.ComponentFinder{ + Builder: func(n string) (packer.Builder,error) { + return &Builder{},nil + }, + Hook: func(n string) (packer.Hook,error) { + return &packer.DispatchHook{},nil + }, + PostProcessor: func(n string) (packer.PostProcessor,error) { + return &packer.MockPostProcessor{},nil + }, + Provisioner: func(n string) (packer.Provisioner,error) { + return &shell.Provisioner{},nil + }, + } + config := packer.CoreConfig{ + Template: tpl, + Components: components, + } + + // create a core using our template + core,err := packer.NewCore(&config) + if err != nil { t.Fatalf("Unable to create core: %s", err) } + + // now we can prepare our build + b,err := core.Build("vmware-iso") + if err != nil { t.Fatalf("Unable to create build: %s", err) } + + warn,err := b.Prepare() + if len(warn) > 0 { + for _,w := range warn { + t.Logf("Configuration warning: %s", w) + } + } + + // and then finally build it + cache := &packer.FileCache{CacheDir: os.TempDir()} + artifacts,err := b.Run(ui, cache) + if err != nil { + t.Fatalf("Failed to build artifact: %s", err) + } + + // check to see that we only got one artifact back + if len(artifacts) == 1 { + return artifacts[0].Destroy() + } + + // otherwise some number of errors happened + t.Logf("Unexpected number of artifacts returned: %d", len(artifacts)) + errors := make([]error, 0) + for _,artifact := range artifacts { + if err := artifact.Destroy(); err != nil { + errors = append(errors, err) + } + } + if len(errors) > 0 { + t.Errorf("%d Errors returned while trying to destroy artifacts", len(errors)) + return fmt.Errorf("Error while trying to destroy artifacts: %v", errors) + } + return nil +} + +func TestStepCreateVmx_SerialFile(t *testing.T) { + tmpfile := tmpnam("SerialFileInput.") + + serialConfig := map[string]string{ + "serial": fmt.Sprintf(`"file:%s"`, filepath.ToSlash(tmpfile)), + } + + error := setupVMwareBuild(t, serialConfig, map[string]string{}) + if error != nil { t.Errorf("Unable to read file: %s", error) } + + f,err := os.Stat(tmpfile) + if err != nil { + t.Errorf("VMware builder did not create a file for serial port: %s", err) + } + + if f != nil { + if err := os.Remove(tmpfile); err != nil { + t.Fatalf("Unable to remove file %s: %s", tmpfile, err) + } + } +} + +func TestStepCreateVmx_SerialPort(t *testing.T) { + var defaultSerial string + if runtime.GOOS == "windows" { + defaultSerial = "COM1" + } else { + defaultSerial = "/dev/ttyS0" + } + + config := map[string]string{ + "serial": fmt.Sprintf(`"device:%s"`, filepath.ToSlash(defaultSerial)), + } + provision := map[string]string{ + "inline": `"dmesg | egrep -o '^serial8250: ttyS1 at' > /dev/fd0"`, + } + + // where to write output + output,vmxData,err := createFloppyOutput("SerialPortOutput.") + if err != nil { t.Fatalf("Error creating output: %s", err) } + defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + config["vmx_data"] = vmxData + t.Logf("Preparing to write output to %s", output) + + // whee + err = setupVMwareBuild(t, config, provision) + if err != nil { t.Errorf("%s", err) } + + // check the output + data,err := readFloppyOutput(output) + if err != nil { t.Errorf("%s", err) } + + if data != "serial8250: ttyS1 at\n" { + t.Errorf("Serial port not detected : %v", data) + } +} + +func TestStepCreateVmx_ParallelPort(t *testing.T) { + var defaultParallel string + if runtime.GOOS == "windows" { + defaultParallel = "LPT1" + } else { + defaultParallel = "/dev/lp0" + } + + config := map[string]string{ + "parallel": fmt.Sprintf(`"device:%s,uni"`, filepath.ToSlash(defaultParallel)), + } + provision := map[string]string{ + "inline": `"cat /proc/modules | egrep -o '^parport ' > /dev/fd0"`, + } + + // where to write output + output,vmxData,err := createFloppyOutput("ParallelPortOutput.") + if err != nil { t.Fatalf("Error creating output: %s", err) } + defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + config["vmx_data"] = vmxData + t.Logf("Preparing to write output to %s", output) + + // whee + error := setupVMwareBuild(t, config, provision) + if error != nil { t.Errorf("%s", error) } + + // check the output + data,err := readFloppyOutput(output) + if err != nil { t.Errorf("%s", err) } + + if data != "parport \n" { + t.Errorf("Parallel port not detected : %v", data) + } +} + +func TestStepCreateVmx_Usb(t *testing.T) { + config := map[string]string{ + "usb": `"TRUE"`, + } + provision := map[string]string{ + "inline": `"dmesg | egrep -m1 -o 'USB hub found$' > /dev/fd0"`, + } + + // where to write output + output,vmxData,err := createFloppyOutput("UsbOutput.") + if err != nil { t.Fatalf("Error creating output: %s", err) } + defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + config["vmx_data"] = vmxData + t.Logf("Preparing to write output to %s", output) + + // whee + error := setupVMwareBuild(t, config, provision) + if error != nil { t.Errorf("%s", error) } + + // check the output + data,err := readFloppyOutput(output) + if err != nil { t.Errorf("%s", err) } + + if data != "USB hub found\n" { + t.Errorf("USB support not detected : %v", data) + } +} + +func TestStepCreateVmx_Sound(t *testing.T) { + config := map[string]string{ + "sound": `"TRUE"`, + } + provision := map[string]string { + "inline": `"cat /proc/modules | egrep -o '^soundcore' > /dev/fd0"`, + } + + // where to write output + output,vmxData,err := createFloppyOutput("SoundOutput.") + if err != nil { t.Fatalf("Error creating output: %s", err) } + defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + config["vmx_data"] = vmxData + t.Logf("Preparing to write output to %s", output) + + // whee + error := setupVMwareBuild(t, config, provision) + if error != nil { t.Errorf("Unable to read file: %s", error) } + + // check the output + data,err := readFloppyOutput(output) + if err != nil { t.Errorf("%s", err) } + + if data != "soundcore\n" { + t.Errorf("Soundcard not detected : %v", data) + } +} From b52e2d3f4575f15fec7e94d48217f55b091d92d6 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 31 May 2016 14:39:38 -0500 Subject: [PATCH 06/23] Added the ability for the vmware-builder to fallback and determine the network device-name using the .vmx configuration in case of a guest using the "custom" connection type. --- builder/vmware/common/driver.go | 83 +++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index fa2cc0c03..5359bf07d 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -212,7 +212,8 @@ func compareVersions(versionFound string, versionWanted string, product string) return nil } -// helper functions that read configuration information from a file +/// helper functions that read configuration information from a file +// read the network<->device configuration out of the specified path func readNetmapConfig(path string) (NetworkMap,error) { fd,err := os.Open(path) if err != nil { return nil, err } @@ -220,6 +221,7 @@ func readNetmapConfig(path string) (NetworkMap,error) { return ReadNetworkMap(fd) } +// read the dhcp configuration out of the specified path func readDhcpConfig(path string) (DhcpConfiguration,error) { fd,err := os.Open(path) if err != nil { return nil, err } @@ -227,6 +229,36 @@ func readDhcpConfig(path string) (DhcpConfiguration,error) { return ReadDhcpConfiguration(fd) } +// read the VMX configuration from the specified path +func readVMXConfig(path string) (map[string]string,error) { + f, err := os.Open(path) + if err != nil { + return map[string]string{}, err + } + defer f.Close() + + vmxBytes, err := ioutil.ReadAll(f) + if err != nil { + return map[string]string{}, err + } + return ParseVMX(string(vmxBytes)), nil +} + +// read the connection type out of a vmx configuration +func readCustomDeviceName(vmxData map[string]string) (string,error) { + + connectionType, ok := vmxData["ethernet0.connectiontype"] + if !ok || connectionType != "custom" { + return "", fmt.Errorf("Unable to determine the device name for the connection type : %s", connectionType) + } + + device, ok := vmxData["ethernet0.vnet"] + if !ok || device == "" { + return "", fmt.Errorf("Unable to determine the device name for the connection type \"%s\" : %s", connectionType, device) + } + return device, nil +} + // This VmwareDriver is a base class that contains default methods // that a Driver can use or implement themselves. type VmwareDriver struct { @@ -244,17 +276,8 @@ 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)) + vmxData, err := readVMXConfig(vmxPath) + if err != nil { return "", err } var ok bool macAddress := "" @@ -283,7 +306,17 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string,error) { // convert the stashed network to a device network := state.Get("vmnetwork").(string) device,err := netmap.NameIntoDevice(network) - if err != nil { return "", err } + + // we were unable to find the device, maybe it's a custom one... + // so, check to see if it's in the .vmx configuration + if err != nil || network == "custom" { + vmxPath := state.Get("vmx_path").(string) + vmxData, err := readVMXConfig(vmxPath) + if err != nil { return "", err } + + device, err = readCustomDeviceName(vmxData) + if err != nil { return "", err } + } // figure out our MAC address for looking up the guest address MACAddress,err := d.GuestAddress(state) @@ -362,7 +395,17 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) { // convert network to name network := state.Get("vmnetwork").(string) device,err := netmap.NameIntoDevice(network) - if err != nil { return "", err } + + // we were unable to find the device, maybe it's a custom one... + // so, check to see if it's in the .vmx configuration + if err != nil || network == "custom" { + vmxPath := state.Get("vmx_path").(string) + vmxData, err := readVMXConfig(vmxPath) + if err != nil { return "", err } + + device, err = readCustomDeviceName(vmxData) + if err != nil { return "", err } + } // parse dhcpd configuration pathDhcpConfig := d.DhcpConfPath(device) @@ -408,7 +451,17 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) { // convert network to name network := state.Get("vmnetwork").(string) device,err := netmap.NameIntoDevice(network) - if err != nil { return "", err } + + // we were unable to find the device, maybe it's a custom one... + // so, check to see if it's in the .vmx configuration + if err != nil || network == "custom" { + vmxPath := state.Get("vmx_path").(string) + vmxData, err := readVMXConfig(vmxPath) + if err != nil { return "", err } + + device, err = readCustomDeviceName(vmxData) + if err != nil { return "", err } + } // parse dhcpd configuration pathDhcpConfig := d.DhcpConfPath(device) From 0d6cf7fac44869751d31f262b2ab8ee694ed41b9 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Wed, 5 Oct 2016 21:51:42 -0500 Subject: [PATCH 07/23] Added support for auto-detection to the serial and parallel port types. Included the yield option to all the serial port types. Added the ability for the network type to fallback to a custom network if the specified network name is not found in netmap.conf. Promoted the scope for both Read{Dhcp,Netmap}Config inside vmwcommon.driver. Updated the documentation for the VMware builder. --- builder/vmware/common/driver.go | 14 +- builder/vmware/iso/step_create_vmx.go | 197 +++++++++++++++--- .../source/docs/builders/vmware-iso.html.md | 56 +++++ 3 files changed, 233 insertions(+), 34 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 5359bf07d..0f0bc1fe8 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -214,7 +214,7 @@ func compareVersions(versionFound string, versionWanted string, product string) /// helper functions that read configuration information from a file // read the network<->device configuration out of the specified path -func readNetmapConfig(path string) (NetworkMap,error) { +func ReadNetmapConfig(path string) (NetworkMap,error) { fd,err := os.Open(path) if err != nil { return nil, err } defer fd.Close() @@ -222,7 +222,7 @@ func readNetmapConfig(path string) (NetworkMap,error) { } // read the dhcp configuration out of the specified path -func readDhcpConfig(path string) (DhcpConfiguration,error) { +func ReadDhcpConfig(path string) (DhcpConfiguration,error) { fd,err := os.Open(path) if err != nil { return nil, err } defer fd.Close() @@ -300,7 +300,7 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string,error) { if _, err := os.Stat(pathNetmap); err != nil { return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) } - netmap,err := readNetmapConfig(pathNetmap) + netmap,err := ReadNetmapConfig(pathNetmap) if err != nil { return "",err } // convert the stashed network to a device @@ -389,7 +389,7 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) { if _, err := os.Stat(pathNetmap); err != nil { return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) } - netmap,err := readNetmapConfig(pathNetmap) + netmap,err := ReadNetmapConfig(pathNetmap) if err != nil { return "",err } // convert network to name @@ -413,7 +413,7 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) { return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) } - config,err := readDhcpConfig(pathDhcpConfig) + config,err := ReadDhcpConfig(pathDhcpConfig) if err != nil { return "",err } // find the entry configured in the dhcpd @@ -445,7 +445,7 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) { if _, err := os.Stat(pathNetmap); err != nil { return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) } - netmap,err := readNetmapConfig(pathNetmap) + netmap,err := ReadNetmapConfig(pathNetmap) if err != nil { return "",err } // convert network to name @@ -468,7 +468,7 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) { if _, err := os.Stat(pathDhcpConfig); err != nil { return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) } - config,err := readDhcpConfig(pathDhcpConfig) + config,err := ReadDhcpConfig(pathDhcpConfig) if err != nil { return "",err } // find the entry configured in the dhcpd diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index c1f4ea8f3..d37f00969 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -21,7 +21,9 @@ type vmxTemplateData struct { ISOPath string Version string - Network string + Network_Type string + Network_Device string + Sound_Present string Usb_Present string @@ -31,10 +33,12 @@ type vmxTemplateData struct { Serial_Host string Serial_Yield string Serial_Filename string + Serial_Auto string Parallel_Present string Parallel_Bidirectional string Parallel_Filename string + Parallel_Auto string } type additionalDiskTemplateData struct { @@ -65,10 +69,17 @@ type serialConfigPipe struct { type serialConfigFile struct { filename string + yield string } type serialConfigDevice struct { devicename string + yield string +} + +type serialConfigAuto struct { + devicename string + yield string } type serialUnion struct { @@ -76,16 +87,33 @@ type serialUnion struct { pipe *serialConfigPipe file *serialConfigFile device *serialConfigDevice + auto *serialConfigAuto } func unformat_serial(config string) (*serialUnion,error) { - comptype := strings.SplitN(config, ":", 2) - if len(comptype) < 1 { + var defaultSerialPort string + if runtime.GOOS == "windows" { + defaultSerialPort = "COM1" + } else { + defaultSerialPort = "/dev/ttyS0" + } + + input := strings.SplitN(config, ":", 2) + if len(input) < 1 { return nil,fmt.Errorf("Unexpected format for serial port: %s", config) } - switch strings.ToUpper(comptype[0]) { + + var formatType, formatOptions string + formatType = input[0] + if len(input) == 2 { + formatOptions = input[1] + } else { + formatOptions = "" + } + + switch strings.ToUpper(formatType) { case "PIPE": - comp := strings.Split(comptype[1], ",") + comp := strings.Split(formatOptions, ",") if len(comp) < 3 || len(comp) > 4 { return nil,fmt.Errorf("Unexpected format for serial port : pipe : %s", config) } @@ -110,16 +138,65 @@ func unformat_serial(config string) (*serialUnion,error) { return &serialUnion{serialType:res, pipe:res},nil case "FILE": - res := &serialConfigFile{ filename : comptype[1] } + comp := strings.Split(formatOptions, ",") + if len(comp) > 2 { + return nil,fmt.Errorf("Unexpected format for serial port : file : %s", config) + } + + res := &serialConfigFile{ yield : "FALSE" } + + res.filename = filepath.FromSlash(comp[0]) + + res.yield = map[bool]string{true:strings.ToUpper(comp[0]), false:"FALSE"}[len(comp) > 1] + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil,fmt.Errorf("Unexpected format for serial port : file : yield : %s : %s", res.yield, config) + } + return &serialUnion{serialType:res, file:res},nil case "DEVICE": + comp := strings.Split(formatOptions, ",") + if len(comp) > 2 { + return nil,fmt.Errorf("Unexpected format for serial port : device : %s", config) + } + res := new(serialConfigDevice) - res.devicename = map[bool]string{true:strings.ToUpper(comptype[1]), false:"COM1"}[len(comptype[1]) > 0] + + if len(comp) == 2 { + res.devicename = map[bool]string{true:filepath.FromSlash(comp[0]), false:defaultSerialPort}[len(comp[0]) > 0] + res.yield = strings.ToUpper(comp[1]) + } else if len(comp) == 1 { + res.devicename = map[bool]string{true:filepath.FromSlash(comp[0]), false:defaultSerialPort}[len(comp[0]) > 0] + res.yield = "FALSE" + } else if len(comp) == 0 { + res.devicename = defaultSerialPort + res.yield = "FALSE" + } + + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil,fmt.Errorf("Unexpected format for serial port : device : yield : %s : %s", res.yield, config) + } + return &serialUnion{serialType:res, device:res},nil + case "AUTO": + res := new(serialConfigAuto) + res.devicename = defaultSerialPort + + if len(formatOptions) > 0 { + res.yield = strings.ToUpper(formatOptions) + } else { + res.yield = "FALSE" + } + + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil,fmt.Errorf("Unexpected format for serial port : auto : yield : %s : %s", res.yield, config) + } + + return &serialUnion{serialType:res, auto:res},nil + default: - return nil,fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(comptype[0]), config) + return nil,fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(formatType), config) } } @@ -128,6 +205,7 @@ type parallelUnion struct { parallelType interface{} file *parallelPortFile device *parallelPortDevice + auto *parallelPortAuto } type parallelPortFile struct { filename string @@ -136,18 +214,30 @@ type parallelPortDevice struct { bidirectional string devicename string } +type parallelPortAuto struct { + bidirectional string +} func unformat_parallel(config string) (*parallelUnion,error) { - comptype := strings.SplitN(config, ":", 2) - if len(comptype) < 1 { + input := strings.SplitN(config, ":", 2) + if len(input) < 1 { return nil,fmt.Errorf("Unexpected format for parallel port: %s", config) } - switch strings.ToUpper(comptype[0]) { + + var formatType, formatOptions string + formatType = input[0] + if len(input) == 2 { + formatOptions = input[1] + } else { + formatOptions = "" + } + + switch strings.ToUpper(formatType) { case "FILE": - res := ¶llelPortFile{ filename: comptype[1] } + res := ¶llelPortFile{ filename: filepath.FromSlash(formatOptions) } return ¶llelUnion{ parallelType:res, file: res},nil case "DEVICE": - comp := strings.Split(comptype[1], ",") + comp := strings.Split(formatOptions, ",") if len(comp) < 1 || len(comp) > 2 { return nil,fmt.Errorf("Unexpected format for parallel port: %s", config) } @@ -156,15 +246,24 @@ func unformat_parallel(config string) (*parallelUnion,error) { 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" + 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 ¶llelUnion{ parallelType:res, device:res },nil + + case "AUTO": + res := new(parallelPortAuto) + switch strings.ToUpper(formatOptions) { + case "": fallthrough + case "UNI": res.bidirectional = "FALSE" + case "BI": res.bidirectional = "TRUE" + default: + return nil,fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(formatOptions), config) + } + return ¶llelUnion{ parallelType:res, auto:res },nil } return nil,fmt.Errorf("Unexpected format for parallel port: %s", config) } @@ -249,7 +348,6 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist 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)], @@ -257,10 +355,42 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist 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 the network type that the user specified + network := config.Network + driver := state.Get("driver").(vmwcommon.VmwareDriver) - // check if serial port has been configured + // read netmap config + pathNetmap := driver.NetmapConfPath() + if _, err := os.Stat(pathNetmap); err != nil { + err := fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + netmap,res := vmwcommon.ReadNetmapConfig(pathNetmap) + if res != nil { + err := fmt.Errorf("Unable to read netmap conf file: %s: %v", pathNetmap, res) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // try and convert the specified network to a device + device,err := netmap.NameIntoDevice(network) + + // success. so we know that it's an actual network type inside netmap.conf + if err == nil { + templateData.Network_Type = network + templateData.Network_Device = device + // we were unable to find the type, so assume it's a custom network device. + } else { + templateData.Network_Type = "custom" + templateData.Network_Device = network + } + // store the network so that we can later figure out what ip address to bind to + state.Put("vmnetwork", network) + + /// check if serial port has been configured if config.Serial != "" { serial,err := unformat_serial(config.Serial) if err != nil { @@ -275,6 +405,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Serial_Yield = "" templateData.Serial_Endpoint = "" templateData.Serial_Host = "" + templateData.Serial_Auto = "FALSE" switch serial.serialType.(type) { case *serialConfigPipe: @@ -289,6 +420,12 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist case *serialConfigDevice: templateData.Serial_Type = "device" templateData.Serial_Filename = filepath.FromSlash(serial.device.devicename) + case *serialConfigAuto: + templateData.Serial_Type = "device" + templateData.Serial_Filename = filepath.FromSlash(serial.auto.devicename) + templateData.Serial_Yield = serial.auto.yield + templateData.Serial_Auto = "TRUE" + default: err := fmt.Errorf("Error procesing VMX template: %v", serial) state.Put("error", err) @@ -297,7 +434,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist } } - // check if parallel port has been configured + /// check if parallel port has been configured if config.Parallel != "" { parallel,err := unformat_parallel(config.Parallel) if err != nil { @@ -307,6 +444,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist return multistep.ActionHalt } + templateData.Parallel_Auto = "FALSE" switch parallel.parallelType.(type) { case *parallelPortFile: templateData.Parallel_Present = "TRUE" @@ -315,6 +453,10 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Parallel_Present = "TRUE" templateData.Parallel_Bidirectional = parallel.device.bidirectional templateData.Parallel_Filename = filepath.FromSlash(parallel.device.devicename) + case *parallelPortAuto: + templateData.Parallel_Present = "TRUE" + templateData.Parallel_Auto = "TRUE" + templateData.Parallel_Bidirectional = parallel.auto.bidirectional default: err := fmt.Errorf("Error procesing VMX template: %v", parallel) state.Put("error", err) @@ -325,7 +467,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist ctx.Data = &templateData - // render the .vmx template + /// render the .vmx template vmxContents, err := interpolate.Render(vmxTemplate, &ctx) if err != nil { err := fmt.Errorf("Error procesing VMX template: %s", err) @@ -383,7 +525,8 @@ ehci.pciSlotNumber = "34" ehci.present = "TRUE" ethernet0.addressType = "generated" ethernet0.bsdName = "en0" -ethernet0.connectionType = "{{ .Network }}" +ethernet0.connectionType = "{{ .Network_Type }}" +ethernet0.vnet = "{{ .Network_Device }}" ethernet0.displayName = "Ethernet" ethernet0.linkStatePropagation.enable = "FALSE" ethernet0.pciSlotNumber = "33" @@ -453,7 +596,7 @@ usb_xhci.present = "TRUE" serial0.present = "{{ .Serial_Present }}" serial0.startConnected = "{{ .Serial_Present }}" serial0.fileName = "{{ .Serial_Filename }}" -serial0.autodetect = "TRUE" +serial0.autodetect = "{{ .Serial_Auto }}" serial0.fileType = "{{ .Serial_Type }}" serial0.yieldOnMsrRead = "{{ .Serial_Yield }}" serial0.pipe.endPoint = "{{ .Serial_Endpoint }}" @@ -463,7 +606,7 @@ serial0.tryNoRxLoss = "{{ .Serial_Host }}" parallel0.present = "{{ .Parallel_Present }}" parallel0.startConnected = "{{ .Parallel_Present }}" parallel0.fileName = "{{ .Parallel_Filename }}" -parallel0.autodetect = "TRUE" +parallel0.autodetect = "{{ .Parallel_Auto }}" parallel0.bidirectional = "{{ .Parallel_Bidirectional }}" virtualHW.productCompatibility = "hosted" diff --git a/website/source/docs/builders/vmware-iso.html.md b/website/source/docs/builders/vmware-iso.html.md index 256bf63e4..44285a850 100644 --- a/website/source/docs/builders/vmware-iso.html.md +++ b/website/source/docs/builders/vmware-iso.html.md @@ -193,6 +193,11 @@ builder. URLs must point to the same file (same checksum). By default this is empty and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. +- `network` (string) - This is the network type that the virtual machine will + be created with. This can be one of the generic values that map to a device + such as "hostonly", "nat", or "bridged". If the network is not one of these + values, then it is assumed to be a VMware network device. (VMnet0..x) + - `output_directory` (string) - This is the path to the directory where the resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` @@ -200,6 +205,19 @@ builder. the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the name of the build. +- `parallel` (string) - This specifies a parallel port to add to the VM. It + has the format of `Type:option1,option2,...`. Type can be one of the + following values: "FILE", "DEVICE", or "AUTO". + + * `FILE:path` - Specifies the path to the local file to be used for the + parallel port. + * `DEVICE:path` - Specifies the path to the local device to be used for the + parallel port. + * `AUTO:direction` - Specifies to use auto-detection to determine the + parallel port. Direction can be `BI` to specify + bidirectional communication or `UNI` to specify + unidirectional communication. + - `remote_cache_datastore` (string) - The path to the datastore where supporting files will be stored during the build on the remote machine. By default this is the same as the `remote_datastore` option. This only has an @@ -234,6 +252,40 @@ builder. - `remote_username` (string) - The username for the SSH user that will access the remote machine. This is required if `remote_type` is enabled. +- `serial` (string) - This specifies a serial port to add to the VM. + It has a format of `Type:option1,option2,...`. The field `Type` can be one + of the following values: `FILE`, `DEVICE`, `PIPE`, or `AUTO`. + + * `FILE:path(,yield)` - Specifies the path to the local file to be used as the + serial port. + * `yield` (bool) - This is an optional boolean that specifies whether + the vm should yield the cpu when polling the port. + By default, the builder will assume this as `FALSE`. + * `DEVICE:path(,yield)` - Specifies the path to the local device to be used + as the serial port. If `path` is empty, then + default to the first serial port. + * `yield` (bool) - This is an optional boolean that specifies whether + the vm should yield the cpu when polling the port. + By default, the builder will assume this as `FALSE`. + * `PIPE:path,endpoint,host(,yield)` - Specifies to use the named-pipe "path" + as a serial port. This has a few + options that determine how the VM + should use the named-pipe. + * `endpoint` (string) - Chooses the type of the VM-end, which can be + either a `client` or `server`. + * `host` (string) - Chooses the type of the host-end, which can be either + an `app` (application) or `vm` (another virtual-machine). + * `yield` (bool) - This is an optional boolean that specifies whether + the vm should yield the cpu when polling the port. + By default, the builder will assume this as `FALSE`. + + * `AUTO:(yield)` - Specifies to use auto-detection to determine the serial + port to use. This has one option to determine how the VM + should support the serial port. + * `yield` (bool) - This is an optional boolean that specifies whether + the vm should yield the cpu when polling the port. + By default, the builder will assume this as `FALSE`. + - `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty string, which tells Packer to just forcefully shut down the machine. @@ -264,6 +316,8 @@ builder. `--noSSLVerify`, `--skipManifestCheck`, and `--targetType` are reserved, and should not be passed to this argument. +- `sound` (boolean) - Enable VMware's virtual soundcard device for the VM. + - `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to upload into the VM. Valid values are "darwin", "linux", and "windows". By default, this is empty, which means VMware tools won't be uploaded. @@ -276,6 +330,8 @@ builder. By default the upload path is set to `{{.Flavor}}.iso`. This setting is not used when `remote_type` is "esx5". +- `usb` (boolean) - Enable VMware's USB bus for the VM. + - `version` (string) - The [vmx hardware version](http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003746) for the new virtual machine. Only the default value has been tested, any From 884af69da14c132a378a6fb3cc10f9501fdbf03b Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Mon, 27 Feb 2017 15:34:53 -0600 Subject: [PATCH 08/23] go fmt on builder/vmware/* --- builder/vmware/common/driver.go | 158 ++- builder/vmware/common/driver_mock.go | 2 +- builder/vmware/common/driver_parser.go | 1122 +++++++++-------- builder/vmware/common/driver_player_unix.go | 2 +- builder/vmware/common/driver_workstation10.go | 1 - builder/vmware/iso/builder.go | 29 +- builder/vmware/iso/step_create_vmx.go | 360 +++--- builder/vmware/iso/step_create_vmx_test.go | 234 ++-- 8 files changed, 1075 insertions(+), 833 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 0f0bc1fe8..745b48d69 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -1,19 +1,19 @@ package common import ( - "errors" "bytes" + "errors" "fmt" + "io/ioutil" "log" + "net" "os" "os/exec" - "io/ioutil" "regexp" "runtime" "strconv" "strings" "time" - "net" "github.com/hashicorp/packer/helper/multistep" ) @@ -214,23 +214,27 @@ func compareVersions(versionFound string, versionWanted string, product string) /// helper functions that read configuration information from a file // read the network<->device configuration out of the specified path -func ReadNetmapConfig(path string) (NetworkMap,error) { - fd,err := os.Open(path) - if err != nil { return nil, err } +func ReadNetmapConfig(path string) (NetworkMap, error) { + fd, err := os.Open(path) + if err != nil { + return nil, err + } defer fd.Close() return ReadNetworkMap(fd) } // read the dhcp configuration out of the specified path -func ReadDhcpConfig(path string) (DhcpConfiguration,error) { - fd,err := os.Open(path) - if err != nil { return nil, err } +func ReadDhcpConfig(path string) (DhcpConfiguration, error) { + fd, err := os.Open(path) + if err != nil { + return nil, err + } defer fd.Close() return ReadDhcpConfiguration(fd) } // read the VMX configuration from the specified path -func readVMXConfig(path string) (map[string]string,error) { +func readVMXConfig(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { return map[string]string{}, err @@ -245,7 +249,7 @@ func readVMXConfig(path string) (map[string]string,error) { } // read the connection type out of a vmx configuration -func readCustomDeviceName(vmxData map[string]string) (string,error) { +func readCustomDeviceName(vmxData map[string]string) (string, error) { connectionType, ok := vmxData["ethernet0.connectiontype"] if !ok || connectionType != "custom" { @@ -266,18 +270,20 @@ type VmwareDriver struct { /// A driver must overload these in order to point to the correct /// files so that the address detection (ip and ethernet) machinery /// works. - DhcpLeasesPath func(string) string - DhcpConfPath func(string) string + DhcpLeasesPath func(string) string + DhcpConfPath func(string) string VmnetnatConfPath func(string) string - NetmapConfPath func() string + NetmapConfPath func() string } -func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string,error) { +func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) { vmxPath := state.Get("vmx_path").(string) log.Println("Lookup up IP information...") vmxData, err := readVMXConfig(vmxPath) - if err != nil { return "", err } + if err != nil { + return "", err + } var ok bool macAddress := "" @@ -287,40 +293,50 @@ func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string,error) { } } - res,err := net.ParseMAC(macAddress) - if err != nil { return "", err } + res, err := net.ParseMAC(macAddress) + if err != nil { + return "", err + } - return res.String(),nil + return res.String(), nil } -func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string,error) { +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 } + 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) + device, err := netmap.NameIntoDevice(network) // we were unable to find the device, maybe it's a custom one... // so, check to see if it's in the .vmx configuration if err != nil || network == "custom" { vmxPath := state.Get("vmx_path").(string) vmxData, err := readVMXConfig(vmxPath) - if err != nil { return "", err } + if err != nil { + return "", err + } device, err = readCustomDeviceName(vmxData) - if err != nil { return "", err } + 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 } + MACAddress, err := d.GuestAddress(state) + if err != nil { + return "", err + } // figure out the correct dhcp leases dhcpLeasesPath := d.DhcpLeasesPath(device) @@ -382,29 +398,35 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string,error) { return curIp, nil } -func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) { +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 } + netmap, err := ReadNetmapConfig(pathNetmap) + if err != nil { + return "", err + } // convert network to name network := state.Get("vmnetwork").(string) - device,err := netmap.NameIntoDevice(network) + device, err := netmap.NameIntoDevice(network) // we were unable to find the device, maybe it's a custom one... // so, check to see if it's in the .vmx configuration if err != nil || network == "custom" { vmxPath := state.Get("vmx_path").(string) vmxData, err := readVMXConfig(vmxPath) - if err != nil { return "", err } + if err != nil { + return "", err + } device, err = readCustomDeviceName(vmxData) - if err != nil { return "", err } + if err != nil { + return "", err + } } // parse dhcpd configuration @@ -413,54 +435,68 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) { return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) } - config,err := ReadDhcpConfig(pathDhcpConfig) - if err != nil { return "",err } + config, err := ReadDhcpConfig(pathDhcpConfig) + if err != nil { + return "", err + } // find the entry configured in the dhcpd - interfaceConfig,err := config.HostByName(device) - if err != nil { return "", err } + 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 } + 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 } + 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 + 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) + return "", fmt.Errorf("Unable to find device %s : %v", device, names) } -func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) { +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 } + netmap, err := ReadNetmapConfig(pathNetmap) + if err != nil { + return "", err + } // convert network to name network := state.Get("vmnetwork").(string) - device,err := netmap.NameIntoDevice(network) + device, err := netmap.NameIntoDevice(network) // we were unable to find the device, maybe it's a custom one... // so, check to see if it's in the .vmx configuration if err != nil || network == "custom" { vmxPath := state.Get("vmx_path").(string) vmxData, err := readVMXConfig(vmxPath) - if err != nil { return "", err } + if err != nil { + return "", err + } device, err = readCustomDeviceName(vmxData) - if err != nil { return "", err } + if err != nil { + return "", err + } } // parse dhcpd configuration @@ -468,15 +504,21 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) { 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 } + config, err := ReadDhcpConfig(pathDhcpConfig) + if err != nil { + return "", err + } // find the entry configured in the dhcpd - interfaceConfig,err := config.HostByName(device) - if err != nil { return "", err } + interfaceConfig, err := config.HostByName(device) + if err != nil { + return "", err + } - address,err := interfaceConfig.IP4() - if err != nil { return "", err } + address, err := interfaceConfig.IP4() + if err != nil { + return "", err + } - return address.String(),nil + return address.String(), nil } diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index da28cd474..562eab382 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -83,7 +83,7 @@ type DriverMock struct { VmnetnatConfPathCalled bool VmnetnatConfPathResult string - + NetmapConfPathCalled bool NetmapConfPathResult string diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go index 7071e424d..dbf15b5b5 100644 --- a/builder/vmware/common/driver_parser.go +++ b/builder/vmware/common/driver_parser.go @@ -1,12 +1,13 @@ package common + import ( "fmt" - "os" "io" - "strings" - "strconv" "net" + "os" "sort" + "strconv" + "strings" ) type sentinelSignaller chan struct{} @@ -20,16 +21,20 @@ func uncomment(eof sentinelSignaller, in <-chan byte) 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 } + case <-eof: + stillReading = false + case ch := <-in: + switch ch { + case '#': + endofline = true + case '\n': + if endofline { + endofline = false } - if !endofline { out <- ch } + } + if !endofline { + out <- ch + } } } }(in, out) @@ -46,62 +51,71 @@ func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) chan string { go func(out chan string) { for stillReading := true; stillReading; { select { - case <-eof: - stillReading = false + case <-eof: + stillReading = false - case ch = <-in: - if quote { - if ch == '"' { - out <- state + string(ch) - state,quote = "",false - continue - } - state += string(ch) + 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 + 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) + 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 } + if len(state) > 0 { + out <- state + } }(out) return out } /** mid-level parsing */ type tkParameter struct { - name string + name string operand []string } + func (e *tkParameter) String() string { var values []string - for _,val := range e.operand { + for _, val := range e.operand { values = append(values, val) } return fmt.Sprintf("%s [%s]", e.name, strings.Join(values, ",")) @@ -109,21 +123,22 @@ func (e *tkParameter) String() string { type tkGroup struct { parent *tkGroup - id tkParameter + 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 { + for _, val := range e.id.operand { id = append(id, val) } var config []string - for _,val := range e.params { + for _, val := range e.params { config = append(config, val.String()) } return fmt.Sprintf("%s {\n%s\n}", strings.Join(id, " "), strings.Join(config, "\n")) @@ -139,11 +154,14 @@ func parseTokenParameter(in chan string) tkParameter { continue } switch token { - case "{": fallthrough - case "}": fallthrough - case ";": goto leave - default: - result.operand = append(result.operand, token) + case "{": + fallthrough + case "}": + fallthrough + case ";": + goto leave + default: + result.operand = append(result.operand, token) } } leave: @@ -151,50 +169,52 @@ leave: } // convert a channel of pseudo-tokens into an tkGroup tree */ -func parseDhcpConfig(eof sentinelSignaller, in chan string) (tkGroup,error) { +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 } + go func(out chan string) { + for _, v := range tokens { + out <- v + } out <- ";" }(out) return parseTokenParameter(out) } - for stillReading,currentgroup := true,&result; stillReading; { + for stillReading, currentgroup := true, &result; stillReading; { select { - case <-eof: - stillReading = false + 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 + 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") } - tokens = []string{} + 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 + return result, nil } func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) chan string { @@ -207,70 +227,85 @@ func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) chan string { go func(out chan string) { for stillReading := true; stillReading; { select { - case <-eof: - stillReading = false + case <-eof: + stillReading = false - case ch = <-in: - if quote { - if ch == '"' { - out <- state + string(ch) - state,quote = "",false - continue - } - state += string(ch) + 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 + 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) + case '\r': + fallthrough + case '\t': + fallthrough + case ' ': + if len(state) == 0 { + continue } - lastnewline = false + 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 } + if len(state) > 0 { + out <- state + } }(out) return out } -func parseNetworkMapConfig(eof sentinelSignaller, in chan string) (NetworkMap,error) { +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) } + _, ok := unsorted[network] + if !ok { + unsorted[network] = make(map[string]string) + } - val,err := strconv.Unquote(value) - if err != nil { return err } + val, err := strconv.Unquote(value) + if err != nil { + return err + } current := unsorted[network] current[attribute] = val @@ -280,135 +315,174 @@ func parseNetworkMapConfig(eof sentinelSignaller, in chan string) (NetworkMap,er 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 } + 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) + } + 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) } + for k := range unsorted { + keys = append(keys, k) + } sort.Strings(keys) - for _,k := range keys { + for _, k := range keys { result = append(result, unsorted[k]) } - return result,nil + return result, nil } /** higher-level parsing */ /// parameters -type pParameter interface { repr() string } +type pParameter interface { + repr() string +} type pParameterInclude struct { filename string } -func (e pParameterInclude) repr() string { return fmt.Sprintf("include-file:filename=%s",e.filename) } + +func (e pParameterInclude) repr() string { return fmt.Sprintf("include-file:filename=%s", e.filename) } type pParameterOption struct { - name string + name string value string } -func (e pParameterOption) repr() string { return fmt.Sprintf("option:%s=%s",e.name,e.value) } + +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 + verb string // allow,deny,ignore attribute string } -func (e pParameterGrant) repr() string { return fmt.Sprintf("grant:%s,%s",e.verb,e.attribute) } + +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,",")) + 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,",")) + return fmt.Sprintf("fixed-address6:%s", strings.Join(e, ",")) } // hardware address 00:00:00:00:00:00 type pParameterHardware struct { - class string + 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)) + for _, v := range e.address { + res = append(res, fmt.Sprintf("%02x", v)) } - return fmt.Sprintf("hardware-address:%s[%s]",e.class,strings.Join(res,":")) + return fmt.Sprintf("hardware-address:%s[%s]", e.class, strings.Join(res, ":")) } type pParameterBoolean struct { parameter string - truancy bool + truancy bool } -func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%v",e.parameter,e.truancy) } + +func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%v", 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) } + +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()) } + +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()) } + +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 + 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()) } + +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 + value string } -func (e pParameterOther) repr() string { return fmt.Sprintf("parameter:%s=%s",e.parameter,e.value) } + +func (e pParameterOther) repr() string { return fmt.Sprintf("parameter:%s=%s", e.parameter, e.value) } type pParameterExpression struct { - parameter string + 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 } +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 + id pDeclarationIdentifier + parent *pDeclaration + parameters []pParameter declarations []pDeclaration } @@ -420,277 +494,305 @@ func (e *pDeclaration) repr() string { res := e.short() var parameters []string - for _,v := range e.parameters { + 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())) + 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()) + 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")) + return fmt.Sprintf("%s\n%s\n%s\n", res, strings.Join(parameters, "\n"), strings.Join(groups, "\n")) } -type pDeclarationGlobal struct {} +type pDeclarationGlobal struct{} + func (e pDeclarationGlobal) repr() string { return fmt.Sprintf("{global}") } -type pDeclarationShared struct { name string } +type pDeclarationShared struct{ name string } + func (e pDeclarationShared) repr() string { return fmt.Sprintf("{shared-network %s}", e.name) } -type pDeclarationSubnet4 struct { net.IPNet } +type pDeclarationSubnet4 struct{ net.IPNet } + func (e pDeclarationSubnet4) repr() string { return fmt.Sprintf("{subnet4 %s}", e.String()) } -type pDeclarationSubnet6 struct { net.IPNet } +type pDeclarationSubnet6 struct{ net.IPNet } + func (e pDeclarationSubnet6) repr() string { return fmt.Sprintf("{subnet6 %s}", e.String()) } -type pDeclarationHost struct { name string } +type pDeclarationHost struct{ name string } + func (e pDeclarationHost) repr() string { return fmt.Sprintf("{host name:%s}", e.name) } -type pDeclarationPool struct {} +type pDeclarationPool struct{} + func (e pDeclarationPool) repr() string { return fmt.Sprintf("{pool}") } -type pDeclarationGroup struct {} +type pDeclarationGroup struct{} + func (e pDeclarationGroup) repr() string { return fmt.Sprintf("{group}") } -type pDeclarationClass struct { name string } +type pDeclarationClass struct{ name string } + func (e pDeclarationClass) repr() string { return fmt.Sprintf("{class}") } /** parsers */ -func parseParameter(val tkParameter) (pParameter,error) { +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 "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 "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 "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 "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< 1 { + if val.operand[0] == "=" { + return pParameterExpression{parameter: val.name, expression: strings.Join(val.operand[1:], "")}, nil } - 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 + } + 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) { +func parseTokenGroup(val tkGroup) (*pDeclaration, error) { params := val.id.operand switch val.id.name { - case "group": - return &pDeclaration{id:pDeclarationGroup{}},nil + case "group": + return &pDeclaration{id: pDeclarationGroup{}}, nil - case "pool": - return &pDeclaration{id:pDeclarationPool{}},nil + case "pool": + return &pDeclaration{id: pDeclarationPool{}}, nil - case "host": - if len(params) == 1 { - return &pDeclaration{id:pDeclarationHost{name: params[0]}},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) + 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 } - 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 + 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 "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 + } + 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) + return nil, fmt.Errorf("Invalid pDeclaration : %v : %v", val.id.name, params) } -func flattenDhcpConfig(root tkGroup) (*pDeclaration,error) { +func flattenDhcpConfig(root tkGroup) (*pDeclaration, error) { var result *pDeclaration - result,err := parseTokenGroup(root) - if err != nil { return nil,err } + 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 } + 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 } + 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 + return result, nil } /** reduce the tree into the things that we care about */ type grant uint + const ( - ALLOW grant = iota + ALLOW grant = iota IGNORE grant = iota - DENY grant = iota + DENY grant = iota ) + type configDeclaration struct { - id []pDeclarationIdentifier + id []pDeclarationIdentifier composites []pDeclaration address []pParameter - options map[string]string - grants map[string]grant - attributes map[string]bool - parameters map[string]string + options map[string]string + grants map[string]grant + attributes map[string]bool + parameters map[string]string expressions map[string]string hostid []pParameterClientMatch @@ -715,28 +817,28 @@ func createDeclaration(node pDeclaration) configDeclaration { 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) + 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 { + 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) + 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) } } } @@ -749,112 +851,141 @@ func (e *configDeclaration) repr() string { var res []string res = make([]string, 0) - for _,v := range e.id { res = append(res, v.repr()) } + 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()) } + 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.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()) } + 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) { +func (e *configDeclaration) IP4() (net.IP, error) { var result []string - for _,entry := range e.address { + for _, entry := range e.address { switch entry.(type) { - case pParameterAddress4: - for _,s := range entry.(pParameterAddress4) { - result = append(result, s) - } + 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) + return nil, fmt.Errorf("More than one address4 returned : %v", result) } else if len(result) == 0 { - return nil,fmt.Errorf("No IP4 addresses found") + 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 + 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) { +func (e *configDeclaration) IP6() (net.IP, error) { var result []string - for _,entry := range e.address { + for _, entry := range e.address { switch entry.(type) { - case pParameterAddress6: - for _,s := range entry.(pParameterAddress6) { - result = append(result, s) - } + 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) + return nil, fmt.Errorf("More than one address6 returned : %v", result) } else if len(result) == 0 { - return nil,fmt.Errorf("No IP6 addresses found") + 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 + 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) { +func (e *configDeclaration) Hardware() (net.HardwareAddr, error) { var result []pParameterHardware - for _,addr := range e.address { + for _, addr := range e.address { switch addr.(type) { - case pParameterHardware: - result = append(result, addr.(pParameterHardware)) + case pParameterHardware: + result = append(result, addr.(pParameterHardware)) } } if len(result) > 0 { - return nil,fmt.Errorf("More than one hardware address returned : %v", result) + return nil, fmt.Errorf("More than one hardware address returned : %v", result) } res := make(net.HardwareAddr, 0) - for _,by := range result[0].address { + for _, by := range result[0].address { res = append(res, by) } - return res,nil + return res, nil } /*** Dhcp Configuration */ type DhcpConfiguration []configDeclaration -func ReadDhcpConfiguration(fd *os.File) (DhcpConfiguration,error) { - fromfile,eof := consumeFile(fd) + +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 } + parsetree, err := parseDhcpConfig(eof, tokenized) + if err != nil { + return nil, err + } - global,err := flattenDhcpConfig(parsetree) - 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) { + 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 { + for _, p := range root.declarations { walkDeclarations(p, out) } } - each := make(chan*configDeclaration) - go func(out chan*configDeclaration) { + each := make(chan *configDeclaration) + go func(out chan *configDeclaration) { walkDeclarations(*global, out) out <- nil }(each) @@ -863,7 +994,7 @@ func ReadDhcpConfiguration(fd *os.File) (DhcpConfiguration,error) { for decl := <-each; decl != nil; decl = <-each { result = append(result, *decl) } - return result,nil + return result, nil } func (e *DhcpConfiguration) Global() configDeclaration { @@ -874,83 +1005,86 @@ func (e *DhcpConfiguration) Global() configDeclaration { return result } -func (e *DhcpConfiguration) SubnetByAddress(address net.IP) (configDeclaration,error) { +func (e *DhcpConfiguration) SubnetByAddress(address net.IP) (configDeclaration, error) { var result []configDeclaration - for _,entry := range *e { + 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) - } + 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()) + 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 configDeclaration{}, fmt.Errorf("More than 1 network declaration found : %v", result) } - return result[0],nil + return result[0], nil } -func (e *DhcpConfiguration) HostByName(host string) (configDeclaration,error) { +func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) { var result []configDeclaration - for _,entry := range *e { + 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) - } + 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) + 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 configDeclaration{}, fmt.Errorf("More than 1 host declaration found : %v", result) } - return result[0],nil + 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) +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 + 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 { +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 val["device"], nil } } - return "",fmt.Errorf("Network name not found : %v", name) + return "", fmt.Errorf("Network name not found : %v", name) } -func (e *NetworkMap) DeviceIntoName(device string) (string,error) { - for _,val := range *e { +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 val["name"], nil } } - return "",fmt.Errorf("Device name not found : %v", device) + return "", fmt.Errorf("Device name not found : %v", device) } func (e *NetworkMap) repr() string { var result []string - for idx,val := range *e { + 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"])) } @@ -958,17 +1092,19 @@ func (e *NetworkMap) repr() string { } /** main */ -func consumeFile(fd *os.File) (chan byte,sentinelSignaller) { +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 } + _, err := fd.Read(b) + if err == io.EOF { + break + } fromfile <- b[0] } close(eof) }() - return fromfile,eof + return fromfile, eof } diff --git a/builder/vmware/common/driver_player_unix.go b/builder/vmware/common/driver_player_unix.go index 01b877d92..068720a6c 100644 --- a/builder/vmware/common/driver_player_unix.go +++ b/builder/vmware/common/driver_player_unix.go @@ -8,9 +8,9 @@ import ( "fmt" "log" "os/exec" + "path/filepath" "regexp" "runtime" - "path/filepath" ) func playerFindVdiskManager() (string, error) { diff --git a/builder/vmware/common/driver_workstation10.go b/builder/vmware/common/driver_workstation10.go index 716717e39..7e6ac8b8f 100644 --- a/builder/vmware/common/driver_workstation10.go +++ b/builder/vmware/common/driver_workstation10.go @@ -33,4 +33,3 @@ func (d *Workstation10Driver) Verify() error { return workstationVerifyVersion(VMWARE_WS_VERSION) } - diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 2cce8fd3c..2dcc082ac 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -39,38 +39,35 @@ type Config struct { 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"` + 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"` + GuestOSType string `mapstructure:"guest_os_type"` + Version string `mapstructure:"version"` + VMName string `mapstructure:"vm_name"` // Network type - Network string `mapstructure:"network"` + Network string `mapstructure:"network"` // device presence - Sound bool `mapstructure:"sound"` - USB bool `mapstructure:"usb"` + Sound bool `mapstructure:"sound"` + USB bool `mapstructure:"usb"` // communication ports - Serial string `mapstructure:"serial"` - Parallel string `mapstructure:"parallel"` + 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"` SkipExport bool `mapstructure:"skip_export"` - VMName string `mapstructure:"vm_name"` VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"` VMXTemplatePath string `mapstructure:"vmx_template_path"` - Version string `mapstructure:"version"` // remote vsphere RemoteType string `mapstructure:"remote_type"` diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index d37f00969..0261123a6 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "strings" vmwcommon "github.com/hashicorp/packer/builder/vmware/common" @@ -21,24 +22,24 @@ type vmxTemplateData struct { ISOPath string Version string - Network_Type string - Network_Device string + Network_Type string + Network_Device string Sound_Present string - Usb_Present string + Usb_Present string - Serial_Present string - Serial_Type string + Serial_Present string + Serial_Type string Serial_Endpoint string - Serial_Host string - Serial_Yield string + Serial_Host string + Serial_Yield string Serial_Filename string - Serial_Auto string + Serial_Auto string - Parallel_Present string + Parallel_Present string Parallel_Bidirectional string - Parallel_Filename string - Parallel_Auto string + Parallel_Filename string + Parallel_Auto string } type additionalDiskTemplateData struct { @@ -63,34 +64,34 @@ type stepCreateVMX struct { type serialConfigPipe struct { filename string endpoint string - host string - yield string + host string + yield string } type serialConfigFile struct { filename string - yield string + yield string } type serialConfigDevice struct { devicename string - yield string + yield string } type serialConfigAuto struct { devicename string - yield string + yield string } type serialUnion struct { serialType interface{} - pipe *serialConfigPipe - file *serialConfigFile - device *serialConfigDevice - auto *serialConfigAuto + pipe *serialConfigPipe + file *serialConfigFile + device *serialConfigDevice + auto *serialConfigAuto } -func unformat_serial(config string) (*serialUnion,error) { +func unformat_serial(config string) (*serialUnion, error) { var defaultSerialPort string if runtime.GOOS == "windows" { defaultSerialPort = "COM1" @@ -100,7 +101,7 @@ func unformat_serial(config string) (*serialUnion,error) { input := strings.SplitN(config, ":", 2) if len(input) < 1 { - return nil,fmt.Errorf("Unexpected format for serial port: %s", config) + return nil, fmt.Errorf("Unexpected format for serial port: %s", config) } var formatType, formatOptions string @@ -112,116 +113,116 @@ func unformat_serial(config string) (*serialUnion,error) { } switch strings.ToUpper(formatType) { - case "PIPE": - comp := strings.Split(formatOptions, ",") - if len(comp) < 3 || len(comp) > 4 { - return nil,fmt.Errorf("Unexpected format for serial port : pipe : %s", config) - } - if res := strings.ToLower(comp[1]); res != "client" && res != "server" { - return nil,fmt.Errorf("Unexpected format for serial port : pipe : endpoint : %s : %s", res, config) - } - if res := strings.ToLower(comp[2]); res != "app" && res != "vm" { - return nil,fmt.Errorf("Unexpected format for serial port : pipe : host : %s : %s", res, config) - } - res := &serialConfigPipe{ - filename : comp[0], - endpoint : comp[1], - host : map[string]string{"app":"TRUE","vm":"FALSE"}[strings.ToLower(comp[2])], - yield : "FALSE", - } - if len(comp) == 4 { - res.yield = strings.ToUpper(comp[3]) - } - if res.yield != "TRUE" && res.yield != "FALSE" { - return nil,fmt.Errorf("Unexpected format for serial port : pipe : yield : %s : %s", res.yield, config) - } - return &serialUnion{serialType:res, pipe:res},nil + case "PIPE": + comp := strings.Split(formatOptions, ",") + if len(comp) < 3 || len(comp) > 4 { + return nil, fmt.Errorf("Unexpected format for serial port : pipe : %s", config) + } + if res := strings.ToLower(comp[1]); res != "client" && res != "server" { + return nil, fmt.Errorf("Unexpected format for serial port : pipe : endpoint : %s : %s", res, config) + } + if res := strings.ToLower(comp[2]); res != "app" && res != "vm" { + return nil, fmt.Errorf("Unexpected format for serial port : pipe : host : %s : %s", res, config) + } + res := &serialConfigPipe{ + filename: comp[0], + endpoint: comp[1], + host: map[string]string{"app": "TRUE", "vm": "FALSE"}[strings.ToLower(comp[2])], + yield: "FALSE", + } + if len(comp) == 4 { + res.yield = strings.ToUpper(comp[3]) + } + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil, fmt.Errorf("Unexpected format for serial port : pipe : yield : %s : %s", res.yield, config) + } + return &serialUnion{serialType: res, pipe: res}, nil - case "FILE": - comp := strings.Split(formatOptions, ",") - if len(comp) > 2 { - return nil,fmt.Errorf("Unexpected format for serial port : file : %s", config) - } + case "FILE": + comp := strings.Split(formatOptions, ",") + if len(comp) > 2 { + return nil, fmt.Errorf("Unexpected format for serial port : file : %s", config) + } - res := &serialConfigFile{ yield : "FALSE" } + res := &serialConfigFile{yield: "FALSE"} - res.filename = filepath.FromSlash(comp[0]) + res.filename = filepath.FromSlash(comp[0]) - res.yield = map[bool]string{true:strings.ToUpper(comp[0]), false:"FALSE"}[len(comp) > 1] - if res.yield != "TRUE" && res.yield != "FALSE" { - return nil,fmt.Errorf("Unexpected format for serial port : file : yield : %s : %s", res.yield, config) - } + res.yield = map[bool]string{true: strings.ToUpper(comp[0]), false: "FALSE"}[len(comp) > 1] + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil, fmt.Errorf("Unexpected format for serial port : file : yield : %s : %s", res.yield, config) + } - return &serialUnion{serialType:res, file:res},nil + return &serialUnion{serialType: res, file: res}, nil - case "DEVICE": - comp := strings.Split(formatOptions, ",") - if len(comp) > 2 { - return nil,fmt.Errorf("Unexpected format for serial port : device : %s", config) - } + case "DEVICE": + comp := strings.Split(formatOptions, ",") + if len(comp) > 2 { + return nil, fmt.Errorf("Unexpected format for serial port : device : %s", config) + } - res := new(serialConfigDevice) + res := new(serialConfigDevice) - if len(comp) == 2 { - res.devicename = map[bool]string{true:filepath.FromSlash(comp[0]), false:defaultSerialPort}[len(comp[0]) > 0] - res.yield = strings.ToUpper(comp[1]) - } else if len(comp) == 1 { - res.devicename = map[bool]string{true:filepath.FromSlash(comp[0]), false:defaultSerialPort}[len(comp[0]) > 0] - res.yield = "FALSE" - } else if len(comp) == 0 { - res.devicename = defaultSerialPort - res.yield = "FALSE" - } - - if res.yield != "TRUE" && res.yield != "FALSE" { - return nil,fmt.Errorf("Unexpected format for serial port : device : yield : %s : %s", res.yield, config) - } - - return &serialUnion{serialType:res, device:res},nil - - case "AUTO": - res := new(serialConfigAuto) + if len(comp) == 2 { + res.devicename = map[bool]string{true: filepath.FromSlash(comp[0]), false: defaultSerialPort}[len(comp[0]) > 0] + res.yield = strings.ToUpper(comp[1]) + } else if len(comp) == 1 { + res.devicename = map[bool]string{true: filepath.FromSlash(comp[0]), false: defaultSerialPort}[len(comp[0]) > 0] + res.yield = "FALSE" + } else if len(comp) == 0 { res.devicename = defaultSerialPort + res.yield = "FALSE" + } - if len(formatOptions) > 0 { - res.yield = strings.ToUpper(formatOptions) - } else { - res.yield = "FALSE" - } + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil, fmt.Errorf("Unexpected format for serial port : device : yield : %s : %s", res.yield, config) + } - if res.yield != "TRUE" && res.yield != "FALSE" { - return nil,fmt.Errorf("Unexpected format for serial port : auto : yield : %s : %s", res.yield, config) - } + return &serialUnion{serialType: res, device: res}, nil - return &serialUnion{serialType:res, auto:res},nil + case "AUTO": + res := new(serialConfigAuto) + res.devicename = defaultSerialPort - default: - return nil,fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(formatType), config) + if len(formatOptions) > 0 { + res.yield = strings.ToUpper(formatOptions) + } else { + res.yield = "FALSE" + } + + if res.yield != "TRUE" && res.yield != "FALSE" { + return nil, fmt.Errorf("Unexpected format for serial port : auto : yield : %s : %s", res.yield, config) + } + + return &serialUnion{serialType: res, auto: res}, nil + + default: + return nil, fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(formatType), config) } } /* parallel port */ type parallelUnion struct { parallelType interface{} - file *parallelPortFile - device *parallelPortDevice - auto *parallelPortAuto + file *parallelPortFile + device *parallelPortDevice + auto *parallelPortAuto } type parallelPortFile struct { filename string } type parallelPortDevice struct { bidirectional string - devicename string + devicename string } type parallelPortAuto struct { bidirectional string } -func unformat_parallel(config string) (*parallelUnion,error) { +func unformat_parallel(config string) (*parallelUnion, error) { input := strings.SplitN(config, ":", 2) if len(input) < 1 { - return nil,fmt.Errorf("Unexpected format for parallel port: %s", config) + return nil, fmt.Errorf("Unexpected format for parallel port: %s", config) } var formatType, formatOptions string @@ -233,39 +234,44 @@ func unformat_parallel(config string) (*parallelUnion,error) { } switch strings.ToUpper(formatType) { - case "FILE": - res := ¶llelPortFile{ filename: filepath.FromSlash(formatOptions) } - return ¶llelUnion{ parallelType:res, file: res},nil - case "DEVICE": - comp := strings.Split(formatOptions, ",") - if len(comp) < 1 || len(comp) > 2 { - return nil,fmt.Errorf("Unexpected format for parallel port: %s", config) + case "FILE": + res := ¶llelPortFile{filename: filepath.FromSlash(formatOptions)} + return ¶llelUnion{parallelType: res, file: res}, nil + case "DEVICE": + comp := strings.Split(formatOptions, ",") + if len(comp) < 1 || len(comp) > 2 { + return nil, fmt.Errorf("Unexpected format for parallel port: %s", config) + } + res := new(parallelPortDevice) + res.bidirectional = "FALSE" + res.devicename = filepath.FromSlash(comp[0]) + if len(comp) > 1 { + switch strings.ToUpper(comp[1]) { + case "BI": + res.bidirectional = "TRUE" + case "UNI": + res.bidirectional = "FALSE" + default: + return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(comp[0]), config) } - 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 ¶llelUnion{parallelType: res, device: res}, nil - case "AUTO": - res := new(parallelPortAuto) - switch strings.ToUpper(formatOptions) { - case "": fallthrough - case "UNI": res.bidirectional = "FALSE" - case "BI": res.bidirectional = "TRUE" - default: - return nil,fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(formatOptions), config) - } - return ¶llelUnion{ parallelType:res, auto:res },nil + case "AUTO": + res := new(parallelPortAuto) + switch strings.ToUpper(formatOptions) { + case "": + fallthrough + case "UNI": + res.bidirectional = "FALSE" + case "BI": + res.bidirectional = "TRUE" + default: + return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(formatOptions), config) + } + return ¶llelUnion{parallelType: res, auto: res}, nil } - return nil,fmt.Errorf("Unexpected format for parallel port: %s", config) + return nil, fmt.Errorf("Unexpected format for parallel port: %s", config) } /* regular steps */ @@ -348,11 +354,11 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist Version: config.Version, ISOPath: isoPath, - Sound_Present: map[bool]string{true:"TRUE",false:"FALSE"}[bool(config.Sound)], - Usb_Present: map[bool]string{true:"TRUE",false:"FALSE"}[bool(config.USB)], + 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", + Serial_Present: "FALSE", + Parallel_Present: "FALSE", } /// Check the network type that the user specified @@ -367,7 +373,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist ui.Error(err.Error()) return multistep.ActionHalt } - netmap,res := vmwcommon.ReadNetmapConfig(pathNetmap) + netmap, res := vmwcommon.ReadNetmapConfig(pathNetmap) if res != nil { err := fmt.Errorf("Unable to read netmap conf file: %s: %v", pathNetmap, res) state.Put("error", err) @@ -376,13 +382,13 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist } // try and convert the specified network to a device - device,err := netmap.NameIntoDevice(network) + device, err := netmap.NameIntoDevice(network) // success. so we know that it's an actual network type inside netmap.conf if err == nil { templateData.Network_Type = network templateData.Network_Device = device - // we were unable to find the type, so assume it's a custom network device. + // we were unable to find the type, so assume it's a custom network device. } else { templateData.Network_Type = "custom" templateData.Network_Device = network @@ -392,7 +398,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist /// check if serial port has been configured if config.Serial != "" { - serial,err := unformat_serial(config.Serial) + serial, err := unformat_serial(config.Serial) if err != nil { err := fmt.Errorf("Error procesing VMX template: %s", err) state.Put("error", err) @@ -408,35 +414,35 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Serial_Auto = "FALSE" switch serial.serialType.(type) { - case *serialConfigPipe: - templateData.Serial_Type = "pipe" - templateData.Serial_Endpoint = serial.pipe.endpoint - templateData.Serial_Host = serial.pipe.host - templateData.Serial_Yield = serial.pipe.yield - templateData.Serial_Filename = filepath.FromSlash(serial.pipe.filename) - case *serialConfigFile: - templateData.Serial_Type = "file" - templateData.Serial_Filename = filepath.FromSlash(serial.file.filename) - case *serialConfigDevice: - templateData.Serial_Type = "device" - templateData.Serial_Filename = filepath.FromSlash(serial.device.devicename) - case *serialConfigAuto: - templateData.Serial_Type = "device" - templateData.Serial_Filename = filepath.FromSlash(serial.auto.devicename) - templateData.Serial_Yield = serial.auto.yield - templateData.Serial_Auto = "TRUE" + case *serialConfigPipe: + templateData.Serial_Type = "pipe" + templateData.Serial_Endpoint = serial.pipe.endpoint + templateData.Serial_Host = serial.pipe.host + templateData.Serial_Yield = serial.pipe.yield + templateData.Serial_Filename = filepath.FromSlash(serial.pipe.filename) + case *serialConfigFile: + templateData.Serial_Type = "file" + templateData.Serial_Filename = filepath.FromSlash(serial.file.filename) + case *serialConfigDevice: + templateData.Serial_Type = "device" + templateData.Serial_Filename = filepath.FromSlash(serial.device.devicename) + case *serialConfigAuto: + templateData.Serial_Type = "device" + templateData.Serial_Filename = filepath.FromSlash(serial.auto.devicename) + templateData.Serial_Yield = serial.auto.yield + templateData.Serial_Auto = "TRUE" - default: - err := fmt.Errorf("Error procesing VMX template: %v", serial) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + 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) + parallel, err := unformat_parallel(config.Parallel) if err != nil { err := fmt.Errorf("Error procesing VMX template: %s", err) state.Put("error", err) @@ -446,22 +452,22 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Parallel_Auto = "FALSE" switch parallel.parallelType.(type) { - case *parallelPortFile: - templateData.Parallel_Present = "TRUE" - templateData.Parallel_Filename = filepath.FromSlash(parallel.file.filename) - case *parallelPortDevice: - templateData.Parallel_Present = "TRUE" - templateData.Parallel_Bidirectional = parallel.device.bidirectional - templateData.Parallel_Filename = filepath.FromSlash(parallel.device.devicename) - case *parallelPortAuto: - templateData.Parallel_Present = "TRUE" - templateData.Parallel_Auto = "TRUE" - templateData.Parallel_Bidirectional = parallel.auto.bidirectional - default: - err := fmt.Errorf("Error procesing VMX template: %v", parallel) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + case *parallelPortFile: + templateData.Parallel_Present = "TRUE" + templateData.Parallel_Filename = filepath.FromSlash(parallel.file.filename) + case *parallelPortDevice: + templateData.Parallel_Present = "TRUE" + templateData.Parallel_Bidirectional = parallel.device.bidirectional + templateData.Parallel_Filename = filepath.FromSlash(parallel.device.devicename) + case *parallelPortAuto: + templateData.Parallel_Present = "TRUE" + templateData.Parallel_Auto = "TRUE" + templateData.Parallel_Bidirectional = parallel.auto.bidirectional + default: + err := fmt.Errorf("Error procesing VMX template: %v", parallel) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } } diff --git a/builder/vmware/iso/step_create_vmx_test.go b/builder/vmware/iso/step_create_vmx_test.go index e540eee8a..7f5c1d91c 100644 --- a/builder/vmware/iso/step_create_vmx_test.go +++ b/builder/vmware/iso/step_create_vmx_test.go @@ -1,37 +1,37 @@ package iso import ( + "bytes" "fmt" - "os" - "path/filepath" - "strings" + "io/ioutil" "math" "math/rand" - "strconv" - "io/ioutil" - "bytes" + "os" + "path/filepath" "runtime" + "strconv" + "strings" - "testing" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/template" "github.com/mitchellh/packer/provisioner/shell" + "github.com/mitchellh/packer/template" + "testing" ) var vmxTestBuilderConfig = map[string]string{ - "type": `"vmware-iso"`, - "iso_url": `"https://archive.org/download/ut-ttylinux-i686-12.6/ut-ttylinux-i686-12.6.iso"`, + "type": `"vmware-iso"`, + "iso_url": `"https://archive.org/download/ut-ttylinux-i686-12.6/ut-ttylinux-i686-12.6.iso"`, "iso_checksum_type": `"md5"`, - "iso_checksum": `"43c1feeae55a44c6ef694b8eb18408a6"`, - "ssh_username": `"root"`, - "ssh_password": `"password"`, - "ssh_wait_timeout": `"45s"`, - "boot_command": `["","rootpassword","udhcpc"]`, - "shutdown_command": `"/sbin/shutdown -h; exit 0"`, + "iso_checksum": `"43c1feeae55a44c6ef694b8eb18408a6"`, + "ssh_username": `"root"`, + "ssh_password": `"password"`, + "ssh_wait_timeout": `"45s"`, + "boot_command": `["","rootpassword","udhcpc"]`, + "shutdown_command": `"/sbin/shutdown -h; exit 0"`, } var vmxTestProvisionerConfig = map[string]string{ - "type": `"shell"`, + "type": `"shell"`, "inline": `["echo hola mundo"]`, } @@ -40,24 +40,26 @@ const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}` func tmpnam(prefix string) string { var path string var err error - + const length = 16 - + dir := os.TempDir() max := int(math.Pow(2, float64(length))) - n,err := rand.Intn(max),nil - for path = filepath.Join(dir, prefix + strconv.Itoa(n)); err == nil; _,err = os.Stat(path) { + n, err := rand.Intn(max), nil + for path = filepath.Join(dir, prefix+strconv.Itoa(n)); err == nil; _, err = os.Stat(path) { n = rand.Intn(max) - path = filepath.Join(dir, prefix + strconv.Itoa(n)) + path = filepath.Join(dir, prefix+strconv.Itoa(n)) } return path } -func createFloppyOutput(prefix string) (string,string,error) { +func createFloppyOutput(prefix string) (string, string, error) { output := tmpnam(prefix) - f,err := os.Create(output) - if err != nil { return "","",fmt.Errorf("Unable to create empty %s: %s", output, err) } + f, err := os.Create(output) + if err != nil { + return "", "", fmt.Errorf("Unable to create empty %s: %s", output, err) + } f.Close() vmxData := []string{ @@ -69,18 +71,24 @@ func createFloppyOutput(prefix string) (string,string,error) { } outputFile := strings.Replace(output, "\\", "\\\\", -1) - vmxString := fmt.Sprintf("{" + strings.Join(vmxData, ",") + "}", outputFile) - return output,vmxString,nil + vmxString := fmt.Sprintf("{"+strings.Join(vmxData, ",")+"}", outputFile) + return output, vmxString, nil } -func readFloppyOutput(path string) (string,error) { - f,err := os.Open(path) - if err != nil { return "",fmt.Errorf("Unable to open file %s", path) } +func readFloppyOutput(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", fmt.Errorf("Unable to open file %s", path) + } defer f.Close() - data,err := ioutil.ReadAll(f) - if err != nil { return "",fmt.Errorf("Unable to read file: %s", err) } - if len(data) == 0 { return "", nil } - return string(data[:bytes.IndexByte(data,0)]),nil + data, err := ioutil.ReadAll(f) + if err != nil { + return "", fmt.Errorf("Unable to read file: %s", err) + } + if len(data) == 0 { + return "", nil + } + return string(data[:bytes.IndexByte(data, 0)]), nil } func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisionerConfig map[string]string) error { @@ -88,72 +96,84 @@ func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisioner // create builder config and update with user-supplied options cfgBuilder := map[string]string{} - for k,v := range vmxTestBuilderConfig { cfgBuilder[k] = v } - for k,v := range builderConfig { cfgBuilder[k] = v } + for k, v := range vmxTestBuilderConfig { + cfgBuilder[k] = v + } + for k, v := range builderConfig { + cfgBuilder[k] = v + } // convert our builder config into a single sprintfable string builderLines := []string{} - for k,v := range cfgBuilder { + for k, v := range cfgBuilder { builderLines = append(builderLines, fmt.Sprintf(`"%s":%s`, k, v)) } // create provisioner config and update with user-supplied options cfgProvisioner := map[string]string{} - for k,v := range vmxTestProvisionerConfig { cfgProvisioner[k] = v } - for k,v := range provisionerConfig { cfgProvisioner[k] = v } + for k, v := range vmxTestProvisionerConfig { + cfgProvisioner[k] = v + } + for k, v := range provisionerConfig { + cfgProvisioner[k] = v + } // convert our provisioner config into a single sprintfable string provisionerLines := []string{} - for k,v := range cfgProvisioner { + for k, v := range cfgProvisioner { provisionerLines = append(provisionerLines, fmt.Sprintf(`"%s":%s`, k, v)) } // and now parse them into a template - configString := fmt.Sprintf(vmxTestTemplate, strings.Join(builderLines,`,`), strings.Join(provisionerLines,`,`)) + configString := fmt.Sprintf(vmxTestTemplate, strings.Join(builderLines, `,`), strings.Join(provisionerLines, `,`)) - tpl,err := template.Parse(strings.NewReader(configString)) + tpl, err := template.Parse(strings.NewReader(configString)) if err != nil { t.Fatalf("Unable to parse test config: %s", err) } // create our config to test the vmware-iso builder components := packer.ComponentFinder{ - Builder: func(n string) (packer.Builder,error) { - return &Builder{},nil + Builder: func(n string) (packer.Builder, error) { + return &Builder{}, nil }, - Hook: func(n string) (packer.Hook,error) { - return &packer.DispatchHook{},nil + Hook: func(n string) (packer.Hook, error) { + return &packer.DispatchHook{}, nil }, - PostProcessor: func(n string) (packer.PostProcessor,error) { - return &packer.MockPostProcessor{},nil + PostProcessor: func(n string) (packer.PostProcessor, error) { + return &packer.MockPostProcessor{}, nil }, - Provisioner: func(n string) (packer.Provisioner,error) { - return &shell.Provisioner{},nil + Provisioner: func(n string) (packer.Provisioner, error) { + return &shell.Provisioner{}, nil }, } config := packer.CoreConfig{ - Template: tpl, + Template: tpl, Components: components, } // create a core using our template - core,err := packer.NewCore(&config) - if err != nil { t.Fatalf("Unable to create core: %s", err) } + core, err := packer.NewCore(&config) + if err != nil { + t.Fatalf("Unable to create core: %s", err) + } // now we can prepare our build - b,err := core.Build("vmware-iso") - if err != nil { t.Fatalf("Unable to create build: %s", err) } + b, err := core.Build("vmware-iso") + if err != nil { + t.Fatalf("Unable to create build: %s", err) + } - warn,err := b.Prepare() + warn, err := b.Prepare() if len(warn) > 0 { - for _,w := range warn { + for _, w := range warn { t.Logf("Configuration warning: %s", w) } } // and then finally build it cache := &packer.FileCache{CacheDir: os.TempDir()} - artifacts,err := b.Run(ui, cache) + artifacts, err := b.Run(ui, cache) if err != nil { t.Fatalf("Failed to build artifact: %s", err) } @@ -166,7 +186,7 @@ func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisioner // otherwise some number of errors happened t.Logf("Unexpected number of artifacts returned: %d", len(artifacts)) errors := make([]error, 0) - for _,artifact := range artifacts { + for _, artifact := range artifacts { if err := artifact.Destroy(); err != nil { errors = append(errors, err) } @@ -186,9 +206,11 @@ func TestStepCreateVmx_SerialFile(t *testing.T) { } error := setupVMwareBuild(t, serialConfig, map[string]string{}) - if error != nil { t.Errorf("Unable to read file: %s", error) } + if error != nil { + t.Errorf("Unable to read file: %s", error) + } - f,err := os.Stat(tmpfile) + f, err := os.Stat(tmpfile) if err != nil { t.Errorf("VMware builder did not create a file for serial port: %s", err) } @@ -216,19 +238,29 @@ func TestStepCreateVmx_SerialPort(t *testing.T) { } // where to write output - output,vmxData,err := createFloppyOutput("SerialPortOutput.") - if err != nil { t.Fatalf("Error creating output: %s", err) } - defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + output, vmxData, err := createFloppyOutput("SerialPortOutput.") + if err != nil { + t.Fatalf("Error creating output: %s", err) + } + defer func() { + if _, err := os.Stat(output); err == nil { + os.Remove(output) + } + }() config["vmx_data"] = vmxData t.Logf("Preparing to write output to %s", output) // whee err = setupVMwareBuild(t, config, provision) - if err != nil { t.Errorf("%s", err) } + if err != nil { + t.Errorf("%s", err) + } // check the output - data,err := readFloppyOutput(output) - if err != nil { t.Errorf("%s", err) } + data, err := readFloppyOutput(output) + if err != nil { + t.Errorf("%s", err) + } if data != "serial8250: ttyS1 at\n" { t.Errorf("Serial port not detected : %v", data) @@ -251,19 +283,29 @@ func TestStepCreateVmx_ParallelPort(t *testing.T) { } // where to write output - output,vmxData,err := createFloppyOutput("ParallelPortOutput.") - if err != nil { t.Fatalf("Error creating output: %s", err) } - defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + output, vmxData, err := createFloppyOutput("ParallelPortOutput.") + if err != nil { + t.Fatalf("Error creating output: %s", err) + } + defer func() { + if _, err := os.Stat(output); err == nil { + os.Remove(output) + } + }() config["vmx_data"] = vmxData t.Logf("Preparing to write output to %s", output) // whee error := setupVMwareBuild(t, config, provision) - if error != nil { t.Errorf("%s", error) } + if error != nil { + t.Errorf("%s", error) + } // check the output - data,err := readFloppyOutput(output) - if err != nil { t.Errorf("%s", err) } + data, err := readFloppyOutput(output) + if err != nil { + t.Errorf("%s", err) + } if data != "parport \n" { t.Errorf("Parallel port not detected : %v", data) @@ -279,19 +321,29 @@ func TestStepCreateVmx_Usb(t *testing.T) { } // where to write output - output,vmxData,err := createFloppyOutput("UsbOutput.") - if err != nil { t.Fatalf("Error creating output: %s", err) } - defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + output, vmxData, err := createFloppyOutput("UsbOutput.") + if err != nil { + t.Fatalf("Error creating output: %s", err) + } + defer func() { + if _, err := os.Stat(output); err == nil { + os.Remove(output) + } + }() config["vmx_data"] = vmxData t.Logf("Preparing to write output to %s", output) // whee error := setupVMwareBuild(t, config, provision) - if error != nil { t.Errorf("%s", error) } + if error != nil { + t.Errorf("%s", error) + } // check the output - data,err := readFloppyOutput(output) - if err != nil { t.Errorf("%s", err) } + data, err := readFloppyOutput(output) + if err != nil { + t.Errorf("%s", err) + } if data != "USB hub found\n" { t.Errorf("USB support not detected : %v", data) @@ -302,24 +354,34 @@ func TestStepCreateVmx_Sound(t *testing.T) { config := map[string]string{ "sound": `"TRUE"`, } - provision := map[string]string { + provision := map[string]string{ "inline": `"cat /proc/modules | egrep -o '^soundcore' > /dev/fd0"`, } // where to write output - output,vmxData,err := createFloppyOutput("SoundOutput.") - if err != nil { t.Fatalf("Error creating output: %s", err) } - defer func() { if _,err := os.Stat(output); err == nil { os.Remove(output) } }() + output, vmxData, err := createFloppyOutput("SoundOutput.") + if err != nil { + t.Fatalf("Error creating output: %s", err) + } + defer func() { + if _, err := os.Stat(output); err == nil { + os.Remove(output) + } + }() config["vmx_data"] = vmxData t.Logf("Preparing to write output to %s", output) // whee error := setupVMwareBuild(t, config, provision) - if error != nil { t.Errorf("Unable to read file: %s", error) } + if error != nil { + t.Errorf("Unable to read file: %s", error) + } // check the output - data,err := readFloppyOutput(output) - if err != nil { t.Errorf("%s", err) } + data, err := readFloppyOutput(output) + if err != nil { + t.Errorf("%s", err) + } if data != "soundcore\n" { t.Errorf("Soundcard not detected : %v", data) From 898b27c16d7f0cb2d8cf0b63384c8b9b4e26a6f6 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Wed, 12 Apr 2017 17:41:36 -0500 Subject: [PATCH 09/23] Added support for the NONE option to be specified for parallel and serial ports in the vmware iso builder. --- builder/vmware/iso/step_create_vmx.go | 12 ++++++++++++ website/source/docs/builders/vmware-iso.html.md | 6 ++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 0261123a6..207d38caa 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -196,6 +196,9 @@ func unformat_serial(config string) (*serialUnion, error) { return &serialUnion{serialType: res, auto: res}, nil + case "NONE": + return &serialUnion{serialType: nil}, nil + default: return nil, fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(formatType), config) } @@ -270,7 +273,11 @@ func unformat_parallel(config string) (*parallelUnion, error) { return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(formatOptions), config) } return ¶llelUnion{parallelType: res, auto: res}, nil + + case "NONE": + return ¶llelUnion{parallelType: nil}, nil } + return nil, fmt.Errorf("Unexpected format for parallel port: %s", config) } @@ -431,6 +438,8 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Serial_Filename = filepath.FromSlash(serial.auto.devicename) templateData.Serial_Yield = serial.auto.yield templateData.Serial_Auto = "TRUE" + case nil: + break default: err := fmt.Errorf("Error procesing VMX template: %v", serial) @@ -463,6 +472,9 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Parallel_Present = "TRUE" templateData.Parallel_Auto = "TRUE" templateData.Parallel_Bidirectional = parallel.auto.bidirectional + case nil: + break + default: err := fmt.Errorf("Error procesing VMX template: %v", parallel) state.Put("error", err) diff --git a/website/source/docs/builders/vmware-iso.html.md b/website/source/docs/builders/vmware-iso.html.md index 44285a850..95ab31bef 100644 --- a/website/source/docs/builders/vmware-iso.html.md +++ b/website/source/docs/builders/vmware-iso.html.md @@ -207,7 +207,7 @@ builder. - `parallel` (string) - This specifies a parallel port to add to the VM. It has the format of `Type:option1,option2,...`. Type can be one of the - following values: "FILE", "DEVICE", or "AUTO". + following values: "FILE", "DEVICE", "AUTO", or "NONE". * `FILE:path` - Specifies the path to the local file to be used for the parallel port. @@ -217,6 +217,7 @@ builder. parallel port. Direction can be `BI` to specify bidirectional communication or `UNI` to specify unidirectional communication. + * `NONE` - Specifies to not use a parallel port. (default) - `remote_cache_datastore` (string) - The path to the datastore where supporting files will be stored during the build on the remote machine. By @@ -254,7 +255,7 @@ builder. - `serial` (string) - This specifies a serial port to add to the VM. It has a format of `Type:option1,option2,...`. The field `Type` can be one - of the following values: `FILE`, `DEVICE`, `PIPE`, or `AUTO`. + of the following values: `FILE`, `DEVICE`, `PIPE`, `AUTO`, or `NONE`. * `FILE:path(,yield)` - Specifies the path to the local file to be used as the serial port. @@ -285,6 +286,7 @@ builder. * `yield` (bool) - This is an optional boolean that specifies whether the vm should yield the cpu when polling the port. By default, the builder will assume this as `FALSE`. + * `NONE` - Specifies to not use a serial port. (default) - `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty From 75fbfa0763bf94b2456d041a6649ce235770d870 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Thu, 13 Apr 2017 17:39:55 -0500 Subject: [PATCH 10/23] Replaced a hacky type assertion in the VMware builder with a call to Driver.GetVmwareDriver() that returns the driver-specific structure for ip and addressing information. Also implemented the addressing functions for the ESXi driver interface. This fixes an issue where a driver might not have defined a VmwareDriver by forcing a developer to implement it via the standard Driver interface. --- builder/vmware/common/driver.go | 1 + builder/vmware/common/driver_fusion5.go | 4 + builder/vmware/common/driver_fusion6.go | 4 + builder/vmware/common/driver_player5.go | 4 + builder/vmware/common/driver_player6.go | 4 + builder/vmware/common/driver_workstation10.go | 4 + builder/vmware/common/driver_workstation9.go | 4 + builder/vmware/iso/driver_esx5.go | 101 +++++++++++++++++- builder/vmware/iso/step_create_vmx.go | 2 +- 9 files changed, 126 insertions(+), 2 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 745b48d69..3e38442a2 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -64,6 +64,7 @@ type Driver interface { /// These methods are generally implemented by the VmwareDriver /// structure within this file. A driver implementation can /// reimplement these, though, if it wants. + GetVmwareDriver() VmwareDriver // Get the guest hw address for the vm GuestAddress(multistep.StateBag) (string, error) diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index 4bc5ae301..e8c49155c 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -173,3 +173,7 @@ const fusionSuppressPlist = ` ` + +func (d *Fusion5Driver) GetVmwareDriver() VmwareDriver { + return d.VmwareDriver +} diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index af42c7ef9..2f0563eb5 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -68,3 +68,7 @@ func (d *Fusion6Driver) Verify() error { return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional") } + +func (d *Fusion6Driver) GetVmwareDriver() VmwareDriver { + return d.Fusion5Driver.VmwareDriver +} diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 240b44a11..04fb4e897 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -209,3 +209,7 @@ func (d *Player5Driver) ToolsIsoPath(flavor string) string { func (d *Player5Driver) ToolsInstall() error { return nil } + +func (d *Player5Driver) GetVmwareDriver() VmwareDriver { + return d.VmwareDriver +} diff --git a/builder/vmware/common/driver_player6.go b/builder/vmware/common/driver_player6.go index d1cc7be88..1ccb84b85 100644 --- a/builder/vmware/common/driver_player6.go +++ b/builder/vmware/common/driver_player6.go @@ -35,3 +35,7 @@ func (d *Player6Driver) Verify() error { return playerVerifyVersion(VMWARE_PLAYER_VERSION) } + +func (d *Player6Driver) GetVmwareDriver() VmwareDriver { + return d.Player5Driver.VmwareDriver +} diff --git a/builder/vmware/common/driver_workstation10.go b/builder/vmware/common/driver_workstation10.go index 7e6ac8b8f..471fce37d 100644 --- a/builder/vmware/common/driver_workstation10.go +++ b/builder/vmware/common/driver_workstation10.go @@ -33,3 +33,7 @@ func (d *Workstation10Driver) Verify() error { return workstationVerifyVersion(VMWARE_WS_VERSION) } + +func (d *Workstation10Driver) GetVmwareDriver() VmwareDriver { + return d.Workstation9Driver.VmwareDriver +} diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index 8a6eb5556..af2d7df35 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -170,3 +170,7 @@ func (d *Workstation9Driver) ToolsIsoPath(flavor string) string { func (d *Workstation9Driver) ToolsInstall() error { return nil } + +func (d *Workstation9Driver) GetVmwareDriver() VmwareDriver { + return d.VmwareDriver +} diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 6df8271f6..d2f00ea81 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -26,7 +26,7 @@ import ( // ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build // virtual machines. This driver can only manage one machine at a time. type ESX5Driver struct { - vmwcommon.VmwareDriver + base vmwcommon.VmwareDriver Host string Port uint @@ -173,6 +173,101 @@ func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) { return host, err } +func (d *ESX5Driver) GuestIP(multistep.StateBag) (string, error) { + // GuestIP is defined by the user as d.Host..but let's validate it just to be sure + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port)) + defer conn.Close() + if err != nil { + return "", err + } + + host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) + return host, err +} + +func (d *ESX5Driver) HostAddress(multistep.StateBag) (string, error) { + // make a connection + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port)) + defer conn.Close() + if err != nil { + return "", err + } + + // get the local address (the host) + host, _, err := net.SplitHostPort(conn.LocalAddr().String()) + if err != nil { + return "", fmt.Errorf("Unable to determine host address for ESXi: %v", err) + } + + // iterate through all the interfaces.. + interfaces, err := net.Interfaces() + if err != nil { + return "", fmt.Errorf("Unable to enumerate host interfaces : %v", err) + } + + for _, intf := range interfaces { + addrs, err := intf.Addrs() + if err != nil { + continue + } + + // ..checking to see if any if it's addrs match the host address + for _, addr := range addrs { + if addr.String() == host { // FIXME: Is this the proper way to compare two HardwareAddrs? + return intf.HardwareAddr.String(), nil + } + } + } + + // ..unfortunately nothing was found + return "", fmt.Errorf("Unable to locate interface matching host address in ESXi: %v", host) +} + +func (d *ESX5Driver) GuestAddress(multistep.StateBag) (string, error) { + // list all the interfaces on the esx host + r, err := d.esxcli("network", "ip", "interface", "list") + if err != nil { + return "", fmt.Errorf("Could not retrieve network interfaces for ESXi: %v", err) + } + + // rip out the interface name and the MAC address from the csv output + addrs := make(map[string]string) + for record, err := r.read(); record != nil && err == nil; record, err = r.read() { + if strings.ToUpper(record["Enabled"]) != "TRUE" { + continue + } + addrs[record["Name"]] = record["MAC Address"] + } + + // list all the addresses on the esx host + r, err = d.esxcli("network", "ip", "interface", "ipv4", "get") + if err != nil { + return "", fmt.Errorf("Could not retrieve network addresses for ESXi: %v", err) + } + + // figure out the interface name that matches the specified d.Host address + var intf string + intf = "" + for record, err := r.read(); record != nil && err == nil; record, err = r.read() { + if record["IPv4 Address"] == d.Host && record["Name"] != "" { + intf = record["Name"] + break + } + } + if intf == "" { + return "", fmt.Errorf("Unable to find matching address for ESXi guest") + } + + // find the MAC address according to the interface name + result, ok := addrs[intf] + if !ok { + return "", fmt.Errorf("Unable to find address for ESXi guest interface") + } + + // ..and we're good + return result, nil +} + func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint, error) { var vncPort uint @@ -530,6 +625,10 @@ func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) { return &esxcliReader{r, header}, nil } +func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver { + return d.base +} + type esxcliReader struct { cr *csv.Reader header []string diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 207d38caa..bf6bb3d6c 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -370,7 +370,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist /// Check the network type that the user specified network := config.Network - driver := state.Get("driver").(vmwcommon.VmwareDriver) + driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver() // read netmap config pathNetmap := driver.NetmapConfPath() From 6423525a33562ca891fef4c8ab89b833a9f70971 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Thu, 13 Apr 2017 19:10:40 -0500 Subject: [PATCH 11/23] Updated imports of github.com/mitchellh/packer to new naming scheme github.com/hashicorp/packer --- builder/vmware/iso/driver_esx5.go | 2 +- builder/vmware/iso/step_create_vmx_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index d2f00ea81..3f18656d0 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -14,12 +14,12 @@ import ( "strings" "time" + vmwcommon "github.com/hashicorp/packer/builder/vmware/common" commonssh "github.com/hashicorp/packer/common/ssh" "github.com/hashicorp/packer/communicator/ssh" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" gossh "golang.org/x/crypto/ssh" ) diff --git a/builder/vmware/iso/step_create_vmx_test.go b/builder/vmware/iso/step_create_vmx_test.go index 7f5c1d91c..31177957f 100644 --- a/builder/vmware/iso/step_create_vmx_test.go +++ b/builder/vmware/iso/step_create_vmx_test.go @@ -12,9 +12,9 @@ import ( "strconv" "strings" - "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/provisioner/shell" - "github.com/mitchellh/packer/template" + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/provisioner/shell" + "github.com/hashicorp/packer/template" "testing" ) From 258804106bff27a6b9d8fd8efed727fb2ce289f4 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Thu, 13 Apr 2017 19:19:43 -0500 Subject: [PATCH 12/23] Added missing GetVmwareDriver() method to VMware Builder's DriverMock. --- builder/vmware/common/driver_mock.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 562eab382..8b533db88 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -206,3 +206,20 @@ func (d *DriverMock) Verify() error { d.VerifyCalled = true return d.VerifyErr } + +func (d *DriverMock) GetVmwareDriver() VmwareDriver { + var state VmwareDriver + state.DhcpLeasesPath = func(string) string { + return "/path/to/dhcp.leases" + } + state.DhcpConfPath = func(string) string { + return "/path/to/dhcp.conf" + } + state.VmnetnatConfPath = func(string) string { + return "/path/to/vmnetnat.conf" + } + state.NetmapConfPath = func() string { + return "/path/to/netmap.conf" + } + return state +} From 58ebc5c9a5cc932678f7b1624171a4704ea133fc Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Thu, 13 Apr 2017 20:01:10 -0500 Subject: [PATCH 13/23] When specifying NONE for serial or parallel in the VMware builder, disable the serial and parallel port devices entirely. --- builder/vmware/iso/step_create_vmx.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index bf6bb3d6c..137b0c736 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -404,7 +404,9 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist state.Put("vmnetwork", network) /// check if serial port has been configured - if config.Serial != "" { + if config.Serial == "" { + templateData.Serial_Present = "FALSE" + } else { serial, err := unformat_serial(config.Serial) if err != nil { err := fmt.Errorf("Error procesing VMX template: %s", err) @@ -439,6 +441,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Serial_Yield = serial.auto.yield templateData.Serial_Auto = "TRUE" case nil: + templateData.Serial_Present = "FALSE" break default: @@ -450,7 +453,9 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist } /// check if parallel port has been configured - if config.Parallel != "" { + if config.Parallel == "" { + templateData.Parallel_Present = "FALSE" + } else { parallel, err := unformat_parallel(config.Parallel) if err != nil { err := fmt.Errorf("Error procesing VMX template: %s", err) @@ -473,6 +478,7 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.Parallel_Auto = "TRUE" templateData.Parallel_Bidirectional = parallel.auto.bidirectional case nil: + templateData.Parallel_Present = "FALSE" break default: From 029c357d8c52e86d6733b6d342da3c55d8fecbc6 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Sat, 17 Jun 2017 15:35:58 -0500 Subject: [PATCH 14/23] Modified some tests to require the PACKER_ACC environment variable to be set before executing them. This turns them into acceptance tests as per CONTRIBUTING.md. --- builder/vmware/common/step_shutdown_test.go | 4 ++++ builder/vmware/iso/step_create_vmx_test.go | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/builder/vmware/common/step_shutdown_test.go b/builder/vmware/common/step_shutdown_test.go index ed0eae486..ff775f2f2 100644 --- a/builder/vmware/common/step_shutdown_test.go +++ b/builder/vmware/common/step_shutdown_test.go @@ -116,6 +116,10 @@ func TestStepShutdown_noCommand(t *testing.T) { } func TestStepShutdown_locks(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.") + } + state := testStepShutdownState(t) step := new(StepShutdown) step.Testing = true diff --git a/builder/vmware/iso/step_create_vmx_test.go b/builder/vmware/iso/step_create_vmx_test.go index 31177957f..7553dff82 100644 --- a/builder/vmware/iso/step_create_vmx_test.go +++ b/builder/vmware/iso/step_create_vmx_test.go @@ -199,6 +199,10 @@ func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisioner } func TestStepCreateVmx_SerialFile(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.") + } + tmpfile := tmpnam("SerialFileInput.") serialConfig := map[string]string{ @@ -223,6 +227,10 @@ func TestStepCreateVmx_SerialFile(t *testing.T) { } func TestStepCreateVmx_SerialPort(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.") + } + var defaultSerial string if runtime.GOOS == "windows" { defaultSerial = "COM1" @@ -268,6 +276,10 @@ func TestStepCreateVmx_SerialPort(t *testing.T) { } func TestStepCreateVmx_ParallelPort(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.") + } + var defaultParallel string if runtime.GOOS == "windows" { defaultParallel = "LPT1" @@ -313,6 +325,10 @@ func TestStepCreateVmx_ParallelPort(t *testing.T) { } func TestStepCreateVmx_Usb(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.") + } + config := map[string]string{ "usb": `"TRUE"`, } @@ -351,6 +367,10 @@ func TestStepCreateVmx_Usb(t *testing.T) { } func TestStepCreateVmx_Sound(t *testing.T) { + if os.Getenv("PACKER_ACC") == "" { + t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the VMware binaries.") + } + config := map[string]string{ "sound": `"TRUE"`, } From 8cc0776f3a0d5335a5ef1eb5b35a1f19121fb5e4 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Sun, 10 Sep 2017 16:02:45 -0500 Subject: [PATCH 15/23] Fixed oversight in VMware builder's mock-driver that neglected to initialize 'HostAddressResult'. --- builder/vmware/common/driver_mock.go | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 8b533db88..9c63a7bf9 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -1,6 +1,7 @@ package common import ( + "net" "sync" "github.com/hashicorp/packer/helper/multistep" @@ -127,13 +128,41 @@ func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) { return d.CommHostResult, d.CommHostErr } +func MockInterface() net.Interface { + interfaces, err := net.Interfaces() + + // Build a dummy interface due to being unable to enumerate interfaces + if err != nil || len(interfaces) == 0 { + return net.Interface{ + Index: 0, + MTU: -1, + Name: "dummy", + HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0}, + Flags: net.FlagLoopback, + } + } + + // Find the first loopback interface + for _, intf := range interfaces { + if intf.Flags&net.FlagLoopback == net.FlagLoopback { + return intf + } + } + + // Fall-back to just the first one + return interfaces[0] +} + func (d *DriverMock) HostAddress(state multistep.StateBag) (string, error) { + intf := MockInterface() + d.HostAddressResult = intf.HardwareAddr.String() d.HostAddressCalled = true d.HostAddressState = state return d.HostAddressResult, d.HostAddressErr } func (d *DriverMock) HostIP(state multistep.StateBag) (string, error) { + d.HostIPResult = "127.0.0.1" d.HostIPCalled = true d.HostIPState = state return d.HostIPResult, d.HostIPErr From 069d00f70b871ded6517a5034aa7a4c1c5b7ccfc Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Mon, 16 Oct 2017 11:59:10 -0500 Subject: [PATCH 16/23] Added the paths suggested by @phekmat and @SwampDragons for VMware Fusion... Although parser for the new mapper format is likely to be needed still. --- builder/vmware/common/driver_fusion5.go | 13 ++++++++++++- builder/vmware/common/driver_fusion6.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index e8c49155c..c6b3780dc 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -141,10 +141,21 @@ func (d *Fusion5Driver) Verify() error { return err } - // default paths + libpath := filepath.Join("Library", "Preferences", "VMware Fusion") + d.VmwareDriver.DhcpLeasesPath = func(device string) string { return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" } + d.VmwareDriver.DhcpConfPath = func(device string) string { + return filepath.Join(libpath, device, "dhcpd.conf") + } + d.VmwareDriver.VmnetnatConfPath = func(device string) string { + return filepath.Join(libpath, device, "nat.conf") + } + d.VmwareDriver.NetmapConfPath = func() string { + // FIXME: Suggested by @phekmat. This will need another parser to be implemented. + return filepath.Join(libpath, "networking") + } return nil } diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index 2f0563eb5..e68bb0de4 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -66,6 +66,23 @@ func (d *Fusion6Driver) Verify() error { } log.Printf("Detected VMware version: %s", matches[1]) + libpath := filepath.Join("Library", "Preferences", "VMware Fusion") + + d.VmwareDriver.DhcpLeasesPath = func(device string) string { + return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" + } + d.VmwareDriver.DhcpConfPath = func(device string) string { + return filepath.Join(libpath, device, "dhcpd.conf") + } + + d.VmwareDriver.VmnetnatConfPath = func(device string) string { + return filepath.Join(libpath, device, "nat.conf") + } + d.VmwareDriver.NetmapConfPath = func() string { + // FIXME: Suggested by @phekmat. This will need another parser to be implemented. + return filepath.Join(libpath, "networking") + } + return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional") } From b2fec18b1e32dc37932323d84156fa5b5d1a8d8a Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Mon, 23 Oct 2017 20:26:16 -0500 Subject: [PATCH 17/23] Added parser for VMware Fusion's networking file. Replaced VmwareDriver's NetmapConfPath with a NetworkMapperInterface in order to handle the differences between VMware Fusion and the rest of the VMware suite. --- builder/vmware/common/driver.go | 29 +- builder/vmware/common/driver_fusion5.go | 16 +- builder/vmware/common/driver_fusion6.go | 16 +- builder/vmware/common/driver_mock.go | 18 +- builder/vmware/common/driver_parser.go | 847 ++++++++++++++++++- builder/vmware/common/driver_player5.go | 15 +- builder/vmware/common/driver_workstation9.go | 15 +- builder/vmware/iso/step_create_vmx.go | 12 +- 8 files changed, 922 insertions(+), 46 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 3e38442a2..6b5cfde1f 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -274,7 +274,10 @@ type VmwareDriver struct { DhcpLeasesPath func(string) string DhcpConfPath func(string) string VmnetnatConfPath func(string) string - NetmapConfPath func() string + + /// This method returns an object with the NetworkNameMapper interface + /// that maps network to device and vice-versa. + NetworkMapper func() (NetworkNameMapper, error) } func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) { @@ -304,12 +307,8 @@ func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) { 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) + // grab network mapper + netmap, err := d.NetworkMapper() if err != nil { return "", err } @@ -401,12 +400,8 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { 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) + // grab mapper for converting network<->device + netmap, err := d.NetworkMapper() if err != nil { return "", err } @@ -471,12 +466,8 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { 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) + // grab mapper for converting network<->device + netmap, err := d.NetworkMapper() if err != nil { return "", err } diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index c6b3780dc..c450e1f61 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -152,9 +152,19 @@ func (d *Fusion5Driver) Verify() error { d.VmwareDriver.VmnetnatConfPath = func(device string) string { return filepath.Join(libpath, device, "nat.conf") } - d.VmwareDriver.NetmapConfPath = func() string { - // FIXME: Suggested by @phekmat. This will need another parser to be implemented. - return filepath.Join(libpath, "networking") + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetworking := filepath.Join(libpath, "networking") + if _, err := os.Stat(pathNetworking); err != nil { + return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking) + } + + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkingConfig(fd) } return nil diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index e68bb0de4..62dd7b218 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -78,9 +78,19 @@ func (d *Fusion6Driver) Verify() error { d.VmwareDriver.VmnetnatConfPath = func(device string) string { return filepath.Join(libpath, device, "nat.conf") } - d.VmwareDriver.NetmapConfPath = func() string { - // FIXME: Suggested by @phekmat. This will need another parser to be implemented. - return filepath.Join(libpath, "networking") + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetworking := filepath.Join(libpath, "networking") + if _, err := os.Stat(pathNetworking); err != nil { + return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking) + } + + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkingConfig(fd) } return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional") diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 9c63a7bf9..f857f4adc 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -92,6 +92,20 @@ type DriverMock struct { VerifyErr error } +type NetworkMapperMock struct { + NameIntoDeviceCalled int + DeviceIntoNameCalled int +} + +func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) { + m.NameIntoDeviceCalled += 1 + return "", nil +} +func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) { + m.DeviceIntoNameCalled += 1 + return "", nil +} + func (d *DriverMock) Clone(dst string, src string) error { d.CloneCalled = true d.CloneDst = dst @@ -247,8 +261,8 @@ func (d *DriverMock) GetVmwareDriver() VmwareDriver { state.VmnetnatConfPath = func(string) string { return "/path/to/vmnetnat.conf" } - state.NetmapConfPath = func() string { - return "/path/to/netmap.conf" + state.NetworkMapper = func() (NetworkNameMapper, error) { + return NetworkMapperMock{}, nil } return state } diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go index dbf15b5b5..7a026fea8 100644 --- a/builder/vmware/common/driver_parser.go +++ b/builder/vmware/common/driver_parser.go @@ -3,8 +3,11 @@ package common import ( "fmt" "io" + "log" + "math" "net" "os" + "reflect" "sort" "strconv" "strings" @@ -1053,6 +1056,11 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) { /*** Network Map */ type NetworkMap []map[string]string +type NetworkNameMapper interface { + NameIntoDevice(string) (string, error) + DeviceIntoName(string) (string, error) +} + func ReadNetworkMap(fd *os.File) (NetworkMap, error) { fromfile, eof := consumeFile(fd) @@ -1066,16 +1074,16 @@ func ReadNetworkMap(fd *os.File) (NetworkMap, error) { return result, nil } -func (e *NetworkMap) NameIntoDevice(name string) (string, error) { - for _, val := range *e { +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 { +func (e NetworkMap) DeviceIntoName(device string) (string, error) { + for _, val := range e { if strings.ToLower(val["device"]) == strings.ToLower(device) { return val["name"], nil } @@ -1091,7 +1099,836 @@ func (e *NetworkMap) repr() string { return strings.Join(result, "\n") } -/** main */ +/*** parser for VMware Fusion's networking file */ +func tokenizeNetworkingConfig(eof sentinelSignaller, in chan byte) chan string { + var ch byte + var state string + var repeat_newline bool + + out := make(chan string) + go func(out chan string) { + for reading := true; reading; { + select { + case <-eof: + reading = false + + case ch = <-in: + switch ch { + case '\r': + fallthrough + case '\t': + fallthrough + case ' ': + if len(state) == 0 { + continue + } + out <- state + state = "" + case '\n': + if repeat_newline { + continue + } + if len(state) > 0 { + out <- state + } + out <- string(ch) + state = "" + repeat_newline = true + continue + default: + state += string(ch) + } + repeat_newline = false + } + } + if len(state) > 0 { + out <- state + } + }(out) + return out +} + +func splitNetworkingConfig(eof sentinelSignaller, in chan string) chan []string { + var out chan []string + + out = make(chan []string) + go func(out chan []string) { + row := make([]string, 0) + for reading := true; reading; { + select { + case <-eof: + reading = false + + case tk := <-in: + switch tk { + case "\n": + if len(row) > 0 { + out <- row + } + row = make([]string, 0) + default: + row = append(row, tk) + } + } + } + if len(row) > 0 { + out <- row + } + }(out) + return out +} + +/// All token types in networking file. +// VERSION token +type networkingVERSION struct { + value string +} + +func networkingReadVersion(row []string) (*networkingVERSION, error) { + if len(row) != 1 { + return nil, fmt.Errorf("Unexpected format for VERSION entry : %v", row) + } + res := &networkingVERSION{value: row[0]} + if !res.Valid() { + return nil, fmt.Errorf("Unexpected format for VERSION entry : %v", row) + } + return res, nil +} + +func (s networkingVERSION) Repr() string { + if !s.Valid() { + return fmt.Sprintf("VERSION{INVALID=\"%v\"}", s.value) + } + return fmt.Sprintf("VERSION{%f}", s.Number()) +} + +func (s networkingVERSION) Valid() bool { + tokens := strings.SplitN(s.value, "=", 2) + if len(tokens) != 2 || tokens[0] != "VERSION" { + return false + } + + tokens = strings.Split(tokens[1], ",") + if len(tokens) != 2 { + return false + } + + for _, t := range tokens { + _, err := strconv.ParseUint(t, 10, 64) + if err != nil { + return false + } + } + return true +} + +func (s networkingVERSION) Number() float64 { + var result float64 + tokens := strings.SplitN(s.value, "=", 2) + tokens = strings.Split(tokens[1], ",") + + integer, err := strconv.ParseUint(tokens[0], 10, 64) + if err != nil { + integer = 0 + } + result = float64(integer) + + mantissa, err := strconv.ParseUint(tokens[1], 10, 64) + if err != nil { + return result + } + denomination := math.Pow(10.0, float64(len(tokens[1]))) + return result + (float64(mantissa) / denomination) +} + +// VNET_X token +type networkingVNET struct { + value string +} + +func (s networkingVNET) Valid() bool { + if strings.ToUpper(s.value) != s.value { + return false + } + tokens := strings.SplitN(s.value, "_", 3) + if len(tokens) != 3 || tokens[0] != "VNET" { + return false + } + _, err := strconv.ParseUint(tokens[1], 10, 64) + if err != nil { + return false + } + return true +} + +func (s networkingVNET) Number() int { + tokens := strings.SplitN(s.value, "_", 3) + res, err := strconv.Atoi(tokens[1]) + if err != nil { + return ^int(0) + } + return res +} + +func (s networkingVNET) Option() string { + tokens := strings.SplitN(s.value, "_", 3) + if len(tokens) == 3 { + return tokens[2] + } + return "" +} + +func (s networkingVNET) Repr() string { + if !s.Valid() { + tokens := strings.SplitN(s.value, "_", 3) + return fmt.Sprintf("VNET{INVALID=%v}", tokens) + } + return fmt.Sprintf("VNET{%d} %s", s.Number(), s.Option()) +} + +// Interface name +type networkingInterface struct { + name string +} + +func (s networkingInterface) Interface() (*net.Interface, error) { + return net.InterfaceByName(s.name) +} + +// networking command entry types +type networkingCommandEntry_answer struct { + vnet networkingVNET + value string +} +type networkingCommandEntry_remove_answer struct { + vnet networkingVNET +} +type networkingCommandEntry_add_nat_portfwd struct { + vnet int + protocol string + port int + target_host net.IP + target_port int +} +type networkingCommandEntry_remove_nat_portfwd struct { + vnet int + protocol string + port int +} +type networkingCommandEntry_add_dhcp_mac_to_ip struct { + vnet int + mac net.HardwareAddr + ip net.IP +} +type networkingCommandEntry_remove_dhcp_mac_to_ip struct { + vnet int + mac net.HardwareAddr +} +type networkingCommandEntry_add_bridge_mapping struct { + intf networkingInterface + vnet int +} +type networkingCommandEntry_remove_bridge_mapping struct { + intf networkingInterface +} +type networkingCommandEntry_add_nat_prefix struct { + vnet int + prefix int +} +type networkingCommandEntry_remove_nat_prefix struct { + vnet int + prefix int +} + +type networkingCommandEntry struct { + entry interface{} + answer *networkingCommandEntry_answer + remove_answer *networkingCommandEntry_remove_answer + add_nat_portfwd *networkingCommandEntry_add_nat_portfwd + remove_nat_portfwd *networkingCommandEntry_remove_nat_portfwd + add_dhcp_mac_to_ip *networkingCommandEntry_add_dhcp_mac_to_ip + remove_dhcp_mac_to_ip *networkingCommandEntry_remove_dhcp_mac_to_ip + add_bridge_mapping *networkingCommandEntry_add_bridge_mapping + remove_bridge_mapping *networkingCommandEntry_remove_bridge_mapping + add_nat_prefix *networkingCommandEntry_add_nat_prefix + remove_nat_prefix *networkingCommandEntry_remove_nat_prefix +} + +func (e networkingCommandEntry) Name() string { + switch e.entry.(type) { + case networkingCommandEntry_answer: + return "answer" + case networkingCommandEntry_remove_answer: + return "remove_answer" + case networkingCommandEntry_add_nat_portfwd: + return "add_nat_portfwd" + case networkingCommandEntry_remove_nat_portfwd: + return "remove_nat_portfwd" + case networkingCommandEntry_add_dhcp_mac_to_ip: + return "add_dhcp_mac_to_ip" + case networkingCommandEntry_remove_dhcp_mac_to_ip: + return "remove_dhcp_mac_to_ip" + case networkingCommandEntry_add_bridge_mapping: + return "add_bridge_mapping" + case networkingCommandEntry_remove_bridge_mapping: + return "remove_bridge_mapping" + case networkingCommandEntry_add_nat_prefix: + return "add_nat_prefix" + case networkingCommandEntry_remove_nat_prefix: + return "remove_nat_prefix" + } + return "" +} + +func (e networkingCommandEntry) Entry() reflect.Value { + this := reflect.ValueOf(e) + switch e.entry.(type) { + case networkingCommandEntry_answer: + return reflect.Indirect(this.FieldByName("answer")) + case networkingCommandEntry_remove_answer: + return reflect.Indirect(this.FieldByName("remove_answer")) + case networkingCommandEntry_add_nat_portfwd: + return reflect.Indirect(this.FieldByName("add_nat_portfwd")) + case networkingCommandEntry_remove_nat_portfwd: + return reflect.Indirect(this.FieldByName("remove_nat_portfwd")) + case networkingCommandEntry_add_dhcp_mac_to_ip: + return reflect.Indirect(this.FieldByName("add_dhcp_mac_to_ip")) + case networkingCommandEntry_remove_dhcp_mac_to_ip: + return reflect.Indirect(this.FieldByName("remove_dhcp_mac_to_ip")) + case networkingCommandEntry_add_bridge_mapping: + return reflect.Indirect(this.FieldByName("add_bridge_mapping")) + case networkingCommandEntry_remove_bridge_mapping: + return reflect.Indirect(this.FieldByName("remove_bridge_mapping")) + case networkingCommandEntry_add_nat_prefix: + return reflect.Indirect(this.FieldByName("add_nat_prefix")) + case networkingCommandEntry_remove_nat_prefix: + return reflect.Indirect(this.FieldByName("remove_nat_prefix")) + } + return reflect.Value{} +} + +func (e networkingCommandEntry) Repr() string { + var result map[string]interface{} + result = make(map[string]interface{}) + + entryN, entry := e.Name(), e.Entry() + entryT := entry.Type() + for i := 0; i < entry.NumField(); i++ { + fld, fldT := entry.Field(i), entryT.Field(i) + result[fldT.Name] = fld + } + return fmt.Sprintf("%s -> %v", entryN, result) +} + +// networking command entry parsers +func parseNetworkingCommand_answer(row []string) (*networkingCommandEntry, error) { + if len(row) != 2 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) + } + vnet := networkingVNET{value: row[0]} + if !vnet.Valid() { + return nil, fmt.Errorf("Invalid format for VNET.") + } + value := row[1] + + result := networkingCommandEntry_answer{vnet: vnet, value: value} + return &networkingCommandEntry{entry: result, answer: &result}, nil +} +func parseNetworkingCommand_remove_answer(row []string) (*networkingCommandEntry, error) { + if len(row) != 1 { + return nil, fmt.Errorf("Expected %d argument. Received %d.", 1, len(row)) + } + vnet := networkingVNET{value: row[0]} + if !vnet.Valid() { + return nil, fmt.Errorf("Invalid format for VNET.") + } + + result := networkingCommandEntry_remove_answer{vnet: vnet} + return &networkingCommandEntry{entry: result, remove_answer: &result}, nil +} +func parseNetworkingCommand_add_nat_portfwd(row []string) (*networkingCommandEntry, error) { + if len(row) != 5 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 5, len(row)) + } + + vnet, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + + protocol := strings.ToLower(row[1]) + if !(protocol == "tcp" || protocol == "udp") { + return nil, fmt.Errorf("Expected \"tcp\" or \"udp\" for second argument. : %v", row[1]) + } + + sport, err := strconv.Atoi(row[2]) + if err != nil { + return nil, fmt.Errorf("Unable to parse third argument as an integer. : %v", row[2]) + } + + dest := net.ParseIP(row[3]) + if dest == nil { + return nil, fmt.Errorf("Unable to parse fourth argument as an IPv4 address. : %v", row[2]) + } + + dport, err := strconv.Atoi(row[4]) + if err != nil { + return nil, fmt.Errorf("Unable to parse fifth argument as an integer. : %v", row[4]) + } + + result := networkingCommandEntry_add_nat_portfwd{vnet: vnet - 1, protocol: protocol, port: sport, target_host: dest, target_port: dport} + return &networkingCommandEntry{entry: result, add_nat_portfwd: &result}, nil +} +func parseNetworkingCommand_remove_nat_portfwd(row []string) (*networkingCommandEntry, error) { + if len(row) != 3 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 3, len(row)) + } + + vnet, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + + protocol := strings.ToLower(row[1]) + if !(protocol == "tcp" || protocol == "udp") { + return nil, fmt.Errorf("Expected \"tcp\" or \"udp\" for second argument. : %v", row[1]) + } + + sport, err := strconv.Atoi(row[2]) + if err != nil { + return nil, fmt.Errorf("Unable to parse third argument as an integer. : %v", row[2]) + } + + result := networkingCommandEntry_remove_nat_portfwd{vnet: vnet - 1, protocol: protocol, port: sport} + return &networkingCommandEntry{entry: result, remove_nat_portfwd: &result}, nil +} +func parseNetworkingCommand_add_dhcp_mac_to_ip(row []string) (*networkingCommandEntry, error) { + if len(row) != 3 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 3, len(row)) + } + + vnet, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + + mac, err := net.ParseMAC(row[1]) + if err != nil { + return nil, fmt.Errorf("Unable to parse second argument as hwaddr. : %v", row[1]) + } + + ip := net.ParseIP(row[2]) + if ip != nil { + return nil, fmt.Errorf("Unable to parse third argument as ipv4. : %v", row[2]) + } + + result := networkingCommandEntry_add_dhcp_mac_to_ip{vnet: vnet - 1, mac: mac, ip: ip} + return &networkingCommandEntry{entry: result, add_dhcp_mac_to_ip: &result}, nil +} +func parseNetworkingCommand_remove_dhcp_mac_to_ip(row []string) (*networkingCommandEntry, error) { + if len(row) != 2 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) + } + + vnet, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + + mac, err := net.ParseMAC(row[1]) + if err != nil { + return nil, fmt.Errorf("Unable to parse second argument as hwaddr. : %v", row[1]) + } + + result := networkingCommandEntry_remove_dhcp_mac_to_ip{vnet: vnet - 1, mac: mac} + return &networkingCommandEntry{entry: result, remove_dhcp_mac_to_ip: &result}, nil +} +func parseNetworkingCommand_add_bridge_mapping(row []string) (*networkingCommandEntry, error) { + if len(row) != 2 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) + } + intf := networkingInterface{name: row[0]} + + vnet, err := strconv.Atoi(row[1]) + if err != nil { + return nil, fmt.Errorf("Unable to parse second argument as an integer. : %v", row[2]) + } + + result := networkingCommandEntry_add_bridge_mapping{intf: intf, vnet: vnet - 1} + return &networkingCommandEntry{entry: result, add_bridge_mapping: &result}, nil +} +func parseNetworkingCommand_remove_bridge_mapping(row []string) (*networkingCommandEntry, error) { + if len(row) != 1 { + return nil, fmt.Errorf("Expected %d argument. Received %d.", 1, len(row)) + } + intf := networkingInterface{name: row[0]} + /* + number, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + */ + result := networkingCommandEntry_remove_bridge_mapping{intf: intf} + return &networkingCommandEntry{entry: result, remove_bridge_mapping: &result}, nil +} +func parseNetworkingCommand_add_nat_prefix(row []string) (*networkingCommandEntry, error) { + if len(row) != 2 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) + } + + vnet, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + + if !strings.HasPrefix(row[1], "/") { + return nil, fmt.Errorf("Expected second argument to begin with \"/\". : %v", row[1]) + } + + prefix, err := strconv.Atoi(row[1][1:]) + if err != nil { + return nil, fmt.Errorf("Unable to parse prefix out of second argument. : %v", row[1]) + } + + result := networkingCommandEntry_add_nat_prefix{vnet: vnet - 1, prefix: prefix} + return &networkingCommandEntry{entry: result, add_nat_prefix: &result}, nil +} +func parseNetworkingCommand_remove_nat_prefix(row []string) (*networkingCommandEntry, error) { + if len(row) != 2 { + return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) + } + + vnet, err := strconv.Atoi(row[0]) + if err != nil { + return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) + } + + if !strings.HasPrefix(row[1], "/") { + return nil, fmt.Errorf("Expected second argument to begin with \"/\". : %v", row[1]) + } + prefix, err := strconv.Atoi(row[1][1:]) + if err != nil { + return nil, fmt.Errorf("Unable to parse prefix out of second argument. : %v", row[1]) + } + + result := networkingCommandEntry_remove_nat_prefix{vnet: vnet - 1, prefix: prefix} + return &networkingCommandEntry{entry: result, remove_nat_prefix: &result}, nil +} + +type networkingCommandParser struct { + command string + callback func([]string) (*networkingCommandEntry, error) +} + +var NetworkingCommandParsers = []networkingCommandParser{ + /* DictRecordParseFunct */ {command: "answer", callback: parseNetworkingCommand_answer}, + /* DictRecordParseFunct */ {command: "remove_answer", callback: parseNetworkingCommand_remove_answer}, + /* NatFwdRecordParseFunct */ {command: "add_nat_portfwd", callback: parseNetworkingCommand_add_nat_portfwd}, + /* NatFwdRecordParseFunct */ {command: "remove_nat_portfwd", callback: parseNetworkingCommand_remove_nat_portfwd}, + /* DhcpMacRecordParseFunct */ {command: "add_dhcp_mac_to_ip", callback: parseNetworkingCommand_add_dhcp_mac_to_ip}, + /* DhcpMacRecordParseFunct */ {command: "remove_dhcp_mac_to_ip", callback: parseNetworkingCommand_remove_dhcp_mac_to_ip}, + /* BridgeMappingRecordParseFunct */ {command: "add_bridge_mapping", callback: parseNetworkingCommand_add_bridge_mapping}, + /* BridgeMappingRecordParseFunct */ {command: "remove_bridge_mapping", callback: parseNetworkingCommand_remove_bridge_mapping}, + /* NatPrefixRecordParseFunct */ {command: "add_nat_prefix", callback: parseNetworkingCommand_add_nat_prefix}, + /* NatPrefixRecordParseFunct */ {command: "remove_nat_prefix", callback: parseNetworkingCommand_remove_nat_prefix}, +} + +func NetworkingParserByCommand(command string) *func([]string) (*networkingCommandEntry, error) { + for _, p := range NetworkingCommandParsers { + if p.command == command { + return &p.callback + } + } + return nil +} + +func parseNetworkingConfig(eof sentinelSignaller, rows chan []string) chan networkingCommandEntry { + var out chan networkingCommandEntry + + out = make(chan networkingCommandEntry) + go func(in chan []string, out chan networkingCommandEntry) { + for reading := true; reading; { + select { + case <-eof: + reading = false + case row := <-in: + if len(row) >= 1 { + parser := NetworkingParserByCommand(row[0]) + if parser == nil { + log.Printf("Invalid command : %v", row) + continue + } + callback := *parser + entry, err := callback(row[1:]) + if err != nil { + log.Printf("Unable to parse command : %v %v", err, row) + continue + } + out <- *entry + } + } + } + }(rows, out) + return out +} + +type NetworkingConfig struct { + answer map[int]map[string]string + nat_portfwd map[int]map[string]string + dhcp_mac_to_ip map[int]map[string]net.IP + //bridge_mapping map[net.Interface]uint64 // XXX: we don't need the actual interface for anything but informing the user. + bridge_mapping map[string]int + nat_prefix map[int][]int +} + +func (c NetworkingConfig) repr() string { + return fmt.Sprintf("answer -> %v\nnat_portfwd -> %v\ndhcp_mac_to_ip -> %v\nbridge_mapping -> %v\nnat_prefix -> %v", c.answer, c.nat_portfwd, c.dhcp_mac_to_ip, c.bridge_mapping, c.nat_prefix) +} + +func flattenNetworkingConfig(eof sentinelSignaller, in chan networkingCommandEntry) NetworkingConfig { + var result NetworkingConfig + var vmnet int + + result.answer = make(map[int]map[string]string) + result.nat_portfwd = make(map[int]map[string]string) + result.dhcp_mac_to_ip = make(map[int]map[string]net.IP) + result.bridge_mapping = make(map[string]int) + result.nat_prefix = make(map[int][]int) + + for reading := true; reading; { + select { + case <-eof: + reading = false + case e := <-in: + switch e.entry.(type) { + case networkingCommandEntry_answer: + vnet := e.answer.vnet + answers, exists := result.answer[vnet.Number()] + if !exists { + answers = make(map[string]string) + result.answer[vnet.Number()] = answers + } + answers[vnet.Option()] = e.answer.value + case networkingCommandEntry_remove_answer: + vnet := e.remove_answer.vnet + answers, exists := result.answer[vnet.Number()] + if exists { + delete(answers, vnet.Option()) + } else { + log.Printf("Unable to remove answer %s as specified by `remove_answer`.\n", vnet.Repr()) + } + case networkingCommandEntry_add_nat_portfwd: + vmnet = e.add_nat_portfwd.vnet + protoport := fmt.Sprintf("%s/%d", e.add_nat_portfwd.protocol, e.add_nat_portfwd.port) + target := fmt.Sprintf("%s:%d", e.add_nat_portfwd.target_host, e.add_nat_portfwd.target_port) + portfwds, exists := result.nat_portfwd[vmnet] + if !exists { + portfwds = make(map[string]string) + result.nat_portfwd[vmnet] = portfwds + } + portfwds[protoport] = target + case networkingCommandEntry_remove_nat_portfwd: + vmnet = e.remove_nat_portfwd.vnet + protoport := fmt.Sprintf("%s/%d", e.remove_nat_portfwd.protocol, e.remove_nat_portfwd.port) + portfwds, exists := result.nat_portfwd[vmnet] + if exists { + delete(portfwds, protoport) + } else { + log.Printf("Unable to remove nat port-forward %s from interface %s%d as requested by `remove_nat_portfwd`.\n", protoport, NetworkingInterfacePrefix, vmnet) + } + case networkingCommandEntry_add_dhcp_mac_to_ip: + vmnet = e.add_dhcp_mac_to_ip.vnet + dhcpmacs, exists := result.dhcp_mac_to_ip[vmnet] + if !exists { + dhcpmacs = make(map[string]net.IP) + result.dhcp_mac_to_ip[vmnet] = dhcpmacs + } + dhcpmacs[e.add_dhcp_mac_to_ip.mac.String()] = e.add_dhcp_mac_to_ip.ip + case networkingCommandEntry_remove_dhcp_mac_to_ip: + vmnet = e.remove_dhcp_mac_to_ip.vnet + dhcpmacs, exists := result.dhcp_mac_to_ip[vmnet] + if exists { + delete(dhcpmacs, e.remove_dhcp_mac_to_ip.mac.String()) + } else { + log.Printf("Unable to remove dhcp_mac_to_ip entry %v from interface %s%d as specified by `remove_dhcp_mac_to_ip`.\n", e.remove_dhcp_mac_to_ip, NetworkingInterfacePrefix, vmnet) + } + case networkingCommandEntry_add_bridge_mapping: + intf := e.add_bridge_mapping.intf + if _, err := intf.Interface(); err != nil { + log.Printf("Interface \"%s\" as specified by `add_bridge_mapping` was not found on the current platform. This is a non-critical error. Ignoring.", intf.name) + } + result.bridge_mapping[intf.name] = e.add_bridge_mapping.vnet + case networkingCommandEntry_remove_bridge_mapping: + intf := e.remove_bridge_mapping.intf + if _, err := intf.Interface(); err != nil { + log.Printf("Interface \"%s\" as specified by `remove_bridge_mapping` was not found on the current platform. This is a non-critical error. Ignoring.", intf.name) + } + delete(result.bridge_mapping, intf.name) + case networkingCommandEntry_add_nat_prefix: + vmnet = e.add_nat_prefix.vnet + _, exists := result.nat_prefix[vmnet] + if exists { + result.nat_prefix[vmnet] = append(result.nat_prefix[vmnet], e.add_nat_prefix.prefix) + } else { + result.nat_prefix[vmnet] = []int{e.add_nat_prefix.prefix} + } + case networkingCommandEntry_remove_nat_prefix: + vmnet = e.remove_nat_prefix.vnet + prefixes, exists := result.nat_prefix[vmnet] + if exists { + for index := 0; index < len(prefixes); index++ { + if prefixes[index] == e.remove_nat_prefix.prefix { + result.nat_prefix[vmnet] = append(prefixes[:index], prefixes[index+1:]...) + break + } + } + } else { + log.Printf("Unable to remove nat prefix /%d from interface %s%d as specified by `remove_nat_prefix`.\n", e.remove_nat_prefix.prefix, NetworkingInterfacePrefix, vmnet) + } + } + } + } + return result +} + +// Constructor for networking file +func ReadNetworkingConfig(fd *os.File) (NetworkingConfig, error) { + // start piecing together different parts of the file + fromfile, eof := consumeFile(fd) + tokenized := tokenizeNetworkingConfig(eof, fromfile) + rows := splitNetworkingConfig(eof, tokenized) + entries := parseNetworkingConfig(eof, rows) + + // parse the version + parsed_version, err := networkingReadVersion(<-rows) + if err != nil { + return NetworkingConfig{}, err + } + + // verify that it's 1.0 since that's all we support. + version := parsed_version.Number() + if version != 1.0 { + return NetworkingConfig{}, fmt.Errorf("Expected version %f of networking file. Received version %f.", 1.0, version) + } + + // convert to a configuration + result := flattenNetworkingConfig(eof, entries) + return result, nil +} + +// netmapper interface +type NetworkingType int + +const ( + NetworkingType_HOSTONLY = iota + 1 + NetworkingType_NAT + NetworkingType_BRIDGED +) + +func networkingConfig_InterfaceTypes(config NetworkingConfig) map[int]NetworkingType { + var result map[int]NetworkingType + result = make(map[int]NetworkingType) + + // defaults + result[0] = NetworkingType_BRIDGED + result[1] = NetworkingType_HOSTONLY + result[8] = NetworkingType_NAT + + // walk through config collecting bridged interfaces + for _, vmnet := range config.bridge_mapping { + result[vmnet] = NetworkingType_BRIDGED + } + + // walk through answers finding out which ones are nat versus hostonly + for vmnet, table := range config.answer { + // everything should be defined as a virtual adapter... + if table["VIRTUAL_ADAPTER"] == "yes" { + + // validate that the VNET entry contains everything we expect it to + _, subnetQ := table["HOSTONLY_SUBNET"] + _, netmaskQ := table["HOSTONLY_NETMASK"] + if !(subnetQ && netmaskQ) { + log.Printf("Interface %s%d is missing some expected keys (HOSTONLY_SUBNET, HOSTONLY_NETMASK). This is non-critical. Ignoring..", NetworkingInterfacePrefix, vmnet) + } + + // distinguish between nat or hostonly + if table["NAT"] == "yes" { + result[vmnet] = NetworkingType_NAT + } else { + result[vmnet] = NetworkingType_HOSTONLY + } + + // if it's not a virtual_adapter, then it must be an alias (really a bridge). + } else { + result[vmnet] = NetworkingType_BRIDGED + } + } + return result +} + +func networkingConfig_NamesToVmnet(config NetworkingConfig) map[NetworkingType][]int { + types := networkingConfig_InterfaceTypes(config) + + // now sort the keys + var keys []int + for vmnet := range types { + keys = append(keys, vmnet) + } + sort.Ints(keys) + + // build result dictionary + var result map[NetworkingType][]int + result = make(map[NetworkingType][]int) + for i := 0; i < len(keys); i++ { + t := types[keys[i]] + result[t] = append(result[t], keys[i]) + } + return result +} + +const NetworkingInterfacePrefix = "vmnet" + +func (e NetworkingConfig) NameIntoDevice(name string) (string, error) { + netmapper := networkingConfig_NamesToVmnet(e) + name = strings.ToLower(name) + + var vmnet int + if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 { + vmnet = netmapper[NetworkingType_HOSTONLY][0] + } else if name == "nat" && len(netmapper[NetworkingType_NAT]) > 0 { + vmnet = netmapper[NetworkingType_NAT][0] + } else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 { + vmnet = netmapper[NetworkingType_BRIDGED][0] + } else { + return "", fmt.Errorf("Network name not found : %v", name) + } + return fmt.Sprintf("%s%d", NetworkingInterfacePrefix, vmnet), nil +} + +func (e NetworkingConfig) DeviceIntoName(device string) (string, error) { + types := networkingConfig_InterfaceTypes(e) + + lowerdevice := strings.ToLower(device) + if !strings.HasPrefix(lowerdevice, NetworkingInterfacePrefix) { + return device, nil + } + vmnet, err := strconv.Atoi(lowerdevice[len(NetworkingInterfacePrefix):]) + if err != nil { + return "", err + } + network := types[vmnet] + switch network { + case NetworkingType_HOSTONLY: + return "hostonly", nil + case NetworkingType_NAT: + return "nat", nil + case NetworkingType_BRIDGED: + return "bridged", nil + } + return "", fmt.Errorf("Unable to determine network type for device %s%d.", NetworkingInterfacePrefix, vmnet) +} + +/** generic async file reader */ func consumeFile(fd *os.File) (chan byte, sentinelSignaller) { fromfile := make(chan byte) eof := make(sentinelSignaller) diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 04fb4e897..2814cbad3 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -196,8 +196,19 @@ func (d *Player5Driver) Verify() error { return playerVmnetnatConfPath(device) } - d.VmwareDriver.NetmapConfPath = func() string { - return playerNetmapConfPath() + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetmap := playerNetmapConfPath() + if _, err := os.Stat(pathNetmap); err != nil { + return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) + } + + fd, err := os.Open(pathNetmap) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkMap(fd) } return nil } diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index af2d7df35..c2c905922 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -157,8 +157,19 @@ func (d *Workstation9Driver) Verify() error { return workstationVmnetnatConfPath(device) } - d.VmwareDriver.NetmapConfPath = func() string { - return workstationNetmapConfPath() + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetmap := workstationNetmapConfPath() + if _, err := os.Stat(pathNetmap); err != nil { + return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) + } + + fd, err := os.Open(pathNetmap) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkMap(fd) } return nil } diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 137b0c736..7728260ad 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -373,16 +373,8 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver() // read netmap config - pathNetmap := driver.NetmapConfPath() - if _, err := os.Stat(pathNetmap); err != nil { - err := fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - netmap, res := vmwcommon.ReadNetmapConfig(pathNetmap) - if res != nil { - err := fmt.Errorf("Unable to read netmap conf file: %s: %v", pathNetmap, res) + netmap, err := driver.NetworkMapper() + if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt From 737e951685e1ddb89e130668634de493e6e6db09 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Fri, 17 Nov 2017 10:26:40 -0600 Subject: [PATCH 18/23] Added missing root path to path-finders for the VMware Fusion implementation in the vmware builder as mentioned by @SwampDragons. --- builder/vmware/common/driver_fusion5.go | 2 +- builder/vmware/common/driver_fusion6.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index c450e1f61..e3c0f061e 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -141,7 +141,7 @@ func (d *Fusion5Driver) Verify() error { return err } - libpath := filepath.Join("Library", "Preferences", "VMware Fusion") + libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion") d.VmwareDriver.DhcpLeasesPath = func(device string) string { return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index 62dd7b218..1f959d35d 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -66,7 +66,7 @@ func (d *Fusion6Driver) Verify() error { } log.Printf("Detected VMware version: %s", matches[1]) - libpath := filepath.Join("Library", "Preferences", "VMware Fusion") + libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion") d.VmwareDriver.DhcpLeasesPath = func(device string) string { return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" From 594ed950c7bcb076ef70704d8a3ef9b9a6a7eb4d Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Wed, 22 Nov 2017 17:11:13 -0600 Subject: [PATCH 19/23] Fixed a race condition in builder/vmware/common/driver_parser.go due to a misunderstanding how channels work when you close them. --- builder/vmware/common/driver_parser.go | 61 ++++++++++++++++---------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go index 7a026fea8..24ecaf0df 100644 --- a/builder/vmware/common/driver_parser.go +++ b/builder/vmware/common/driver_parser.go @@ -17,8 +17,9 @@ 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 { +func uncomment(eof sentinelSignaller, in <-chan byte) (chan byte, sentinelSignaller) { out := make(chan byte) + eoc := make(sentinelSignaller) go func(in <-chan byte, out chan byte) { var endofline bool @@ -40,16 +41,19 @@ func uncomment(eof sentinelSignaller, in <-chan byte) chan byte { } } } + close(eoc) }(in, out) - return out + return out, eoc } // convert a byte channel into a channel of pseudo-tokens -func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) chan string { +func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) (chan string, sentinelSignaller) { var ch byte var state string var quote bool + eot := make(sentinelSignaller) + out := make(chan string) go func(out chan string) { for stillReading := true; stillReading; { @@ -106,8 +110,9 @@ func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) chan string { if len(state) > 0 { out <- state } + close(eot) }(out) - return out + return out, eot } /** mid-level parsing */ @@ -220,12 +225,14 @@ func parseDhcpConfig(eof sentinelSignaller, in chan string) (tkGroup, error) { return result, nil } -func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) chan string { +func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) (chan string, sentinelSignaller) { var ch byte var state string var quote bool var lastnewline bool + eot := make(sentinelSignaller) + out := make(chan string) go func(out chan string) { for stillReading := true; stillReading; { @@ -291,8 +298,9 @@ func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) chan string { if len(state) > 0 { out <- state } + close(eot) }(out) - return out + return out, eot } func parseNetworkMapConfig(eof sentinelSignaller, in chan string) (NetworkMap, error) { @@ -966,9 +974,9 @@ 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) + uncommented, eoc := uncomment(eof, fromfile) + tokenized, eot := tokenizeDhcpConfig(eoc, uncommented) + parsetree, err := parseDhcpConfig(eot, tokenized) if err != nil { return nil, err } @@ -1064,10 +1072,10 @@ type NetworkNameMapper interface { func ReadNetworkMap(fd *os.File) (NetworkMap, error) { fromfile, eof := consumeFile(fd) - uncommented := uncomment(eof, fromfile) - tokenized := tokenizeNetworkMapConfig(eof, uncommented) + uncommented, eoc := uncomment(eof, fromfile) + tokenized, eot := tokenizeNetworkMapConfig(eoc, uncommented) - result, err := parseNetworkMapConfig(eof, tokenized) + result, err := parseNetworkMapConfig(eot, tokenized) if err != nil { return nil, err } @@ -1100,11 +1108,13 @@ func (e *NetworkMap) repr() string { } /*** parser for VMware Fusion's networking file */ -func tokenizeNetworkingConfig(eof sentinelSignaller, in chan byte) chan string { +func tokenizeNetworkingConfig(eof sentinelSignaller, in chan byte) (chan string, sentinelSignaller) { var ch byte var state string var repeat_newline bool + eot := make(sentinelSignaller) + out := make(chan string) go func(out chan string) { for reading := true; reading; { @@ -1144,13 +1154,16 @@ func tokenizeNetworkingConfig(eof sentinelSignaller, in chan byte) chan string { if len(state) > 0 { out <- state } + close(eot) }(out) - return out + return out, eot } -func splitNetworkingConfig(eof sentinelSignaller, in chan string) chan []string { +func splitNetworkingConfig(eof sentinelSignaller, in chan string) (chan []string, sentinelSignaller) { var out chan []string + eos := make(sentinelSignaller) + out = make(chan []string) go func(out chan []string) { row := make([]string, 0) @@ -1174,8 +1187,9 @@ func splitNetworkingConfig(eof sentinelSignaller, in chan string) chan []string if len(row) > 0 { out <- row } + close(eos) }(out) - return out + return out, eos } /// All token types in networking file. @@ -1642,9 +1656,11 @@ func NetworkingParserByCommand(command string) *func([]string) (*networkingComma return nil } -func parseNetworkingConfig(eof sentinelSignaller, rows chan []string) chan networkingCommandEntry { +func parseNetworkingConfig(eof sentinelSignaller, rows chan []string) (chan networkingCommandEntry, sentinelSignaller) { var out chan networkingCommandEntry + eop := make(sentinelSignaller) + out = make(chan networkingCommandEntry) go func(in chan []string, out chan networkingCommandEntry) { for reading := true; reading; { @@ -1668,8 +1684,9 @@ func parseNetworkingConfig(eof sentinelSignaller, rows chan []string) chan netwo } } } + close(eop) }(rows, out) - return out + return out, eop } type NetworkingConfig struct { @@ -1795,9 +1812,9 @@ func flattenNetworkingConfig(eof sentinelSignaller, in chan networkingCommandEnt func ReadNetworkingConfig(fd *os.File) (NetworkingConfig, error) { // start piecing together different parts of the file fromfile, eof := consumeFile(fd) - tokenized := tokenizeNetworkingConfig(eof, fromfile) - rows := splitNetworkingConfig(eof, tokenized) - entries := parseNetworkingConfig(eof, rows) + tokenized, eot := tokenizeNetworkingConfig(eof, fromfile) + rows, eos := splitNetworkingConfig(eot, tokenized) + entries, eop := parseNetworkingConfig(eos, rows) // parse the version parsed_version, err := networkingReadVersion(<-rows) @@ -1812,7 +1829,7 @@ func ReadNetworkingConfig(fd *os.File) (NetworkingConfig, error) { } // convert to a configuration - result := flattenNetworkingConfig(eof, entries) + result := flattenNetworkingConfig(eop, entries) return result, nil } From 74946071d255e74145240232aa9cc6770685c840 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Mon, 25 Dec 2017 14:08:08 -0600 Subject: [PATCH 20/23] Added support for specifying the disk adapter type to the vmware builders. This was squashed from the vmware-diskAdapterType branch (#2968) as submitted by Rami Abughazaleh . This closes #5671 and possibly #4885. arizvisa: Updated icnocop's documentation to include the possible disk adapter types that one can specify. arizvisa: Tweaked icnocop's support for the `disk_adapter_type` option to the VMWare builder that caused conflicts due to version skew. icnocop: Updated links to the Virtual Disk Manager User's Guide PDF to open in a new window and also added the Adobe PDF icon icnocop: Added support for vmware to specify the disk adapter type, ide or scsi (lsilogic or buslogic) --- builder/vmware/common/driver.go | 2 +- builder/vmware/common/driver_fusion5.go | 4 +- builder/vmware/common/driver_mock.go | 14 +-- builder/vmware/common/driver_player5.go | 4 +- builder/vmware/common/driver_workstation9.go | 4 +- builder/vmware/iso/builder.go | 6 ++ builder/vmware/iso/driver_esx5.go | 4 +- builder/vmware/iso/step_create_disk.go | 4 +- builder/vmware/iso/step_create_vmx.go | 87 +++++++++++++++--- .../images/Adobe_PDF_file_icon_24x24.png | Bin 0 -> 1288 bytes .../source/docs/builders/vmware-iso.html.md | 11 ++- 11 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 website/source/assets/images/Adobe_PDF_file_icon_24x24.png diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 6b5cfde1f..8fa185efd 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -29,7 +29,7 @@ type Driver interface { CompactDisk(string) error // CreateDisk creates a virtual disk with the given size. - CreateDisk(string, string, string) error + CreateDisk(string, string, string, string) error // Checks if the VMX file at the given path is running. IsRunning(string) (bool, error) diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index e3c0f061e..9c81efd4a 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -41,8 +41,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error { return nil } -func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error { - cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output) +func (d *Fusion5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error { + cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", adapter_type, "-t", type_id, output) if _, _, err := runAndLog(cmd); err != nil { return err } diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index f857f4adc..a0955db88 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -19,11 +19,12 @@ type DriverMock struct { CompactDiskPath string CompactDiskErr error - CreateDiskCalled bool - CreateDiskOutput string - CreateDiskSize string - CreateDiskTypeId string - CreateDiskErr error + CreateDiskCalled bool + CreateDiskOutput string + CreateDiskSize string + CreateDiskAdapterType string + CreateDiskTypeId string + CreateDiskErr error IsRunningCalled bool IsRunningPath string @@ -119,10 +120,11 @@ func (d *DriverMock) CompactDisk(path string) error { return d.CompactDiskErr } -func (d *DriverMock) CreateDisk(output string, size string, typeId string) error { +func (d *DriverMock) CreateDisk(output string, size string, adapterType string, typeId string) error { d.CreateDiskCalled = true d.CreateDiskOutput = output d.CreateDiskSize = size + d.CreateDiskAdapterType = adapterType d.CreateDiskTypeId = typeId return d.CreateDiskErr } diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 2814cbad3..fb14dcb52 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -64,12 +64,12 @@ func (d *Player5Driver) qemuCompactDisk(diskPath string) error { return nil } -func (d *Player5Driver) CreateDisk(output string, size string, type_id string) error { +func (d *Player5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error { var cmd *exec.Cmd if d.QemuImgPath != "" { cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size) } else { - cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output) + cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output) } if _, _, err := runAndLog(cmd); err != nil { return err diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index c2c905922..25b4ddd2c 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -42,8 +42,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error { return nil } -func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error { - cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output) +func (d *Workstation9Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error { + cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output) if _, _, err := runAndLog(cmd); err != nil { return err } diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 2dcc082ac..5e2908964 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -40,6 +40,7 @@ type Config struct { // disk drives AdditionalDiskSize []uint `mapstructure:"disk_additional_size"` + DiskAdapterType string `mapstructure:"disk_adapter_type"` DiskName string `mapstructure:"vmdk_name"` DiskSize uint `mapstructure:"disk_size"` DiskTypeId string `mapstructure:"disk_type_id"` @@ -126,6 +127,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.DiskSize = 40000 } + if b.config.DiskAdapterType == "" { + // Default is lsilogic + b.config.DiskAdapterType = "lsilogic" + } + if b.config.DiskTypeId == "" { // Default is growable virtual disk split in 2GB files. b.config.DiskTypeId = "1" diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 3f18656d0..40cc35eb1 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -50,9 +50,9 @@ func (d *ESX5Driver) CompactDisk(diskPathLocal string) error { return nil } -func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string) error { +func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, adapter_type string, typeId string) error { diskPath := d.datastorePath(diskPathLocal) - return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", "lsilogic", diskPath) + return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", adapter_type, diskPath) } func (d *ESX5Driver) IsRunning(string) (bool, error) { diff --git a/builder/vmware/iso/step_create_disk.go b/builder/vmware/iso/step_create_disk.go index ddee55c7f..788de2ddc 100644 --- a/builder/vmware/iso/step_create_disk.go +++ b/builder/vmware/iso/step_create_disk.go @@ -28,7 +28,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep ui.Say("Creating virtual machine disk") full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk") - if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskTypeId); err != nil { + if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskAdapterType, config.DiskTypeId); err != nil { err := fmt.Errorf("Error creating disk: %s", err) state.Put("error", err) ui.Error(err.Error()) @@ -46,7 +46,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep additionalpath := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.vmdk", config.DiskName, i+1)) size := fmt.Sprintf("%dM", uint64(additionalsize)) - if err := driver.CreateDisk(additionalpath, size, config.DiskTypeId); err != nil { + if err := driver.CreateDisk(additionalpath, size, config.DiskAdapterType, config.DiskTypeId); err != nil { err := fmt.Errorf("Error creating additional disk: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 7728260ad..4c3eb58ae 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -16,11 +16,20 @@ import ( ) type vmxTemplateData struct { - Name string - GuestOS string - DiskName string - ISOPath string - Version string + Name string + GuestOS string + ISOPath string + Version string + + SCSI_Present string + SCSI_diskAdapterType string + SATA_Present string + NVME_Present string + + DiskName string + DiskType string + CDROMType string + CDROMType_MasterSlave string Network_Type string Network_Device string @@ -287,6 +296,11 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist isoPath := state.Get("iso_path").(string) ui := state.Get("ui").(packer.Ui) + // Convert the iso_path into a path relative to the .vmx file if possible + if relativeIsoPath, err := filepath.Rel(config.VMXTemplatePath, filepath.FromSlash(isoPath)); err == nil { + isoPath = relativeIsoPath + } + ui.Say("Building and writing VMX file") vmxTemplate := DefaultVMXTemplate @@ -361,6 +375,15 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist Version: config.Version, ISOPath: isoPath, + SCSI_Present: "FALSE", + SCSI_diskAdapterType: "lsilogic", + SATA_Present: "FALSE", + NVME_Present: "FALSE", + + DiskType: "scsi", + CDROMType: "ide", + CDROMType_MasterSlave: "0", + Sound_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.Sound)], Usb_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.USB)], @@ -368,6 +391,36 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist Parallel_Present: "FALSE", } + /// Use the disk adapter type that the user specified to tweak the .vmx + // Also sync the cdrom adapter type according to what's common for that disk type. + diskAdapterType := strings.ToLower(config.DiskAdapterType) + switch diskAdapterType { + case "ide": + templateData.DiskType = "ide" + templateData.CDROMType = "ide" + templateData.CDROMType_MasterSlave = "1" + case "sata": + templateData.SATA_Present = "TRUE" + templateData.DiskType = "sata" + templateData.CDROMType = "sata" + templateData.CDROMType_MasterSlave = "1" + case "nvme": + templateData.NVME_Present = "TRUE" + templateData.DiskType = "nvme" + templateData.SATA_Present = "TRUE" + templateData.CDROMType = "sata" + templateData.CDROMType_MasterSlave = "0" + case "scsi": + diskAdapterType = "lsilogic" + fallthrough + default: + templateData.SCSI_Present = "TRUE" + templateData.SCSI_diskAdapterType = diskAdapterType + templateData.DiskType = "scsi" + templateData.CDROMType = "ide" + templateData.CDROMType_MasterSlave = "0" + } + /// Check the network type that the user specified network := config.Network driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver() @@ -556,9 +609,21 @@ gui.fullScreenAtPowerOn = "FALSE" gui.viewModeAtPowerOn = "windowed" hgfs.linkRootShare = "TRUE" hgfs.mapRootShare = "TRUE" -ide1:0.present = "TRUE" -ide1:0.fileName = "{{ .ISOPath }}" -ide1:0.deviceType = "cdrom-image" + +scsi0.present = "{{ .SCSI_Present }}" +scsi0.virtualDev = "{{ .SCSI_diskAdapterType }}" +scsi0.pciSlotNumber = "16" +scsi0:0.redo = "" +sata0.present = "{{ .SATA_Present }}" +nvme0.present = "{{ .NVME_Present }}" + +{{ .DiskType }}0:0.present = "TRUE" +{{ .DiskType }}0:0.fileName = "{{ .DiskName }}.vmdk" + +{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.present = "TRUE" +{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.fileName = "{{ .ISOPath }}" +{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.deviceType = "cdrom-image" + isolation.tools.hgfs.disable = "FALSE" memsize = "512" nvram = "{{ .Name }}.nvram" @@ -587,12 +652,6 @@ powerType.suspend = "soft" proxyApps.publishToHost = "FALSE" replay.filename = "" replay.supported = "FALSE" -scsi0.pciSlotNumber = "16" -scsi0.present = "TRUE" -scsi0.virtualDev = "lsilogic" -scsi0:0.fileName = "{{ .DiskName }}.vmdk" -scsi0:0.present = "TRUE" -scsi0:0.redo = "" // Sound sound.startConnected = "{{ .Sound_Present }}" diff --git a/website/source/assets/images/Adobe_PDF_file_icon_24x24.png b/website/source/assets/images/Adobe_PDF_file_icon_24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..a722e5b334140fcb2a527b44465dbf4ca6e05cd4 GIT binary patch literal 1288 zcmV+j1^4=iP)5)P}~C2Cc=S;&*9j zNffFDZ6Bl*O8Znwp|lTus1LpfT3TAc4+vN&eGoJ+l82T;tCmzu;tY<)iN>)rCNpzy z?#DT2@70HM?|8@Qh%VUd^Rf3@>%Z3jzt53NSL0Cy0PoSI+=?H3(~W=j@O6MQGXp?C zL{L#w6$1oA8%?3s403tvALrISi1YLFGTpPVuz)aQ=Qn#0BNnYLiTeQ2x`hY=>Rc7a zKvfX|)Zy|GHXN{i@#61#TU)<2v-Nhnt&K(_0?4HW`J1SMfR%=AMl0rA&3)<6VfsCh zMibOy{Y`KVZM7>n-Xj;+2;ch=@qwSj!8_+t@yT=R>+APzY;0WacDqhQf{*HMbged& z1v=lx-}V*U(i!CKW&Dw^QJ#Jk20i?BUndTSl&}09VuUP(^FxXoR=^3(dc8;*&2Opd z)7@_O`~7~um!_#`QI=u2xfurU_CmSvg`hwEZHV7^FqGHdg#7$bikt7Hxc(O8hreKy zwHTT(ECa(lFcK&;K@7&@JP9F~nH^nQTYEOo^Uw7AebZpLD7dJ&n2#Lep)1dyb5WJS zA09(H9jIpz0#Q6Mltdt6L<5TvRVAxs#2BT~Xp9dWIB<8CWe;_`U9>$KEU|& zOKd*=1Z5GZ9s4dN5M0W5=?wi7f29;hVNgWK4FaG7S(=fiDQTK&qtRe67|a3q(x{kI zgVEAyij56o3x=S&MqVKNU^$_^ndv)+!X?%*xTu&WlcM-)A2^{_G1bt0h*JhzO#B#E3SUkfi9Kj|ep9uf+9wNRm`KnK<4K z@UJn;6TnqC;qPQ zP(Jxb;;Dbb$_n=Wd&s2jyI8cR(+Nzoh-7svA5lK@1mWyyl4JKHJ|lnfPsqOggg2IH zJ^p*7-9bXwfx_>FKvhu@%%{Y~GlUmUP@Z^>+HW4geeUz zE{QS5k884c@4a(QcQDQE*|OOrZQ{%A3b44iNC<%o7cPuXo;>-N@p#;sCi+P&#u)4M ydTVuc^*_7*DrT$(w1Fm675Ee;KnZLD{r>^{G#E%4x}j_U0000 Virtual Disk Manager User's Guide for desktop VMware clients. + For ESXi, refer to the proper ESXi documentation. + +- `disable_vnc` (boolean) - Whether to create a VNC connection or not. A `boot_command` cannot be used when this is `false`. Defaults to `false`. - `floppy_files` (array of strings) - A list of files to place onto a floppy From eb0445ca961eac0c40c6ae0a631936fffa15247c Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 16 Jan 2018 13:17:37 -0600 Subject: [PATCH 21/23] Added support for specifying both the network adapter type and cdrom adapter type as requested by @night199uk. Also included the respective documentation for these new options. --- builder/vmware/iso/builder.go | 8 +++- builder/vmware/iso/step_create_vmx.go | 41 +++++++++++++++++-- .../source/docs/builders/vmware-iso.html.md | 12 ++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 5e2908964..cc5a9673b 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -46,13 +46,17 @@ type Config struct { DiskTypeId string `mapstructure:"disk_type_id"` Format string `mapstructure:"format"` + // cdrom drive + CdromAdapterType string `mapstructure:"cdrom_adapter_type"` + // platform information GuestOSType string `mapstructure:"guest_os_type"` Version string `mapstructure:"version"` VMName string `mapstructure:"vm_name"` - // Network type - Network string `mapstructure:"network"` + // Network adapter and type + NetworkAdapterType string `mapstructure:"network_adapter_type"` + Network string `mapstructure:"network"` // device presence Sound bool `mapstructure:"sound"` diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 4c3eb58ae..0eb753dc8 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -31,8 +31,9 @@ type vmxTemplateData struct { CDROMType string CDROMType_MasterSlave string - Network_Type string - Network_Device string + Network_Type string + Network_Device string + Network_Adapter string Sound_Present string Usb_Present string @@ -384,6 +385,8 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist CDROMType: "ide", CDROMType_MasterSlave: "0", + Network_Adapter: "e1000", + Sound_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.Sound)], Usb_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.USB)], @@ -421,6 +424,38 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist templateData.CDROMType_MasterSlave = "0" } + /// Handle the cdrom adapter type. If the disk adapter type and the + // cdrom adapter type are the same, then ensure that the cdrom is the + // slave device on whatever bus the disk adapter is on. + cdromAdapterType := strings.ToLower(config.CdromAdapterType) + if cdromAdapterType == diskAdapterType { + templateData.CDROMType_MasterSlave = "1" + } else { + templateData.CDROMType_MasterSlave = "0" + } + + switch cdromAdapterType { + case "ide": + templateData.CDROMType = "ide" + case "sata": + templateData.SATA_Present = "TRUE" + templateData.CDROMType = "sata" + case "scsi": + templateData.SCSI_Present = "TRUE" + templateData.CDROMType = "scsi" + default: + err := fmt.Errorf("Error procesing VMX template: %s", cdromAdapterType) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + /// Assign the network adapter type into the template if one was specified. + network_adapter := strings.ToLower(config.NetworkAdapterType) + if network_adapter != "" { + templateData.Network_Adapter = network_adapter + } + /// Check the network type that the user specified network := config.Network driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver() @@ -600,7 +635,7 @@ ethernet0.displayName = "Ethernet" ethernet0.linkStatePropagation.enable = "FALSE" ethernet0.pciSlotNumber = "33" ethernet0.present = "TRUE" -ethernet0.virtualDev = "e1000" +ethernet0.virtualDev = "{{ .Network_Adapter }}" ethernet0.wakeOnPcktRcv = "FALSE" extendedConfigFile = "{{ .Name }}.vmxf" floppy0.present = "FALSE" diff --git a/website/source/docs/builders/vmware-iso.html.md b/website/source/docs/builders/vmware-iso.html.md index 0efe03a0f..2f4e3e44f 100644 --- a/website/source/docs/builders/vmware-iso.html.md +++ b/website/source/docs/builders/vmware-iso.html.md @@ -140,6 +140,12 @@ builder. Virtual Disk Manager User's Guide for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. +- `cdrom_adapter_type` (string) - The adapter type (or bus) that will be used + by the cdrom device. This is chosen by default based on the disk adapter + type. VMware tends to lean towards "ide" for the cdrom device unless + "sata" is chosen for the disk adapter and so Packer attempts to mirror + this logic. This field can be specified as either "ide", "sata", or "scsi". + - `disable_vnc` (boolean) - Whether to create a VNC connection or not. A `boot_command` cannot be used when this is `false`. Defaults to `false`. @@ -207,6 +213,12 @@ builder. such as "hostonly", "nat", or "bridged". If the network is not one of these values, then it is assumed to be a VMware network device. (VMnet0..x) +- `network_adapter_type` (string) - This is the ethernet adapter type the the + virtual machine will be created with. By default the "e1000" network adapter + type will be used by Packer. For more information, please consult the + Choosing a network adapter for your virtual machine for desktop VMware + clients. For ESXi, refer to the proper ESXi documentation. + - `output_directory` (string) - This is the path to the directory where the resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` From aefe41a44ad0631d2095a6d99464f080e4f28533 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 16 Jan 2018 13:27:21 -0600 Subject: [PATCH 22/23] Fixed an issue with the previous commit so that when the user does not specify the cdrom_adapter_type to fallback to the original decision made by the disk adapter type selection. --- builder/vmware/iso/step_create_vmx.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 0eb753dc8..ffff9d999 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -428,7 +428,9 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist // cdrom adapter type are the same, then ensure that the cdrom is the // slave device on whatever bus the disk adapter is on. cdromAdapterType := strings.ToLower(config.CdromAdapterType) - if cdromAdapterType == diskAdapterType { + if cdromAdapterType == "" { + cdromAdapterType = templateData.CDROMType + } else if cdromAdapterType == diskAdapterType { templateData.CDROMType_MasterSlave = "1" } else { templateData.CDROMType_MasterSlave = "0" From fa2dddd26d1584f952f6d1bf71db14415d43bd70 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Fri, 2 Feb 2018 19:45:18 -0600 Subject: [PATCH 23/23] Fixed some things mucked up during rebase. --- builder/vmware/common/step_type_boot_command.go | 1 - builder/vmware/iso/driver_esx5.go | 1 - 2 files changed, 2 deletions(-) diff --git a/builder/vmware/common/step_type_boot_command.go b/builder/vmware/common/step_type_boot_command.go index c169c59eb..02404af91 100644 --- a/builder/vmware/common/step_type_boot_command.go +++ b/builder/vmware/common/step_type_boot_command.go @@ -7,7 +7,6 @@ import ( "net" "os" "regexp" - "runtime" "strings" "time" "unicode" diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 40cc35eb1..1dc352999 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -19,7 +19,6 @@ import ( "github.com/hashicorp/packer/communicator/ssh" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" - "github.com/mitchellh/multistep" gossh "golang.org/x/crypto/ssh" )