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