github.com/mitchellh/packer@v1.3.2/builder/qemu/step_run.go (about)

     1  package qemu
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/packer/helper/multistep"
    12  	"github.com/hashicorp/packer/packer"
    13  	"github.com/hashicorp/packer/template/interpolate"
    14  )
    15  
    16  // stepRun runs the virtual machine
    17  type stepRun struct {
    18  	BootDrive string
    19  	Message   string
    20  }
    21  
    22  type qemuArgsTemplateData struct {
    23  	HTTPIP      string
    24  	HTTPPort    uint
    25  	HTTPDir     string
    26  	OutputDir   string
    27  	Name        string
    28  	SSHHostPort uint
    29  }
    30  
    31  func (s *stepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
    32  	driver := state.Get("driver").(Driver)
    33  	ui := state.Get("ui").(packer.Ui)
    34  
    35  	ui.Say(s.Message)
    36  
    37  	command, err := getCommandArgs(s.BootDrive, state)
    38  	if err != nil {
    39  		err := fmt.Errorf("Error processing QemuArgs: %s", err)
    40  		ui.Error(err.Error())
    41  		return multistep.ActionHalt
    42  	}
    43  
    44  	if err := driver.Qemu(command...); err != nil {
    45  		err := fmt.Errorf("Error launching VM: %s", err)
    46  		ui.Error(err.Error())
    47  		return multistep.ActionHalt
    48  	}
    49  
    50  	return multistep.ActionContinue
    51  }
    52  
    53  func (s *stepRun) Cleanup(state multistep.StateBag) {
    54  	driver := state.Get("driver").(Driver)
    55  	ui := state.Get("ui").(packer.Ui)
    56  
    57  	if err := driver.Stop(); err != nil {
    58  		ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
    59  	}
    60  }
    61  
    62  func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
    63  	config := state.Get("config").(*Config)
    64  	isoPath := state.Get("iso_path").(string)
    65  	vncIP := state.Get("vnc_ip").(string)
    66  	vncPort := state.Get("vnc_port").(uint)
    67  	ui := state.Get("ui").(packer.Ui)
    68  	driver := state.Get("driver").(Driver)
    69  
    70  	vnc := fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
    71  	vmName := config.VMName
    72  	imgPath := filepath.Join(config.OutputDir, vmName)
    73  
    74  	defaultArgs := make(map[string]interface{})
    75  	var deviceArgs []string
    76  	var driveArgs []string
    77  	var sshHostPort uint
    78  
    79  	defaultArgs["-name"] = vmName
    80  	defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
    81  	if config.Comm.Type != "none" {
    82  		sshHostPort = state.Get("sshHostPort").(uint)
    83  		defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", sshHostPort, config.Comm.Port())
    84  	} else {
    85  		defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
    86  	}
    87  
    88  	qemuVersion, err := driver.Version()
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	parts := strings.Split(qemuVersion, ".")
    93  	qemuMajor, err := strconv.Atoi(parts[0])
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	if qemuMajor >= 2 {
    98  		if config.DiskInterface == "virtio-scsi" {
    99  			deviceArgs = append(deviceArgs, "virtio-scsi-pci,id=scsi0", "scsi-hd,bus=scsi0.0,drive=drive0")
   100  			driveArgs = append(driveArgs, fmt.Sprintf("if=none,file=%s,id=drive0,cache=%s,discard=%s,format=%s,detect-zeroes=%s", imgPath, config.DiskCache, config.DiskDiscard, config.Format, config.DetectZeroes))
   101  		} else {
   102  			driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s,detect-zeroes=%s", imgPath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format, config.DetectZeroes))
   103  		}
   104  	} else {
   105  		driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
   106  	}
   107  	deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
   108  
   109  	if config.Headless == true {
   110  		vncIpRaw, vncIpOk := state.GetOk("vnc_ip")
   111  		vncPortRaw, vncPortOk := state.GetOk("vnc_port")
   112  
   113  		if vncIpOk && vncPortOk {
   114  			vncIp := vncIpRaw.(string)
   115  			vncPort := vncPortRaw.(uint)
   116  
   117  			ui.Message(fmt.Sprintf(
   118  				"The VM will be run headless, without a GUI. If you want to\n"+
   119  					"view the screen of the VM, connect via VNC without a password to\n"+
   120  					"vnc://%s:%d", vncIp, vncPort))
   121  		} else {
   122  			ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
   123  				"If the run isn't succeeding as you expect, please enable the GUI\n" +
   124  				"to inspect the progress of the build.")
   125  		}
   126  	} else {
   127  		if qemuMajor >= 2 {
   128  			if !config.UseDefaultDisplay {
   129  				defaultArgs["-display"] = "sdl"
   130  			}
   131  		} else {
   132  			ui.Message("WARNING: The version of qemu  on your host doesn't support display mode.\n" +
   133  				"The display parameter will be ignored.")
   134  		}
   135  	}
   136  
   137  	defaultArgs["-device"] = deviceArgs
   138  	defaultArgs["-drive"] = driveArgs
   139  
   140  	if !config.DiskImage {
   141  		defaultArgs["-cdrom"] = isoPath
   142  	}
   143  	defaultArgs["-boot"] = bootDrive
   144  	defaultArgs["-m"] = "512M"
   145  	defaultArgs["-vnc"] = vnc
   146  
   147  	// Append the accelerator to the machine type if it is specified
   148  	if config.Accelerator != "none" {
   149  		defaultArgs["-machine"] = fmt.Sprintf("%s,accel=%s", defaultArgs["-machine"], config.Accelerator)
   150  	} else {
   151  		ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
   152  			"The installation may take considerably longer to finish.\n")
   153  	}
   154  
   155  	// Determine if we have a floppy disk to attach
   156  	if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
   157  		defaultArgs["-fda"] = floppyPathRaw.(string)
   158  	} else {
   159  		log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
   160  	}
   161  
   162  	inArgs := make(map[string][]string)
   163  	if len(config.QemuArgs) > 0 {
   164  		ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
   165  
   166  		httpPort := state.Get("http_port").(uint)
   167  		ctx := config.ctx
   168  		if config.Comm.Type != "none" {
   169  			ctx.Data = qemuArgsTemplateData{
   170  				"10.0.2.2",
   171  				httpPort,
   172  				config.HTTPDir,
   173  				config.OutputDir,
   174  				config.VMName,
   175  				sshHostPort,
   176  			}
   177  		} else {
   178  			ctx.Data = qemuArgsTemplateData{
   179  				HTTPIP:    "10.0.2.2",
   180  				HTTPPort:  httpPort,
   181  				HTTPDir:   config.HTTPDir,
   182  				OutputDir: config.OutputDir,
   183  				Name:      config.VMName,
   184  			}
   185  		}
   186  		newQemuArgs, err := processArgs(config.QemuArgs, &ctx)
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  
   191  		// because qemu supports multiple appearances of the same
   192  		// switch, just different values, each key in the args hash
   193  		// will have an array of string values
   194  		for _, qemuArgs := range newQemuArgs {
   195  			key := qemuArgs[0]
   196  			val := strings.Join(qemuArgs[1:], "")
   197  			if _, ok := inArgs[key]; !ok {
   198  				inArgs[key] = make([]string, 0)
   199  			}
   200  			if len(val) > 0 {
   201  				inArgs[key] = append(inArgs[key], val)
   202  			}
   203  		}
   204  	}
   205  
   206  	// get any remaining missing default args from the default settings
   207  	for key := range defaultArgs {
   208  		if _, ok := inArgs[key]; !ok {
   209  			arg := make([]string, 1)
   210  			switch defaultArgs[key].(type) {
   211  			case string:
   212  				arg[0] = defaultArgs[key].(string)
   213  			case []string:
   214  				arg = defaultArgs[key].([]string)
   215  			}
   216  			inArgs[key] = arg
   217  		}
   218  	}
   219  
   220  	// Check if we are missing the netDevice #6804
   221  	if x, ok := inArgs["-device"]; ok {
   222  		if !strings.Contains(strings.Join(x, ""), config.NetDevice) {
   223  			inArgs["-device"] = append(inArgs["-device"], fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
   224  		}
   225  	}
   226  
   227  	// Flatten to array of strings
   228  	outArgs := make([]string, 0)
   229  	for key, values := range inArgs {
   230  		if len(values) > 0 {
   231  			for idx := range values {
   232  				outArgs = append(outArgs, key, values[idx])
   233  			}
   234  		} else {
   235  			outArgs = append(outArgs, key)
   236  		}
   237  	}
   238  
   239  	return outArgs, nil
   240  }
   241  
   242  func processArgs(args [][]string, ctx *interpolate.Context) ([][]string, error) {
   243  	var err error
   244  
   245  	if args == nil {
   246  		return make([][]string, 0), err
   247  	}
   248  
   249  	newArgs := make([][]string, len(args))
   250  	for argsIdx, rowArgs := range args {
   251  		parms := make([]string, len(rowArgs))
   252  		newArgs[argsIdx] = parms
   253  		for i, parm := range rowArgs {
   254  			parms[i], err = interpolate.Render(parm, ctx)
   255  			if err != nil {
   256  				return nil, err
   257  			}
   258  		}
   259  	}
   260  
   261  	return newArgs, err
   262  }