Replace boot command parser with PEG parser.
This commit is contained in:
parent
508064d0af
commit
673245afcf
3
Makefile
3
Makefile
|
@ -42,6 +42,7 @@ package:
|
|||
|
||||
deps:
|
||||
@go get golang.org/x/tools/cmd/stringer
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/kardianos/govendor
|
||||
@govendor sync
|
||||
|
||||
|
@ -73,6 +74,8 @@ fmt-examples:
|
|||
# source files.
|
||||
generate: deps ## Generate dynamically generated code
|
||||
go generate .
|
||||
gofmt -w common/boot_command/boot_command.go
|
||||
goimports -w common/boot_command/boot_command.go
|
||||
gofmt -w command/plugin.go
|
||||
|
||||
test: deps fmt-check ## Run unit tests
|
||||
|
|
|
@ -94,6 +94,7 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
|||
|
||||
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])
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import (
|
|||
// Produces:
|
||||
// <nothing>
|
||||
type StepRun struct {
|
||||
BootWait time.Duration
|
||||
DurationBeforeStop time.Duration
|
||||
Headless bool
|
||||
|
||||
|
@ -65,24 +64,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait the wait amount
|
||||
if int64(s.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String()))
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -5,28 +5,16 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"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"
|
||||
"github.com/mitchellh/go-vnc"
|
||||
)
|
||||
|
||||
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:
|
||||
|
@ -37,13 +25,19 @@ type bootCommandTemplateData struct {
|
|||
// Produces:
|
||||
// <nothing>
|
||||
type StepTypeBootCommand struct {
|
||||
VNCEnabled bool
|
||||
BootCommand []string
|
||||
VNCEnabled bool
|
||||
BootWait time.Duration
|
||||
VMName string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
type bootCommandTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort uint
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if !s.VNCEnabled {
|
||||
log.Println("Skipping boot command step...")
|
||||
return multistep.ActionContinue
|
||||
|
@ -57,6 +51,19 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
|||
vncPort := state.Get("vnc_port").(uint)
|
||||
vncPassword := state.Get("vnc_password")
|
||||
|
||||
// ----------------
|
||||
// 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)
|
||||
|
@ -110,6 +117,8 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
|||
s.VMName,
|
||||
}
|
||||
|
||||
d := bootcommand.NewVNCDriver(c)
|
||||
|
||||
ui.Say("Typing the boot command over VNC...")
|
||||
for i, command := range s.BootCommand {
|
||||
command, err := interpolate.Render(command, &s.Ctx)
|
||||
|
@ -130,335 +139,24 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
|||
pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command[%d]: %s", i, command), state)
|
||||
}
|
||||
|
||||
vncSendString(c, command)
|
||||
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 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 multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func vncSendString(c *vnc.ClientConn, original string) {
|
||||
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
|
||||
special := make(map[string]uint32)
|
||||
special["<bs>"] = 0xFF08
|
||||
special["<del>"] = 0xFFFF
|
||||
special["<enter>"] = 0xFF0D
|
||||
special["<esc>"] = 0xFF1B
|
||||
special["<f1>"] = 0xFFBE
|
||||
special["<f2>"] = 0xFFBF
|
||||
special["<f3>"] = 0xFFC0
|
||||
special["<f4>"] = 0xFFC1
|
||||
special["<f5>"] = 0xFFC2
|
||||
special["<f6>"] = 0xFFC3
|
||||
special["<f7>"] = 0xFFC4
|
||||
special["<f8>"] = 0xFFC5
|
||||
special["<f9>"] = 0xFFC6
|
||||
special["<f10>"] = 0xFFC7
|
||||
special["<f11>"] = 0xFFC8
|
||||
special["<f12>"] = 0xFFC9
|
||||
special["<return>"] = 0xFF0D
|
||||
special["<tab>"] = 0xFF09
|
||||
special["<up>"] = 0xFF52
|
||||
special["<down>"] = 0xFF54
|
||||
special["<left>"] = 0xFF51
|
||||
special["<right>"] = 0xFF53
|
||||
special["<spacebar>"] = 0x020
|
||||
special["<insert>"] = 0xFF63
|
||||
special["<home>"] = 0xFF50
|
||||
special["<end>"] = 0xFF57
|
||||
special["<pageUp>"] = 0xFF55
|
||||
special["<pageDown>"] = 0xFF56
|
||||
special["<leftAlt>"] = 0xFFE9
|
||||
special["<leftCtrl>"] = 0xFFE3
|
||||
special["<leftShift>"] = 0xFFE1
|
||||
special["<rightAlt>"] = 0xFFEA
|
||||
special["<rightCtrl>"] = 0xFFE4
|
||||
special["<rightShift>"] = 0xFFE2
|
||||
special["<leftSuper>"] = 0xFFEB
|
||||
special["<rightSuper>"] = 0xFFEC
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
azOnRegex := regexp.MustCompile("^<(?P<ordinary>[a-zA-Z])On>")
|
||||
azOffRegex := regexp.MustCompile("^<(?P<ordinary>[a-zA-Z])Off>")
|
||||
|
||||
// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
|
||||
for len(original) > 0 {
|
||||
var keyCode uint32
|
||||
keyShift := false
|
||||
|
||||
if strings.HasPrefix(original, "<leftAltOn>") {
|
||||
keyCode = special["<leftAlt>"]
|
||||
original = original[len("<leftAltOn>"):]
|
||||
log.Printf("Special code '<leftAltOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftCtrlOn>") {
|
||||
keyCode = special["<leftCtrl>"]
|
||||
original = original[len("<leftCtrlOn>"):]
|
||||
log.Printf("Special code '<leftCtrlOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftShiftOn>") {
|
||||
keyCode = special["<leftShift>"]
|
||||
original = original[len("<leftShiftOn>"):]
|
||||
log.Printf("Special code '<leftShiftOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftSuperOn>") {
|
||||
keyCode = special["<leftSuper>"]
|
||||
original = original[len("<leftSuperOn>"):]
|
||||
log.Printf("Special code '<leftSuperOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if azOnRegex.MatchString(original) {
|
||||
m := azOnRegex.FindStringSubmatch(original)
|
||||
r, _ := utf8.DecodeRuneInString(m[1])
|
||||
original = original[len("<aOn>"):]
|
||||
keyCode = uint32(r)
|
||||
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||
|
||||
log.Printf("Special code '%s' found, replacing with %d, shift %v", m[0], keyCode, keyShift)
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(KeyLeftShift, true)
|
||||
}
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftAltOff>") {
|
||||
keyCode = special["<leftAlt>"]
|
||||
original = original[len("<leftAltOff>"):]
|
||||
log.Printf("Special code '<leftAltOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftCtrlOff>") {
|
||||
keyCode = special["<leftCtrl>"]
|
||||
original = original[len("<leftCtrlOff>"):]
|
||||
log.Printf("Special code '<leftCtrlOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftShiftOff>") {
|
||||
keyCode = special["<leftShift>"]
|
||||
original = original[len("<leftShiftOff>"):]
|
||||
log.Printf("Special code '<leftShiftOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<leftSuperOff>") {
|
||||
keyCode = special["<leftSuper>"]
|
||||
original = original[len("<leftSuperOff>"):]
|
||||
log.Printf("Special code '<leftSuperOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if azOffRegex.MatchString(original) {
|
||||
m := azOffRegex.FindStringSubmatch(original)
|
||||
r, _ := utf8.DecodeRuneInString(m[1])
|
||||
original = original[len("<aOff>"):]
|
||||
keyCode = uint32(r)
|
||||
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||
|
||||
log.Printf("Special code '%s' found, replacing with %d, shift %v", m[0], keyCode, keyShift)
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(KeyLeftShift, false)
|
||||
}
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightAltOn>") {
|
||||
keyCode = special["<rightAlt>"]
|
||||
original = original[len("<rightAltOn>"):]
|
||||
log.Printf("Special code '<rightAltOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightCtrlOn>") {
|
||||
keyCode = special["<rightCtrl>"]
|
||||
original = original[len("<rightCtrlOn>"):]
|
||||
log.Printf("Special code '<rightCtrlOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightShiftOn>") {
|
||||
keyCode = special["<rightShift>"]
|
||||
original = original[len("<rightShiftOn>"):]
|
||||
log.Printf("Special code '<rightShiftOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightSuperOn>") {
|
||||
keyCode = special["<rightSuper>"]
|
||||
original = original[len("<rightSuperOn>"):]
|
||||
log.Printf("Special code '<rightSuperOn>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightAltOff>") {
|
||||
keyCode = special["<rightAlt>"]
|
||||
original = original[len("<rightAltOff>"):]
|
||||
log.Printf("Special code '<rightAltOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightCtrlOff>") {
|
||||
keyCode = special["<rightCtrl>"]
|
||||
original = original[len("<rightCtrlOff>"):]
|
||||
log.Printf("Special code '<rightCtrlOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightShiftOff>") {
|
||||
keyCode = special["<rightShift>"]
|
||||
original = original[len("<rightShiftOff>"):]
|
||||
log.Printf("Special code '<rightShiftOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<rightSuperOff>") {
|
||||
keyCode = special["<rightSuper>"]
|
||||
original = original[len("<rightSuperOff>"):]
|
||||
log.Printf("Special code '<rightSuperOff>' found, replacing with: %d", keyCode)
|
||||
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait>") {
|
||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||
time.Sleep(1 * time.Second)
|
||||
original = original[len("<wait>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait5>") {
|
||||
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
original = original[len("<wait5>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait10>") {
|
||||
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
original = original[len("<wait10>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(original, specialCode) {
|
||||
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
|
||||
keyCode = specialValue
|
||||
original = original[len(specialCode):]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if keyCode == 0 {
|
||||
r, size := utf8.DecodeRuneInString(original)
|
||||
original = original[size:]
|
||||
keyCode = uint32(r)
|
||||
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||
|
||||
log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift)
|
||||
}
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(KeyLeftShift, true)
|
||||
}
|
||||
|
||||
c.KeyEvent(keyCode, true)
|
||||
time.Sleep(keyInterval)
|
||||
c.KeyEvent(keyCode, false)
|
||||
time.Sleep(keyInterval)
|
||||
|
||||
if keyShift {
|
||||
c.KeyEvent(KeyLeftShift, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -319,11 +319,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Format: b.config.Format,
|
||||
},
|
||||
&vmwcommon.StepRun{
|
||||
BootWait: b.config.BootWait,
|
||||
DurationBeforeStop: 5 * time.Second,
|
||||
Headless: b.config.Headless,
|
||||
},
|
||||
&vmwcommon.StepTypeBootCommand{
|
||||
BootWait: b.config.BootWait,
|
||||
VNCEnabled: !b.config.DisableVNC,
|
||||
BootCommand: b.config.BootCommand,
|
||||
VMName: b.config.VMName,
|
||||
|
|
|
@ -87,11 +87,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
VNCDisablePassword: b.config.VNCDisablePassword,
|
||||
},
|
||||
&vmwcommon.StepRun{
|
||||
BootWait: b.config.BootWait,
|
||||
DurationBeforeStop: 5 * time.Second,
|
||||
Headless: b.config.Headless,
|
||||
},
|
||||
&vmwcommon.StepTypeBootCommand{
|
||||
BootWait: b.config.BootWait,
|
||||
VNCEnabled: !b.config.DisableVNC,
|
||||
BootCommand: b.config.BootCommand,
|
||||
VMName: b.config.VMName,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
package bootcommand
|
||||
|
||||
|
||||
|
||||
/*
|
||||
func main() {
|
||||
in := "<wait><wait10><wait1s><wait1m2ns>"
|
||||
in += "foo/bar > one"
|
||||
in += "<fOn> b<fOff>"
|
||||
in += "<f3><f12><spacebar><leftalt><rightshift><rightsuper>"
|
||||
got, err := ParseReader("", strings.NewReader(in))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s\n", got)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Input <- expr:Expr EOF {
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
Expr <- l:( Wait / CharToggle / Special / Literal)+ {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
Wait = ExprStart "wait" duration:( Duration / Integer )? ExprEnd {
|
||||
var d time.Duration
|
||||
switch t := duration.(type) {
|
||||
case time.Duration:
|
||||
d = t
|
||||
case int64:
|
||||
d = time.Duration(t) * time.Second
|
||||
default:
|
||||
d = time.Second
|
||||
}
|
||||
return &waitExpression{d}, nil
|
||||
}
|
||||
|
||||
CharToggle = ExprStart lit:(Literal) t:(On / Off) ExprEnd {
|
||||
return &literal{lit.(*literal).s, t.(KeyAction)}, nil
|
||||
}
|
||||
|
||||
Special = ExprStart s:(SpecialKey) t:(On / Off)? ExprEnd {
|
||||
if t == nil {
|
||||
//return fmt.Sprintf("S(%s)", s), nil
|
||||
return &specialExpression{string(s.([]byte)), KeyPress}, nil
|
||||
}
|
||||
return &specialExpression{string(s.([]byte)), t.(KeyAction)}, nil
|
||||
//return fmt.Sprintf("S%s(%s)", t, s), nil
|
||||
}
|
||||
|
||||
Number = '-'? Integer ( '.' Digit+ )? {
|
||||
return string(c.text), nil
|
||||
}
|
||||
|
||||
Integer = '0' / NonZeroDigit Digit* {
|
||||
return strconv.ParseInt(string(c.text), 10, 64)
|
||||
}
|
||||
|
||||
Duration = ( Number TimeUnit )+ {
|
||||
return time.ParseDuration(string(c.text))
|
||||
}
|
||||
|
||||
On = "on"i {
|
||||
return KeyOn, nil
|
||||
}
|
||||
|
||||
Off = "off"i {
|
||||
return KeyOff, nil
|
||||
}
|
||||
|
||||
Literal = . {
|
||||
r, _ := utf8.DecodeRune(c.text)
|
||||
return &literal{r, KeyPress}, nil
|
||||
}
|
||||
|
||||
ExprEnd = ">"
|
||||
ExprStart = "<"
|
||||
SpecialKey = "bs"i / "del"i / "enter"i / "esc"i / "f10"i / "f11"i / "f12"i
|
||||
/ "f1"i / "f2"i / "f3"i / "f4"i / "f5"i / "f6"i / "f7"i / "f8"i / "f9"i
|
||||
/ "return"i / "tab"i / "up"i / "down"i / "spacebar"i / "insert"i / "home"i
|
||||
/ "end"i / "pageUp"i / "pageDown"i / "leftAlt"i / "leftCtrl"i / "leftShift"i
|
||||
/ "rightAlt"i / "rightCtrl"i / "rightShift"i / "leftSuper"i / "rightSuper"i
|
||||
/ "left"i / "right"i
|
||||
|
||||
NonZeroDigit = [1-9]
|
||||
Digit = [0-9]
|
||||
TimeUnit = ("ns" / "us" / "µs" / "ms" / "s" / "m" / "h")
|
||||
|
||||
_ "whitespace" <- [ \n\t\r]*
|
||||
|
||||
EOF <- !.
|
|
@ -0,0 +1,130 @@
|
|||
package bootcommand
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
TODO:
|
||||
* tests
|
||||
* comments
|
||||
*/
|
||||
|
||||
// Keys actions can take 3 states
|
||||
// we either want to
|
||||
// * press the key once
|
||||
// * press and hold
|
||||
// * press and release
|
||||
|
||||
type KeyAction int
|
||||
|
||||
const (
|
||||
KeyOn KeyAction = iota
|
||||
KeyOff
|
||||
KeyPress
|
||||
)
|
||||
|
||||
func onOffToAction(t string) KeyAction {
|
||||
if strings.EqualFold(t, "on") {
|
||||
return KeyOn
|
||||
} else if strings.EqualFold(t, "off") {
|
||||
return KeyOff
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown state '%s'. Expecting On or Off.", t))
|
||||
}
|
||||
|
||||
func (k KeyAction) String() string {
|
||||
switch k {
|
||||
case KeyOn:
|
||||
return "On"
|
||||
case KeyOff:
|
||||
return "Off"
|
||||
case KeyPress:
|
||||
return "Press"
|
||||
}
|
||||
panic(fmt.Sprintf("Unknwon KeyAction %d", k))
|
||||
}
|
||||
|
||||
// BCDriver is our access to the VM we want to type boot commands to
|
||||
type BCDriver interface {
|
||||
SendKey(key rune, action KeyAction) error
|
||||
SendSpecial(special string, action KeyAction) error
|
||||
}
|
||||
|
||||
type expression interface {
|
||||
Do(context.Context, BCDriver) error
|
||||
}
|
||||
|
||||
type expressionSequence []expression
|
||||
|
||||
func (s expressionSequence) Do(ctx context.Context, b BCDriver) error {
|
||||
for _, exp := range s {
|
||||
if err := exp.Do(ctx, b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateExpressionSequence(command string) (expressionSequence, error) {
|
||||
got, err := ParseReader("", strings.NewReader(command))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if got == nil {
|
||||
return nil, fmt.Errorf("No expressions found.")
|
||||
}
|
||||
seq := expressionSequence{}
|
||||
for _, exp := range got.([]interface{}) {
|
||||
seq = append(seq, exp.(expression))
|
||||
}
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
type waitExpression struct {
|
||||
d time.Duration
|
||||
}
|
||||
|
||||
func (w *waitExpression) Do(ctx context.Context, _ BCDriver) error {
|
||||
log.Printf("[INFO] Waiting %s", w.d)
|
||||
select {
|
||||
case <-time.After(w.d):
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *waitExpression) String() string {
|
||||
return fmt.Sprintf("Wait<%s>", w.d)
|
||||
}
|
||||
|
||||
type specialExpression struct {
|
||||
s string
|
||||
action KeyAction
|
||||
}
|
||||
|
||||
func (s *specialExpression) Do(ctx context.Context, driver BCDriver) error {
|
||||
return driver.SendSpecial(s.s, s.action)
|
||||
}
|
||||
|
||||
func (s *specialExpression) String() string {
|
||||
return fmt.Sprintf("Spec-%s(%s)", s.action, s.s)
|
||||
}
|
||||
|
||||
type literal struct {
|
||||
s rune
|
||||
action KeyAction
|
||||
}
|
||||
|
||||
func (l *literal) Do(ctx context.Context, driver BCDriver) error {
|
||||
return driver.SendKey(l.s, l.action)
|
||||
}
|
||||
|
||||
func (l *literal) String() string {
|
||||
return fmt.Sprintf("LIT-%s(%s)", l.action, string(l.s))
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package bootcommand
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func toIfaceSlice(v interface{}) []interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return v.([]interface{})
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
in := "<wait><wait20><wait3s><wait4m2ns>"
|
||||
in += "foo/bar > one 界"
|
||||
in += "<fOn> b<fOff>"
|
||||
in += "<f3><f12><spacebar><leftalt><rightshift><rightsuper>"
|
||||
got, err := ParseReader("", strings.NewReader(in))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
gL := toIfaceSlice(got)
|
||||
for _, g := range gL {
|
||||
log.Printf("%s\n", g)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package bootcommand
|
||||
|
||||
//go:generate pigeon -o boot_command.go boot_command.pigeon
|
|
@ -0,0 +1,135 @@
|
|||
package bootcommand
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
vnc "github.com/mitchellh/go-vnc"
|
||||
)
|
||||
|
||||
const shiftedChars = "~!@#$%^&*()_+{}|:\"<>?"
|
||||
const KeyLeftShift uint32 = 0xFFE1
|
||||
|
||||
func NewVNCDriver(c *vnc.ClientConn) *bcDriver {
|
||||
// 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
|
||||
}
|
||||
|
||||
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
|
||||
sMap := make(map[string]uint32)
|
||||
sMap["bs"] = 0xFF08
|
||||
sMap["del"] = 0xFFFF
|
||||
sMap["enter"] = 0xFF0D
|
||||
sMap["esc"] = 0xFF1B
|
||||
sMap["f1"] = 0xFFBE
|
||||
sMap["f2"] = 0xFFBF
|
||||
sMap["f3"] = 0xFFC0
|
||||
sMap["f4"] = 0xFFC1
|
||||
sMap["f5"] = 0xFFC2
|
||||
sMap["f6"] = 0xFFC3
|
||||
sMap["f7"] = 0xFFC4
|
||||
sMap["f8"] = 0xFFC5
|
||||
sMap["f9"] = 0xFFC6
|
||||
sMap["f10"] = 0xFFC7
|
||||
sMap["f11"] = 0xFFC8
|
||||
sMap["f12"] = 0xFFC9
|
||||
sMap["return"] = 0xFF0D
|
||||
sMap["tab"] = 0xFF09
|
||||
sMap["up"] = 0xFF52
|
||||
sMap["down"] = 0xFF54
|
||||
sMap["left"] = 0xFF51
|
||||
sMap["right"] = 0xFF53
|
||||
sMap["spacebar"] = 0x020
|
||||
sMap["insert"] = 0xFF63
|
||||
sMap["home"] = 0xFF50
|
||||
sMap["end"] = 0xFF57
|
||||
sMap["pageUp"] = 0xFF55
|
||||
sMap["pageDown"] = 0xFF56
|
||||
sMap["leftAlt"] = 0xFFE9
|
||||
sMap["leftCtrl"] = 0xFFE3
|
||||
sMap["leftShift"] = 0xFFE1
|
||||
sMap["rightAlt"] = 0xFFEA
|
||||
sMap["rightCtrl"] = 0xFFE4
|
||||
sMap["rightShift"] = 0xFFE2
|
||||
sMap["leftSuper"] = 0xFFEB
|
||||
sMap["rightSuper"] = 0xFFEC
|
||||
|
||||
return &bcDriver{
|
||||
c: c,
|
||||
interval: keyInterval,
|
||||
specialMap: sMap,
|
||||
}
|
||||
}
|
||||
|
||||
type bcDriver struct {
|
||||
c *vnc.ClientConn
|
||||
interval time.Duration
|
||||
specialMap map[string]uint32
|
||||
// keyEvent can set this error which will prevent it from continuing
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *bcDriver) keyEvent(k uint32, down bool) error {
|
||||
if d.err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := d.c.KeyEvent(k, down); err != nil {
|
||||
d.err = err
|
||||
return err
|
||||
}
|
||||
time.Sleep(d.interval)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *bcDriver) SendKey(key rune, action KeyAction) error {
|
||||
keyShift := unicode.IsUpper(key) || strings.ContainsRune(shiftedChars, key)
|
||||
keyCode := uint32(key)
|
||||
log.Printf("Sending char '%c', code 0x%X, shift %v", key, keyCode, keyShift)
|
||||
|
||||
switch action {
|
||||
case KeyOn:
|
||||
if keyShift {
|
||||
d.keyEvent(KeyLeftShift, true)
|
||||
}
|
||||
d.keyEvent(keyCode, true)
|
||||
case KeyOff:
|
||||
if keyShift {
|
||||
d.keyEvent(KeyLeftShift, false)
|
||||
}
|
||||
d.keyEvent(keyCode, false)
|
||||
case KeyPress:
|
||||
if keyShift {
|
||||
d.keyEvent(KeyLeftShift, true)
|
||||
}
|
||||
d.keyEvent(keyCode, true)
|
||||
d.keyEvent(keyCode, false)
|
||||
if keyShift {
|
||||
d.keyEvent(KeyLeftShift, false)
|
||||
}
|
||||
}
|
||||
return d.err
|
||||
}
|
||||
|
||||
func (d *bcDriver) SendSpecial(special string, action KeyAction) error {
|
||||
keyCode := d.specialMap[special]
|
||||
log.Printf("Special code '<%s>' found, replacing with: 0x%X", special, keyCode)
|
||||
|
||||
switch action {
|
||||
case KeyOn:
|
||||
d.keyEvent(keyCode, true)
|
||||
case KeyOff:
|
||||
d.keyEvent(keyCode, false)
|
||||
case KeyPress:
|
||||
d.keyEvent(keyCode, true)
|
||||
d.keyEvent(keyCode, false)
|
||||
}
|
||||
|
||||
return d.err
|
||||
}
|
Loading…
Reference in New Issue