diff --git a/builder/vsphere/common/step_http_ip_discover.go b/builder/vsphere/common/step_http_ip_discover.go new file mode 100644 index 000000000..627fbe52b --- /dev/null +++ b/builder/vsphere/common/step_http_ip_discover.go @@ -0,0 +1,54 @@ +package common + +import ( + "context" + "fmt" + "net" + + "github.com/hashicorp/packer/helper/multistep" +) + +// Step to discover the http ip +// which guests use to reach the vm host +// To make sure the IP is set before boot command and http server steps +type StepHTTPIPDiscover struct { + HTTPIP string +} + +func (s *StepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ip, err := getHostIP(s.HTTPIP) + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + state.Put("http_ip", ip) + + return multistep.ActionContinue +} + +func (s *StepHTTPIPDiscover) Cleanup(state multistep.StateBag) {} + +func getHostIP(s string) (string, error) { + if s != "" { + if net.ParseIP(s) != nil { + return s, nil + } else { + return "", fmt.Errorf("invalid IP address") + } + } + + addrs, err := net.InterfaceAddrs() + if err != nil { + return "", err + } + + for _, a := range addrs { + ipnet, ok := a.(*net.IPNet) + if ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String(), nil + } + } + } + return "", fmt.Errorf("IP not found") +} diff --git a/builder/vsphere/common/step_http_ip_discover_test.go b/builder/vsphere/common/step_http_ip_discover_test.go new file mode 100644 index 000000000..fd4480daa --- /dev/null +++ b/builder/vsphere/common/step_http_ip_discover_test.go @@ -0,0 +1,44 @@ +package common + +import ( + "context" + "testing" + + "github.com/hashicorp/packer/helper/multistep" +) + +func TestStepHTTPIPDiscover_Run(t *testing.T) { + state := new(multistep.BasicStateBag) + step := new(StepHTTPIPDiscover) + + // without setting HTTPIP + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + _, ok := state.GetOk("http_ip") + if !ok { + t.Fatal("should have http_ip") + } + + // setting HTTPIP + ip := "10.0.2.2" + step = &StepHTTPIPDiscover{ + HTTPIP: ip, + } + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + httpIp, ok := state.GetOk("http_ip") + if !ok { + t.Fatal("should have http_ip") + } + if httpIp != ip { + t.Fatalf("bad: Http ip is %s but was supposed to be %s", httpIp, ip) + } +} diff --git a/builder/vsphere/driver/vm_keyboard.go b/builder/vsphere/driver/vm_keyboard.go index 616ec958c..b11041225 100644 --- a/builder/vsphere/driver/vm_keyboard.go +++ b/builder/vsphere/driver/vm_keyboard.go @@ -1,73 +1,29 @@ package driver import ( - "strings" - "unicode" - "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/types" "golang.org/x/mobile/event/key" ) type KeyInput struct { - Message string Scancode key.Code Alt bool Ctrl bool Shift bool } -var scancodeMap = make(map[rune]key.Code) - -func init() { - scancodeIndex := make(map[string]key.Code) - scancodeIndex["abcdefghijklmnopqrstuvwxyz"] = key.CodeA - scancodeIndex["ABCDEFGHIJKLMNOPQRSTUVWXYZ"] = key.CodeA - scancodeIndex["1234567890"] = key.Code1 - scancodeIndex["!@#$%^&*()"] = key.Code1 - scancodeIndex[" "] = key.CodeSpacebar - scancodeIndex["-=[]\\"] = key.CodeHyphenMinus - scancodeIndex["_+{}|"] = key.CodeHyphenMinus - scancodeIndex[";'`,./"] = key.CodeSemicolon - scancodeIndex[":\"~<>?"] = key.CodeSemicolon - - for chars, start := range scancodeIndex { - for i, r := range chars { - scancodeMap[r] = start + key.Code(i) - } - } -} - -const shiftedChars = "!@#$%^&*()_+{}|:\"~<>?" - func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) { var spec types.UsbScanCodeSpec - for _, r := range input.Message { - scancode := scancodeMap[r] - shift := input.Shift || unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ - // https://github.com/lamw/vghetto-scripts/blob/f74bc8ba20064f46592bcce5a873b161a7fa3d72/powershell/VMKeystrokes.ps1#L130 - UsbHidCode: int32(scancode)<<16 | 7, - Modifiers: &types.UsbScanCodeSpecModifierType{ - LeftControl: &input.Ctrl, - LeftAlt: &input.Alt, - LeftShift: &shift, - }, - }) - } - - if input.Scancode != 0 { - spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ - UsbHidCode: int32(input.Scancode)<<16 | 7, - Modifiers: &types.UsbScanCodeSpecModifierType{ - LeftControl: &input.Ctrl, - LeftAlt: &input.Alt, - LeftShift: &input.Shift, - }, - }) - } + spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ + UsbHidCode: int32(input.Scancode)<<16 | 7, + Modifiers: &types.UsbScanCodeSpecModifierType{ + LeftControl: &input.Ctrl, + LeftAlt: &input.Alt, + LeftShift: &input.Shift, + }, + }) req := &types.PutUsbScanCodes{ This: vm.vm.Reference(), diff --git a/builder/vsphere/iso/builder.go b/builder/vsphere/iso/builder.go index 3bba2aa83..2c224b642 100644 --- a/builder/vsphere/iso/builder.go +++ b/builder/vsphere/iso/builder.go @@ -30,6 +30,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { state := new(multistep.BasicStateBag) + state.Put("debug", b.config.PackerDebug) state.Put("hook", hook) state.Put("ui", ui) @@ -89,6 +90,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Host: b.config.Host, SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads, }, + &common.StepHTTPIPDiscover{ + HTTPIP: b.config.BootConfig.HTTPIP, + }, &packerCommon.StepHTTPServer{ HTTPDir: b.config.HTTPDir, HTTPPortMin: b.config.HTTPPortMin, diff --git a/builder/vsphere/iso/config.go b/builder/vsphere/iso/config.go index 7be966781..a705d329e 100644 --- a/builder/vsphere/iso/config.go +++ b/builder/vsphere/iso/config.go @@ -76,7 +76,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...) - errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare()...) + errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...) diff --git a/builder/vsphere/iso/config.hcl2spec.go b/builder/vsphere/iso/config.hcl2spec.go index 3c0803818..11158f58c 100644 --- a/builder/vsphere/iso/config.hcl2spec.go +++ b/builder/vsphere/iso/config.hcl2spec.go @@ -69,8 +69,9 @@ type FlatConfig struct { FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"` FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"` BootOrder *string `mapstructure:"boot_order" cty:"boot_order" hcl:"boot_order"` - BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` HTTPIP *string `mapstructure:"http_ip" cty:"http_ip" hcl:"http_ip"` WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout" hcl:"ip_wait_timeout"` SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout" hcl:"ip_settle_timeout"` @@ -196,8 +197,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false}, "floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false}, "boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.String, Required: false}, - "boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false}, + "boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false}, "boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false}, + "boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false}, "http_ip": &hcldec.AttrSpec{Name: "http_ip", Type: cty.String, Required: false}, "ip_wait_timeout": &hcldec.AttrSpec{Name: "ip_wait_timeout", Type: cty.String, Required: false}, "ip_settle_timeout": &hcldec.AttrSpec{Name: "ip_settle_timeout", Type: cty.String, Required: false}, diff --git a/builder/vsphere/iso/step_boot_command.go b/builder/vsphere/iso/step_boot_command.go index 70c5adc7c..805b89113 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -3,15 +3,10 @@ package iso import ( "context" "fmt" - "log" - "net" - "os" - "strings" "time" - "unicode/utf8" "github.com/hashicorp/packer/builder/vsphere/driver" - packerCommon "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -19,9 +14,8 @@ import ( ) type BootConfig struct { - BootCommand []string `mapstructure:"boot_command"` - BootWait time.Duration `mapstructure:"boot_wait"` // example: "1m30s"; default: "10s" - HTTPIP string `mapstructure:"http_ip"` + bootcommand.BootConfig `mapstructure:",squash"` + HTTPIP string `mapstructure:"http_ip"` } type bootCommandTemplateData struct { @@ -30,14 +24,11 @@ type bootCommandTemplateData struct { Name string } -func (c *BootConfig) Prepare() []error { - var errs []error - +func (c *BootConfig) Prepare(ctx *interpolate.Context) []error { if c.BootWait == 0 { c.BootWait = 10 * time.Second } - - return errs + return c.BootConfig.Prepare(ctx) } type StepBootCommand struct { @@ -46,44 +37,8 @@ type StepBootCommand struct { Ctx interpolate.Context } -var special = map[string]key.Code{ - "": key.CodeReturnEnter, - "": key.CodeEscape, - "": key.CodeDeleteBackspace, - "": key.CodeDeleteForward, - "": key.CodeTab, - "": key.CodeF1, - "": key.CodeF2, - "": key.CodeF3, - "": key.CodeF4, - "": key.CodeF5, - "": key.CodeF6, - "": key.CodeF7, - "": key.CodeF8, - "": key.CodeF9, - "": key.CodeF10, - "": key.CodeF11, - "": key.CodeF12, - "": key.CodeInsert, - "": key.CodeHome, - "": key.CodeEnd, - "": key.CodePageUp, - "": key.CodePageDown, - "": key.CodeLeftArrow, - "": key.CodeRightArrow, - "": key.CodeUpArrow, - "": key.CodeDownArrow, -} - -var keyInterval = packerCommon.PackerKeyDefault - -func init() { - if delay, err := time.ParseDuration(os.Getenv(packerCommon.PackerKeyEnv)); err == nil { - keyInterval = delay - } -} - -func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + debug := state.Get("debug").(bool) ui := state.Get("ui").(packer.Ui) vm := state.Get("vm").(*driver.VirtualMachine) @@ -91,28 +46,25 @@ func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multi return multistep.ActionContinue } - ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait)) - wait := time.After(s.Config.BootWait) -WAITLOOP: - for { + // Wait the for the vm to boot. + if int64(s.Config.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String())) select { - case <-wait: - break WAITLOOP - case <-time.After(1 * time.Second): - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } + case <-time.After(s.Config.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt } } + var pauseFn multistep.DebugPauseFn + if debug { + pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) + } + port := state.Get("http_port").(int) if port > 0 { - ip, err := getHostIP(s.Config.HTTPIP) - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - state.Put("http_ip", ip) + ip := state.Get("http_ip").(string) s.Ctx.Data = &bootCommandTemplateData{ ip, port, @@ -121,136 +73,61 @@ WAITLOOP: ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port)) } - ui.Say("Typing boot command...") - var keyAlt bool - var keyCtrl bool - var keyShift bool - for _, command := range s.Config.BootCommand { - message, err := interpolate.Render(command, &s.Ctx) - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - for len(message) > 0 { - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } - - if strings.HasPrefix(message, "") { - log.Printf("Waiting 1 second") - time.Sleep(1 * time.Second) - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - log.Printf("Waiting 5 seconds") - time.Sleep(5 * time.Second) - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - log.Printf("Waiting 10 seconds") - time.Sleep(10 * time.Second) - message = message[len(""):] - continue - } + sendCodes := func(code key.Code, down bool) error { + var keyAlt, keyCtrl, keyShift bool - if strings.HasPrefix(message, "") { - keyAlt = true - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - keyAlt = false - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - keyCtrl = true - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - keyCtrl = false - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - keyShift = true - message = message[len(""):] - continue - } - - if strings.HasPrefix(message, "") { - keyShift = false - message = message[len(""):] - continue - } - - var scancode key.Code - for specialCode, specialValue := range special { - if strings.HasPrefix(message, specialCode) { - scancode = specialValue - log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) - message = message[len(specialCode):] - } - } - - var char rune - if scancode == 0 { - var size int - char, size = utf8.DecodeRuneInString(message) - message = message[size:] - } + switch code { + case key.CodeLeftAlt: + keyAlt = down + case key.CodeLeftControl: + keyCtrl = down + default: + keyShift = down + } - _, err := vm.TypeOnKeyboard(driver.KeyInput{ - Message: string(char), - Scancode: scancode, - Ctrl: keyCtrl, - Alt: keyAlt, - Shift: keyShift, - }) - if err != nil { - state.Put("error", fmt.Errorf("error typing a boot command: %v", err)) - return multistep.ActionHalt - } - time.Sleep(keyInterval) + _, err := vm.TypeOnKeyboard(driver.KeyInput{ + Scancode: code, + Ctrl: keyCtrl, + Alt: keyAlt, + Shift: keyShift, + }) + if err != nil { + return fmt.Errorf("error typing a boot command: %v", err) } + return nil } + d := bootcommand.NewUSBDriver(sendCodes, s.Config.BootGroupInterval) - return multistep.ActionContinue -} - -func (s *StepBootCommand) Cleanup(state multistep.StateBag) {} - -func getHostIP(s string) (string, error) { - if s != "" { - if net.ParseIP(s) != nil { - return s, nil - } else { - return "", fmt.Errorf("invalid IP address") - } + ui.Say("Typing boot command...") + flatBootCommand := s.Config.FlatBootCommand() + command, err := interpolate.Render(flatBootCommand, &s.Ctx) + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } - addrs, err := net.InterfaceAddrs() + seq, err := bootcommand.GenerateExpressionSequence(command) if err != nil { - return "", err + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } - for _, a := range addrs { - ipnet, ok := a.(*net.IPNet) - if ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String(), nil - } - } + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } - return "", fmt.Errorf("IP not found") + + if pauseFn != nil { + pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state) + } + + return multistep.ActionContinue } + +func (s *StepBootCommand) Cleanup(_ multistep.StateBag) {} diff --git a/common/bootcommand/usb_driver.go b/common/bootcommand/usb_driver.go new file mode 100644 index 000000000..519c28525 --- /dev/null +++ b/common/bootcommand/usb_driver.go @@ -0,0 +1,136 @@ +package bootcommand + +import ( + "fmt" + "log" + "os" + "strings" + "time" + "unicode" + + "github.com/hashicorp/packer/common" + "golang.org/x/mobile/event/key" +) + +// SendUsbScanCodes will be called to send codes to the VM +type SendUsbScanCodes func(k key.Code, down bool) error + +type usbDriver struct { + sendImpl SendUsbScanCodes + interval time.Duration + specialMap map[string]key.Code + scancodeMap map[rune]key.Code +} + +func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { + // We delay (default 100ms) between each key event to allow for CPU or + // network latency. See PackerKeyEnv for tuning. + keyInterval := common.PackerKeyDefault + if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil { + keyInterval = delay + } + // override interval based on builder-specific override. + if interval > time.Duration(0) { + keyInterval = interval + } + + special := map[string]key.Code{ + "enter": key.CodeReturnEnter, + "return": key.CodeReturnEnter, + "esc": key.CodeEscape, + "bs": key.CodeDeleteBackspace, + "del": key.CodeDeleteForward, + "tab": key.CodeTab, + "f1": key.CodeF1, + "f2": key.CodeF2, + "f3": key.CodeF3, + "f4": key.CodeF4, + "f5": key.CodeF5, + "f6": key.CodeF6, + "f7": key.CodeF7, + "f8": key.CodeF8, + "f9": key.CodeF9, + "f10": key.CodeF10, + "f11": key.CodeF11, + "f12": key.CodeF12, + "insert": key.CodeInsert, + "home": key.CodeHome, + "end": key.CodeEnd, + "pageUp": key.CodePageUp, + "pageDown": key.CodePageDown, + "left": key.CodeLeftArrow, + "right": key.CodeRightArrow, + "up": key.CodeUpArrow, + "down": key.CodeDownArrow, + "leftalt": key.CodeLeftAlt, + "leftctrl": key.CodeLeftControl, + "leftshift": key.CodeLeftShift, + "rightalt": key.CodeRightAlt, + "rightctrl": key.CodeRightControl, + "rightshift": key.CodeRightShift, + "leftsuper": key.CodeLeftGUI, + "rightsuper": key.CodeRightGUI, + "spacebar": key.CodeSpacebar, + } + + scancodeIndex := make(map[string]key.Code) + scancodeIndex["abcdefghijklmnopqrstuvwxyz"] = key.CodeA + scancodeIndex["ABCDEFGHIJKLMNOPQRSTUVWXYZ"] = key.CodeA + scancodeIndex["1234567890"] = key.Code1 + scancodeIndex["!@#$%^&*()"] = key.Code1 + scancodeIndex[" "] = key.CodeSpacebar + scancodeIndex["-=[]\\"] = key.CodeHyphenMinus + scancodeIndex["_+{}|"] = key.CodeHyphenMinus + scancodeIndex[";'`,./"] = key.CodeSemicolon + scancodeIndex[":\"~<>?"] = key.CodeSemicolon + + var scancodeMap = make(map[rune]key.Code) + for chars, start := range scancodeIndex { + for i, r := range chars { + scancodeMap[r] = start + key.Code(i) + } + } + + return &usbDriver{ + sendImpl: send, + specialMap: special, + interval: keyInterval, + scancodeMap: scancodeMap, + } +} + +func (d *usbDriver) keyEvent(k key.Code, down bool) error { + if err := d.sendImpl(k, down); err != nil { + return err + } + time.Sleep(d.interval) + return nil +} + +func (d *usbDriver) Flush() error { + return nil +} + +func (d *usbDriver) SendKey(k rune, action KeyAction) error { + keyShift := unicode.IsUpper(k) || strings.ContainsRune(shiftedChars, k) + keyCode := d.scancodeMap[k] + log.Printf("Sending char '%c', code %s, shift %v", k, keyCode, keyShift) + return d.keyEvent(keyCode, keyShift) +} + +func (d *usbDriver) SendSpecial(special string, action KeyAction) (err error) { + keyCode, ok := d.specialMap[special] + if !ok { + return fmt.Errorf("special %s not found.", special) + } + log.Printf("Special code '<%s>' found, replacing with: %s", special, keyCode) + + switch action { + case KeyOn: + err = d.keyEvent(keyCode, true) + case KeyOff, KeyPress: + err = d.keyEvent(keyCode, false) + } + + return err +} diff --git a/common/bootcommand/usb_driver_test.go b/common/bootcommand/usb_driver_test.go new file mode 100644 index 000000000..a25948940 --- /dev/null +++ b/common/bootcommand/usb_driver_test.go @@ -0,0 +1,103 @@ +package bootcommand + +import ( + "context" + "testing" + "time" + + "golang.org/x/mobile/event/key" +) + +func TestUSBDriver(t *testing.T) { + tc := []struct { + command string + code key.Code + shift bool + }{ + { + "", + key.CodeLeftShift, + false, + }, + { + "", + key.CodeLeftShift, + false, + }, + { + "", + key.CodeLeftShift, + true, + }, + { + "", + key.CodeLeftGUI, + false, + }, + { + "", + key.CodeSpacebar, + false, + }, + { + "", + key.CodeReturnEnter, + false, + }, + { + "a", + key.CodeA, + false, + }, + { + "A", + key.CodeA, + true, + }, + { + "_", + key.CodeHyphenMinus, + true, + }, + } + for _, tt := range tc { + t.Run(tt.command, func(t *testing.T) { + var code key.Code + var shift bool + sendCodes := func(c key.Code, d bool) error { + code = c + shift = d + return nil + } + d := NewUSBDriver(sendCodes, time.Duration(0)) + seq, err := GenerateExpressionSequence(tt.command) + if err != nil { + t.Fatalf("bad: not expected error: %s", err.Error()) + } + err = seq.Do(context.Background(), d) + if err != nil { + t.Fatalf("bad: not expected error: %s", err.Error()) + } + if code != tt.code { + t.Fatalf("bad: wrong scan code: \n expected: %s \n actual: %s", tt.code, code) + } + if shift != tt.shift { + t.Fatalf("bad: wrong shift: \n expected: %t \n actual: %t", tt.shift, shift) + } + }) + } +} + +func TestUSBDriver_KeyIntervalNotGiven(t *testing.T) { + d := NewUSBDriver(nil, time.Duration(0)) + if d.interval != time.Duration(100)*time.Millisecond { + t.Fatal("not expected key interval") + } +} + +func TestUSBDriver_KeyIntervalGiven(t *testing.T) { + d := NewUSBDriver(nil, time.Duration(5000)*time.Millisecond) + if d.interval != time.Duration(5000)*time.Millisecond { + t.Fatal("not expected key interval") + } +} diff --git a/website/pages/docs/builders/virtualbox/iso.mdx b/website/pages/docs/builders/virtualbox/iso.mdx index 7618be1b0..941fd0297 100644 --- a/website/pages/docs/builders/virtualbox/iso.mdx +++ b/website/pages/docs/builders/virtualbox/iso.mdx @@ -154,6 +154,11 @@ necessary for this build to succeed and can be found further down the page. @include 'common/bootcommand/BootConfig.mdx' +Please note that for the Virtuabox builder, the IP address of the HTTP server +Packer launches for you to access files like the preseed file in the example +above (`{{ .HTTPIP }}`) is hardcoded to 10.0.2.2. If you change the network +of your VM you must guarantee that you can still access this HTTP server. + The boot command is sent to the VM through the `VBoxManage` utility in as few invocations as possible. We send each character in groups of 25, with a default delay of 100ms between groups. The delay alleviates issues with latency and CPU @@ -176,35 +181,8 @@ contention. If you notice missing keys, you can tune this delay by specifying @include 'common/bootcommand/BootConfig-not-required.mdx' -@include 'builders/boot-command.mdx' - @include 'builders/virtualbox-ssh-key-pair.mdx' -Example boot command. This is actually a working boot command used to start an -Ubuntu 12.04 installer: - -```text -[ - "", - "/install/vmlinuz noapic ", - "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", - "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", - "hostname={{ .Name }} ", - "fb=false debconf/frontend=noninteractive ", - "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", - "keyboard-configuration/variant=USA console-setup/ask_detect=false ", - "initrd=/install/initrd.gz -- " -] -``` - -Please note that for the Virtuabox builder, the IP address of the HTTP server -Packer launches for you to access files like the preseed file in the example -above (`{{ .HTTPIP }}`) is hardcoded to 10.0.2.2. If you change the network -of your VM you must guarantee that you can still access this HTTP server. - -For more examples of various boot commands, see the sample projects from our -[community templates page](/community-tools#templates). - ## Guest Additions Packer will automatically download the proper guest additions for the version of diff --git a/website/pages/docs/builders/virtualbox/ovf.mdx b/website/pages/docs/builders/virtualbox/ovf.mdx index 856cb158c..3f556c12e 100644 --- a/website/pages/docs/builders/virtualbox/ovf.mdx +++ b/website/pages/docs/builders/virtualbox/ovf.mdx @@ -145,6 +145,11 @@ necessary for this build to succeed and can be found further down the page. @include 'common/bootcommand/BootConfig.mdx' +Please note that for the Virtuabox builder, the IP address of the HTTP server +Packer launches for you to access files like the preseed file in the example +above (`{{ .HTTPIP }}`) is hardcoded to 10.0.2.2. If you change the network +of your VM you must guarantee that you can still access this HTTP server. + The boot command is sent to the VM through the `VBoxManage` utility in as few invocations as possible. We send each character in groups of 25, with a default delay of 100ms between groups. The delay alleviates issues with latency and CPU @@ -167,30 +172,8 @@ contention. If you notice missing keys, you can tune this delay by specifying @include 'common/bootcommand/BootConfig-not-required.mdx' -@include 'builders/boot-command.mdx' - @include 'builders/virtualbox-ssh-key-pair.mdx' -Example boot command. This is actually a working boot command used to start an -Ubuntu 12.04 installer: - -```text -[ - "", - "/install/vmlinuz noapic ", - "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", - "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", - "hostname={{ .Name }} ", - "fb=false debconf/frontend=noninteractive ", - "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", - "keyboard-configuration/variant=USA console-setup/ask_detect=false ", - "initrd=/install/initrd.gz -- " -] -``` - -For more examples of various boot commands, see the sample projects from our -[community templates page](/community-tools#templates). - ## Guest Additions Packer will automatically download the proper guest additions for the version of diff --git a/website/pages/docs/builders/virtualbox/vm.mdx b/website/pages/docs/builders/virtualbox/vm.mdx index f491e4f0f..90a3bc151 100644 --- a/website/pages/docs/builders/virtualbox/vm.mdx +++ b/website/pages/docs/builders/virtualbox/vm.mdx @@ -159,6 +159,11 @@ builder. @include 'common/bootcommand/BootConfig.mdx' +Please note that for the Virtuabox builder, the IP address of the HTTP server +Packer launches for you to access files like the preseed file in the example +above (`{{ .HTTPIP }}`) is hardcoded to 10.0.2.2. If you change the network +of your VM you must guarantee that you can still access this HTTP server. + The boot command is sent to the VM through the `VBoxManage` utility in as few invocations as possible. We send each character in groups of 25, with a default delay of 100ms between groups. The delay alleviates issues with latency and CPU @@ -181,34 +186,8 @@ contention. If you notice missing keys, you can tune this delay by specifying @include 'common/bootcommand/BootConfig-not-required.mdx' -@include 'builders/boot-command.mdx' - @include 'builders/virtualbox-ssh-key-pair.mdx' -Example boot command. This is actually a working boot command used to start an -Ubuntu 12.04 installer: - -```text -[ - "", - "/install/vmlinuz noapic ", - "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", - "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", - "hostname={{ .Name }} ", - "fb=false debconf/frontend=noninteractive ", - "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", - "keyboard-configuration/variant=USA console-setup/ask_detect=false ", - "initrd=/install/initrd.gz -- " -] -``` - -Please note that for the Virtuabox builder, the IP address of the HTTP server -Packer launches for you to access files like the preseed file in the example -above (`{{ .HTTPIP }}`) is hardcoded to 10.0.2.2. If you change the network -of your VM you must guarantee that you can still access this HTTP server. - -For more examples of various boot commands, see the sample projects from our -[community templates page](/community-tools#templates). ## Guest Additions diff --git a/website/pages/docs/builders/vmware/vsphere-iso.mdx b/website/pages/docs/builders/vmware/vsphere-iso.mdx index cd204c54e..6aa4a6bb0 100644 --- a/website/pages/docs/builders/vmware/vsphere-iso.mdx +++ b/website/pages/docs/builders/vmware/vsphere-iso.mdx @@ -162,6 +162,31 @@ from the datastore. Example: @include 'helper/communicator/WinRM-not-required.mdx' +## Boot Configuration + +@include 'common/bootcommand/BootConfig.mdx' + +We send each character to the VM with a default delay of 100ms between groups. +The delay alleviates possible issues with latency and CPU +contention. If you notice missing keys, you can tune this delay by specifying +"boot_keygroup_interval" in your Packer template, for example: + +```json +{ + "builders": [ + { + "type": "vsphere-iso", + "boot_keygroup_interval": "500ms" + ... + } + ] +} +``` + +#### Optional: + +@include 'common/bootcommand/BootConfig-not-required.mdx' + ## Working with Clusters #### Standalone Hosts