github.com/rahart/packer@v0.12.2-0.20161229105310-282bb6ad370f/builder/qemu/step_run.go (about)

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