mirror of https://github.com/hashicorp/packer
Merge pull request #9406 from hashicorp/fix_9084
Add usb_driver to common boot_command and use it on vspherepull/9429/head
commit
004ace4340
@ -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")
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
package bootcommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"golang.org/x/mobile/event/key"
|
||||
)
|
||||
|
||||
// SendUsbScanCodes will be called to send codes to the VM
|
||||
type SendUsbScanCodes func(k key.Code, down bool) error
|
||||
|
||||
type usbDriver struct {
|
||||
sendImpl SendUsbScanCodes
|
||||
interval time.Duration
|
||||
specialMap map[string]key.Code
|
||||
scancodeMap map[rune]key.Code
|
||||
}
|
||||
|
||||
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,
|
||||
"return": 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,
|
||||
"leftsuper": key.CodeLeftGUI,
|
||||
"rightsuper": key.CodeRightGUI,
|
||||
"spacebar": key.CodeSpacebar,
|
||||
}
|
||||
|
||||
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 err := d.sendImpl(k, down); err != nil {
|
||||
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)
|
||||
return d.keyEvent(keyCode, keyShift)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
log.Printf("Special code '<%s>' found, replacing with: %s", special, keyCode)
|
||||
|
||||
switch action {
|
||||
case KeyOn:
|
||||
err = d.keyEvent(keyCode, true)
|
||||
case KeyOff, KeyPress:
|
||||
err = d.keyEvent(keyCode, false)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
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
|
||||
}{
|
||||
{
|
||||
"<leftShift>",
|
||||
key.CodeLeftShift,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"<leftShiftOff>",
|
||||
key.CodeLeftShift,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"<leftShiftOn>",
|
||||
key.CodeLeftShift,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"<leftsuper>",
|
||||
key.CodeLeftGUI,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"<spacebar>",
|
||||
key.CodeSpacebar,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"<return>",
|
||||
key.CodeReturnEnter,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"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")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue