Implement new parser for HyperV boot command

This commit is contained in:
Matthew Hooker 2018-04-13 12:49:54 -07:00
parent f9ad264f4d
commit bdb1eee7d8
No known key found for this signature in database
GPG Key ID: 7B5F933D9CE8C6A1
5 changed files with 43 additions and 234 deletions

View File

@ -3,15 +3,12 @@ package common
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
) )
type StepRun struct { type StepRun struct {
BootWait time.Duration
vmName string vmName string
} }
@ -32,22 +29,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste
s.vmName = vmName 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 return multistep.ActionContinue
} }

View File

@ -3,12 +3,11 @@ package common
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"strings" "strings"
"unicode" "time"
"unicode/utf8"
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/boot_command"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate" "github.com/hashicorp/packer/template/interpolate"
@ -23,16 +22,28 @@ type bootCommandTemplateData struct {
// This step "types" the boot command into the VM via the Hyper-V virtual keyboard // This step "types" the boot command into the VM via the Hyper-V virtual keyboard
type StepTypeBootCommand struct { type StepTypeBootCommand struct {
BootCommand []string BootCommand []string
BootWait time.Duration
SwitchName string SwitchName string
Ctx interpolate.Context 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 {
httpPort := state.Get("http_port").(uint) httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
vmName := state.Get("vmName").(string) 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
}
}
hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName) hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName)
if err != nil { if err != nil {
@ -52,7 +63,9 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
} }
ui.Say("Typing the boot command...") ui.Say("Typing the boot command...")
scanCodesToSend := []string{}
// Flatten command so we send it all at once
commands := []string{}
for _, command := range s.BootCommand { for _, command := range s.BootCommand {
command, err := interpolate.Render(command, &s.Ctx) command, err := interpolate.Render(command, &s.Ctx)
@ -64,13 +77,26 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
return multistep.ActionHalt return multistep.ActionHalt
} }
scanCodesToSend = append(scanCodesToSend, scancodes(command)...) commands = append(commands, command)
} }
scanCodesToSendString := strings.Join(scanCodesToSend, " ") sendCodes := func(codes []string) error {
scanCodesToSendString := strings.Join(codes, " ")
return driver.TypeScanCodes(vmName, scanCodesToSendString)
}
d := bootcommand.NewPCATDriver(sendCodes, -1)
if err := driver.TypeScanCodes(vmName, scanCodesToSendString); err != nil { flatCommands := strings.Join(commands, "")
err := fmt.Errorf("Error sending boot command: %s", err) seq, err := bootcommand.GenerateExpressionSequence(flatCommands)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
@ -80,202 +106,3 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
} }
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
func scancodes(message string) []string {
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
//
// Scancodes represent raw keyboard output and are fed to the VM by using
// powershell to use Msvm_Keyboard
//
// 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["<bs>"] = []string{"0e", "8e"}
special["<del>"] = []string{"53", "d3"}
special["<enter>"] = []string{"1c", "9c"}
special["<esc>"] = []string{"01", "81"}
special["<f1>"] = []string{"3b", "bb"}
special["<f2>"] = []string{"3c", "bc"}
special["<f3>"] = []string{"3d", "bd"}
special["<f4>"] = []string{"3e", "be"}
special["<f5>"] = []string{"3f", "bf"}
special["<f6>"] = []string{"40", "c0"}
special["<f7>"] = []string{"41", "c1"}
special["<f8>"] = []string{"42", "c2"}
special["<f9>"] = []string{"43", "c3"}
special["<f10>"] = []string{"44", "c4"}
special["<return>"] = []string{"1c", "9c"}
special["<tab>"] = []string{"0f", "8f"}
special["<up>"] = []string{"48", "c8"}
special["<down>"] = []string{"50", "d0"}
special["<left>"] = []string{"4b", "cb"}
special["<right>"] = []string{"4d", "cd"}
special["<spacebar>"] = []string{"39", "b9"}
special["<insert>"] = []string{"52", "d2"}
special["<home>"] = []string{"47", "c7"}
special["<end>"] = []string{"4f", "cf"}
special["<pageUp>"] = []string{"49", "c9"}
special["<pageDown>"] = []string{"51", "d1"}
special["<leftAlt>"] = []string{"38", "b8"}
special["<leftCtrl>"] = []string{"1d", "9d"}
special["<leftShift>"] = []string{"2a", "aa"}
special["<rightAlt>"] = []string{"e038", "e0b8"}
special["<rightCtrl>"] = []string{"e01d", "e09d"}
special["<rightShift>"] = []string{"36", "b6"}
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, "<leftAltOn>") {
scancode = []string{"38"}
message = message[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: 38")
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
scancode = []string{"1d"}
message = message[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d")
}
if strings.HasPrefix(message, "<leftShiftOn>") {
scancode = []string{"2a"}
message = message[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a")
}
if strings.HasPrefix(message, "<leftAltOff>") {
scancode = []string{"b8"}
message = message[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: b8")
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
scancode = []string{"9d"}
message = message[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d")
}
if strings.HasPrefix(message, "<leftShiftOff>") {
scancode = []string{"aa"}
message = message[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: aa")
}
if strings.HasPrefix(message, "<rightAltOn>") {
scancode = []string{"e038"}
message = message[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: e038")
}
if strings.HasPrefix(message, "<rightCtrlOn>") {
scancode = []string{"e01d"}
message = message[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d")
}
if strings.HasPrefix(message, "<rightShiftOn>") {
scancode = []string{"36"}
message = message[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: 36")
}
if strings.HasPrefix(message, "<rightAltOff>") {
scancode = []string{"e0b8"}
message = message[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8")
}
if strings.HasPrefix(message, "<rightCtrlOff>") {
scancode = []string{"e09d"}
message = message[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d")
}
if strings.HasPrefix(message, "<rightShiftOff>") {
scancode = []string{"b6"}
message = message[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: b6")
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}
message = message[len("<wait>"):]
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
scancode = []string{"wait5"}
message = message[len("<wait5>"):]
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
scancode = []string{"wait10"}
message = message[len("<wait10>"):]
}
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
}

View File

@ -404,12 +404,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SwitchVlanId: b.config.SwitchVlanId, SwitchVlanId: b.config.SwitchVlanId,
}, },
&hypervcommon.StepRun{ &hypervcommon.StepRun{},
BootWait: b.config.BootWait,
},
&hypervcommon.StepTypeBootCommand{ &hypervcommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand, BootCommand: b.config.BootCommand,
BootWait: b.config.BootWait,
SwitchName: b.config.SwitchName, SwitchName: b.config.SwitchName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },

View File

@ -434,12 +434,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SwitchVlanId: b.config.SwitchVlanId, SwitchVlanId: b.config.SwitchVlanId,
}, },
&hypervcommon.StepRun{ &hypervcommon.StepRun{},
BootWait: b.config.BootWait,
},
&hypervcommon.StepTypeBootCommand{ &hypervcommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand, BootCommand: b.config.BootCommand,
BootWait: b.config.BootWait,
SwitchName: b.config.SwitchName, SwitchName: b.config.SwitchName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },

View File

@ -14,7 +14,10 @@ TODO:
* fix vbox tests * fix vbox tests
* comments * comments
* lower-case specials * lower-case specials
* check that `<del>` works on parallels. It's different now. * pc-at abstraction
* check that `<del>` works. It's different now.
* parallels
* hyperv-
*/ */
// KeysAction represents what we want to do with a key press. // KeysAction represents what we want to do with a key press.