github.com/tonnydourado/packer@v0.6.1-0.20140701134019-5d0cd9676a37/builder/parallels/iso/builder.go (about) 1 package iso 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/mitchellh/multistep" 7 parallelscommon "github.com/mitchellh/packer/builder/parallels/common" 8 "github.com/mitchellh/packer/common" 9 "github.com/mitchellh/packer/packer" 10 "log" 11 "strings" 12 ) 13 14 const BuilderId = "rickard-von-essen.parallels" 15 16 type Builder struct { 17 config config 18 runner multistep.Runner 19 } 20 21 type config struct { 22 common.PackerConfig `mapstructure:",squash"` 23 parallelscommon.FloppyConfig `mapstructure:",squash"` 24 parallelscommon.OutputConfig `mapstructure:",squash"` 25 parallelscommon.RunConfig `mapstructure:",squash"` 26 parallelscommon.ShutdownConfig `mapstructure:",squash"` 27 parallelscommon.SSHConfig `mapstructure:",squash"` 28 parallelscommon.PrlctlConfig `mapstructure:",squash"` 29 parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` 30 31 BootCommand []string `mapstructure:"boot_command"` 32 DiskSize uint `mapstructure:"disk_size"` 33 ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` 34 ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"` 35 ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"` 36 GuestOSType string `mapstructure:"guest_os_type"` 37 GuestOSDistribution string `mapstructure:"guest_os_distribution"` 38 HardDriveInterface string `mapstructure:"hard_drive_interface"` 39 HostInterfaces []string `mapstructure:"host_interfaces"` 40 HTTPDir string `mapstructure:"http_directory"` 41 HTTPPortMin uint `mapstructure:"http_port_min"` 42 HTTPPortMax uint `mapstructure:"http_port_max"` 43 ISOChecksum string `mapstructure:"iso_checksum"` 44 ISOChecksumType string `mapstructure:"iso_checksum_type"` 45 ISOUrls []string `mapstructure:"iso_urls"` 46 VMName string `mapstructure:"vm_name"` 47 48 RawSingleISOUrl string `mapstructure:"iso_url"` 49 50 tpl *packer.ConfigTemplate 51 } 52 53 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 54 55 md, err := common.DecodeConfig(&b.config, raws...) 56 if err != nil { 57 return nil, err 58 } 59 60 b.config.tpl, err = packer.NewConfigTemplate() 61 if err != nil { 62 return nil, err 63 } 64 b.config.tpl.UserVars = b.config.PackerUserVars 65 66 // Accumulate any errors and warnings 67 errs := common.CheckUnusedConfig(md) 68 errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(b.config.tpl)...) 69 errs = packer.MultiErrorAppend( 70 errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) 71 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) 72 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) 73 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) 74 errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(b.config.tpl)...) 75 errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(b.config.tpl)...) 76 warnings := make([]string, 0) 77 78 if b.config.DiskSize == 0 { 79 b.config.DiskSize = 40000 80 } 81 82 if b.config.ParallelsToolsMode == "" { 83 b.config.ParallelsToolsMode = "upload" 84 } 85 86 if b.config.ParallelsToolsGuestPath == "" { 87 b.config.ParallelsToolsGuestPath = "prl-tools.iso" 88 } 89 90 if b.config.ParallelsToolsHostPath == "" { 91 b.config.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" 92 } 93 94 if b.config.HardDriveInterface == "" { 95 b.config.HardDriveInterface = "sata" 96 } 97 98 if b.config.GuestOSType == "" { 99 b.config.GuestOSType = "other" 100 } 101 102 if b.config.GuestOSDistribution == "" { 103 b.config.GuestOSDistribution = "other" 104 } 105 106 if b.config.HTTPPortMin == 0 { 107 b.config.HTTPPortMin = 8000 108 } 109 110 if b.config.HTTPPortMax == 0 { 111 b.config.HTTPPortMax = 9000 112 } 113 114 if len(b.config.HostInterfaces) == 0 { 115 b.config.HostInterfaces = []string{"en0", "en1", "en2", "en3", "en4", "en5", "en6", "en7", 116 "en8", "en9", "ppp0", "ppp1", "ppp2"} 117 } 118 119 if b.config.VMName == "" { 120 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 121 } 122 123 // Errors 124 templates := map[string]*string{ 125 "parallels_tools_mode": &b.config.ParallelsToolsMode, 126 "parallels_tools_host_path": &b.config.ParallelsToolsHostPath, 127 "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, 128 "guest_os_type": &b.config.GuestOSType, 129 "guest_os_distribution": &b.config.GuestOSDistribution, 130 "hard_drive_interface": &b.config.HardDriveInterface, 131 "http_directory": &b.config.HTTPDir, 132 "iso_checksum": &b.config.ISOChecksum, 133 "iso_checksum_type": &b.config.ISOChecksumType, 134 "iso_url": &b.config.RawSingleISOUrl, 135 "vm_name": &b.config.VMName, 136 } 137 138 for n, ptr := range templates { 139 var err error 140 *ptr, err = b.config.tpl.Process(*ptr, nil) 141 if err != nil { 142 errs = packer.MultiErrorAppend( 143 errs, fmt.Errorf("Error processing %s: %s", n, err)) 144 } 145 } 146 147 for i, url := range b.config.ISOUrls { 148 var err error 149 b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil) 150 if err != nil { 151 errs = packer.MultiErrorAppend( 152 errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err)) 153 } 154 } 155 156 validates := map[string]*string{ 157 "parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath, 158 } 159 160 for n, ptr := range validates { 161 if err := b.config.tpl.Validate(*ptr); err != nil { 162 errs = packer.MultiErrorAppend( 163 errs, fmt.Errorf("Error parsing %s: %s", n, err)) 164 } 165 } 166 167 for i, command := range b.config.BootCommand { 168 if err := b.config.tpl.Validate(command); err != nil { 169 errs = packer.MultiErrorAppend(errs, 170 fmt.Errorf("Error processing boot_command[%d]: %s", i, err)) 171 } 172 } 173 174 if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" && b.config.HardDriveInterface != "scsi" { 175 errs = packer.MultiErrorAppend( 176 errs, errors.New("hard_drive_interface can only be ide, sata, or scsi")) 177 } 178 179 if b.config.HTTPPortMin > b.config.HTTPPortMax { 180 errs = packer.MultiErrorAppend( 181 errs, errors.New("http_port_min must be less than http_port_max")) 182 } 183 184 if b.config.ISOChecksumType == "" { 185 errs = packer.MultiErrorAppend( 186 errs, errors.New("The iso_checksum_type must be specified.")) 187 } else { 188 b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) 189 if b.config.ISOChecksumType != "none" { 190 if b.config.ISOChecksum == "" { 191 errs = packer.MultiErrorAppend( 192 errs, errors.New("Due to large file sizes, an iso_checksum is required")) 193 } else { 194 b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) 195 } 196 197 if h := common.HashForType(b.config.ISOChecksumType); h == nil { 198 errs = packer.MultiErrorAppend( 199 errs, 200 fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) 201 } 202 } 203 } 204 205 if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { 206 errs = packer.MultiErrorAppend( 207 errs, errors.New("One of iso_url or iso_urls must be specified.")) 208 } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { 209 errs = packer.MultiErrorAppend( 210 errs, errors.New("Only one of iso_url or iso_urls may be specified.")) 211 } else if b.config.RawSingleISOUrl != "" { 212 b.config.ISOUrls = []string{b.config.RawSingleISOUrl} 213 } 214 215 for i, url := range b.config.ISOUrls { 216 b.config.ISOUrls[i], err = common.DownloadableURL(url) 217 if err != nil { 218 errs = packer.MultiErrorAppend( 219 errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) 220 } 221 } 222 223 validMode := false 224 validModes := []string{ 225 parallelscommon.ParallelsToolsModeDisable, 226 parallelscommon.ParallelsToolsModeAttach, 227 parallelscommon.ParallelsToolsModeUpload, 228 } 229 230 for _, mode := range validModes { 231 if b.config.ParallelsToolsMode == mode { 232 validMode = true 233 break 234 } 235 } 236 237 if !validMode { 238 errs = packer.MultiErrorAppend(errs, 239 fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes)) 240 } 241 242 // Warnings 243 if b.config.ISOChecksumType == "none" { 244 warnings = append(warnings, 245 "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ 246 "a checksum is highly recommended.") 247 } 248 249 if b.config.ShutdownCommand == "" { 250 warnings = append(warnings, 251 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 252 "will forcibly halt the virtual machine, which may result in data loss.") 253 } 254 255 if errs != nil && len(errs.Errors) > 0 { 256 return warnings, errs 257 } 258 259 return warnings, nil 260 } 261 262 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 263 // Create the driver that we'll use to communicate with Parallels 264 driver, err := parallelscommon.NewDriver() 265 if err != nil { 266 return nil, fmt.Errorf("Failed creating Parallels driver: %s", err) 267 } 268 269 steps := []multistep.Step{ 270 &common.StepDownload{ 271 Checksum: b.config.ISOChecksum, 272 ChecksumType: b.config.ISOChecksumType, 273 Description: "ISO", 274 ResultKey: "iso_path", 275 Url: b.config.ISOUrls, 276 }, 277 ¶llelscommon.StepOutputDir{ 278 Force: b.config.PackerForce, 279 Path: b.config.OutputDir, 280 }, 281 &common.StepCreateFloppy{ 282 Files: b.config.FloppyFiles, 283 }, 284 new(stepHTTPServer), 285 new(stepCreateVM), 286 new(stepCreateDisk), 287 new(stepAttachISO), 288 ¶llelscommon.StepAttachParallelsTools{ 289 ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, 290 ParallelsToolsMode: b.config.ParallelsToolsMode, 291 }, 292 new(parallelscommon.StepAttachFloppy), 293 ¶llelscommon.StepPrlctl{ 294 Commands: b.config.Prlctl, 295 Tpl: b.config.tpl, 296 }, 297 ¶llelscommon.StepRun{ 298 BootWait: b.config.BootWait, 299 Headless: b.config.Headless, // TODO: migth work on Enterprise Ed. 300 }, 301 ¶llelscommon.StepTypeBootCommand{ 302 BootCommand: b.config.BootCommand, 303 HostInterfaces: b.config.HostInterfaces, 304 VMName: b.config.VMName, 305 Tpl: b.config.tpl, 306 }, 307 &common.StepConnectSSH{ 308 SSHAddress: parallelscommon.SSHAddress, 309 SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig), 310 SSHWaitTimeout: b.config.SSHWaitTimeout, 311 }, 312 ¶llelscommon.StepUploadVersion{ 313 Path: b.config.PrlctlVersionFile, 314 }, 315 ¶llelscommon.StepUploadParallelsTools{ 316 ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, 317 ParallelsToolsHostPath: b.config.ParallelsToolsHostPath, 318 ParallelsToolsMode: b.config.ParallelsToolsMode, 319 Tpl: b.config.tpl, 320 }, 321 new(common.StepProvision), 322 ¶llelscommon.StepShutdown{ 323 Command: b.config.ShutdownCommand, 324 Timeout: b.config.ShutdownTimeout, 325 }, 326 new(parallelscommon.StepRemoveDevices), 327 } 328 329 // Setup the state bag 330 state := new(multistep.BasicStateBag) 331 state.Put("cache", cache) 332 state.Put("config", &b.config) 333 state.Put("driver", driver) 334 state.Put("hook", hook) 335 state.Put("ui", ui) 336 337 // Run 338 if b.config.PackerDebug { 339 b.runner = &multistep.DebugRunner{ 340 Steps: steps, 341 PauseFn: common.MultistepDebugFn(ui), 342 } 343 } else { 344 b.runner = &multistep.BasicRunner{Steps: steps} 345 } 346 347 b.runner.Run(state) 348 349 // If there was an error, return that 350 if rawErr, ok := state.GetOk("error"); ok { 351 return nil, rawErr.(error) 352 } 353 354 // If we were interrupted or cancelled, then just exit. 355 if _, ok := state.GetOk(multistep.StateCancelled); ok { 356 return nil, errors.New("Build was cancelled.") 357 } 358 359 if _, ok := state.GetOk(multistep.StateHalted); ok { 360 return nil, errors.New("Build was halted.") 361 } 362 363 return parallelscommon.NewArtifact(b.config.OutputDir) 364 } 365 366 func (b *Builder) Cancel() { 367 if b.runner != nil { 368 log.Println("Cancelling the step runner...") 369 b.runner.Cancel() 370 } 371 }