From 0d6cf7fac44869751d31f262b2ab8ee694ed41b9 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Wed, 5 Oct 2016 21:51:42 -0500 Subject: [PATCH] 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 | 195 +++++++++++++++--- .../source/docs/builders/vmware-iso.html.md | 56 +++++ 3 files changed, 232 insertions(+), 33 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", } + /// Check the network type that the user specified + network := config.Network + driver := state.Get("driver").(vmwcommon.VmwareDriver) + + // 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", config.Network) + state.Put("vmnetwork", network) - // check if serial port has been configured + /// 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