github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/vmware/iso/builder.go (about) 1 package iso 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "os" 9 "time" 10 11 vmwcommon "github.com/hashicorp/packer/builder/vmware/common" 12 "github.com/hashicorp/packer/common" 13 "github.com/hashicorp/packer/helper/communicator" 14 "github.com/hashicorp/packer/helper/config" 15 "github.com/hashicorp/packer/packer" 16 "github.com/hashicorp/packer/template/interpolate" 17 "github.com/mitchellh/multistep" 18 ) 19 20 const BuilderIdESX = "mitchellh.vmware-esx" 21 22 type Builder struct { 23 config Config 24 runner multistep.Runner 25 } 26 27 type Config struct { 28 common.PackerConfig `mapstructure:",squash"` 29 common.HTTPConfig `mapstructure:",squash"` 30 common.ISOConfig `mapstructure:",squash"` 31 common.FloppyConfig `mapstructure:",squash"` 32 vmwcommon.DriverConfig `mapstructure:",squash"` 33 vmwcommon.OutputConfig `mapstructure:",squash"` 34 vmwcommon.RunConfig `mapstructure:",squash"` 35 vmwcommon.ShutdownConfig `mapstructure:",squash"` 36 vmwcommon.SSHConfig `mapstructure:",squash"` 37 vmwcommon.ToolsConfig `mapstructure:",squash"` 38 vmwcommon.VMXConfig `mapstructure:",squash"` 39 40 AdditionalDiskSize []uint `mapstructure:"disk_additional_size"` 41 BootCommand []string `mapstructure:"boot_command"` 42 DiskName string `mapstructure:"vmdk_name"` 43 DiskSize uint `mapstructure:"disk_size"` 44 DiskTypeId string `mapstructure:"disk_type_id"` 45 Format string `mapstructure:"format"` 46 GuestOSType string `mapstructure:"guest_os_type"` 47 KeepRegistered bool `mapstructure:"keep_registered"` 48 OVFToolOptions []string `mapstructure:"ovftool_options"` 49 SkipCompaction bool `mapstructure:"skip_compaction"` 50 SkipExport bool `mapstructure:"skip_export"` 51 VMName string `mapstructure:"vm_name"` 52 VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"` 53 VMXTemplatePath string `mapstructure:"vmx_template_path"` 54 Version string `mapstructure:"version"` 55 56 RemoteType string `mapstructure:"remote_type"` 57 RemoteDatastore string `mapstructure:"remote_datastore"` 58 RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"` 59 RemoteCacheDirectory string `mapstructure:"remote_cache_directory"` 60 RemoteHost string `mapstructure:"remote_host"` 61 RemotePort uint `mapstructure:"remote_port"` 62 RemoteUser string `mapstructure:"remote_username"` 63 RemotePassword string `mapstructure:"remote_password"` 64 RemotePrivateKey string `mapstructure:"remote_private_key_file"` 65 66 CommConfig communicator.Config `mapstructure:",squash"` 67 68 ctx interpolate.Context 69 } 70 71 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 72 err := config.Decode(&b.config, &config.DecodeOpts{ 73 Interpolate: true, 74 InterpolateContext: &b.config.ctx, 75 InterpolateFilter: &interpolate.RenderFilter{ 76 Exclude: []string{ 77 "boot_command", 78 "tools_upload_path", 79 }, 80 }, 81 }, raws...) 82 if err != nil { 83 return nil, err 84 } 85 86 // Accumulate any errors and warnings 87 var errs *packer.MultiError 88 warnings := make([]string, 0) 89 90 isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) 91 warnings = append(warnings, isoWarnings...) 92 errs = packer.MultiErrorAppend(errs, isoErrs...) 93 errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) 94 errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(&b.config.ctx)...) 95 errs = packer.MultiErrorAppend(errs, 96 b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) 97 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) 98 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) 99 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) 100 errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...) 101 errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...) 102 errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) 103 104 if b.config.DiskName == "" { 105 b.config.DiskName = "disk" 106 } 107 108 if b.config.DiskSize == 0 { 109 b.config.DiskSize = 40000 110 } 111 112 if b.config.DiskTypeId == "" { 113 // Default is growable virtual disk split in 2GB files. 114 b.config.DiskTypeId = "1" 115 116 if b.config.RemoteType == "esx5" { 117 b.config.DiskTypeId = "zeroedthick" 118 } 119 } 120 121 if b.config.GuestOSType == "" { 122 b.config.GuestOSType = "other" 123 } 124 125 if b.config.VMName == "" { 126 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 127 } 128 129 if b.config.Version == "" { 130 b.config.Version = "9" 131 } 132 133 if b.config.RemoteUser == "" { 134 b.config.RemoteUser = "root" 135 } 136 137 if b.config.RemoteDatastore == "" { 138 b.config.RemoteDatastore = "datastore1" 139 } 140 141 if b.config.RemoteCacheDatastore == "" { 142 b.config.RemoteCacheDatastore = b.config.RemoteDatastore 143 } 144 145 if b.config.RemoteCacheDirectory == "" { 146 b.config.RemoteCacheDirectory = "packer_cache" 147 } 148 149 if b.config.RemotePort == 0 { 150 b.config.RemotePort = 22 151 } 152 if b.config.VMXTemplatePath != "" { 153 if err := b.validateVMXTemplatePath(); err != nil { 154 errs = packer.MultiErrorAppend( 155 errs, fmt.Errorf("vmx_template_path is invalid: %s", err)) 156 } 157 158 } 159 160 // Remote configuration validation 161 if b.config.RemoteType != "" { 162 if b.config.RemoteHost == "" { 163 errs = packer.MultiErrorAppend(errs, 164 fmt.Errorf("remote_host must be specified")) 165 } 166 } 167 168 if b.config.Format != "" { 169 if !(b.config.Format == "ova" || b.config.Format == "ovf" || b.config.Format == "vmx") { 170 errs = packer.MultiErrorAppend(errs, 171 fmt.Errorf("format must be one of ova, ovf, or vmx")) 172 } 173 } 174 175 // Warnings 176 if b.config.ShutdownCommand == "" { 177 warnings = append(warnings, 178 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 179 "will forcibly halt the virtual machine, which may result in data loss.") 180 } 181 182 if errs != nil && len(errs.Errors) > 0 { 183 return warnings, errs 184 } 185 186 return warnings, nil 187 } 188 189 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 190 driver, err := NewDriver(&b.config) 191 if err != nil { 192 return nil, fmt.Errorf("Failed creating VMware driver: %s", err) 193 } 194 195 // Determine the output dir implementation 196 var dir OutputDir 197 switch d := driver.(type) { 198 case OutputDir: 199 dir = d 200 default: 201 dir = new(vmwcommon.LocalOutputDir) 202 } 203 204 exportOutputPath := b.config.OutputDir 205 206 if b.config.RemoteType != "" && b.config.Format != "" { 207 b.config.OutputDir = b.config.VMName 208 } 209 dir.SetOutputDir(b.config.OutputDir) 210 211 // Setup the state bag 212 state := new(multistep.BasicStateBag) 213 state.Put("cache", cache) 214 state.Put("config", &b.config) 215 state.Put("debug", b.config.PackerDebug) 216 state.Put("dir", dir) 217 state.Put("driver", driver) 218 state.Put("hook", hook) 219 state.Put("ui", ui) 220 221 steps := []multistep.Step{ 222 &vmwcommon.StepPrepareTools{ 223 RemoteType: b.config.RemoteType, 224 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 225 }, 226 &common.StepDownload{ 227 Checksum: b.config.ISOChecksum, 228 ChecksumType: b.config.ISOChecksumType, 229 Description: "ISO", 230 Extension: b.config.TargetExtension, 231 ResultKey: "iso_path", 232 TargetPath: b.config.TargetPath, 233 Url: b.config.ISOUrls, 234 }, 235 &vmwcommon.StepOutputDir{ 236 Force: b.config.PackerForce, 237 }, 238 &common.StepCreateFloppy{ 239 Files: b.config.FloppyConfig.FloppyFiles, 240 Directories: b.config.FloppyConfig.FloppyDirectories, 241 }, 242 &stepRemoteUpload{ 243 Key: "floppy_path", 244 Message: "Uploading Floppy to remote machine...", 245 }, 246 &stepRemoteUpload{ 247 Key: "iso_path", 248 Message: "Uploading ISO to remote machine...", 249 }, 250 &stepCreateDisk{}, 251 &stepCreateVMX{}, 252 &vmwcommon.StepConfigureVMX{ 253 CustomData: b.config.VMXData, 254 }, 255 &vmwcommon.StepSuppressMessages{}, 256 &common.StepHTTPServer{ 257 HTTPDir: b.config.HTTPDir, 258 HTTPPortMin: b.config.HTTPPortMin, 259 HTTPPortMax: b.config.HTTPPortMax, 260 }, 261 &vmwcommon.StepConfigureVNC{ 262 VNCBindAddress: b.config.VNCBindAddress, 263 VNCPortMin: b.config.VNCPortMin, 264 VNCPortMax: b.config.VNCPortMax, 265 VNCDisablePassword: b.config.VNCDisablePassword, 266 }, 267 &StepRegister{ 268 Format: b.config.Format, 269 }, 270 &vmwcommon.StepRun{ 271 BootWait: b.config.BootWait, 272 DurationBeforeStop: 5 * time.Second, 273 Headless: b.config.Headless, 274 }, 275 &vmwcommon.StepTypeBootCommand{ 276 BootCommand: b.config.BootCommand, 277 VMName: b.config.VMName, 278 Ctx: b.config.ctx, 279 }, 280 &communicator.StepConnect{ 281 Config: &b.config.SSHConfig.Comm, 282 Host: driver.CommHost, 283 SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), 284 }, 285 &vmwcommon.StepUploadTools{ 286 RemoteType: b.config.RemoteType, 287 ToolsUploadFlavor: b.config.ToolsUploadFlavor, 288 ToolsUploadPath: b.config.ToolsUploadPath, 289 Ctx: b.config.ctx, 290 }, 291 &common.StepProvision{}, 292 &vmwcommon.StepShutdown{ 293 Command: b.config.ShutdownCommand, 294 Timeout: b.config.ShutdownTimeout, 295 }, 296 &vmwcommon.StepCleanFiles{}, 297 &vmwcommon.StepCompactDisk{ 298 Skip: b.config.SkipCompaction, 299 }, 300 &vmwcommon.StepConfigureVMX{ 301 CustomData: b.config.VMXDataPost, 302 SkipFloppy: true, 303 }, 304 &vmwcommon.StepCleanVMX{ 305 RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet, 306 }, 307 &StepUploadVMX{ 308 RemoteType: b.config.RemoteType, 309 }, 310 &StepExport{ 311 Format: b.config.Format, 312 SkipExport: b.config.SkipExport, 313 OutputDir: exportOutputPath, 314 }, 315 } 316 317 // Run! 318 b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state) 319 b.runner.Run(state) 320 321 // If there was an error, return that 322 if rawErr, ok := state.GetOk("error"); ok { 323 return nil, rawErr.(error) 324 } 325 326 // If we were interrupted or cancelled, then just exit. 327 if _, ok := state.GetOk(multistep.StateCancelled); ok { 328 return nil, errors.New("Build was cancelled.") 329 } 330 331 if _, ok := state.GetOk(multistep.StateHalted); ok { 332 return nil, errors.New("Build was halted.") 333 } 334 335 // Compile the artifact list 336 var files []string 337 if b.config.RemoteType != "" && b.config.Format != "" { 338 dir = new(vmwcommon.LocalOutputDir) 339 dir.SetOutputDir(exportOutputPath) 340 files, err = dir.ListFiles() 341 } else { 342 files, err = state.Get("dir").(OutputDir).ListFiles() 343 } 344 if err != nil { 345 return nil, err 346 } 347 348 // Set the proper builder ID 349 builderId := vmwcommon.BuilderId 350 if b.config.RemoteType != "" { 351 builderId = BuilderIdESX 352 } 353 354 return &Artifact{ 355 builderId: builderId, 356 id: b.config.VMName, 357 dir: dir, 358 f: files, 359 }, nil 360 } 361 362 func (b *Builder) Cancel() { 363 if b.runner != nil { 364 log.Println("Cancelling the step runner...") 365 b.runner.Cancel() 366 } 367 } 368 369 func (b *Builder) validateVMXTemplatePath() error { 370 f, err := os.Open(b.config.VMXTemplatePath) 371 if err != nil { 372 return err 373 } 374 defer f.Close() 375 376 data, err := ioutil.ReadAll(f) 377 if err != nil { 378 return err 379 } 380 381 return interpolate.Validate(string(data), &b.config.ctx) 382 }