github.com/raghuse92/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 }