From 7344d9e52ccace4277e448f663cae9640df41aea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 12 Jun 2013 00:26:08 -0700 Subject: [PATCH] builder/virtualbox: type boot command --- builder/virtualbox/builder.go | 1 + builder/virtualbox/step_type_boot_command.go | 151 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 builder/virtualbox/step_type_boot_command.go diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 6bba76fb1..e1f3bc1c2 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -163,6 +163,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer new(stepAttachISO), new(stepForwardSSH), new(stepRun), + new(stepTypeBootCommand), } // Setup the state bag diff --git a/builder/virtualbox/step_type_boot_command.go b/builder/virtualbox/step_type_boot_command.go new file mode 100644 index 000000000..423a0e9a4 --- /dev/null +++ b/builder/virtualbox/step_type_boot_command.go @@ -0,0 +1,151 @@ +package virtualbox + +import ( + "bytes" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "strings" + "text/template" + "time" + "unicode" + "unicode/utf8" +) + +const KeyLeftShift uint32 = 0xFFE1 + +type bootCommandTemplateData struct { + HTTPIP string + HTTPPort uint + Name string +} + +// This step "types" the boot command into the VM over VNC. +// +// Uses: +// config *config +// driver Driver +// http_port int +// ui packer.Ui +// vmName string +// +// Produces: +// +type stepTypeBootCommand struct{} + +func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + driver := state["driver"].(Driver) + httpPort := state["http_port"].(uint) + ui := state["ui"].(packer.Ui) + vmName := state["vmName"].(string) + + tplData := &bootCommandTemplateData{ + "10.0.2.2", + httpPort, + config.VMName, + } + + ui.Say("Typing the boot command...") + for _, command := range config.BootCommand { + var buf bytes.Buffer + t := template.Must(template.New("boot").Parse(command)) + t.Execute(&buf, tplData) + + for _, code := range scancodes(buf.String()) { + if code == "wait" { + time.Sleep(1 * time.Second) + continue + } + + if err := driver.VBoxManage("controlvm", vmName, "keyboardputscancode", code); err != nil { + ui.Error(fmt.Sprintf("Error sending boot command: %s", err)) + return multistep.ActionHalt + } + } + } + + time.Sleep(15 * time.Second) + return multistep.ActionContinue +} + +func (*stepTypeBootCommand) Cleanup(map[string]interface{}) {} + +func scancodes(message string) []string { + special := make(map[string][]string) + special[""] = []string{"1c", "9c"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"01", "81"} + + shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" + + scancodeIndex := make(map[string]uint) + scancodeIndex["1234567890-="] = 0x02 + scancodeIndex["!@#$%^&*()_+"] = 0x02 + scancodeIndex["qwertyuiop[]"] = 0x10 + scancodeIndex["QWERTYUIOP{}"] = 0x10 + scancodeIndex["asdfghjkl;'`"] = 0x1e + scancodeIndex[`ASDFGHJKL:"~`] = 0x1e + scancodeIndex[`\zxcvbnm,./`] = 0x2b + scancodeIndex["|ZXCVBNM<>?"] = 0x2b + scancodeIndex[" "] = 0x39 + + scancodeMap := make(map[rune]uint) + for chars, start := range scancodeIndex { + var i uint = 0 + for len(chars) > 0 { + r, size := utf8.DecodeRuneInString(chars) + chars = chars[size:] + scancodeMap[r] = start + i + i += 1 + } + } + + result := make([]string, 0, len(message) * 2) + for len(message) > 0 { + var scancode []string + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep at this point.") + scancode = []string{"wait"} + 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 = 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...) + } + + return result +}