package iso import ( "context" "fmt" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/jetbrains-infra/packer-builder-vsphere/driver" "golang.org/x/mobile/event/key" "log" "os" "strings" "time" "unicode/utf8" ) type BootConfig struct { BootCommand []string `mapstructure:"boot_command"` RawBootWait string `mapstructure:"boot_wait"` // example: "1m30s"; default: "10s" bootWait time.Duration } func (c *BootConfig) Prepare() []error { var errs []error if c.RawBootWait == "" { c.RawBootWait = "10s" } var err error c.bootWait, err = time.ParseDuration(c.RawBootWait) if err != nil { errs = append(errs, fmt.Errorf("failed parsing boot_wait: %s", err)) } return errs } type StepBootCommand struct { Config *BootConfig } 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 = common.PackerKeyDefault func init() { if delay, err := time.ParseDuration(os.Getenv(common.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 } } } ui.Say("Typing boot command...") var keyAlt bool var keyCtrl bool var keyShift bool for _, message := range s.Config.BootCommand { 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) {}