261 lines
5.8 KiB
Go
261 lines
5.8 KiB
Go
package iso
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/hashicorp/packer/builder/vsphere/driver"
|
|
packerCommon "github.com/hashicorp/packer/common"
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/template/interpolate"
|
|
"golang.org/x/mobile/event/key"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type BootConfig struct {
|
|
BootCommand []string `mapstructure:"boot_command"`
|
|
BootWait time.Duration `mapstructure:"boot_wait"` // example: "1m30s"; default: "10s"
|
|
HTTPIP string `mapstructure:"http_ip"`
|
|
}
|
|
|
|
type bootCommandTemplateData struct {
|
|
HTTPIP string
|
|
HTTPPort int
|
|
Name string
|
|
}
|
|
|
|
func (c *BootConfig) Prepare() []error {
|
|
var errs []error
|
|
|
|
if c.BootWait == 0 {
|
|
c.BootWait = 10 * time.Second
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
type StepBootCommand struct {
|
|
Config *BootConfig
|
|
VMName string
|
|
Ctx interpolate.Context
|
|
}
|
|
|
|
var special = map[string]key.Code{
|
|
"<enter>": 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,
|
|
}
|
|
|
|
var keyInterval = packerCommon.PackerKeyDefault
|
|
|
|
func init() {
|
|
if delay, err := time.ParseDuration(os.Getenv(packerCommon.PackerKeyEnv)); err == nil {
|
|
keyInterval = delay
|
|
}
|
|
}
|
|
|
|
func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
|
ui := state.Get("ui").(packer.Ui)
|
|
vm := state.Get("vm").(*driver.VirtualMachine)
|
|
|
|
if s.Config.BootCommand == nil {
|
|
return multistep.ActionContinue
|
|
}
|
|
|
|
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait))
|
|
wait := time.After(s.Config.BootWait)
|
|
WAITLOOP:
|
|
for {
|
|
select {
|
|
case <-wait:
|
|
break WAITLOOP
|
|
case <-time.After(1 * time.Second):
|
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
return multistep.ActionHalt
|
|
}
|
|
}
|
|
}
|
|
|
|
port := state.Get("http_port").(int)
|
|
if port > 0 {
|
|
ip, err := getHostIP(s.Config.HTTPIP)
|
|
if err != nil {
|
|
state.Put("error", err)
|
|
return multistep.ActionHalt
|
|
}
|
|
err = packerCommon.SetHTTPIP(ip)
|
|
if err != nil {
|
|
state.Put("error", err)
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
s.Ctx.Data = &bootCommandTemplateData{
|
|
ip,
|
|
port,
|
|
s.VMName,
|
|
}
|
|
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
|
|
}
|
|
|
|
ui.Say("Typing boot command...")
|
|
var keyAlt bool
|
|
var keyCtrl bool
|
|
var keyShift bool
|
|
for _, command := range s.Config.BootCommand {
|
|
message, err := interpolate.Render(command, &s.Ctx)
|
|
if err != nil {
|
|
state.Put("error", err)
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
for len(message) > 0 {
|
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<wait>") {
|
|
log.Printf("Waiting 1 second")
|
|
time.Sleep(1 * time.Second)
|
|
message = message[len("<wait>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<wait5>") {
|
|
log.Printf("Waiting 5 seconds")
|
|
time.Sleep(5 * time.Second)
|
|
message = message[len("<wait5>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<wait10>") {
|
|
log.Printf("Waiting 10 seconds")
|
|
time.Sleep(10 * time.Second)
|
|
message = message[len("<wait10>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<leftAltOn>") {
|
|
keyAlt = true
|
|
message = message[len("<leftAltOn>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<leftAltOff>") {
|
|
keyAlt = false
|
|
message = message[len("<leftAltOff>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<leftCtrlOn>") {
|
|
keyCtrl = true
|
|
message = message[len("<leftCtrlOn>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<leftCtrlOff>") {
|
|
keyCtrl = false
|
|
message = message[len("<leftCtrlOff>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<leftShiftOn>") {
|
|
keyShift = true
|
|
message = message[len("<leftShiftOn>"):]
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(message, "<leftShiftOff>") {
|
|
keyShift = false
|
|
message = message[len("<leftShiftOff>"):]
|
|
continue
|
|
}
|
|
|
|
var scancode key.Code
|
|
for specialCode, specialValue := range special {
|
|
if strings.HasPrefix(message, specialCode) {
|
|
scancode = specialValue
|
|
log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue)
|
|
message = message[len(specialCode):]
|
|
}
|
|
}
|
|
|
|
var char rune
|
|
if scancode == 0 {
|
|
var size int
|
|
char, size = utf8.DecodeRuneInString(message)
|
|
message = message[size:]
|
|
}
|
|
|
|
_, err := vm.TypeOnKeyboard(driver.KeyInput{
|
|
Message: string(char),
|
|
Scancode: scancode,
|
|
Ctrl: keyCtrl,
|
|
Alt: keyAlt,
|
|
Shift: keyShift,
|
|
})
|
|
if err != nil {
|
|
state.Put("error", fmt.Errorf("error typing a boot command: %v", err))
|
|
return multistep.ActionHalt
|
|
}
|
|
time.Sleep(keyInterval)
|
|
}
|
|
}
|
|
|
|
return multistep.ActionContinue
|
|
}
|
|
|
|
func (s *StepBootCommand) 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")
|
|
}
|