github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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.DiskTypeId == "" { 143 // Default is growable virtual disk split in 2GB files. 144 b.config.DiskTypeId = "1" 145 146 if b.config.RemoteType == "esx5" { 147 b.config.DiskTypeId = "zeroedthick" 148 } 149 } 150 151 if b.config.GuestOSType == "" { 152 b.config.GuestOSType = "other" 153 } 154 155 if b.config.VMName == "" { 156 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 157 } 158 159 if b.config.Version == "" { 160 b.config.Version = "9" 161 } 162 163 if b.config.RemoteUser == "" { 164 b.config.RemoteUser = "root" 165 } 166 167 if b.config.RemoteDatastore == "" { 168 b.config.RemoteDatastore = "datastore1" 169 } 170 171 if b.config.RemoteCacheDatastore == "" { 172 b.config.RemoteCacheDatastore = b.config.RemoteDatastore 173 } 174 175 if b.config.RemoteCacheDirectory == "" { 176 b.config.RemoteCacheDirectory = "packer_cache" 177 } 178 179 if b.config.RemotePort == 0 { 180 b.config.RemotePort = 22 181 } 182 183 if b.config.VMXTemplatePath != "" { 184 if err := b.validateVMXTemplatePath(); err != nil { 185 errs = packer.MultiErrorAppend( 186 errs, fmt.Errorf("vmx_template_path is invalid: %s", err)) 187 } 188 189 } 190 191 if b.config.Network == "" { 192 b.config.Network = "nat" 193 } 194 195 if !b.config.Sound { 196 b.config.Sound = false 197 } 198 199 if !b.config.USB { 200 b.config.USB = false 201 } 202 203 // Remote configuration validation 204 if b.config.RemoteType != "" { 205 if b.config.RemoteHost == "" { 206 errs = packer.MultiErrorAppend(errs, 207 fmt.Errorf("remote_host must be specified")) 208 } 209 if b.config.RemoteType != "esx5" { 210 errs = packer.MultiErrorAppend(errs, 211 fmt.Errorf("Only 'esx5' value is accepted for remote_type")) 212 } 213 } 214 215 if b.config.Format == "" { 216 b.config.Format = "ovf" 217 } 218 219 if !(b.config.Format == "ova" || b.config.Format == "ovf" || b.config.Format == "vmx") { 220 errs = packer.MultiErrorAppend(errs, 221 fmt.Errorf("format must be one of ova, ovf, or vmx")) 222 } 223 224 if b.config.RemoteType == "esx5" && b.config.SkipExport != true && b.config.RemotePassword == "" { 225 errs = packer.MultiErrorAppend(errs, 226 fmt.Errorf("exporting the vm (with ovftool) requires that you set a value for remote_password")) 227 } 228 229 // Warnings 230 if b.config.ShutdownCommand == "" { 231 warnings = append(warnings, 232 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 233 "will forcibly halt the virtual machine, which may result in data loss.") 234 } 235 236 if b.config.Headless && b.config.DisableVNC { 237 warnings = append(warnings, 238 "Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+ 239 "you won't be able to see any output.") 240 } 241 242 if errs != nil && len(errs.Errors) > 0 { 243 return warnings, errs 244 } 245 246 return warnings, nil 247 } 248 249 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 250 driver, err := NewDriver(&b.config) 251 if err != nil { 252 return nil, fmt.Errorf("Failed creating VMware driver: %s", err) 253 } 254 255 // Determine the output dir implementation 256 var dir OutputDir 257 switch d := driver.(type) { 258 case OutputDir: 259 dir = d 260 default: 261 dir = new(vmwcommon.LocalOutputDir) 262 } 263 264 exportOutputPath := b.config.OutputDir 265 266 if b.config.RemoteType != "" { 267 b.config.OutputDir = b.config.VMName 268 } 269 dir.SetOutputDir(b.config.OutputDir) 270 271 // Setup the state bag 272 state := new(multistep.BasicStateBag) 273 state.Put("cache", cache) 274 state.Put("config", &b.config) 275 state.Put("debug", b.config.PackerDebug) 276 state.Put("dir", dir) 277 state.Put("driver", driver) 278 state.Put("hook", hook) 279 state.Put("ui", ui) 280 281 steps := []multistep.Step{ 282 &vmwcommon.StepPrepareTools{ 283 RemoteType: b.config.RemoteType, 284 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 285 }, 286 &common.StepDownload{ 287 Checksum: b.config.ISOChecksum, 288 ChecksumType: b.config.ISOChecksumType, 289 Description: "ISO", 290 Extension: b.config.TargetExtension, 291 ResultKey: "iso_path", 292 TargetPath: b.config.TargetPath, 293 Url: b.config.ISOUrls, 294 }, 295 &vmwcommon.StepOutputDir{ 296 Force: b.config.PackerForce, 297 }, 298 &common.StepCreateFloppy{ 299 Files: b.config.FloppyConfig.FloppyFiles, 300 Directories: b.config.FloppyConfig.FloppyDirectories, 301 }, 302 &stepRemoteUpload{ 303 Key: "floppy_path", 304 Message: "Uploading Floppy to remote machine...", 305 DoCleanup: true, 306 }, 307 &stepRemoteUpload{ 308 Key: "iso_path", 309 Message: "Uploading ISO to remote machine...", 310 }, 311 &stepCreateDisk{}, 312 &stepCreateVMX{}, 313 &vmwcommon.StepConfigureVMX{ 314 CustomData: b.config.VMXData, 315 }, 316 &vmwcommon.StepSuppressMessages{}, 317 &common.StepHTTPServer{ 318 HTTPDir: b.config.HTTPDir, 319 HTTPPortMin: b.config.HTTPPortMin, 320 HTTPPortMax: b.config.HTTPPortMax, 321 }, 322 &vmwcommon.StepConfigureVNC{ 323 Enabled: !b.config.DisableVNC, 324 VNCBindAddress: b.config.VNCBindAddress, 325 VNCPortMin: b.config.VNCPortMin, 326 VNCPortMax: b.config.VNCPortMax, 327 VNCDisablePassword: b.config.VNCDisablePassword, 328 }, 329 &StepRegister{ 330 Format: b.config.Format, 331 }, 332 &vmwcommon.StepRun{ 333 DurationBeforeStop: 5 * time.Second, 334 Headless: b.config.Headless, 335 }, 336 &vmwcommon.StepTypeBootCommand{ 337 BootWait: b.config.BootWait, 338 VNCEnabled: !b.config.DisableVNC, 339 BootCommand: b.config.FlatBootCommand(), 340 VMName: b.config.VMName, 341 Ctx: b.config.ctx, 342 }, 343 &communicator.StepConnect{ 344 Config: &b.config.SSHConfig.Comm, 345 Host: driver.CommHost, 346 SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), 347 }, 348 &vmwcommon.StepUploadTools{ 349 RemoteType: b.config.RemoteType, 350 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 351 ToolsUploadPath: b.config.ToolsUploadPath, 352 Ctx: b.config.ctx, 353 }, 354 &common.StepProvision{}, 355 &vmwcommon.StepShutdown{ 356 Command: b.config.ShutdownCommand, 357 Timeout: b.config.ShutdownTimeout, 358 }, 359 &vmwcommon.StepCleanFiles{}, 360 &vmwcommon.StepCompactDisk{ 361 Skip: b.config.SkipCompaction, 362 }, 363 &vmwcommon.StepConfigureVMX{ 364 CustomData: b.config.VMXDataPost, 365 SkipFloppy: true, 366 }, 367 &vmwcommon.StepCleanVMX{ 368 RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet, 369 VNCEnabled: !b.config.DisableVNC, 370 }, 371 &StepUploadVMX{ 372 RemoteType: b.config.RemoteType, 373 }, 374 &StepExport{ 375 Format: b.config.Format, 376 SkipExport: b.config.SkipExport, 377 OutputDir: exportOutputPath, 378 }, 379 } 380 381 // Run! 382 b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state) 383 b.runner.Run(state) 384 385 // If there was an error, return that 386 if rawErr, ok := state.GetOk("error"); ok { 387 return nil, rawErr.(error) 388 } 389 390 // If we were interrupted or cancelled, then just exit. 391 if _, ok := state.GetOk(multistep.StateCancelled); ok { 392 return nil, errors.New("Build was cancelled.") 393 } 394 395 if _, ok := state.GetOk(multistep.StateHalted); ok { 396 return nil, errors.New("Build was halted.") 397 } 398 399 // Compile the artifact list 400 var files []string 401 if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport { 402 dir = new(vmwcommon.LocalOutputDir) 403 dir.SetOutputDir(exportOutputPath) 404 files, err = dir.ListFiles() 405 } else { 406 files, err = state.Get("dir").(OutputDir).ListFiles() 407 } 408 if err != nil { 409 return nil, err 410 } 411 412 // Set the proper builder ID 413 builderId := vmwcommon.BuilderId 414 if b.config.RemoteType != "" { 415 builderId = BuilderIdESX 416 } 417 418 config := make(map[string]string) 419 config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered) 420 config[ArtifactConfFormat] = b.config.Format 421 config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport) 422 423 return &Artifact{ 424 builderId: builderId, 425 id: b.config.VMName, 426 dir: dir, 427 f: files, 428 config: config, 429 }, nil 430 } 431 432 func (b *Builder) Cancel() { 433 if b.runner != nil { 434 log.Println("Cancelling the step runner...") 435 b.runner.Cancel() 436 } 437 } 438 439 func (b *Builder) validateVMXTemplatePath() error { 440 f, err := os.Open(b.config.VMXTemplatePath) 441 if err != nil { 442 return err 443 } 444 defer f.Close() 445 446 data, err := ioutil.ReadAll(f) 447 if err != nil { 448 return err 449 } 450 451 return interpolate.Validate(string(data), &b.config.ctx) 452 }