[feature] Support boot command for pvm builder

Similar to [GH-1082] but for parallels-pvm builder.
This commit is contained in:
Rickard von Essen 2014-05-13 23:39:00 +02:00
parent 1b659d270f
commit 4d994deb3a
8 changed files with 100 additions and 34 deletions

View File

@ -1,4 +1,4 @@
package iso
package common
// Interface to help find the host IP that is available from within
// the Parallels virtual machines.

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"bytes"

View File

@ -1,4 +1,4 @@
package iso
package common
import "testing"

View File

@ -1,9 +1,8 @@
package iso
package common
import (
"fmt"
"github.com/mitchellh/multistep"
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
"github.com/mitchellh/packer/packer"
"log"
"strings"
@ -24,7 +23,6 @@ type bootCommandTemplateData struct {
// Parallels Virtualization SDK - C API.
//
// Uses:
// config *config
// driver Driver
// http_port int
// ui packer.Ui
@ -32,24 +30,32 @@ type bootCommandTemplateData struct {
//
// Produces:
// <nothing>
type stepTypeBootCommand struct{}
type StepTypeBootCommand struct {
BootCommand []string
HostInterfaces []string
VMName string
Tpl *packer.ConfigTemplate
}
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
driver := state.Get("driver").(parallelscommon.Driver)
driver := state.Get("driver").(Driver)
// Determine the host IP
ipFinder := &IfconfigIPFinder{Devices: config.HostInterfaces}
hostIp := "0.0.0.0"
hostIp, err := ipFinder.HostIP()
if err != nil {
err := fmt.Errorf("Error detecting host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
if len(s.HostInterfaces) > 0 {
// Determine the host IP
ipFinder := &IfconfigIPFinder{Devices: s.HostInterfaces}
ip, err := ipFinder.HostIP()
if err != nil {
err := fmt.Errorf("Error detecting host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
hostIp = ip
}
ui.Say(fmt.Sprintf("Host IP for the Parallels machine: %s", hostIp))
@ -57,12 +63,12 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
tplData := &bootCommandTemplateData{
hostIp,
httpPort,
config.VMName,
s.VMName,
}
ui.Say("Typing the boot command...")
for _, command := range config.BootCommand {
command, err := config.tpl.Process(command, tplData)
for _, command := range s.BootCommand {
command, err := s.Tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
@ -73,7 +79,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
codes := []string{}
for _, code := range scancodes(command) {
if code == "wait" {
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@ -85,7 +91,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
}
if code == "wait5" {
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@ -97,7 +103,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
}
if code == "wait10" {
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@ -116,7 +122,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
codes = append(codes, code)
}
log.Printf("Sending scancodes: %#v", codes)
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@ -127,7 +133,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
func scancodes(message string) []string {
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html

View File

@ -298,7 +298,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BootWait: b.config.BootWait,
Headless: b.config.Headless, // TODO: migth work on Enterprise Ed.
},
new(stepTypeBootCommand),
&parallelscommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand,
HostInterfaces: b.config.HostInterfaces,
VMName: b.config.VMName,
Tpl: b.config.tpl,
},
&common.StepConnectSSH{
SSHAddress: parallelscommon.SSHAddress,
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),

View File

@ -43,6 +43,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("http_port", uint(0))
// Build the steps.
steps := []multistep.Step{
@ -70,6 +71,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BootWait: b.config.BootWait,
Headless: b.config.Headless,
},
&parallelscommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand,
HostInterfaces: []string{},
VMName: b.config.VMName,
Tpl: b.config.tpl,
},
&common.StepConnectSSH{
SSHAddress: parallelscommon.SSHAddress,
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),

View File

@ -19,12 +19,12 @@ type Config struct {
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
ParallelsToolsMode string `mapstructure:"parallels_tools_mode"`
ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"`
ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"`
SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
ParallelsToolsMode string `mapstructure:"parallels_tools_mode"`
ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"`
ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"`
SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"`
tpl *packer.ConfigTemplate
}
@ -86,6 +86,13 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
}
}
for i, command := range c.BootCommand {
if err := c.tpl.Validate(command); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
}
}
validMode := false
validModes := []string{
parallelscommon.ParallelsToolsModeDisable,

View File

@ -53,6 +53,19 @@ each category, the available options are alphabetized and described.
### Optional:
* `boot_command` (array of strings) - This is an array of commands to type
when the virtual machine is first booted. The goal of these commands should
be to type just enough to initialize the operating system installer. Special
keys can be typed as well, and are covered in the section below on the boot
command. If this is not specified, it is assumed the installer will start
itself.
* `boot_wait` (string) - The time to wait after booting the initial virtual
machine before typing the `boot_command`. The value of this should be
a duration. Examples are "5s" and "1m30s" which will cause Packer to wait
five seconds and one minute 30 seconds, respectively. If this isn't specified,
the default is 10 seconds.
* `floppy_files` (array of strings) - A list of files to put onto a floppy
disk that is attached when the VM is booted for the first time. This is
most useful for unattended Windows installs, which look for an
@ -135,6 +148,34 @@ uploaded is controllable by `parallels_tools_path`, and defaults to
of the SSH user. Parallels Tools ISO's can be found in:
"/Applications/Parallels Desktop.app/Contents/Resources/Tools/"
## Boot Command
The `boot_command` specifies the keys to type when the virtual machine is first booted. This command is typed after `boot_wait`.
As documented above, the `boot_command` is an array of strings. The
strings are all typed in sequence. It is an array only to improve readability
within the template.
The boot command is "typed" character for character using the `prltype` (part
of prl-utils, see [Parallels Builder](/docs/builders/parallels.html))
command connected to the machine, simulating a human actually typing the
keyboard. There are a set of special keys available. If these are in your
boot command, they will be replaced by the proper key:
* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress.
* `<esc>` - Simulates pressing the escape key.
* `<tab>` - Simulates pressing the tab key.
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending
any additional keys. This is useful if you have to generally wait for the UI
to update before typing more.
In addition to the special keys, each command to type is treated as a
[configuration template](/docs/templates/configuration-templates.html).
The available variables are:
## prlctl Commands
In order to perform extra customization of the virtual machine, a template can
define extra calls to `prlctl` to perform.