github.com/homburg/packer@v0.6.1-0.20140528012651-1dcaf1716848/builder/vmware/iso/builder.go (about) 1 package iso 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/mitchellh/multistep" 7 vmwcommon "github.com/mitchellh/packer/builder/vmware/common" 8 "github.com/mitchellh/packer/common" 9 "github.com/mitchellh/packer/packer" 10 "io/ioutil" 11 "log" 12 "math/rand" 13 "os" 14 "strings" 15 "time" 16 ) 17 18 const BuilderIdESX = "mitchellh.vmware-esx" 19 20 type Builder struct { 21 config config 22 runner multistep.Runner 23 } 24 25 type config struct { 26 common.PackerConfig `mapstructure:",squash"` 27 vmwcommon.DriverConfig `mapstructure:",squash"` 28 vmwcommon.OutputConfig `mapstructure:",squash"` 29 vmwcommon.RunConfig `mapstructure:",squash"` 30 vmwcommon.ShutdownConfig `mapstructure:",squash"` 31 vmwcommon.SSHConfig `mapstructure:",squash"` 32 vmwcommon.ToolsConfig `mapstructure:",squash"` 33 vmwcommon.VMXConfig `mapstructure:",squash"` 34 35 DiskName string `mapstructure:"vmdk_name"` 36 DiskSize uint `mapstructure:"disk_size"` 37 DiskTypeId string `mapstructure:"disk_type_id"` 38 FloppyFiles []string `mapstructure:"floppy_files"` 39 GuestOSType string `mapstructure:"guest_os_type"` 40 ISOChecksum string `mapstructure:"iso_checksum"` 41 ISOChecksumType string `mapstructure:"iso_checksum_type"` 42 ISOUrls []string `mapstructure:"iso_urls"` 43 VMName string `mapstructure:"vm_name"` 44 HTTPDir string `mapstructure:"http_directory"` 45 HTTPPortMin uint `mapstructure:"http_port_min"` 46 HTTPPortMax uint `mapstructure:"http_port_max"` 47 BootCommand []string `mapstructure:"boot_command"` 48 SkipCompaction bool `mapstructure:"skip_compaction"` 49 VMXTemplatePath string `mapstructure:"vmx_template_path"` 50 VNCPortMin uint `mapstructure:"vnc_port_min"` 51 VNCPortMax uint `mapstructure:"vnc_port_max"` 52 53 RemoteType string `mapstructure:"remote_type"` 54 RemoteDatastore string `mapstructure:"remote_datastore"` 55 RemoteHost string `mapstructure:"remote_host"` 56 RemotePort uint `mapstructure:"remote_port"` 57 RemoteUser string `mapstructure:"remote_username"` 58 RemotePassword string `mapstructure:"remote_password"` 59 60 RawSingleISOUrl string `mapstructure:"iso_url"` 61 62 tpl *packer.ConfigTemplate 63 } 64 65 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 66 md, err := common.DecodeConfig(&b.config, raws...) 67 if err != nil { 68 return nil, err 69 } 70 71 b.config.tpl, err = packer.NewConfigTemplate() 72 if err != nil { 73 return nil, err 74 } 75 b.config.tpl.UserVars = b.config.PackerUserVars 76 77 // Accumulate any errors 78 errs := common.CheckUnusedConfig(md) 79 errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(b.config.tpl)...) 80 errs = packer.MultiErrorAppend(errs, 81 b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) 82 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) 83 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) 84 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) 85 errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(b.config.tpl)...) 86 errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...) 87 warnings := make([]string, 0) 88 89 if b.config.DiskName == "" { 90 b.config.DiskName = "disk" 91 } 92 93 if b.config.DiskSize == 0 { 94 b.config.DiskSize = 40000 95 } 96 97 if b.config.DiskTypeId == "" { 98 // Default is growable virtual disk split in 2GB files. 99 b.config.DiskTypeId = "1" 100 101 if b.config.RemoteType == "esx5" { 102 b.config.DiskTypeId = "zeroedthick" 103 } 104 } 105 106 if b.config.FloppyFiles == nil { 107 b.config.FloppyFiles = make([]string, 0) 108 } 109 110 if b.config.GuestOSType == "" { 111 b.config.GuestOSType = "other" 112 } 113 114 if b.config.VMName == "" { 115 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 116 } 117 118 if b.config.HTTPPortMin == 0 { 119 b.config.HTTPPortMin = 8000 120 } 121 122 if b.config.HTTPPortMax == 0 { 123 b.config.HTTPPortMax = 9000 124 } 125 126 if b.config.VNCPortMin == 0 { 127 b.config.VNCPortMin = 5900 128 } 129 130 if b.config.VNCPortMax == 0 { 131 b.config.VNCPortMax = 6000 132 } 133 134 if b.config.RemoteUser == "" { 135 b.config.RemoteUser = "root" 136 } 137 138 if b.config.RemoteDatastore == "" { 139 b.config.RemoteDatastore = "datastore1" 140 } 141 142 if b.config.RemotePort == 0 { 143 b.config.RemotePort = 22 144 } 145 146 // Errors 147 templates := map[string]*string{ 148 "disk_name": &b.config.DiskName, 149 "guest_os_type": &b.config.GuestOSType, 150 "http_directory": &b.config.HTTPDir, 151 "iso_checksum": &b.config.ISOChecksum, 152 "iso_checksum_type": &b.config.ISOChecksumType, 153 "iso_url": &b.config.RawSingleISOUrl, 154 "vm_name": &b.config.VMName, 155 "vmx_template_path": &b.config.VMXTemplatePath, 156 "remote_type": &b.config.RemoteType, 157 "remote_host": &b.config.RemoteHost, 158 "remote_datastore": &b.config.RemoteDatastore, 159 "remote_user": &b.config.RemoteUser, 160 "remote_password": &b.config.RemotePassword, 161 } 162 163 for n, ptr := range templates { 164 var err error 165 *ptr, err = b.config.tpl.Process(*ptr, nil) 166 if err != nil { 167 errs = packer.MultiErrorAppend( 168 errs, fmt.Errorf("Error processing %s: %s", n, err)) 169 } 170 } 171 172 for i, url := range b.config.ISOUrls { 173 var err error 174 b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil) 175 if err != nil { 176 errs = packer.MultiErrorAppend( 177 errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err)) 178 } 179 } 180 181 for i, command := range b.config.BootCommand { 182 if err := b.config.tpl.Validate(command); err != nil { 183 errs = packer.MultiErrorAppend(errs, 184 fmt.Errorf("Error processing boot_command[%d]: %s", i, err)) 185 } 186 } 187 188 for i, file := range b.config.FloppyFiles { 189 var err error 190 b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil) 191 if err != nil { 192 errs = packer.MultiErrorAppend(errs, 193 fmt.Errorf("Error processing floppy_files[%d]: %s", 194 i, err)) 195 } 196 } 197 198 if b.config.HTTPPortMin > b.config.HTTPPortMax { 199 errs = packer.MultiErrorAppend( 200 errs, errors.New("http_port_min must be less than http_port_max")) 201 } 202 203 if b.config.ISOChecksumType == "" { 204 errs = packer.MultiErrorAppend( 205 errs, errors.New("The iso_checksum_type must be specified.")) 206 } else { 207 b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) 208 if b.config.ISOChecksumType != "none" { 209 if b.config.ISOChecksum == "" { 210 errs = packer.MultiErrorAppend( 211 errs, errors.New("Due to large file sizes, an iso_checksum is required")) 212 } else { 213 b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) 214 } 215 216 if h := common.HashForType(b.config.ISOChecksumType); h == nil { 217 errs = packer.MultiErrorAppend( 218 errs, 219 fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) 220 } 221 } 222 } 223 224 if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { 225 errs = packer.MultiErrorAppend( 226 errs, errors.New("One of iso_url or iso_urls must be specified.")) 227 } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { 228 errs = packer.MultiErrorAppend( 229 errs, errors.New("Only one of iso_url or iso_urls may be specified.")) 230 } else if b.config.RawSingleISOUrl != "" { 231 b.config.ISOUrls = []string{b.config.RawSingleISOUrl} 232 } 233 234 for i, url := range b.config.ISOUrls { 235 b.config.ISOUrls[i], err = common.DownloadableURL(url) 236 if err != nil { 237 errs = packer.MultiErrorAppend( 238 errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) 239 } 240 } 241 242 if b.config.VMXTemplatePath != "" { 243 if err := b.validateVMXTemplatePath(); err != nil { 244 errs = packer.MultiErrorAppend( 245 errs, fmt.Errorf("vmx_template_path is invalid: %s", err)) 246 } 247 248 } 249 250 if b.config.VNCPortMin > b.config.VNCPortMax { 251 errs = packer.MultiErrorAppend( 252 errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) 253 } 254 255 // Remote configuration validation 256 if b.config.RemoteType != "" { 257 if b.config.RemoteHost == "" { 258 errs = packer.MultiErrorAppend(errs, 259 fmt.Errorf("remote_host must be specified")) 260 } 261 } 262 263 // Warnings 264 if b.config.ISOChecksumType == "none" { 265 warnings = append(warnings, 266 "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ 267 "a checksum is highly recommended.") 268 } 269 270 if b.config.ShutdownCommand == "" { 271 warnings = append(warnings, 272 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 273 "will forcibly halt the virtual machine, which may result in data loss.") 274 } 275 276 if errs != nil && len(errs.Errors) > 0 { 277 return warnings, errs 278 } 279 280 return warnings, nil 281 } 282 283 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 284 driver, err := NewDriver(&b.config) 285 if err != nil { 286 return nil, fmt.Errorf("Failed creating VMware driver: %s", err) 287 } 288 289 // Determine the output dir implementation 290 var dir OutputDir 291 switch d := driver.(type) { 292 case OutputDir: 293 dir = d 294 default: 295 dir = new(vmwcommon.LocalOutputDir) 296 } 297 dir.SetOutputDir(b.config.OutputDir) 298 299 // Setup the state bag 300 state := new(multistep.BasicStateBag) 301 state.Put("cache", cache) 302 state.Put("config", &b.config) 303 state.Put("dir", dir) 304 state.Put("driver", driver) 305 state.Put("hook", hook) 306 state.Put("ui", ui) 307 308 // Seed the random number generator 309 rand.Seed(time.Now().UTC().UnixNano()) 310 311 steps := []multistep.Step{ 312 &vmwcommon.StepPrepareTools{ 313 RemoteType: b.config.RemoteType, 314 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 315 }, 316 &common.StepDownload{ 317 Checksum: b.config.ISOChecksum, 318 ChecksumType: b.config.ISOChecksumType, 319 Description: "ISO", 320 ResultKey: "iso_path", 321 Url: b.config.ISOUrls, 322 }, 323 &vmwcommon.StepOutputDir{ 324 Force: b.config.PackerForce, 325 }, 326 &common.StepCreateFloppy{ 327 Files: b.config.FloppyFiles, 328 }, 329 &stepRemoteUpload{ 330 Key: "floppy_path", 331 Message: "Uploading Floppy to remote machine...", 332 }, 333 &stepRemoteUpload{ 334 Key: "iso_path", 335 Message: "Uploading ISO to remote machine...", 336 }, 337 &stepCreateDisk{}, 338 &stepCreateVMX{}, 339 &vmwcommon.StepConfigureVMX{ 340 CustomData: b.config.VMXData, 341 }, 342 &vmwcommon.StepSuppressMessages{}, 343 &stepHTTPServer{}, 344 &stepConfigureVNC{}, 345 &StepRegister{}, 346 &vmwcommon.StepRun{ 347 BootWait: b.config.BootWait, 348 DurationBeforeStop: 5 * time.Second, 349 Headless: b.config.Headless, 350 }, 351 &stepTypeBootCommand{}, 352 &common.StepConnectSSH{ 353 SSHAddress: driver.SSHAddress, 354 SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), 355 SSHWaitTimeout: b.config.SSHWaitTimeout, 356 NoPty: b.config.SSHSkipRequestPty, 357 }, 358 &vmwcommon.StepUploadTools{ 359 RemoteType: b.config.RemoteType, 360 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 361 ToolsUploadPath: b.config.ToolsUploadPath, 362 Tpl: b.config.tpl, 363 }, 364 &common.StepProvision{}, 365 &vmwcommon.StepShutdown{ 366 Command: b.config.ShutdownCommand, 367 Timeout: b.config.ShutdownTimeout, 368 }, 369 &vmwcommon.StepCleanFiles{}, 370 &vmwcommon.StepCleanVMX{}, 371 &vmwcommon.StepConfigureVMX{ 372 CustomData: b.config.VMXDataPost, 373 }, 374 &vmwcommon.StepCompactDisk{ 375 Skip: b.config.SkipCompaction, 376 }, 377 } 378 379 // Run! 380 if b.config.PackerDebug { 381 b.runner = &multistep.DebugRunner{ 382 Steps: steps, 383 PauseFn: common.MultistepDebugFn(ui), 384 } 385 } else { 386 b.runner = &multistep.BasicRunner{Steps: steps} 387 } 388 389 b.runner.Run(state) 390 391 // If there was an error, return that 392 if rawErr, ok := state.GetOk("error"); ok { 393 return nil, rawErr.(error) 394 } 395 396 // If we were interrupted or cancelled, then just exit. 397 if _, ok := state.GetOk(multistep.StateCancelled); ok { 398 return nil, errors.New("Build was cancelled.") 399 } 400 401 if _, ok := state.GetOk(multistep.StateHalted); ok { 402 return nil, errors.New("Build was halted.") 403 } 404 405 // Compile the artifact list 406 files, err := state.Get("dir").(OutputDir).ListFiles() 407 if err != nil { 408 return nil, err 409 } 410 411 // Set the proper builder ID 412 builderId := vmwcommon.BuilderId 413 if b.config.RemoteType != "" { 414 builderId = BuilderIdESX 415 } 416 417 return &Artifact{ 418 builderId: builderId, 419 dir: dir, 420 f: files, 421 }, nil 422 } 423 424 func (b *Builder) Cancel() { 425 if b.runner != nil { 426 log.Println("Cancelling the step runner...") 427 b.runner.Cancel() 428 } 429 } 430 431 func (b *Builder) validateVMXTemplatePath() error { 432 f, err := os.Open(b.config.VMXTemplatePath) 433 if err != nil { 434 return err 435 } 436 defer f.Close() 437 438 data, err := ioutil.ReadAll(f) 439 if err != nil { 440 return err 441 } 442 443 return b.config.tpl.Validate(string(data)) 444 }