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