Add VNC Password support to QEMU builder
This commit is contained in:
parent
afe9ba2869
commit
5c5943b8ba
|
@ -126,6 +126,7 @@ type Config struct {
|
|||
VNCBindAddress string `mapstructure:"vnc_bind_address"`
|
||||
VNCPortMin int `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax int `mapstructure:"vnc_port_max"`
|
||||
VNCUsePassword bool `mapstructure:"vnc_use_password"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
|
||||
// These are deprecated, but we keep them around for BC
|
||||
|
@ -357,6 +358,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
}
|
||||
|
||||
if b.config.VNCUsePassword && !b.config.QMPEnable {
|
||||
b.config.QMPEnable = true
|
||||
}
|
||||
|
||||
if b.config.QMPEnable && b.config.QMPSocketPath == "" {
|
||||
socketName := fmt.Sprintf("%s.monitor", b.config.VMName)
|
||||
b.config.QMPSocketPath = filepath.Join(b.config.OutputDir, socketName)
|
||||
|
|
|
@ -113,6 +113,25 @@ func TestBuilderPrepare_VNCBindAddress(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCPassword(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test a default boot_wait
|
||||
config["vnc_use_password"] = true
|
||||
warns, err := b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !b.config.QMPEnable {
|
||||
t.Fatalf("QMP should be enabled.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_DiskCompaction(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
|
|
@ -30,27 +30,63 @@ func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Opening QMP socket at: %s", config.QMPSocketPath)
|
||||
msg := fmt.Sprintf("QMP socket at: %s", config.QMPSocketPath)
|
||||
ui.Say(msg)
|
||||
log.Print(msg)
|
||||
|
||||
// Open QMP socket
|
||||
var err error
|
||||
s.monitor, err = qmp.NewSocketMonitor("unix", config.QMPSocketPath, 2*time.Second)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error opening QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
// Only initialize and open QMP when we have a use for it.
|
||||
// Handles cases where user may want the socket, but we don't
|
||||
if config.VNCUsePassword {
|
||||
// Open QMP socket
|
||||
var err error
|
||||
var cmd []byte
|
||||
var result []byte
|
||||
s.monitor, err = qmp.NewSocketMonitor("unix", config.QMPSocketPath, 2*time.Second)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error opening QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
QMPMonitor := s.monitor
|
||||
vncPassword := state.Get("vnc_password")
|
||||
|
||||
// Connect to QMP
|
||||
// function automatically calls capabilities so is immediately ready for commands
|
||||
err = QMPMonitor.Connect()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("QMP socket open SUCCESS")
|
||||
|
||||
cmd = []byte(fmt.Sprintf("{ \"execute\": \"change-vnc-password\", \"arguments\": { \"password\": \"%s\" } }",
|
||||
vncPassword))
|
||||
result, err = QMPMonitor.Run(cmd)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
msg = fmt.Sprintf("QMP Command: %s\nResult: %s", cmd, result)
|
||||
log.Printf(msg)
|
||||
|
||||
// Put QMP monitor in statebag in case there is a use in a following step
|
||||
// Uncomment for future case as it is unused for now
|
||||
//state.Put("qmp_monitor", QMPMonitor)
|
||||
}
|
||||
QMPMonitor := s.monitor
|
||||
|
||||
log.Printf("QMP socket open SUCCESS")
|
||||
|
||||
state.Put("qmp_monitor", QMPMonitor)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigureQMP) Cleanup(multistep.StateBag) {
|
||||
if s.monitor != nil {
|
||||
err := s.monitor.Disconnect()
|
||||
if err != nil {
|
||||
log.Printf("failed to disconnect QMP: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"github.com/hashicorp/packer/common/net"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -22,6 +23,21 @@ type stepConfigureVNC struct {
|
|||
l *net.Listener
|
||||
}
|
||||
|
||||
func VNCPassword() string {
|
||||
length := int(8)
|
||||
|
||||
charSet := []byte("012345689abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
charSetLength := len(charSet)
|
||||
|
||||
password := make([]byte, length)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
password[i] = charSet[rand.Intn(charSetLength)]
|
||||
}
|
||||
|
||||
return string(password)
|
||||
}
|
||||
|
||||
func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
@ -33,6 +49,7 @@ func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
ui.Say(msg)
|
||||
log.Print(msg)
|
||||
|
||||
var vncPassword string
|
||||
var err error
|
||||
s.l, err = net.ListenRangeConfig{
|
||||
Addr: config.VNCBindAddress,
|
||||
|
@ -41,7 +58,7 @@ func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
Network: "tcp",
|
||||
}.Listen(ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error finding port: %s", err)
|
||||
err := fmt.Errorf("Error finding VNC port: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
|
@ -49,9 +66,15 @@ func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
s.l.Listener.Close() // free port, but don't unlock lock file
|
||||
vncPort := s.l.Port
|
||||
|
||||
if config.VNCUsePassword {
|
||||
vncPassword = VNCPassword()
|
||||
} else {
|
||||
vncPassword = ""
|
||||
}
|
||||
|
||||
log.Printf("Found available VNC port: %d on IP: %s", vncPort, config.VNCBindAddress)
|
||||
state.Put("vnc_port", vncPort)
|
||||
state.Put("vnc_ip", config.VNCBindAddress)
|
||||
state.Put("vnc_password", vncPassword)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
|
@ -62,12 +62,10 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
|
|||
func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
isoPath := state.Get("iso_path").(string)
|
||||
vncIP := state.Get("vnc_ip").(string)
|
||||
vncIP := config.VNCBindAddress
|
||||
vncPort := state.Get("vnc_port").(int)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
driver := state.Get("driver").(Driver)
|
||||
|
||||
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
|
||||
vmName := config.VMName
|
||||
imgPath := filepath.Join(config.OutputDir, vmName)
|
||||
|
||||
|
@ -75,6 +73,13 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
var deviceArgs []string
|
||||
var driveArgs []string
|
||||
var sshHostPort int
|
||||
var vnc string
|
||||
|
||||
if !config.VNCUsePassword {
|
||||
vnc = fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
|
||||
} else {
|
||||
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort-5900)
|
||||
}
|
||||
|
||||
defaultArgs["-name"] = vmName
|
||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
||||
|
@ -141,17 +146,23 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
||||
|
||||
if config.Headless == true {
|
||||
vncIpRaw, vncIpOk := state.GetOk("vnc_ip")
|
||||
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
|
||||
vncPass := state.Get("vnc_password")
|
||||
|
||||
if vncIpOk && vncPortOk {
|
||||
vncIp := vncIpRaw.(string)
|
||||
if vncPortOk && vncPass != nil && len(vncPass.(string)) > 0 {
|
||||
vncPort := vncPortRaw.(int)
|
||||
|
||||
ui.Message(fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC to vnc://%s:%d\n"+
|
||||
"with the password: %s", vncIP, vncPort, vncPass))
|
||||
} else if vncPortOk {
|
||||
vncPort := vncPortRaw.(int)
|
||||
|
||||
ui.Message(fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC without a password to\n"+
|
||||
"vnc://%s:%d", vncIp, vncPort))
|
||||
"vnc://%s:%d", vncIP, vncPort))
|
||||
} else {
|
||||
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
|
||||
"If the run isn't succeeding as you expect, please enable the GUI\n" +
|
||||
|
|
|
@ -41,7 +41,8 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
|||
httpPort := state.Get("http_port").(int)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vncPort := state.Get("vnc_port").(int)
|
||||
vncIP := state.Get("vnc_ip").(string)
|
||||
vncIP := config.VNCBindAddress
|
||||
vncPassword := state.Get("vnc_password")
|
||||
|
||||
if config.VNCConfig.DisableVNC {
|
||||
log.Println("Skipping boot command step...")
|
||||
|
@ -76,7 +77,15 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
|||
}
|
||||
defer nc.Close()
|
||||
|
||||
c, err := vnc.Client(nc, &vnc.ClientConfig{Exclusive: false})
|
||||
var auth []vnc.ClientAuth
|
||||
|
||||
if vncPassword != nil && len(vncPassword.(string)) > 0 {
|
||||
auth = []vnc.ClientAuth{&vnc.PasswordAuth{Password: vncPassword.(string)}}
|
||||
} else {
|
||||
auth = []vnc.ClientAuth{new(vnc.ClientAuthNone)}
|
||||
}
|
||||
|
||||
c, err := vnc.Client(nc, &vnc.ClientConfig{Auth: auth, Exclusive: false})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error handshaking with VNC: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -397,6 +397,10 @@ default port of `5985` or whatever value you have the service set to listen on.
|
|||
Packer uses a randomly chosen port in this range that appears available. By
|
||||
default this is `5900` to `6000`. The minimum and maximum ports are inclusive.
|
||||
|
||||
- `vnc_use_password` (bool) - Whether or not to set a password on the VNC server.
|
||||
This option automatically sets `qmp_enable` to true.
|
||||
Defaults to `false`.
|
||||
|
||||
## Boot Command
|
||||
|
||||
The `boot_command` configuration is very important: it specifies the keys to
|
||||
|
|
Loading…
Reference in New Issue