Skip to content
Open
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
4 changes: 3 additions & 1 deletion builder/xenserver/common/common_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import (
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
xenapi "github.com/terra-farm/go-xen-api-client"
)

type CommonConfig struct {
bootcommand.VNCConfig `mapstructure:",squash"`

Username string `mapstructure:"remote_username"`
Password string `mapstructure:"remote_password"`
HostIp string `mapstructure:"remote_host"`
Expand All @@ -28,7 +31,6 @@ type CommonConfig struct {
HostPortMin uint `mapstructure:"host_port_min"`
HostPortMax uint `mapstructure:"host_port_max"`

BootCommand []string `mapstructure:"boot_command"`
ShutdownCommand string `mapstructure:"shutdown_command"`

RawBootWait string `mapstructure:"boot_wait"`
Expand Down
2 changes: 2 additions & 0 deletions builder/xenserver/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type Config struct {
RawInstallTimeout string `mapstructure:"install_timeout"`
InstallTimeout time.Duration ``
SourcePath string `mapstructure:"source_path"`

Firmware string `mapstructure:"firmware"`

ctx interpolate.Context
}
Expand Down
2 changes: 2 additions & 0 deletions builder/xenserver/common/config.hcl2spec.go

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

3 changes: 2 additions & 1 deletion builder/xenserver/common/step_start_vm_paused.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func (self *StepStartVmPaused) Run(ctx context.Context, state multistep.StateBag

c := state.Get("client").(*Connection)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(Config)

ui.Say("Step: Start VM Paused")

Expand All @@ -34,7 +35,7 @@ func (self *StepStartVmPaused) Run(ctx context.Context, state multistep.StateBag
return multistep.ActionHalt
}

err = c.client.VM.SetHVMBootParams(c.session, instance, map[string]string{"order": "cd"})
err = c.client.VM.SetHVMBootParams(c.session, instance, map[string]string{"order": "cd", "firmware": config.Firmware})
if err != nil {
ui.Error(fmt.Sprintf("Unable to set HVM boot params: %s", err.Error()))
return multistep.ActionHalt
Expand Down
185 changes: 59 additions & 126 deletions builder/xenserver/common/step_type_boot_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import (
"log"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the /* Heavily borrowed from builder/quemu/step_type_boot_command.go */ comment at the top of this file? That shouldn't be the case now that we are using the bootcommand package :)

"net"
"strings"
"time"
"unicode"
"unicode/utf8"

"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
"github.com/mitchellh/go-vnc"
)

Expand Down Expand Up @@ -105,18 +103,38 @@ func (self *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateB
return multistep.ActionHalt
}

buffer := make([]byte, 10000)
_, err = tlsConn.Read(buffer)
if err != nil && err != io.EOF {
err := fmt.Errorf("failed to read vnc session response: %v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Look for \r\n\r\n sequence. Everything after the HTTP Header is for the vnc client.

builder := strings.Builder{}
buffer := make([]byte, 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment elsewhere with a git diff that shows how to avoid the unbuffered reading without consuming the buffer. Please give that a try.

sequenceProgress := 0

for {
if _, err := io.ReadFull(tlsConn, buffer); err != nil {
err := fmt.Errorf("failed to start vnc session: %v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

ui.Say(fmt.Sprintf("Received response: %s", string(buffer)))
builder.WriteByte(buffer[0])

if buffer[0] == '\n' && sequenceProgress % 2 == 1 {
sequenceProgress++
} else if buffer[0] == '\r' && sequenceProgress % 2 == 0 {
sequenceProgress++
} else {
sequenceProgress = 0
}

if sequenceProgress == 4 {
break
}
}

ui.Say(fmt.Sprintf("Received response: %s", builder.String()))

vncClient, err := vnc.Client(tlsConn, &vnc.ClientConfig{Exclusive: true})
vncClient, err := vnc.Client(tlsConn, &vnc.ClientConfig{Exclusive: false})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you notice any bad behavior with the previous setting?


if err != nil {
err := fmt.Errorf("Error establishing VNC session: %s", err)
Expand All @@ -126,6 +144,7 @@ func (self *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateB
}

defer vncClient.Close()


log.Printf("Connected to the VNC console: %s", vncClient.DesktopName)

Expand All @@ -148,125 +167,39 @@ func (self *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateB
uint(httpPort),
}

ui.Say("Typing boot commands over VNC...")
for _, command := range config.BootCommand {

command, err := interpolate.Render(command, &self.Ctx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

// Check for interrupts
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
vncDriver := bootcommand.NewVNCDriver(vncClient, config.VNCConfig.BootKeyInterval)

vncSendString(vncClient, command)
ui.Say("Typing boot commands over VNC...")

command, err := interpolate.Render(config.VNCConfig.FlatBootCommand(), &self.Ctx)

if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

seq, err := bootcommand.GenerateExpressionSequence(command)

if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

if err := seq.Do(ctx, vncDriver); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}


ui.Say("Finished typing.")

return multistep.ActionContinue
}

func (self *StepTypeBootCommand) Cleanup(multistep.StateBag) {}

// Taken from qemu's builder plugin - not an exported function.
func vncSendString(c *vnc.ClientConn, original string) {
// Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h
special := make(map[string]uint32)
special["<bs>"] = 0xFF08
special["<del>"] = 0xFFFF
special["<enter>"] = 0xFF0D
special["<esc>"] = 0xFF1B
special["<f1>"] = 0xFFBE
special["<f2>"] = 0xFFBF
special["<f3>"] = 0xFFC0
special["<f4>"] = 0xFFC1
special["<f5>"] = 0xFFC2
special["<f6>"] = 0xFFC3
special["<f7>"] = 0xFFC4
special["<f8>"] = 0xFFC5
special["<f9>"] = 0xFFC6
special["<f10>"] = 0xFFC7
special["<f11>"] = 0xFFC8
special["<f12>"] = 0xFFC9
special["<return>"] = 0xFF0D
special["<tab>"] = 0xFF09
special["<up>"] = 0xFF52
special["<down>"] = 0xFF54
special["<left>"] = 0xFF51
special["<right>"] = 0xFF53
special["<spacebar>"] = 0x020
special["<insert>"] = 0xFF63
special["<home>"] = 0xFF50
special["<end>"] = 0xFF57
special["<pageUp>"] = 0xFF55
special["<pageDown>"] = 0xFF56

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

// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
for len(original) > 0 {
var keyCode uint32
keyShift := false

if strings.HasPrefix(original, "<wait>") {
log.Printf("Special code '<wait>' found, sleeping one second")
time.Sleep(1 * time.Second)
original = original[len("<wait>"):]
continue
}

if strings.HasPrefix(original, "<wait5>") {
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
time.Sleep(5 * time.Second)
original = original[len("<wait5>"):]
continue
}

if strings.HasPrefix(original, "<wait10>") {
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
time.Sleep(10 * time.Second)
original = original[len("<wait10>"):]
continue
}

for specialCode, specialValue := range special {
if strings.HasPrefix(original, specialCode) {
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
keyCode = specialValue
original = original[len(specialCode):]
break
}
}

if keyCode == 0 {
r, size := utf8.DecodeRuneInString(original)
original = original[size:]
keyCode = uint32(r)
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)

log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift)
}

if keyShift {
c.KeyEvent(uint32(KeyLeftShift), true)
}

c.KeyEvent(keyCode, true)
time.Sleep(time.Second / 10)
c.KeyEvent(keyCode, false)
time.Sleep(time.Second / 10)

if keyShift {
c.KeyEvent(uint32(KeyLeftShift), false)
}

// no matter what, wait a small period
time.Sleep(50 * time.Millisecond)
}
}
4 changes: 4 additions & 0 deletions builder/xenserver/iso/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri
if self.config.CloneTemplate == "" {
self.config.CloneTemplate = "Other install media"
}

if self.config.Firmware == "" {
self.config.Firmware = "bios"
}

if len(self.config.PlatformArgs) == 0 {
pargs := make(map[string]string)
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ=
golang.org/x/mobile v0.0.0-20201208152944-da85bec010a2 h1:3HADozU50HyrJ2jklLtr3xr0itFkz9u4LxCJhqKVdjI=
golang.org/x/mobile v0.0.0-20201208152944-da85bec010a2/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
Expand Down