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