From 585a86fe03a9be562393dc66f2457b21cd3cf9ea Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 11 Jun 2020 18:37:32 +0200 Subject: [PATCH 1/7] Add usb_driver to common boot_command and use it on vsphere --- builder/vsphere/common/StepHTTPIPDiscover.go | 54 ++++ builder/vsphere/driver/vm_keyboard.go | 56 +--- builder/vsphere/iso/builder.go | 4 + builder/vsphere/iso/config.go | 2 +- builder/vsphere/iso/step_boot_command.go | 265 ++++++------------ builder/vsphere/iso/step_boot_command_test.go | 53 ++++ common/bootcommand/usb_driver.go | 186 ++++++++++++ 7 files changed, 378 insertions(+), 242 deletions(-) create mode 100644 builder/vsphere/common/StepHTTPIPDiscover.go create mode 100644 builder/vsphere/iso/step_boot_command_test.go create mode 100644 common/bootcommand/usb_driver.go diff --git a/builder/vsphere/common/StepHTTPIPDiscover.go b/builder/vsphere/common/StepHTTPIPDiscover.go new file mode 100644 index 000000000..627fbe52b --- /dev/null +++ b/builder/vsphere/common/StepHTTPIPDiscover.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/driver/vm_keyboard.go b/builder/vsphere/driver/vm_keyboard.go index 616ec958c..4cf41af5b 100644 --- a/builder/vsphere/driver/vm_keyboard.go +++ b/builder/vsphere/driver/vm_keyboard.go @@ -1,9 +1,6 @@ package driver import ( - "strings" - "unicode" - "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/types" "golang.org/x/mobile/event/key" @@ -17,58 +14,7 @@ type KeyInput struct { 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, - }, - }) - } - +func (vm *VirtualMachine) TypeOnKeyboard(spec types.UsbScanCodeSpec) (int32, error) { req := &types.PutUsbScanCodes{ This: vm.vm.Reference(), Spec: spec, 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/step_boot_command.go b/builder/vsphere/iso/step_boot_command.go index 70c5adc7c..aaaf58d94 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -3,25 +3,19 @@ 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" + "github.com/vmware/govmomi/vim25/types" "golang.org/x/mobile/event/key" + "time" ) 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,13 +24,15 @@ type bootCommandTemplateData struct { Name string } -func (c *BootConfig) Prepare() []error { +func (c *BootConfig) Prepare(ctx *interpolate.Context) []error { var errs []error if c.BootWait == 0 { c.BootWait = 10 * time.Second } + c.BootConfig.Prepare(ctx) + return errs } @@ -46,44 +42,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 +51,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 +78,72 @@ 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 - } - - 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:] + sendCodes := func(codes []key.Code, downs []bool) error { + var spec types.UsbScanCodeSpec + + for i, code := range codes { + var keyAlt, keyCtrl, keyShift bool + + switch code { + case key.CodeLeftAlt: + // + keyAlt = downs[i] + case key.CodeLeftControl: + // + keyCtrl = downs[i] + case key.CodeLeftShift: + // + keyShift = downs[i] } - _, err := vm.TypeOnKeyboard(driver.KeyInput{ - Message: string(char), - Scancode: scancode, - Ctrl: keyCtrl, - Alt: keyAlt, - Shift: keyShift, + spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ + UsbHidCode: int32(code)<<16 | 7, + Modifiers: &types.UsbScanCodeSpecModifierType{ + LeftControl: &keyCtrl, + LeftAlt: &keyAlt, + LeftShift: &keyShift, + }, }) - if err != nil { - state.Put("error", fmt.Errorf("error typing a boot command: %v", err)) - return multistep.ActionHalt - } - time.Sleep(keyInterval) } - } - 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") + _, err := vm.TypeOnKeyboard(spec) + if err != nil { + return fmt.Errorf("error typing a boot command: %v", err) } + return nil } + d := bootcommand.NewUSBDriver(sendCodes, s.Config.BootGroupInterval) - addrs, err := net.InterfaceAddrs() + ui.Say("Typing boot command...") + flatBootCommand := s.Config.FlatBootCommand() + command, err := interpolate.Render(flatBootCommand, &s.Ctx) if err != nil { - return "", err + err := fmt.Errorf("Error preparing 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 - } - } + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } - return "", fmt.Errorf("IP not found") + + 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 + } + + 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/builder/vsphere/iso/step_boot_command_test.go b/builder/vsphere/iso/step_boot_command_test.go new file mode 100644 index 000000000..beac28350 --- /dev/null +++ b/builder/vsphere/iso/step_boot_command_test.go @@ -0,0 +1,53 @@ +package iso + +import ( + "bytes" + "context" + "github.com/hashicorp/packer/builder/vsphere/driver" + "github.com/hashicorp/packer/common/bootcommand" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "testing" +) + +func TestStepBootCommand_Run(t *testing.T) { + state := new(multistep.BasicStateBag) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + state.Put("debug", false) + state.Put("vm", new(driver.VirtualMachine)) + + state.Put("http_port", 2222) + state.Put("http_ip", "0.0.0.0") + + step := &StepBootCommand{ + Config: &BootConfig{ + BootConfig: bootcommand.BootConfig{ + BootCommand: []string{ + " initrd=/install/initrd.gz", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "/install/vmlinuz", + " initrd=/install/initrd.gz", + " priority=critical", + " locale=en_US", + " file=/media/preseed_hardcoded_ip.cfg", + " netcfg/get_ipaddress=0.0.0.0", + " netcfg/get_gateway=0.0.0.0", + "", + }, + }, + }, + } + step.Run(context.TODO(), state) +} diff --git a/common/bootcommand/usb_driver.go b/common/bootcommand/usb_driver.go new file mode 100644 index 000000000..7d0d34386 --- /dev/null +++ b/common/bootcommand/usb_driver.go @@ -0,0 +1,186 @@ +package bootcommand + +import ( + "fmt" + "github.com/hashicorp/packer/builder/vsphere/driver" + "github.com/hashicorp/packer/common" + "golang.org/x/mobile/event/key" + "log" + "os" + "strings" + "time" + "unicode" +) + +// SendUsbScanCodes will be called to send codes to the VM +type SendUsbScanCodes func([]key.Code, []bool) error + +type usbDriver struct { + vm *driver.VirtualMachine + + sendImpl SendUsbScanCodes + interval time.Duration + specialMap map[string]key.Code + scancodeMap map[rune]key.Code + + codeBuffer []key.Code + downBuffer []bool + + // keyEvent can set this error which will prevent it from continuing + err error +} + +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, + "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, + } + + 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 d.err != nil { +// return nil +// } +// if err := d.sendImpl(k, down); err != nil { +// d.err = err +// return err +// } +// //time.Sleep(d.interval) +// return nil +//} + +// Flush does nothing here +func (d *usbDriver) Flush() error { + defer func() { + d.codeBuffer = nil + }() + + if err := d.sendImpl(d.codeBuffer, d.downBuffer); err != nil { + return err + } + time.Sleep(d.interval) + 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) + + switch action { + case KeyOn: + if keyShift { + d.send(key.CodeLeftShift, true) + } + d.send(keyCode, true) + case KeyOff: + if keyShift { + d.send(key.CodeLeftShift, false) + } + d.send(keyCode, false) + case KeyPress: + if keyShift { + d.send(key.CodeLeftShift, true) + } + d.send(keyCode, true) + d.send(keyCode, false) + if keyShift { + d.send(key.CodeLeftShift, false) + } + } + return d.err +} + +func (d *usbDriver) SendSpecial(special string, action KeyAction) 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: + d.send(keyCode, true) + case KeyOff: + d.send(keyCode, false) + case KeyPress: + d.send(keyCode, true) + d.send(keyCode, false) + } + + return d.err +} + +// send stores the codes in an internal buffer. Use Flush to send them. +func (d *usbDriver) send(code key.Code, down bool) { + // slices to keep the input order + d.codeBuffer = append(d.codeBuffer, code) + d.downBuffer = append(d.downBuffer, down) + +} From ba767d1663db82d02fa18c087e4d99043d141fd3 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 12 Jun 2020 11:00:21 +0200 Subject: [PATCH 2/7] remove duplicated press --- builder/vsphere/iso/step_boot_command.go | 5 ++- builder/vsphere/iso/step_boot_command_test.go | 1 - common/bootcommand/usb_driver.go | 42 ++----------------- 3 files changed, 6 insertions(+), 42 deletions(-) diff --git a/builder/vsphere/iso/step_boot_command.go b/builder/vsphere/iso/step_boot_command.go index aaaf58d94..b002ca3ad 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/packer/template/interpolate" "github.com/vmware/govmomi/vim25/types" "golang.org/x/mobile/event/key" + "log" "time" ) @@ -91,11 +92,11 @@ func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) mul case key.CodeLeftControl: // keyCtrl = downs[i] - case key.CodeLeftShift: - // + default: keyShift = downs[i] } + log.Printf("Sending code %s, shift %v", code, downs[i]) spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ UsbHidCode: int32(code)<<16 | 7, Modifiers: &types.UsbScanCodeSpecModifierType{ diff --git a/builder/vsphere/iso/step_boot_command_test.go b/builder/vsphere/iso/step_boot_command_test.go index beac28350..1e52b3f11 100644 --- a/builder/vsphere/iso/step_boot_command_test.go +++ b/builder/vsphere/iso/step_boot_command_test.go @@ -26,7 +26,6 @@ func TestStepBootCommand_Run(t *testing.T) { Config: &BootConfig{ BootConfig: bootcommand.BootConfig{ BootCommand: []string{ - " initrd=/install/initrd.gz", "", "", "", diff --git a/common/bootcommand/usb_driver.go b/common/bootcommand/usb_driver.go index 7d0d34386..cde3e95ec 100644 --- a/common/bootcommand/usb_driver.go +++ b/common/bootcommand/usb_driver.go @@ -103,19 +103,7 @@ func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { } } -//func (d *usbDriver) keyEvent(k key.Code, down bool) error { -// if d.err != nil { -// return nil -// } -// if err := d.sendImpl(k, down); err != nil { -// d.err = err -// return err -// } -// //time.Sleep(d.interval) -// return nil -//} - -// Flush does nothing here +// Flush sends codes to the vm func (d *usbDriver) Flush() error { defer func() { d.codeBuffer = nil @@ -132,28 +120,7 @@ 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) - - switch action { - case KeyOn: - if keyShift { - d.send(key.CodeLeftShift, true) - } - d.send(keyCode, true) - case KeyOff: - if keyShift { - d.send(key.CodeLeftShift, false) - } - d.send(keyCode, false) - case KeyPress: - if keyShift { - d.send(key.CodeLeftShift, true) - } - d.send(keyCode, true) - d.send(keyCode, false) - if keyShift { - d.send(key.CodeLeftShift, false) - } - } + d.send(keyCode, keyShift) return d.err } @@ -167,10 +134,7 @@ func (d *usbDriver) SendSpecial(special string, action KeyAction) error { switch action { case KeyOn: d.send(keyCode, true) - case KeyOff: - d.send(keyCode, false) - case KeyPress: - d.send(keyCode, true) + case KeyOff, KeyPress: d.send(keyCode, false) } From 65cfb880fda682c3c3d41f3179b145c1a631ed41 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 12 Jun 2020 12:42:00 +0200 Subject: [PATCH 3/7] Fix boot_command and update docs --- builder/vsphere/driver/vm_keyboard.go | 14 ++++- builder/vsphere/iso/config.hcl2spec.go | 6 ++- builder/vsphere/iso/step_boot_command.go | 47 +++++++---------- builder/vsphere/iso/step_boot_command_test.go | 52 ------------------- common/bootcommand/usb_driver.go | 44 +++++++--------- .../pages/docs/builders/virtualbox/iso.mdx | 32 ++---------- .../pages/docs/builders/virtualbox/ovf.mdx | 27 ++-------- website/pages/docs/builders/virtualbox/vm.mdx | 31 ++--------- .../docs/builders/vmware/vsphere-iso.mdx | 25 +++++++++ 9 files changed, 92 insertions(+), 186 deletions(-) delete mode 100644 builder/vsphere/iso/step_boot_command_test.go diff --git a/builder/vsphere/driver/vm_keyboard.go b/builder/vsphere/driver/vm_keyboard.go index 4cf41af5b..b11041225 100644 --- a/builder/vsphere/driver/vm_keyboard.go +++ b/builder/vsphere/driver/vm_keyboard.go @@ -7,14 +7,24 @@ import ( ) type KeyInput struct { - Message string Scancode key.Code Alt bool Ctrl bool Shift bool } -func (vm *VirtualMachine) TypeOnKeyboard(spec types.UsbScanCodeSpec) (int32, error) { +func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) { + var spec types.UsbScanCodeSpec + + 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(), Spec: spec, 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 b002ca3ad..200fc6d6b 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -8,9 +8,7 @@ import ( "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" - "github.com/vmware/govmomi/vim25/types" "golang.org/x/mobile/event/key" - "log" "time" ) @@ -79,35 +77,26 @@ func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) mul ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port)) } - sendCodes := func(codes []key.Code, downs []bool) error { - var spec types.UsbScanCodeSpec - - for i, code := range codes { - var keyAlt, keyCtrl, keyShift bool - - switch code { - case key.CodeLeftAlt: - // - keyAlt = downs[i] - case key.CodeLeftControl: - // - keyCtrl = downs[i] - default: - keyShift = downs[i] - } - - log.Printf("Sending code %s, shift %v", code, downs[i]) - spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{ - UsbHidCode: int32(code)<<16 | 7, - Modifiers: &types.UsbScanCodeSpecModifierType{ - LeftControl: &keyCtrl, - LeftAlt: &keyAlt, - LeftShift: &keyShift, - }, - }) + sendCodes := func(code key.Code, down bool) error { + var keyAlt, keyCtrl, keyShift bool + + switch code { + case key.CodeLeftAlt: + // + keyAlt = down + case key.CodeLeftControl: + // + keyCtrl = down + default: + keyShift = down } - _, err := vm.TypeOnKeyboard(spec) + _, 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) } diff --git a/builder/vsphere/iso/step_boot_command_test.go b/builder/vsphere/iso/step_boot_command_test.go deleted file mode 100644 index 1e52b3f11..000000000 --- a/builder/vsphere/iso/step_boot_command_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package iso - -import ( - "bytes" - "context" - "github.com/hashicorp/packer/builder/vsphere/driver" - "github.com/hashicorp/packer/common/bootcommand" - "github.com/hashicorp/packer/helper/multistep" - "github.com/hashicorp/packer/packer" - "testing" -) - -func TestStepBootCommand_Run(t *testing.T) { - state := new(multistep.BasicStateBag) - state.Put("ui", &packer.BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - }) - state.Put("debug", false) - state.Put("vm", new(driver.VirtualMachine)) - - state.Put("http_port", 2222) - state.Put("http_ip", "0.0.0.0") - - step := &StepBootCommand{ - Config: &BootConfig{ - BootConfig: bootcommand.BootConfig{ - BootCommand: []string{ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "/install/vmlinuz", - " initrd=/install/initrd.gz", - " priority=critical", - " locale=en_US", - " file=/media/preseed_hardcoded_ip.cfg", - " netcfg/get_ipaddress=0.0.0.0", - " netcfg/get_gateway=0.0.0.0", - "", - }, - }, - }, - } - step.Run(context.TODO(), state) -} diff --git a/common/bootcommand/usb_driver.go b/common/bootcommand/usb_driver.go index cde3e95ec..6ea6291ff 100644 --- a/common/bootcommand/usb_driver.go +++ b/common/bootcommand/usb_driver.go @@ -2,18 +2,19 @@ package bootcommand import ( "fmt" - "github.com/hashicorp/packer/builder/vsphere/driver" - "github.com/hashicorp/packer/common" - "golang.org/x/mobile/event/key" "log" "os" "strings" "time" "unicode" + + "github.com/hashicorp/packer/builder/vsphere/driver" + "github.com/hashicorp/packer/common" + "golang.org/x/mobile/event/key" ) // SendUsbScanCodes will be called to send codes to the VM -type SendUsbScanCodes func([]key.Code, []bool) error +type SendUsbScanCodes func(k key.Code, down bool) error type usbDriver struct { vm *driver.VirtualMachine @@ -23,9 +24,6 @@ type usbDriver struct { specialMap map[string]key.Code scancodeMap map[rune]key.Code - codeBuffer []key.Code - downBuffer []bool - // keyEvent can set this error which will prevent it from continuing err error } @@ -103,25 +101,27 @@ func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { } } -// Flush sends codes to the vm -func (d *usbDriver) Flush() error { - defer func() { - d.codeBuffer = nil - }() - - if err := d.sendImpl(d.codeBuffer, d.downBuffer); err != nil { +func (d *usbDriver) keyEvent(k key.Code, down bool) error { + if d.err != nil { + return d.err + } + if err := d.sendImpl(k, down); err != nil { + d.err = err 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) - d.send(keyCode, keyShift) - return d.err + return d.keyEvent(keyCode, keyShift) } func (d *usbDriver) SendSpecial(special string, action KeyAction) error { @@ -133,18 +133,10 @@ func (d *usbDriver) SendSpecial(special string, action KeyAction) error { switch action { case KeyOn: - d.send(keyCode, true) + d.keyEvent(keyCode, true) case KeyOff, KeyPress: - d.send(keyCode, false) + d.keyEvent(keyCode, false) } return d.err } - -// send stores the codes in an internal buffer. Use Flush to send them. -func (d *usbDriver) send(code key.Code, down bool) { - // slices to keep the input order - d.codeBuffer = append(d.codeBuffer, code) - d.downBuffer = append(d.downBuffer, down) - -} 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 From 653eb95bdbb44dfdbe991a316e09e5c6ec0ef5eb Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 12 Jun 2020 14:10:46 +0200 Subject: [PATCH 4/7] adds tests to usb_driver and step_http_ip_discover --- ...IPDiscover.go => step_http_ip_discover.go} | 0 .../common/step_http_ip_discover_test.go | 44 ++++++++++ builder/vsphere/iso/step_boot_command.go | 3 +- common/bootcommand/usb_driver.go | 18 +--- common/bootcommand/usb_driver_test.go | 88 +++++++++++++++++++ 5 files changed, 138 insertions(+), 15 deletions(-) rename builder/vsphere/common/{StepHTTPIPDiscover.go => step_http_ip_discover.go} (100%) create mode 100644 builder/vsphere/common/step_http_ip_discover_test.go create mode 100644 common/bootcommand/usb_driver_test.go diff --git a/builder/vsphere/common/StepHTTPIPDiscover.go b/builder/vsphere/common/step_http_ip_discover.go similarity index 100% rename from builder/vsphere/common/StepHTTPIPDiscover.go rename to builder/vsphere/common/step_http_ip_discover.go 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/iso/step_boot_command.go b/builder/vsphere/iso/step_boot_command.go index 200fc6d6b..dc3b89786 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -3,13 +3,14 @@ package iso import ( "context" "fmt" + "time" + "github.com/hashicorp/packer/builder/vsphere/driver" "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" "golang.org/x/mobile/event/key" - "time" ) type BootConfig struct { diff --git a/common/bootcommand/usb_driver.go b/common/bootcommand/usb_driver.go index 6ea6291ff..c3eeeabc4 100644 --- a/common/bootcommand/usb_driver.go +++ b/common/bootcommand/usb_driver.go @@ -8,7 +8,6 @@ import ( "time" "unicode" - "github.com/hashicorp/packer/builder/vsphere/driver" "github.com/hashicorp/packer/common" "golang.org/x/mobile/event/key" ) @@ -17,15 +16,10 @@ import ( type SendUsbScanCodes func(k key.Code, down bool) error type usbDriver struct { - vm *driver.VirtualMachine - sendImpl SendUsbScanCodes interval time.Duration specialMap map[string]key.Code scancodeMap map[rune]key.Code - - // keyEvent can set this error which will prevent it from continuing - err error } func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { @@ -102,11 +96,7 @@ func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { } func (d *usbDriver) keyEvent(k key.Code, down bool) error { - if d.err != nil { - return d.err - } if err := d.sendImpl(k, down); err != nil { - d.err = err return err } time.Sleep(d.interval) @@ -124,7 +114,7 @@ func (d *usbDriver) SendKey(k rune, action KeyAction) error { return d.keyEvent(keyCode, keyShift) } -func (d *usbDriver) SendSpecial(special string, action KeyAction) error { +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) @@ -133,10 +123,10 @@ func (d *usbDriver) SendSpecial(special string, action KeyAction) error { switch action { case KeyOn: - d.keyEvent(keyCode, true) + err = d.keyEvent(keyCode, true) case KeyOff, KeyPress: - d.keyEvent(keyCode, false) + err = d.keyEvent(keyCode, false) } - return d.err + return err } diff --git a/common/bootcommand/usb_driver_test.go b/common/bootcommand/usb_driver_test.go new file mode 100644 index 000000000..dfa85f7d1 --- /dev/null +++ b/common/bootcommand/usb_driver_test.go @@ -0,0 +1,88 @@ +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, + }, + { + "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") + } +} From 45058847c0bad9f8de3519e3f9ef9671e2402b76 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 12 Jun 2020 14:22:00 +0200 Subject: [PATCH 5/7] return BootConfig prepare err --- builder/vsphere/iso/step_boot_command.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/builder/vsphere/iso/step_boot_command.go b/builder/vsphere/iso/step_boot_command.go index dc3b89786..876b49505 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -25,15 +25,10 @@ type bootCommandTemplateData struct { } func (c *BootConfig) Prepare(ctx *interpolate.Context) []error { - var errs []error - if c.BootWait == 0 { c.BootWait = 10 * time.Second } - - c.BootConfig.Prepare(ctx) - - return errs + return c.BootConfig.Prepare(ctx) } type StepBootCommand struct { From c1c350a657a111dc301cc7cf7f69f04991fd97a4 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 12 Jun 2020 14:24:54 +0200 Subject: [PATCH 6/7] remove comments --- builder/vsphere/iso/step_boot_command.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/builder/vsphere/iso/step_boot_command.go b/builder/vsphere/iso/step_boot_command.go index 876b49505..805b89113 100644 --- a/builder/vsphere/iso/step_boot_command.go +++ b/builder/vsphere/iso/step_boot_command.go @@ -78,10 +78,8 @@ func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) mul switch code { case key.CodeLeftAlt: - // keyAlt = down case key.CodeLeftControl: - // keyCtrl = down default: keyShift = down From 2d79ab732b54130fed1d9b8666e6ccddd6c92707 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 12 Jun 2020 15:17:41 +0200 Subject: [PATCH 7/7] adds missing special char to the list --- common/bootcommand/usb_driver.go | 4 ++++ common/bootcommand/usb_driver_test.go | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/common/bootcommand/usb_driver.go b/common/bootcommand/usb_driver.go index c3eeeabc4..519c28525 100644 --- a/common/bootcommand/usb_driver.go +++ b/common/bootcommand/usb_driver.go @@ -36,6 +36,7 @@ func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { special := map[string]key.Code{ "enter": key.CodeReturnEnter, + "return": key.CodeReturnEnter, "esc": key.CodeEscape, "bs": key.CodeDeleteBackspace, "del": key.CodeDeleteForward, @@ -67,6 +68,9 @@ func NewUSBDriver(send SendUsbScanCodes, interval time.Duration) *usbDriver { "rightalt": key.CodeRightAlt, "rightctrl": key.CodeRightControl, "rightshift": key.CodeRightShift, + "leftsuper": key.CodeLeftGUI, + "rightsuper": key.CodeRightGUI, + "spacebar": key.CodeSpacebar, } scancodeIndex := make(map[string]key.Code) diff --git a/common/bootcommand/usb_driver_test.go b/common/bootcommand/usb_driver_test.go index dfa85f7d1..a25948940 100644 --- a/common/bootcommand/usb_driver_test.go +++ b/common/bootcommand/usb_driver_test.go @@ -29,6 +29,21 @@ func TestUSBDriver(t *testing.T) { key.CodeLeftShift, true, }, + { + "", + key.CodeLeftGUI, + false, + }, + { + "", + key.CodeSpacebar, + false, + }, + { + "", + key.CodeReturnEnter, + false, + }, { "a", key.CodeA,