package iso import ( "context" "fmt" "log" "net" "os" "strings" "time" "unicode/utf8" "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" ) 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{ "": key.CodeReturnEnter, "": key.CodeEscape, "": key.CodeDeleteBackspace, "": key.CodeDeleteForward, "": key.CodeTab, "": key.CodeF1, "": key.CodeF2, "": key.CodeF3, "": key.CodeF4, "": key.CodeF5, "": key.CodeF6, "": key.CodeF7, "": key.CodeF8, "": key.CodeF9, "": key.CodeF10, "": key.CodeF11, "": key.CodeF12, "": key.CodeInsert, "": key.CodeHome, "": key.CodeEnd, "": key.CodePageUp, "": key.CodePageDown, "": key.CodeLeftArrow, "": key.CodeRightArrow, "": key.CodeUpArrow, "": 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 } state.Put("http_ip", ip) 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, "") { log.Printf("Waiting 1 second") time.Sleep(1 * time.Second) message = message[len(""):] continue } if strings.HasPrefix(message, "") { log.Printf("Waiting 5 seconds") time.Sleep(5 * time.Second) message = message[len(""):] continue } if strings.HasPrefix(message, "") { log.Printf("Waiting 10 seconds") time.Sleep(10 * time.Second) message = message[len(""):] continue } if strings.HasPrefix(message, "") { keyAlt = true message = message[len(""):] continue } if strings.HasPrefix(message, "") { keyAlt = false message = message[len(""):] continue } if strings.HasPrefix(message, "") { keyCtrl = true message = message[len(""):] continue } if strings.HasPrefix(message, "") { keyCtrl = false message = message[len(""):] continue } if strings.HasPrefix(message, "") { keyShift = true message = message[len(""):] continue } if strings.HasPrefix(message, "") { keyShift = false message = message[len(""):] 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") }