2013-09-02 23:23:52 -04:00
|
|
|
package qemu
|
|
|
|
|
|
|
|
import (
|
2018-01-22 18:32:33 -05:00
|
|
|
"context"
|
2013-09-02 23:23:52 -04:00
|
|
|
"fmt"
|
2013-12-06 19:20:25 -05:00
|
|
|
"log"
|
2013-09-02 23:23:52 -04:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2014-10-03 11:39:57 -04:00
|
|
|
|
2019-05-06 17:27:19 -04:00
|
|
|
"github.com/hashicorp/go-version"
|
2018-01-19 19:18:44 -05:00
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
2017-04-04 16:39:01 -04:00
|
|
|
"github.com/hashicorp/packer/packer"
|
|
|
|
"github.com/hashicorp/packer/template/interpolate"
|
2013-09-02 23:23:52 -04:00
|
|
|
)
|
|
|
|
|
2013-11-06 00:40:49 -05:00
|
|
|
// stepRun runs the virtual machine
|
2013-09-02 23:23:52 -04:00
|
|
|
type stepRun struct {
|
2013-11-06 00:40:49 -05:00
|
|
|
BootDrive string
|
|
|
|
Message string
|
2013-09-02 23:23:52 -04:00
|
|
|
}
|
|
|
|
|
2013-12-06 19:20:25 -05:00
|
|
|
type qemuArgsTemplateData struct {
|
2015-10-29 14:24:20 -04:00
|
|
|
HTTPIP string
|
2019-03-19 09:47:21 -04:00
|
|
|
HTTPPort int
|
2015-10-29 14:24:20 -04:00
|
|
|
HTTPDir string
|
|
|
|
OutputDir string
|
|
|
|
Name string
|
2019-03-19 09:47:21 -04:00
|
|
|
SSHHostPort int
|
2013-12-06 19:20:25 -05:00
|
|
|
}
|
|
|
|
|
2019-03-29 11:50:02 -04:00
|
|
|
func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
2013-11-06 00:40:49 -05:00
|
|
|
driver := state.Get("driver").(Driver)
|
2013-09-02 23:23:52 -04:00
|
|
|
ui := state.Get("ui").(packer.Ui)
|
|
|
|
|
2013-11-06 00:40:49 -05:00
|
|
|
ui.Say(s.Message)
|
|
|
|
|
2013-12-06 19:20:25 -05:00
|
|
|
command, err := getCommandArgs(s.BootDrive, state)
|
|
|
|
if err != nil {
|
2016-09-19 17:34:10 -04:00
|
|
|
err := fmt.Errorf("Error processing QemuArgs: %s", err)
|
2013-12-06 19:20:25 -05:00
|
|
|
ui.Error(err.Error())
|
|
|
|
return multistep.ActionHalt
|
|
|
|
}
|
|
|
|
|
2013-11-06 00:40:49 -05:00
|
|
|
if err := driver.Qemu(command...); err != nil {
|
|
|
|
err := fmt.Errorf("Error launching VM: %s", err)
|
|
|
|
ui.Error(err.Error())
|
|
|
|
return multistep.ActionHalt
|
2013-09-02 23:23:52 -04:00
|
|
|
}
|
|
|
|
|
2013-11-06 00:40:49 -05:00
|
|
|
return multistep.ActionContinue
|
2013-09-02 23:23:52 -04:00
|
|
|
}
|
|
|
|
|
2013-11-06 00:40:49 -05:00
|
|
|
func (s *stepRun) Cleanup(state multistep.StateBag) {
|
|
|
|
driver := state.Get("driver").(Driver)
|
|
|
|
ui := state.Get("ui").(packer.Ui)
|
|
|
|
|
|
|
|
if err := driver.Stop(); err != nil {
|
|
|
|
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
|
2013-09-02 23:23:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-06 19:20:25 -05:00
|
|
|
func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
|
2015-05-27 16:39:43 -04:00
|
|
|
config := state.Get("config").(*Config)
|
2013-09-02 23:23:52 -04:00
|
|
|
isoPath := state.Get("iso_path").(string)
|
2016-05-25 05:10:12 -04:00
|
|
|
vncIP := state.Get("vnc_ip").(string)
|
2019-03-19 09:47:21 -04:00
|
|
|
vncPort := state.Get("vnc_port").(int)
|
2013-11-06 00:40:49 -05:00
|
|
|
ui := state.Get("ui").(packer.Ui)
|
2015-09-08 06:40:23 -04:00
|
|
|
driver := state.Get("driver").(Driver)
|
2013-11-06 00:40:49 -05:00
|
|
|
|
2016-05-25 05:10:12 -04:00
|
|
|
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
|
2013-11-06 00:40:49 -05:00
|
|
|
vmName := config.VMName
|
2015-05-17 10:35:39 -04:00
|
|
|
imgPath := filepath.Join(config.OutputDir, vmName)
|
2013-09-02 23:23:52 -04:00
|
|
|
|
2015-09-08 06:58:21 -04:00
|
|
|
defaultArgs := make(map[string]interface{})
|
|
|
|
var deviceArgs []string
|
|
|
|
var driveArgs []string
|
2019-03-19 09:47:21 -04:00
|
|
|
var sshHostPort int
|
2013-09-02 23:23:52 -04:00
|
|
|
|
2013-10-07 21:58:08 -04:00
|
|
|
defaultArgs["-name"] = vmName
|
2014-09-02 12:24:31 -04:00
|
|
|
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
2016-07-06 11:52:40 -04:00
|
|
|
if config.Comm.Type != "none" {
|
2019-03-19 09:47:21 -04:00
|
|
|
sshHostPort = state.Get("sshHostPort").(int)
|
2016-07-06 11:52:40 -04:00
|
|
|
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", sshHostPort, config.Comm.Port())
|
|
|
|
} else {
|
|
|
|
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
|
|
|
|
}
|
2015-09-08 06:58:21 -04:00
|
|
|
|
2019-07-03 20:33:59 -04:00
|
|
|
if config.QMPEnable {
|
|
|
|
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
|
|
|
|
}
|
|
|
|
|
2019-05-06 17:27:19 -04:00
|
|
|
rawVersion, err := driver.Version()
|
2015-09-08 06:58:21 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-05-06 17:27:19 -04:00
|
|
|
qemuVersion, err := version.NewVersion(rawVersion)
|
2019-05-07 03:37:57 -04:00
|
|
|
v2 := version.Must(version.NewVersion("2.0"))
|
2019-05-06 17:27:19 -04:00
|
|
|
|
|
|
|
if qemuVersion.GreaterThanOrEqual(v2) {
|
2015-09-08 06:58:21 -04:00
|
|
|
if config.DiskInterface == "virtio-scsi" {
|
2019-06-24 19:35:06 -04:00
|
|
|
if config.DiskImage {
|
|
|
|
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0", "scsi-hd,bus=scsi0.0,drive=drive0")
|
|
|
|
driveArgumentString := fmt.Sprintf("if=none,file=%s,id=drive0,cache=%s,discard=%s,format=%s", imgPath, config.DiskCache, config.DiskDiscard, config.Format)
|
|
|
|
if config.DetectZeroes != "off" {
|
|
|
|
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
|
|
|
}
|
|
|
|
driveArgs = append(driveArgs, driveArgumentString)
|
|
|
|
} else {
|
|
|
|
deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0")
|
|
|
|
diskFullPaths := state.Get("qemu_disk_paths").([]string)
|
|
|
|
for i, diskFullPath := range diskFullPaths {
|
|
|
|
deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i))
|
|
|
|
driveArgumentString := fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", diskFullPath, i, config.DiskCache, config.DiskDiscard, config.Format)
|
|
|
|
if config.DetectZeroes != "off" {
|
|
|
|
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
|
|
|
}
|
|
|
|
driveArgs = append(driveArgs, driveArgumentString)
|
|
|
|
}
|
2018-12-03 18:52:13 -05:00
|
|
|
}
|
2015-09-08 06:58:21 -04:00
|
|
|
} else {
|
2019-06-24 19:35:06 -04:00
|
|
|
if config.DiskImage {
|
|
|
|
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
|
|
|
|
if config.DetectZeroes != "off" {
|
|
|
|
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
|
|
|
}
|
|
|
|
driveArgs = append(driveArgs, driveArgumentString)
|
|
|
|
} else {
|
|
|
|
diskFullPaths := state.Get("qemu_disk_paths").([]string)
|
|
|
|
for _, diskFullPath := range diskFullPaths {
|
|
|
|
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", diskFullPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
|
|
|
|
if config.DetectZeroes != "off" {
|
|
|
|
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
|
|
|
|
}
|
|
|
|
driveArgs = append(driveArgs, driveArgumentString)
|
|
|
|
}
|
2018-12-03 18:52:13 -05:00
|
|
|
}
|
2015-09-08 06:40:23 -04:00
|
|
|
}
|
|
|
|
} else {
|
2016-09-15 10:59:05 -04:00
|
|
|
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
|
2015-09-08 06:40:23 -04:00
|
|
|
}
|
2015-09-08 06:58:21 -04:00
|
|
|
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
|
|
|
|
|
|
|
if config.Headless == true {
|
2016-05-25 05:10:12 -04:00
|
|
|
vncIpRaw, vncIpOk := state.GetOk("vnc_ip")
|
|
|
|
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
|
|
|
|
|
|
|
|
if vncIpOk && vncPortOk {
|
|
|
|
vncIp := vncIpRaw.(string)
|
2019-03-19 09:47:21 -04:00
|
|
|
vncPort := vncPortRaw.(int)
|
2016-05-25 05:10:12 -04:00
|
|
|
|
|
|
|
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"+
|
2017-02-02 04:55:28 -05:00
|
|
|
"vnc://%s:%d", vncIp, vncPort))
|
2016-05-25 05:10:12 -04:00
|
|
|
} 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" +
|
|
|
|
"to inspect the progress of the build.")
|
|
|
|
}
|
2015-09-08 06:58:21 -04:00
|
|
|
} else {
|
2019-05-06 17:27:19 -04:00
|
|
|
if qemuVersion.GreaterThanOrEqual(v2) {
|
2016-12-16 20:33:33 -05:00
|
|
|
if !config.UseDefaultDisplay {
|
|
|
|
defaultArgs["-display"] = "sdl"
|
|
|
|
}
|
2015-09-08 06:58:21 -04:00
|
|
|
} else {
|
|
|
|
ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
|
|
|
|
"The display parameter will be ignored.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultArgs["-device"] = deviceArgs
|
|
|
|
defaultArgs["-drive"] = driveArgs
|
|
|
|
|
2014-11-06 15:13:09 -05:00
|
|
|
if !config.DiskImage {
|
|
|
|
defaultArgs["-cdrom"] = isoPath
|
|
|
|
}
|
2013-10-07 21:58:08 -04:00
|
|
|
defaultArgs["-boot"] = bootDrive
|
2018-12-29 15:58:05 -05:00
|
|
|
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
|
|
|
|
if config.CpuCount > 1 {
|
|
|
|
defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount)
|
|
|
|
}
|
2013-10-07 21:58:08 -04:00
|
|
|
defaultArgs["-vnc"] = vnc
|
|
|
|
|
2014-09-02 12:24:31 -04:00
|
|
|
// Append the accelerator to the machine type if it is specified
|
|
|
|
if config.Accelerator != "none" {
|
2015-09-08 06:58:21 -04:00
|
|
|
defaultArgs["-machine"] = fmt.Sprintf("%s,accel=%s", defaultArgs["-machine"], config.Accelerator)
|
2014-09-02 12:24:31 -04:00
|
|
|
} else {
|
|
|
|
ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
|
2014-09-02 12:55:06 -04:00
|
|
|
"The installation may take considerably longer to finish.\n")
|
2014-09-02 12:24:31 -04:00
|
|
|
}
|
|
|
|
|
2013-12-06 19:20:25 -05:00
|
|
|
// Determine if we have a floppy disk to attach
|
|
|
|
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
|
|
|
|
defaultArgs["-fda"] = floppyPathRaw.(string)
|
|
|
|
} else {
|
|
|
|
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
|
|
|
|
}
|
|
|
|
|
2013-10-07 21:58:08 -04:00
|
|
|
inArgs := make(map[string][]string)
|
|
|
|
if len(config.QemuArgs) > 0 {
|
|
|
|
ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
|
|
|
|
|
2019-03-19 09:47:21 -04:00
|
|
|
httpPort := state.Get("http_port").(int)
|
2019-03-29 11:50:02 -04:00
|
|
|
ictx := config.ctx
|
2016-07-06 11:52:40 -04:00
|
|
|
if config.Comm.Type != "none" {
|
2019-03-29 11:50:02 -04:00
|
|
|
ictx.Data = qemuArgsTemplateData{
|
2016-07-06 11:52:40 -04:00
|
|
|
"10.0.2.2",
|
|
|
|
httpPort,
|
|
|
|
config.HTTPDir,
|
|
|
|
config.OutputDir,
|
|
|
|
config.VMName,
|
|
|
|
sshHostPort,
|
|
|
|
}
|
|
|
|
} else {
|
2019-03-29 11:50:02 -04:00
|
|
|
ictx.Data = qemuArgsTemplateData{
|
2016-07-06 11:52:40 -04:00
|
|
|
HTTPIP: "10.0.2.2",
|
|
|
|
HTTPPort: httpPort,
|
|
|
|
HTTPDir: config.HTTPDir,
|
|
|
|
OutputDir: config.OutputDir,
|
|
|
|
Name: config.VMName,
|
|
|
|
}
|
2013-12-06 19:20:25 -05:00
|
|
|
}
|
2019-03-29 11:50:02 -04:00
|
|
|
newQemuArgs, err := processArgs(config.QemuArgs, &ictx)
|
2013-12-06 19:20:25 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// because qemu supports multiple appearances of the same
|
2013-10-07 21:58:08 -04:00
|
|
|
// switch, just different values, each key in the args hash
|
|
|
|
// will have an array of string values
|
2013-12-06 19:20:25 -05:00
|
|
|
for _, qemuArgs := range newQemuArgs {
|
2013-10-07 21:58:08 -04:00
|
|
|
key := qemuArgs[0]
|
|
|
|
val := strings.Join(qemuArgs[1:], "")
|
|
|
|
if _, ok := inArgs[key]; !ok {
|
|
|
|
inArgs[key] = make([]string, 0)
|
|
|
|
}
|
|
|
|
if len(val) > 0 {
|
|
|
|
inArgs[key] = append(inArgs[key], val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get any remaining missing default args from the default settings
|
|
|
|
for key := range defaultArgs {
|
|
|
|
if _, ok := inArgs[key]; !ok {
|
|
|
|
arg := make([]string, 1)
|
2015-09-08 06:58:21 -04:00
|
|
|
switch defaultArgs[key].(type) {
|
|
|
|
case string:
|
|
|
|
arg[0] = defaultArgs[key].(string)
|
|
|
|
case []string:
|
|
|
|
arg = defaultArgs[key].([]string)
|
|
|
|
}
|
2013-10-07 21:58:08 -04:00
|
|
|
inArgs[key] = arg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-06 08:40:43 -04:00
|
|
|
// Check if we are missing the netDevice #6804
|
|
|
|
if x, ok := inArgs["-device"]; ok {
|
|
|
|
if !strings.Contains(strings.Join(x, ""), config.NetDevice) {
|
|
|
|
inArgs["-device"] = append(inArgs["-device"], fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-07 21:58:08 -04:00
|
|
|
// Flatten to array of strings
|
|
|
|
outArgs := make([]string, 0)
|
|
|
|
for key, values := range inArgs {
|
|
|
|
if len(values) > 0 {
|
|
|
|
for idx := range values {
|
|
|
|
outArgs = append(outArgs, key, values[idx])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
outArgs = append(outArgs, key)
|
|
|
|
}
|
2013-09-02 23:23:52 -04:00
|
|
|
}
|
2013-10-07 21:58:08 -04:00
|
|
|
|
2013-12-06 19:20:25 -05:00
|
|
|
return outArgs, nil
|
|
|
|
}
|
|
|
|
|
2015-05-27 16:39:43 -04:00
|
|
|
func processArgs(args [][]string, ctx *interpolate.Context) ([][]string, error) {
|
2013-12-06 19:20:25 -05:00
|
|
|
var err error
|
|
|
|
|
|
|
|
if args == nil {
|
|
|
|
return make([][]string, 0), err
|
|
|
|
}
|
|
|
|
|
|
|
|
newArgs := make([][]string, len(args))
|
|
|
|
for argsIdx, rowArgs := range args {
|
|
|
|
parms := make([]string, len(rowArgs))
|
|
|
|
newArgs[argsIdx] = parms
|
|
|
|
for i, parm := range rowArgs {
|
2015-05-27 16:39:43 -04:00
|
|
|
parms[i], err = interpolate.Render(parm, ctx)
|
2013-12-06 19:20:25 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newArgs, err
|
2013-10-07 21:58:08 -04:00
|
|
|
}
|