Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Boot command #53

Merged
merged 11 commits into from
Feb 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions driver/vm_keyboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package driver

import (
"strings"
"unicode"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/vim25/methods"
"golang.org/x/mobile/event/key"
)

type KeyInput struct {
Message string
Scancode key.Code
Alt bool
Ctrl bool
Shift bool
}

var scancodeMap = make(map[rune]key.Code)

func init() {
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

for chars, start := range scancodeIndex {
for i, r := range chars {
scancodeMap[r] = start + key.Code(i)
}
}
}

const shiftedChars = "!@#$%^&*()_+{}|:\"~<>?"

func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) {
var spec types.UsbScanCodeSpec

for _, r := range input.Message {
scancode := scancodeMap[r]
shift := input.Shift || unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)

spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
// https://github.com/lamw/vghetto-scripts/blob/f74bc8ba20064f46592bcce5a873b161a7fa3d72/powershell/VMKeystrokes.ps1#L130
UsbHidCode: int32(scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &shift,
},
})
}

if input.Scancode != 0 {
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
UsbHidCode: int32(input.Scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &input.Shift,
},
})
}

req := &types.PutUsbScanCodes{
This: vm.vm.Reference(),
Spec: spec,
}

resp, err := methods.PutUsbScanCodes(vm.driver.ctx, vm.driver.client.RoundTripper, req)
if err != nil {
return 0, err
}

return resp.Returnval, nil
}
16 changes: 16 additions & 0 deletions examples/ubuntu/preseed.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
d-i passwd/user-fullname string jetbrains
d-i passwd/username string jetbrains
d-i passwd/user-password password jetbrains
d-i passwd/user-password-again password jetbrains
d-i user-setup/allow-password-weak boolean true

d-i partman-auto/disk string /dev/sda
d-i partman-auto/method string regular
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true

d-i pkgsel/include string open-vm-tools openssh-server

d-i finish-install/reboot_in_progress note
63 changes: 63 additions & 0 deletions examples/ubuntu/ubuntu-16.04.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"builders": [
{
"type": "vsphere-iso",

"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",

"vm_name": "example-ubuntu",
"host": "esxi-1.vsphere65.test",

"guest_os_type": "ubuntu64Guest",

"ssh_username": "jetbrains",
"ssh_password": "jetbrains",

"CPUs": 1,
"RAM": 1024,
"RAM_reserve_all": true,

"disk_controller_type": "pvscsi",
"disk_size": 32,
"disk_thin_provisioned": true,

"network_card": "vmxnet3",

"iso_paths": [
"[datastore1] ISO/ubuntu-16.04.3-server-amd64.iso"
],
"floppy_files": [
"{{template_dir}}/preseed.cfg"
],
"boot_command": [
"<enter><wait><f6><esc><wait>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs>",
"/install/vmlinuz",
" initrd=/install/initrd.gz",
" priority=critical",
" locale=en_US",
" file=/media/preseed.cfg",
"<enter>"
],
"boot_order": "disk,cdrom"
}
],

"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
}
]
}
9 changes: 7 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ import:
- package: github.com/vmware/govmomi
version: v0.15.0
- package: golang.org/x/crypto
- package: golang.org/x/mobile
3 changes: 3 additions & 0 deletions iso/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepRun{
Config: &b.config.RunConfig,
},
&StepBootCommand{
Config: &b.config.BootConfig,
},
&common.StepWaitForIp{},
&communicator.StepConnect{
Config: &b.config.Comm,
Expand Down
1 change: 1 addition & 0 deletions iso/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
BootConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
Expand Down
173 changes: 173 additions & 0 deletions iso/step_boot_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package iso

import (
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"github.com/mitchellh/multistep"
"fmt"
"time"
"strings"
"golang.org/x/mobile/event/key"
"unicode/utf8"
"github.com/hashicorp/packer/common"
"os"
"log"
)

type BootConfig struct {
BootCommand []string `mapstructure:"boot_command"`
}

func (c *BootConfig) Prepare() []error {
return nil
}

type StepBootCommand struct {
Config *BootConfig
}

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 = common.PackerKeyDefault

func init() {
if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil {
keyInterval = delay
}
}

func (s *StepBootCommand) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)

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, "<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) {}