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