github.phpd.cn/hashicorp/packer@v1.3.2/builder/vmware/iso/builder.go (about) 1 package iso 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "strconv" 10 "time" 11 12 vmwcommon "github.com/hashicorp/packer/builder/vmware/common" 13 "github.com/hashicorp/packer/common" 14 "github.com/hashicorp/packer/common/bootcommand" 15 "github.com/hashicorp/packer/helper/communicator" 16 "github.com/hashicorp/packer/helper/config" 17 "github.com/hashicorp/packer/helper/multistep" 18 "github.com/hashicorp/packer/packer" 19 "github.com/hashicorp/packer/template/interpolate" 20 ) 21 22 const BuilderIdESX = "mitchellh.vmware-esx" 23 24 type Builder struct { 25 config Config 26 runner multistep.Runner 27 } 28 29 type Config struct { 30 common.PackerConfig `mapstructure:",squash"` 31 common.HTTPConfig `mapstructure:",squash"` 32 common.ISOConfig `mapstructure:",squash"` 33 common.FloppyConfig `mapstructure:",squash"` 34 bootcommand.VNCConfig `mapstructure:",squash"` 35 vmwcommon.DriverConfig `mapstructure:",squash"` 36 vmwcommon.OutputConfig `mapstructure:",squash"` 37 vmwcommon.RunConfig `mapstructure:",squash"` 38 vmwcommon.ShutdownConfig `mapstructure:",squash"` 39 vmwcommon.SSHConfig `mapstructure:",squash"` 40 vmwcommon.ToolsConfig `mapstructure:",squash"` 41 vmwcommon.VMXConfig `mapstructure:",squash"` 42 43 // disk drives 44 AdditionalDiskSize []uint `mapstructure:"disk_additional_size"` 45 DiskAdapterType string `mapstructure:"disk_adapter_type"` 46 DiskName string `mapstructure:"vmdk_name"` 47 DiskSize uint `mapstructure:"disk_size"` 48 DiskTypeId string `mapstructure:"disk_type_id"` 49 Format string `mapstructure:"format"` 50 51 // cdrom drive 52 CdromAdapterType string `mapstructure:"cdrom_adapter_type"` 53 54 // platform information 55 GuestOSType string `mapstructure:"guest_os_type"` 56 Version string `mapstructure:"version"` 57 VMName string `mapstructure:"vm_name"` 58 59 // Network adapter and type 60 NetworkAdapterType string `mapstructure:"network_adapter_type"` 61 Network string `mapstructure:"network"` 62 63 // device presence 64 Sound bool `mapstructure:"sound"` 65 USB bool `mapstructure:"usb"` 66 67 // communication ports 68 Serial string `mapstructure:"serial"` 69 Parallel string `mapstructure:"parallel"` 70 71 // booting a guest 72 KeepRegistered bool `mapstructure:"keep_registered"` 73 OVFToolOptions []string `mapstructure:"ovftool_options"` 74 SkipCompaction bool `mapstructure:"skip_compaction"` 75 SkipExport bool `mapstructure:"skip_export"` 76 VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"` 77 VMXTemplatePath string `mapstructure:"vmx_template_path"` 78 79 // remote vsphere 80 RemoteType string `mapstructure:"remote_type"` 81 RemoteDatastore string `mapstructure:"remote_datastore"` 82 RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"` 83 RemoteCacheDirectory string `mapstructure:"remote_cache_directory"` 84 RemoteHost string `mapstructure:"remote_host"` 85 RemotePort uint `mapstructure:"remote_port"` 86 RemoteUser string `mapstructure:"remote_username"` 87 RemotePassword string `mapstructure:"remote_password"` 88 RemotePrivateKey string `mapstructure:"remote_private_key_file"` 89 90 CommConfig communicator.Config `mapstructure:",squash"` 91 92 ctx interpolate.Context 93 } 94 95 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 96 err := config.Decode(&b.config, &config.DecodeOpts{ 97 Interpolate: true, 98 InterpolateContext: &b.config.ctx, 99 InterpolateFilter: &interpolate.RenderFilter{ 100 Exclude: []string{ 101 "boot_command", 102 "tools_upload_path", 103 }, 104 }, 105 }, raws...) 106 if err != nil { 107 return nil, err 108 } 109 110 // Accumulate any errors and warnings 111 var errs *packer.MultiError 112 warnings := make([]string, 0) 113 114 isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) 115 warnings = append(warnings, isoWarnings...) 116 errs = packer.MultiErrorAppend(errs, isoErrs...) 117 errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) 118 errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(&b.config.ctx)...) 119 errs = packer.MultiErrorAppend(errs, 120 b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) 121 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) 122 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) 123 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) 124 errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...) 125 errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...) 126 errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) 127 errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...) 128 129 if b.config.DiskName == "" { 130 b.config.DiskName = "disk" 131 } 132 133 if b.config.DiskSize == 0 { 134 b.config.DiskSize = 40000 135 } 136 137 if b.config.DiskAdapterType == "" { 138 // Default is lsilogic 139 b.config.DiskAdapterType = "lsilogic" 140 } 141 142 if !b.config.SkipCompaction { 143 if b.config.RemoteType == "esx5" { 144 if b.config.DiskTypeId == "" { 145 b.config.SkipCompaction = true 146 } 147 } 148 } 149 150 if b.config.DiskTypeId == "" { 151 // Default is growable virtual disk split in 2GB files. 152 b.config.DiskTypeId = "1" 153 154 if b.config.RemoteType == "esx5" { 155 b.config.DiskTypeId = "zeroedthick" 156 } 157 } 158 159 if b.config.RemoteType == "esx5" { 160 if b.config.DiskTypeId != "thin" && !b.config.SkipCompaction { 161 errs = packer.MultiErrorAppend( 162 errs, fmt.Errorf("skip_compaction must be 'true' for disk_type_id: %s", b.config.DiskTypeId)) 163 } 164 } 165 166 if b.config.GuestOSType == "" { 167 b.config.GuestOSType = "other" 168 } 169 170 if b.config.VMName == "" { 171 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 172 } 173 174 if b.config.Version == "" { 175 b.config.Version = "9" 176 } 177 178 if b.config.RemoteUser == "" { 179 b.config.RemoteUser = "root" 180 } 181 182 if b.config.RemoteDatastore == "" { 183 b.config.RemoteDatastore = "datastore1" 184 } 185 186 if b.config.RemoteCacheDatastore == "" { 187 b.config.RemoteCacheDatastore = b.config.RemoteDatastore 188 } 189 190 if b.config.RemoteCacheDirectory == "" { 191 b.config.RemoteCacheDirectory = "packer_cache" 192 } 193 194 if b.config.RemotePort == 0 { 195 b.config.RemotePort = 22 196 } 197 198 if b.config.VMXTemplatePath != "" { 199 if err := b.validateVMXTemplatePath(); err != nil { 200 errs = packer.MultiErrorAppend( 201 errs, fmt.Errorf("vmx_template_path is invalid: %s", err)) 202 } 203 204 } 205 206 if b.config.Network == "" { 207 b.config.Network = "nat" 208 } 209 210 if !b.config.Sound { 211 b.config.Sound = false 212 } 213 214 if !b.config.USB { 215 b.config.USB = false 216 } 217 218 // Remote configuration validation 219 if b.config.RemoteType != "" { 220 if b.config.RemoteHost == "" { 221 errs = packer.MultiErrorAppend(errs, 222 fmt.Errorf("remote_host must be specified")) 223 } 224 if b.config.RemoteType != "esx5" { 225 errs = packer.MultiErrorAppend(errs, 226 fmt.Errorf("Only 'esx5' value is accepted for remote_type")) 227 } 228 } 229 230 if b.config.Format == "" { 231 b.config.Format = "ovf" 232 } 233 234 if !(b.config.Format == "ova" || b.config.Format == "ovf" || b.config.Format == "vmx") { 235 errs = packer.MultiErrorAppend(errs, 236 fmt.Errorf("format must be one of ova, ovf, or vmx")) 237 } 238 239 if b.config.RemoteType == "esx5" && b.config.SkipExport != true && b.config.RemotePassword == "" { 240 errs = packer.MultiErrorAppend(errs, 241 fmt.Errorf("exporting the vm (with ovftool) requires that you set a value for remote_password")) 242 } 243 244 // Warnings 245 if b.config.ShutdownCommand == "" { 246 warnings = append(warnings, 247 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 248 "will forcibly halt the virtual machine, which may result in data loss.") 249 } 250 251 if b.config.Headless && b.config.DisableVNC { 252 warnings = append(warnings, 253 "Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+ 254 "you won't be able to see any output.") 255 } 256 257 if errs != nil && len(errs.Errors) > 0 { 258 return warnings, errs 259 } 260 261 return warnings, nil 262 } 263 264 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 265 driver, err := NewDriver(&b.config) 266 if err != nil { 267 return nil, fmt.Errorf("Failed creating VMware driver: %s", err) 268 } 269 270 // Determine the output dir implementation 271 var dir OutputDir 272 switch d := driver.(type) { 273 case OutputDir: 274 dir = d 275 default: 276 dir = new(vmwcommon.LocalOutputDir) 277 } 278 279 exportOutputPath := b.config.OutputDir 280 281 if b.config.RemoteType != "" { 282 b.config.OutputDir = b.config.VMName 283 } 284 dir.SetOutputDir(b.config.OutputDir) 285 286 // Setup the state bag 287 state := new(multistep.BasicStateBag) 288 state.Put("cache", cache) 289 state.Put("config", &b.config) 290 state.Put("debug", b.config.PackerDebug) 291 state.Put("dir", dir) 292 state.Put("driver", driver) 293 state.Put("hook", hook) 294 state.Put("ui", ui) 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 Extension: b.config.TargetExtension, 306 ResultKey: "iso_path", 307 TargetPath: b.config.TargetPath, 308 Url: b.config.ISOUrls, 309 }, 310 &vmwcommon.StepOutputDir{ 311 Force: b.config.PackerForce, 312 }, 313 &common.StepCreateFloppy{ 314 Files: b.config.FloppyConfig.FloppyFiles, 315 Directories: b.config.FloppyConfig.FloppyDirectories, 316 }, 317 &stepRemoteUpload{ 318 Key: "floppy_path", 319 Message: "Uploading Floppy to remote machine...", 320 DoCleanup: true, 321 }, 322 &stepRemoteUpload{ 323 Key: "iso_path", 324 Message: "Uploading ISO to remote machine...", 325 }, 326 &stepCreateDisk{}, 327 &stepCreateVMX{}, 328 &vmwcommon.StepConfigureVMX{ 329 CustomData: b.config.VMXData, 330 }, 331 &vmwcommon.StepSuppressMessages{}, 332 &common.StepHTTPServer{ 333 HTTPDir: b.config.HTTPDir, 334 HTTPPortMin: b.config.HTTPPortMin, 335 HTTPPortMax: b.config.HTTPPortMax, 336 }, 337 &vmwcommon.StepConfigureVNC{ 338 Enabled: !b.config.DisableVNC, 339 VNCBindAddress: b.config.VNCBindAddress, 340 VNCPortMin: b.config.VNCPortMin, 341 VNCPortMax: b.config.VNCPortMax, 342 VNCDisablePassword: b.config.VNCDisablePassword, 343 }, 344 &StepRegister{ 345 Format: b.config.Format, 346 }, 347 &vmwcommon.StepRun{ 348 DurationBeforeStop: 5 * time.Second, 349 Headless: b.config.Headless, 350 }, 351 &vmwcommon.StepTypeBootCommand{ 352 BootWait: b.config.BootWait, 353 VNCEnabled: !b.config.DisableVNC, 354 BootCommand: b.config.FlatBootCommand(), 355 VMName: b.config.VMName, 356 Ctx: b.config.ctx, 357 KeyInterval: b.config.VNCConfig.BootKeyInterval, 358 }, 359 &communicator.StepConnect{ 360 Config: &b.config.SSHConfig.Comm, 361 Host: driver.CommHost, 362 SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(), 363 }, 364 &vmwcommon.StepUploadTools{ 365 RemoteType: b.config.RemoteType, 366 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 367 ToolsUploadPath: b.config.ToolsUploadPath, 368 Ctx: b.config.ctx, 369 }, 370 &common.StepProvision{}, 371 &common.StepCleanupTempKeys{ 372 Comm: &b.config.SSHConfig.Comm, 373 }, 374 &vmwcommon.StepShutdown{ 375 Command: b.config.ShutdownCommand, 376 Timeout: b.config.ShutdownTimeout, 377 }, 378 &vmwcommon.StepCleanFiles{}, 379 &vmwcommon.StepCompactDisk{ 380 Skip: b.config.SkipCompaction, 381 }, 382 &vmwcommon.StepConfigureVMX{ 383 CustomData: b.config.VMXDataPost, 384 SkipFloppy: true, 385 }, 386 &vmwcommon.StepCleanVMX{ 387 RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet, 388 VNCEnabled: !b.config.DisableVNC, 389 }, 390 &StepUploadVMX{ 391 RemoteType: b.config.RemoteType, 392 }, 393 &StepExport{ 394 Format: b.config.Format, 395 SkipExport: b.config.SkipExport, 396 OutputDir: exportOutputPath, 397 }, 398 } 399 400 // Run! 401 b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state) 402 b.runner.Run(state) 403 404 // If there was an error, return that 405 if rawErr, ok := state.GetOk("error"); ok { 406 return nil, rawErr.(error) 407 } 408 409 // If we were interrupted or cancelled, then just exit. 410 if _, ok := state.GetOk(multistep.StateCancelled); ok { 411 return nil, errors.New("Build was cancelled.") 412 } 413 414 if _, ok := state.GetOk(multistep.StateHalted); ok { 415 return nil, errors.New("Build was halted.") 416 } 417 418 // Compile the artifact list 419 var files []string 420 if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport { 421 dir = new(vmwcommon.LocalOutputDir) 422 dir.SetOutputDir(exportOutputPath) 423 files, err = dir.ListFiles() 424 } else { 425 files, err = state.Get("dir").(OutputDir).ListFiles() 426 } 427 if err != nil { 428 return nil, err 429 } 430 431 // Set the proper builder ID 432 builderId := vmwcommon.BuilderId 433 if b.config.RemoteType != "" { 434 builderId = BuilderIdESX 435 } 436 437 config := make(map[string]string) 438 config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered) 439 config[ArtifactConfFormat] = b.config.Format 440 config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport) 441 442 return &Artifact{ 443 builderId: builderId, 444 id: b.config.VMName, 445 dir: dir, 446 f: files, 447 config: config, 448 }, nil 449 } 450 451 func (b *Builder) Cancel() { 452 if b.runner != nil { 453 log.Println("Cancelling the step runner...") 454 b.runner.Cancel() 455 } 456 } 457 458 func (b *Builder) validateVMXTemplatePath() error { 459 f, err := os.Open(b.config.VMXTemplatePath) 460 if err != nil { 461 return err 462 } 463 defer f.Close() 464 465 data, err := ioutil.ReadAll(f) 466 if err != nil { 467 return err 468 } 469 470 return interpolate.Validate(string(data), &b.config.ctx) 471 }