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