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 }