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