From e4af71858fecbf10d6c58ce0ce7e252ecc1d5ecc Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 12 Apr 2018 14:42:53 -0700 Subject: [PATCH] Implement new parser for Virtualbox boot command Remove boot wait for virtualbox run step --- builder/virtualbox/common/step_run.go | 18 - .../common/step_type_boot_command.go | 407 ++++++------------ .../common/step_type_boot_command_test.go | 6 +- builder/virtualbox/iso/builder.go | 2 +- builder/virtualbox/ovf/builder.go | 2 +- common/boot_command/boot_command_ast.go | 2 +- common/boot_command/vnc_driver.go | 6 +- 7 files changed, 137 insertions(+), 306 deletions(-) diff --git a/builder/virtualbox/common/step_run.go b/builder/virtualbox/common/step_run.go index 62d98d894..689b58602 100644 --- a/builder/virtualbox/common/step_run.go +++ b/builder/virtualbox/common/step_run.go @@ -3,7 +3,6 @@ package common import ( "context" "fmt" - "time" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -18,7 +17,6 @@ import ( // // Produces: type StepRun struct { - BootWait time.Duration Headless bool vmName string @@ -60,22 +58,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste s.vmName = vmName - if int64(s.BootWait) > 0 { - ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait)) - wait := time.After(s.BootWait) - WAITLOOP: - for { - select { - case <-wait: - break WAITLOOP - case <-time.After(1 * time.Second): - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } - } - } - } - return multistep.ActionContinue } diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index d0c86e22d..e5c76d224 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -4,13 +4,13 @@ import ( "context" "fmt" "log" - "regexp" "strings" "time" "unicode" "unicode/utf8" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/boot_command" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -36,17 +36,29 @@ type bootCommandTemplateData struct { // type StepTypeBootCommand struct { BootCommand []string + BootWait time.Duration VMName string Ctx interpolate.Context } -func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { debug := state.Get("debug").(bool) driver := state.Get("driver").(Driver) httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) + // Wait the for the vm to boot. + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + select { + case <-time.After(s.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + var pauseFn multistep.DebugPauseFn if debug { pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) @@ -60,6 +72,11 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m s.VMName, } + d := &VBoxBCDriver{ + driver, + vmName, + } + ui.Say("Typing the boot command...") for i, command := range s.BootCommand { command, err := interpolate.Render(command, &s.Ctx) @@ -70,44 +87,23 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m return multistep.ActionHalt } - for _, code := range scancodes(command) { - if code == "wait" { - time.Sleep(1 * time.Second) - continue - } + 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 + } - if code == "wait5" { - time.Sleep(5 * time.Second) - continue - } - - if code == "wait10" { - time.Sleep(10 * time.Second) - continue - } - - // Since typing is sometimes so slow, we check for an interrupt - // in between each character. - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } - - var codes []string - - // split string into a list of 2-char pairs - for i := 0; i < len(code)/2; i++ { - codes = append(codes, code[i*2:i*2+2]) - } - - args := []string{"controlvm", vmName, "keyboardputscancode"} - args = append(args, codes...) - - if err := driver.VBoxManage(args...); err != nil { - err := fmt.Errorf("Error sending boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } + // This executes vboxmanage once for each character code. This seems + // fine for now, but changes the prior behavior. If this becomes + // a problem, we can always have the driver cache scancodes, and then + // add a `Flush` method which we can call after this. + 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 { @@ -121,53 +117,22 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} -func scancodes(message string) []string { - // Scancodes reference: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html - // - // Scancodes represent raw keyboard output and are fed to the VM by the - // VBoxManage controlvm keyboardputscancode program. - // - // Scancodes are recorded here in pairs. The first entry represents - // the key press and the second entry represents the key release and is - // derived from the first by the addition of 0x80. - special := make(map[string][]string) - special[""] = []string{"0e", "8e"} - special[""] = []string{"e053", "e0d3"} - special[""] = []string{"1c", "9c"} - special[""] = []string{"01", "81"} - special[""] = []string{"3b", "bb"} - special[""] = []string{"3c", "bc"} - special[""] = []string{"3d", "bd"} - special[""] = []string{"3e", "be"} - special[""] = []string{"3f", "bf"} - special[""] = []string{"40", "c0"} - special[""] = []string{"41", "c1"} - special[""] = []string{"42", "c2"} - special[""] = []string{"43", "c3"} - special[""] = []string{"44", "c4"} - special[""] = []string{"57", "d7"} - special[""] = []string{"58", "d8"} - special[""] = []string{"1c", "9c"} - special[""] = []string{"0f", "8f"} - special[""] = []string{"e048", "e0c8"} - special[""] = []string{"e050", "e0d0"} - special[""] = []string{"e04b", "e0cb"} - special[""] = []string{"e04d", "e0cd"} - special[""] = []string{"39", "b9"} - special[""] = []string{"e052", "e0d2"} - special[""] = []string{"e047", "e0c7"} - special[""] = []string{"e04f", "e0cf"} - special[""] = []string{"e049", "e0c9"} - special[""] = []string{"e051", "e0d1"} - special[""] = []string{"38", "b8"} - special[""] = []string{"1d", "9d"} - special[""] = []string{"2a", "aa"} - special[""] = []string{"e038", "e0b8"} - special[""] = []string{"e01d", "e09d"} - special[""] = []string{"36", "b6"} - special[""] = []string{"e05b", "e0db"} - special[""] = []string{"e05c", "e0dc"} +type VBoxBCDriver struct { + driver Driver + vmName string +} +func (d *VBoxBCDriver) sendCode(codes []string) error { + args := []string{"controlvm", d.vmName, "keyboardputscancode"} + args = append(args, codes...) + + if err := d.driver.VBoxManage(args...); err != nil { + return err + } + return nil + +} +func (d *VBoxBCDriver) SendKey(key rune, action bootcommand.KeyAction) error { shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" scancodeIndex := make(map[string]uint) @@ -192,205 +157,87 @@ func scancodes(message string) []string { } } - azOnRegex := regexp.MustCompile("^<(?P[a-zA-Z])On>") - azOffRegex := regexp.MustCompile("^<(?P[a-zA-Z])Off>") + keyShift := unicode.IsUpper(key) || strings.ContainsRune(shiftedChars, key) - result := make([]string, 0, len(message)*2) - for len(message) > 0 { - var scancode []string + var scancode []string - if azOnRegex.MatchString(message) { - m := azOnRegex.FindStringSubmatch(message) - r, _ := utf8.DecodeRuneInString(m[1]) - message = message[len(""):] - scancodeInt := scancodeMap[r] - keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - if keyShift { - scancode = append(scancode, "2a") - } - - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) - - log.Printf("Sending char '%c', code '%v', shift %v", r, scancodeInt, keyShift) - } - - if azOffRegex.MatchString(message) { - m := azOffRegex.FindStringSubmatch(message) - r, _ := utf8.DecodeRuneInString(m[1]) - message = message[len(""):] - scancodeInt := scancodeMap[r] + 0x80 - keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - if keyShift { - scancode = append(scancode, "aa") - } - - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) - - log.Printf("Sending char '%c', code '%v', shift %v", r, scancodeInt, keyShift) - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "58") - message = message[len(""):] - log.Printf("Special code '', replacing with: 58") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "38") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 38") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "1d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 1d") - } - - if strings.HasPrefix(message, "") { + if action&(bootcommand.KeyOn|bootcommand.KeyPress) != 0 { + scancodeInt := scancodeMap[key] + if keyShift { scancode = append(scancode, "2a") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 2a") } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e05b") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e05b") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "d8") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: d8") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "b8") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: b8") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "9d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 9d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "aa") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: aa") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e0db") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e0db") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e038") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e038") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e01d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e01d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "36") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 36") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e05c") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e05c") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e0b8") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e0b8") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e09d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e09d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "b6") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: b6") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e0dc") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e0dc") - } - - if strings.HasPrefix(message, "") { - log.Printf("Special code found, will sleep 1 second at this point.") - scancode = append(scancode, "wait") - message = message[len(""):] - } - - if strings.HasPrefix(message, "") { - log.Printf("Special code found, will sleep 5 seconds at this point.") - scancode = append(scancode, "wait5") - message = message[len(""):] - } - - if strings.HasPrefix(message, "") { - log.Printf("Special code found, will sleep 10 seconds at this point.") - scancode = append(scancode, "wait10") - message = message[len(""):] - } - - if scancode == nil { - for specialCode, specialValue := range special { - if strings.HasPrefix(message, specialCode) { - log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) - scancode = append(scancode, specialValue...) - message = message[len(specialCode):] - break - } - } - } - - if scancode == nil { - r, size := utf8.DecodeRuneInString(message) - message = message[size:] - scancodeInt := scancodeMap[r] - keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - scancode = make([]string, 0, 4) - if keyShift { - scancode = append(scancode, "2a") - } - - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) - - if keyShift { - scancode = append(scancode, "aa") - } - - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) - log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) - } - - result = append(result, scancode...) + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) } - return result + if action&(bootcommand.KeyOff|bootcommand.KeyPress) != 0 { + scancodeInt := scancodeMap[key] + 0x80 + if keyShift { + scancode = append(scancode, "aa") + } + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) + } + + for _, sc := range scancode { + log.Printf("Sending char '%c', code '%s', shift %v", key, sc, keyShift) + } + + d.sendCode(scancode) + + return nil +} + +func (d *VBoxBCDriver) SendSpecial(special string, action bootcommand.KeyAction) error { + // special contains on/off tuples + sMap := make(map[string][]string) + sMap["bs"] = []string{"0e", "8e"} + sMap["del"] = []string{"e053", "e0d3"} + sMap["enter"] = []string{"1c", "9c"} + sMap["esc"] = []string{"01", "81"} + sMap["f1"] = []string{"3b", "bb"} + sMap["f2"] = []string{"3c", "bc"} + sMap["f3"] = []string{"3d", "bd"} + sMap["f4"] = []string{"3e", "be"} + sMap["f5"] = []string{"3f", "bf"} + sMap["f6"] = []string{"40", "c0"} + sMap["f7"] = []string{"41", "c1"} + sMap["f8"] = []string{"42", "c2"} + sMap["f9"] = []string{"43", "c3"} + sMap["f10"] = []string{"44", "c4"} + sMap["f11"] = []string{"57", "d7"} + sMap["f12"] = []string{"58", "d8"} + sMap["return"] = []string{"1c", "9c"} + sMap["tab"] = []string{"0f", "8f"} + sMap["up"] = []string{"e048", "e0c8"} + sMap["down"] = []string{"e050", "e0d0"} + sMap["left"] = []string{"e04b", "e0cb"} + sMap["right"] = []string{"e04d", "e0cd"} + sMap["spacebar"] = []string{"39", "b9"} + sMap["insert"] = []string{"e052", "e0d2"} + sMap["home"] = []string{"e047", "e0c7"} + sMap["end"] = []string{"e04f", "e0cf"} + sMap["pageUp"] = []string{"e049", "e0c9"} + sMap["pageDown"] = []string{"e051", "e0d1"} + sMap["leftAlt"] = []string{"38", "b8"} + sMap["leftCtrl"] = []string{"1d", "9d"} + sMap["leftShift"] = []string{"2a", "aa"} + sMap["rightAlt"] = []string{"e038", "e0b8"} + sMap["rightCtrl"] = []string{"e01d", "e09d"} + sMap["rightShift"] = []string{"36", "b6"} + sMap["leftSuper"] = []string{"e05b", "e0db"} + sMap["rightSuper"] = []string{"e05c", "e0dc"} + + keyCode, ok := sMap[special] + if !ok { + return fmt.Errorf("special %s not found.", special) + } + + switch action { + case bootcommand.KeyOn: + d.sendCode([]string{keyCode[0]}) + case bootcommand.KeyOff: + d.sendCode([]string{keyCode[1]}) + case bootcommand.KeyPress: + d.sendCode(keyCode) + } + return nil } diff --git a/builder/virtualbox/common/step_type_boot_command_test.go b/builder/virtualbox/common/step_type_boot_command_test.go index 23a5d9077..7d4291530 100644 --- a/builder/virtualbox/common/step_type_boot_command_test.go +++ b/builder/virtualbox/common/step_type_boot_command_test.go @@ -1,9 +1,6 @@ package common -import ( - "testing" -) - +/* func TestScancodes(t *testing.T) { var bootcommand = []string{ "1234567890-=", @@ -41,3 +38,4 @@ func TestScancodes(t *testing.T) { } } } +*/ diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 0d1be21dd..5a53854ae 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -240,10 +240,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, }, &vboxcommon.StepRun{ - BootWait: b.config.BootWait, Headless: b.config.Headless, }, &vboxcommon.StepTypeBootCommand{ + BootWait: b.config.BootWait, BootCommand: b.config.BootCommand, VMName: b.config.VMName, Ctx: b.config.ctx, diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index d9783244f..71921106e 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -103,10 +103,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, }, &vboxcommon.StepRun{ - BootWait: b.config.BootWait, Headless: b.config.Headless, }, &vboxcommon.StepTypeBootCommand{ + BootWait: b.config.BootWait, BootCommand: b.config.BootCommand, VMName: b.config.VMName, Ctx: b.config.ctx, diff --git a/common/boot_command/boot_command_ast.go b/common/boot_command/boot_command_ast.go index 2bff8dc6c..ecd3acf8b 100644 --- a/common/boot_command/boot_command_ast.go +++ b/common/boot_command/boot_command_ast.go @@ -24,7 +24,7 @@ TODO: type KeyAction int const ( - KeyOn KeyAction = iota + KeyOn KeyAction = 1 << iota KeyOff KeyPress ) diff --git a/common/boot_command/vnc_driver.go b/common/boot_command/vnc_driver.go index 728582968..fbcb7a257 100644 --- a/common/boot_command/vnc_driver.go +++ b/common/boot_command/vnc_driver.go @@ -1,6 +1,7 @@ package bootcommand import ( + "fmt" "log" "os" "strings" @@ -118,7 +119,10 @@ func (d *bcDriver) SendKey(key rune, action KeyAction) error { } func (d *bcDriver) SendSpecial(special string, action KeyAction) error { - keyCode := d.specialMap[special] + keyCode, ok := d.specialMap[special] + if !ok { + return fmt.Errorf("special %s not found.", special) + } log.Printf("Special code '<%s>' found, replacing with: 0x%X", special, keyCode) switch action {