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"`
|
VNCBindAddress string `mapstructure:"vnc_bind_address"`
|
||||||
VNCPortMin int `mapstructure:"vnc_port_min"`
|
VNCPortMin int `mapstructure:"vnc_port_min"`
|
||||||
VNCPortMax int `mapstructure:"vnc_port_max"`
|
VNCPortMax int `mapstructure:"vnc_port_max"`
|
||||||
|
VNCUsePassword bool `mapstructure:"vnc_use_password"`
|
||||||
VMName string `mapstructure:"vm_name"`
|
VMName string `mapstructure:"vm_name"`
|
||||||
|
|
||||||
// These are deprecated, but we keep them around for BC
|
// 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"))
|
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 == "" {
|
if b.config.QMPEnable && b.config.QMPSocketPath == "" {
|
||||||
socketName := fmt.Sprintf("%s.monitor", b.config.VMName)
|
socketName := fmt.Sprintf("%s.monitor", b.config.VMName)
|
||||||
b.config.QMPSocketPath = filepath.Join(b.config.OutputDir, socketName)
|
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) {
|
func TestBuilderPrepare_DiskCompaction(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
|
|
@ -30,27 +30,63 @@ func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
return multistep.ActionContinue
|
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)
|
ui.Say(msg)
|
||||||
log.Print(msg)
|
log.Print(msg)
|
||||||
|
|
||||||
// Open QMP socket
|
// Only initialize and open QMP when we have a use for it.
|
||||||
var err error
|
// Handles cases where user may want the socket, but we don't
|
||||||
s.monitor, err = qmp.NewSocketMonitor("unix", config.QMPSocketPath, 2*time.Second)
|
if config.VNCUsePassword {
|
||||||
if err != nil {
|
// Open QMP socket
|
||||||
err := fmt.Errorf("Error opening QMP socket: %s", err)
|
var err error
|
||||||
state.Put("error", err)
|
var cmd []byte
|
||||||
ui.Error(err.Error())
|
var result []byte
|
||||||
return multistep.ActionHalt
|
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
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepConfigureQMP) Cleanup(multistep.StateBag) {
|
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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common/net"
|
"github.com/hashicorp/packer/common/net"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
@ -22,6 +23,21 @@ type stepConfigureVNC struct {
|
||||||
l *net.Listener
|
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 {
|
func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
@ -33,6 +49,7 @@ func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
ui.Say(msg)
|
ui.Say(msg)
|
||||||
log.Print(msg)
|
log.Print(msg)
|
||||||
|
|
||||||
|
var vncPassword string
|
||||||
var err error
|
var err error
|
||||||
s.l, err = net.ListenRangeConfig{
|
s.l, err = net.ListenRangeConfig{
|
||||||
Addr: config.VNCBindAddress,
|
Addr: config.VNCBindAddress,
|
||||||
|
@ -41,7 +58,7 @@ func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
}.Listen(ctx)
|
}.Listen(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error finding port: %s", err)
|
err := fmt.Errorf("Error finding VNC port: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
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
|
s.l.Listener.Close() // free port, but don't unlock lock file
|
||||||
vncPort := s.l.Port
|
vncPort := s.l.Port
|
||||||
|
|
||||||
|
if config.VNCUsePassword {
|
||||||
|
vncPassword = VNCPassword()
|
||||||
|
} else {
|
||||||
|
vncPassword = ""
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Found available VNC port: %d on IP: %s", vncPort, config.VNCBindAddress)
|
log.Printf("Found available VNC port: %d on IP: %s", vncPort, config.VNCBindAddress)
|
||||||
state.Put("vnc_port", vncPort)
|
state.Put("vnc_port", vncPort)
|
||||||
state.Put("vnc_ip", config.VNCBindAddress)
|
state.Put("vnc_password", vncPassword)
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,12 +62,10 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
|
||||||
func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
|
func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
isoPath := state.Get("iso_path").(string)
|
isoPath := state.Get("iso_path").(string)
|
||||||
vncIP := state.Get("vnc_ip").(string)
|
vncIP := config.VNCBindAddress
|
||||||
vncPort := state.Get("vnc_port").(int)
|
vncPort := state.Get("vnc_port").(int)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
|
|
||||||
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
|
|
||||||
vmName := config.VMName
|
vmName := config.VMName
|
||||||
imgPath := filepath.Join(config.OutputDir, vmName)
|
imgPath := filepath.Join(config.OutputDir, vmName)
|
||||||
|
|
||||||
|
@ -75,6 +73,13 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
||||||
var deviceArgs []string
|
var deviceArgs []string
|
||||||
var driveArgs []string
|
var driveArgs []string
|
||||||
var sshHostPort int
|
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["-name"] = vmName
|
||||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
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))
|
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
||||||
|
|
||||||
if config.Headless == true {
|
if config.Headless == true {
|
||||||
vncIpRaw, vncIpOk := state.GetOk("vnc_ip")
|
|
||||||
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
|
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
|
||||||
|
vncPass := state.Get("vnc_password")
|
||||||
|
|
||||||
if vncIpOk && vncPortOk {
|
if vncPortOk && vncPass != nil && len(vncPass.(string)) > 0 {
|
||||||
vncIp := vncIpRaw.(string)
|
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)
|
vncPort := vncPortRaw.(int)
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf(
|
ui.Message(fmt.Sprintf(
|
||||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
"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"+
|
"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 {
|
} else {
|
||||||
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
|
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" +
|
"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)
|
httpPort := state.Get("http_port").(int)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vncPort := state.Get("vnc_port").(int)
|
vncPort := state.Get("vnc_port").(int)
|
||||||
vncIP := state.Get("vnc_ip").(string)
|
vncIP := config.VNCBindAddress
|
||||||
|
vncPassword := state.Get("vnc_password")
|
||||||
|
|
||||||
if config.VNCConfig.DisableVNC {
|
if config.VNCConfig.DisableVNC {
|
||||||
log.Println("Skipping boot command step...")
|
log.Println("Skipping boot command step...")
|
||||||
|
@ -76,7 +77,15 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
||||||
}
|
}
|
||||||
defer nc.Close()
|
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 {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error handshaking with VNC: %s", err)
|
err := fmt.Errorf("Error handshaking with VNC: %s", err)
|
||||||
state.Put("error", 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
|
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.
|
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
|
## Boot Command
|
||||||
|
|
||||||
The `boot_command` configuration is very important: it specifies the keys to
|
The `boot_command` configuration is very important: it specifies the keys to
|
||||||
|
|
Loading…
Reference in New Issue