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