github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/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 Version string `mapstructure:"version"` 44 VMName string `mapstructure:"vm_name"` 45 BootCommand []string `mapstructure:"boot_command"` 46 SkipCompaction bool `mapstructure:"skip_compaction"` 47 VMXTemplatePath string `mapstructure:"vmx_template_path"` 48 49 RemoteType string `mapstructure:"remote_type"` 50 RemoteDatastore string `mapstructure:"remote_datastore"` 51 RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"` 52 RemoteCacheDirectory string `mapstructure:"remote_cache_directory"` 53 RemoteHost string `mapstructure:"remote_host"` 54 RemotePort uint `mapstructure:"remote_port"` 55 RemoteUser string `mapstructure:"remote_username"` 56 RemotePassword string `mapstructure:"remote_password"` 57 58 RawSingleISOUrl string `mapstructure:"iso_url"` 59 60 tpl *packer.ConfigTemplate 61 } 62 63 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 64 md, err := common.DecodeConfig(&b.config, raws...) 65 if err != nil { 66 return nil, err 67 } 68 69 b.config.tpl, err = packer.NewConfigTemplate() 70 if err != nil { 71 return nil, err 72 } 73 b.config.tpl.UserVars = b.config.PackerUserVars 74 75 // Accumulate any errors 76 errs := common.CheckUnusedConfig(md) 77 errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(b.config.tpl)...) 78 errs = packer.MultiErrorAppend(errs, 79 b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) 80 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) 81 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) 82 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) 83 errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(b.config.tpl)...) 84 errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...) 85 warnings := make([]string, 0) 86 87 if b.config.DiskName == "" { 88 b.config.DiskName = "disk" 89 } 90 91 if b.config.DiskSize == 0 { 92 b.config.DiskSize = 40000 93 } 94 95 if b.config.DiskTypeId == "" { 96 // Default is growable virtual disk split in 2GB files. 97 b.config.DiskTypeId = "1" 98 99 if b.config.RemoteType == "esx5" { 100 b.config.DiskTypeId = "zeroedthick" 101 } 102 } 103 104 if b.config.FloppyFiles == nil { 105 b.config.FloppyFiles = make([]string, 0) 106 } 107 108 if b.config.GuestOSType == "" { 109 b.config.GuestOSType = "other" 110 } 111 112 if b.config.VMName == "" { 113 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 114 } 115 116 if b.config.Version == "" { 117 b.config.Version = "9" 118 } 119 120 if b.config.RemoteUser == "" { 121 b.config.RemoteUser = "root" 122 } 123 124 if b.config.RemoteDatastore == "" { 125 b.config.RemoteDatastore = "datastore1" 126 } 127 128 if b.config.RemoteCacheDatastore == "" { 129 b.config.RemoteCacheDatastore = b.config.RemoteDatastore 130 } 131 132 if b.config.RemoteCacheDirectory == "" { 133 b.config.RemoteCacheDirectory = "packer_cache" 134 } 135 136 if b.config.RemotePort == 0 { 137 b.config.RemotePort = 22 138 } 139 140 // Errors 141 templates := map[string]*string{ 142 "disk_name": &b.config.DiskName, 143 "guest_os_type": &b.config.GuestOSType, 144 "iso_checksum": &b.config.ISOChecksum, 145 "iso_checksum_type": &b.config.ISOChecksumType, 146 "iso_url": &b.config.RawSingleISOUrl, 147 "vm_name": &b.config.VMName, 148 "vmx_template_path": &b.config.VMXTemplatePath, 149 "remote_type": &b.config.RemoteType, 150 "remote_host": &b.config.RemoteHost, 151 "remote_datastore": &b.config.RemoteDatastore, 152 "remote_cache_datastore": &b.config.RemoteCacheDatastore, 153 "remote_cache_directory": &b.config.RemoteCacheDirectory, 154 "remote_user": &b.config.RemoteUser, 155 "remote_password": &b.config.RemotePassword, 156 } 157 158 for n, ptr := range templates { 159 var err error 160 *ptr, err = b.config.tpl.Process(*ptr, nil) 161 if err != nil { 162 errs = packer.MultiErrorAppend( 163 errs, fmt.Errorf("Error processing %s: %s", n, err)) 164 } 165 } 166 167 for i, url := range b.config.ISOUrls { 168 var err error 169 b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil) 170 if err != nil { 171 errs = packer.MultiErrorAppend( 172 errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err)) 173 } 174 } 175 176 for i, command := range b.config.BootCommand { 177 if err := b.config.tpl.Validate(command); err != nil { 178 errs = packer.MultiErrorAppend(errs, 179 fmt.Errorf("Error processing boot_command[%d]: %s", i, err)) 180 } 181 } 182 183 for i, file := range b.config.FloppyFiles { 184 var err error 185 b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil) 186 if err != nil { 187 errs = packer.MultiErrorAppend(errs, 188 fmt.Errorf("Error processing floppy_files[%d]: %s", 189 i, err)) 190 } 191 } 192 193 if b.config.ISOChecksumType == "" { 194 errs = packer.MultiErrorAppend( 195 errs, errors.New("The iso_checksum_type must be specified.")) 196 } else { 197 b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) 198 if b.config.ISOChecksumType != "none" { 199 if b.config.ISOChecksum == "" { 200 errs = packer.MultiErrorAppend( 201 errs, errors.New("Due to large file sizes, an iso_checksum is required")) 202 } else { 203 b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) 204 } 205 206 if h := common.HashForType(b.config.ISOChecksumType); h == nil { 207 errs = packer.MultiErrorAppend( 208 errs, 209 fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) 210 } 211 } 212 } 213 214 if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { 215 errs = packer.MultiErrorAppend( 216 errs, errors.New("One of iso_url or iso_urls must be specified.")) 217 } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { 218 errs = packer.MultiErrorAppend( 219 errs, errors.New("Only one of iso_url or iso_urls may be specified.")) 220 } else if b.config.RawSingleISOUrl != "" { 221 b.config.ISOUrls = []string{b.config.RawSingleISOUrl} 222 } 223 224 for i, url := range b.config.ISOUrls { 225 b.config.ISOUrls[i], err = common.DownloadableURL(url) 226 if err != nil { 227 errs = packer.MultiErrorAppend( 228 errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) 229 } 230 } 231 232 if b.config.VMXTemplatePath != "" { 233 if err := b.validateVMXTemplatePath(); err != nil { 234 errs = packer.MultiErrorAppend( 235 errs, fmt.Errorf("vmx_template_path is invalid: %s", err)) 236 } 237 238 } 239 240 // Remote configuration validation 241 if b.config.RemoteType != "" { 242 if b.config.RemoteHost == "" { 243 errs = packer.MultiErrorAppend(errs, 244 fmt.Errorf("remote_host must be specified")) 245 } 246 } 247 248 // Warnings 249 if b.config.ISOChecksumType == "none" { 250 warnings = append(warnings, 251 "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ 252 "a checksum is highly recommended.") 253 } 254 255 if b.config.ShutdownCommand == "" { 256 warnings = append(warnings, 257 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 258 "will forcibly halt the virtual machine, which may result in data loss.") 259 } 260 261 if errs != nil && len(errs.Errors) > 0 { 262 return warnings, errs 263 } 264 265 return warnings, nil 266 } 267 268 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 269 driver, err := NewDriver(&b.config) 270 if err != nil { 271 return nil, fmt.Errorf("Failed creating VMware driver: %s", err) 272 } 273 274 // Determine the output dir implementation 275 var dir OutputDir 276 switch d := driver.(type) { 277 case OutputDir: 278 dir = d 279 default: 280 dir = new(vmwcommon.LocalOutputDir) 281 } 282 dir.SetOutputDir(b.config.OutputDir) 283 284 // Setup the state bag 285 state := new(multistep.BasicStateBag) 286 state.Put("cache", cache) 287 state.Put("config", &b.config) 288 state.Put("dir", dir) 289 state.Put("driver", driver) 290 state.Put("hook", hook) 291 state.Put("ui", ui) 292 293 // Seed the random number generator 294 rand.Seed(time.Now().UTC().UnixNano()) 295 296 steps := []multistep.Step{ 297 &vmwcommon.StepPrepareTools{ 298 RemoteType: b.config.RemoteType, 299 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 300 }, 301 &common.StepDownload{ 302 Checksum: b.config.ISOChecksum, 303 ChecksumType: b.config.ISOChecksumType, 304 Description: "ISO", 305 ResultKey: "iso_path", 306 Url: b.config.ISOUrls, 307 }, 308 &vmwcommon.StepOutputDir{ 309 Force: b.config.PackerForce, 310 }, 311 &common.StepCreateFloppy{ 312 Files: b.config.FloppyFiles, 313 }, 314 &stepRemoteUpload{ 315 Key: "floppy_path", 316 Message: "Uploading Floppy to remote machine...", 317 }, 318 &stepRemoteUpload{ 319 Key: "iso_path", 320 Message: "Uploading ISO to remote machine...", 321 }, 322 &stepCreateDisk{}, 323 &stepCreateVMX{}, 324 &vmwcommon.StepConfigureVMX{ 325 CustomData: b.config.VMXData, 326 }, 327 &vmwcommon.StepSuppressMessages{}, 328 &vmwcommon.StepHTTPServer{ 329 HTTPDir: b.config.HTTPDir, 330 HTTPPortMin: b.config.HTTPPortMin, 331 HTTPPortMax: b.config.HTTPPortMax, 332 }, 333 &vmwcommon.StepConfigureVNC{ 334 VNCPortMin: b.config.VNCPortMin, 335 VNCPortMax: b.config.VNCPortMax, 336 }, 337 &StepRegister{}, 338 &vmwcommon.StepRun{ 339 BootWait: b.config.BootWait, 340 DurationBeforeStop: 5 * time.Second, 341 Headless: b.config.Headless, 342 }, 343 &vmwcommon.StepTypeBootCommand{ 344 BootCommand: b.config.BootCommand, 345 VMName: b.config.VMName, 346 Tpl: b.config.tpl, 347 }, 348 &common.StepConnectSSH{ 349 SSHAddress: driver.SSHAddress, 350 SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), 351 SSHWaitTimeout: b.config.SSHWaitTimeout, 352 NoPty: b.config.SSHSkipRequestPty, 353 }, 354 &vmwcommon.StepUploadTools{ 355 RemoteType: b.config.RemoteType, 356 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 357 ToolsUploadPath: b.config.ToolsUploadPath, 358 Tpl: b.config.tpl, 359 }, 360 &common.StepProvision{}, 361 &vmwcommon.StepShutdown{ 362 Command: b.config.ShutdownCommand, 363 Timeout: b.config.ShutdownTimeout, 364 }, 365 &vmwcommon.StepCleanFiles{}, 366 &vmwcommon.StepConfigureVMX{ 367 CustomData: b.config.VMXDataPost, 368 SkipFloppy: true, 369 }, 370 &vmwcommon.StepCleanVMX{}, 371 &StepUploadVMX{ 372 RemoteType: b.config.RemoteType, 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 }