//go:generate struct-markdown //go:generate mapstructure-to-hcl2 -type Config package iso import ( "fmt" "io/ioutil" "os" "strings" vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/common/shutdowncommand" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" ) type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` bootcommand.VNCConfig `mapstructure:",squash"` vmwcommon.DriverConfig `mapstructure:",squash"` vmwcommon.HWConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` vmwcommon.RunConfig `mapstructure:",squash"` shutdowncommand.ShutdownConfig `mapstructure:",squash"` vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.ToolsConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` vmwcommon.ExportConfig `mapstructure:",squash"` // The size(s) of any additional // hard disks for the VM in megabytes. If this is not specified then the VM // will only contain a primary hard disk. The builder uses expandable, not // fixed-size virtual hard disks, so the actual file representing the disk will // not use the full size unless it is full. AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false"` // The adapter type of the VMware virtual disk to create. This option is // for advanced usage, modify only if you know what you're doing. Some of // the options you can specify are `ide`, `sata`, `nvme` or `scsi` (which // uses the "lsilogic" scsi interface by default). If you specify another // option, Packer will assume that you're specifying a `scsi` interface of // that specified type. For more information, please consult [Virtual Disk // Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) // for desktop VMware clients. For ESXi, refer to the proper ESXi // documentation. DiskAdapterType string `mapstructure:"disk_adapter_type" required:"false"` // The filename of the virtual disk that'll be created, // without the extension. This defaults to packer. DiskName string `mapstructure:"vmdk_name" required:"false"` // The size of the hard disk for the VM in megabytes. // The builder uses expandable, not fixed-size virtual hard disks, so the // actual file representing the disk will not use the full size unless it // is full. By default this is set to 40000 (about 40 GB). DiskSize uint `mapstructure:"disk_size" required:"false"` // The type of VMware virtual disk to create. This // option is for advanced usage. // // For desktop VMware clients: // // Type ID | Description // ------- | --- // `0` | Growable virtual disk contained in a single file (monolithic sparse). // `1` | Growable virtual disk split into 2GB files (split sparse). // `2` | Preallocated virtual disk contained in a single file (monolithic flat). // `3` | Preallocated virtual disk split into 2GB files (split flat). // `4` | Preallocated virtual disk compatible with ESX server (VMFS flat). // `5` | Compressed disk optimized for streaming. // // The default is `1`. // // For ESXi, this defaults to `zeroedthick`. The available options for ESXi // are: `zeroedthick`, `eagerzeroedthick`, `thin`. `rdm:dev`, `rdmp:dev`, // `2gbsparse` are not supported. Due to default disk compaction, when using // `zeroedthick` or `eagerzeroedthick` set `skip_compaction` to `true`. // // For more information, please consult the [Virtual Disk Manager User's // Guide](https://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop // VMware clients. For ESXi, refer to the proper ESXi documentation. DiskTypeId string `mapstructure:"disk_type_id" required:"false"` // The adapter type (or bus) that will be used // by the cdrom device. This is chosen by default based on the disk adapter // type. VMware tends to lean towards ide for the cdrom device unless // sata is chosen for the disk adapter and so Packer attempts to mirror // this logic. This field can be specified as either ide, sata, or scsi. CdromAdapterType string `mapstructure:"cdrom_adapter_type" required:"false"` // The guest OS type being installed. This will be // set in the VMware VMX. By default this is other. By specifying a more // specific OS type, VMware may perform some optimizations or virtual hardware // changes to better support the operating system running in the // virtual machine. Valid values differ by platform and version numbers, and may // not match other VMware API's representation of the guest OS names. Consult your // platform for valid values. GuestOSType string `mapstructure:"guest_os_type" required:"false"` // The [vmx hardware // version](http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003746) // for the new virtual machine. Only the default value has been tested, any // other value is experimental. Default value is `9`. Version string `mapstructure:"version" required:"false"` // This is the name of the VMX file for the new virtual // machine, without the file extension. By default this is packer-BUILDNAME, // where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name" required:"false"` VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"` // Path to a [configuration template](/docs/templates/engine.html) that // defines the contents of the virtual machine VMX file for VMware. The // engine has access to the template variables `{{ .DiskNumber }}` and // `{{ .DiskName }}`. // // This is for **advanced users only** as this can render the virtual machine // non-functional. See below for more information. For basic VMX // modifications, try `vmx_data` first. VMXTemplatePath string `mapstructure:"vmx_template_path" required:"false"` ctx interpolate.Context } func (c *Config) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ "boot_command", "tools_upload_path", }, }, }, raws...) if err != nil { return nil, err } // Accumulate any errors and warnings var errs *packer.MultiError warnings := make([]string, 0) isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx) warnings = append(warnings, isoWarnings...) errs = packer.MultiErrorAppend(errs, isoErrs...) errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.HWConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.DriverConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...) errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...) if c.DiskName == "" { c.DiskName = "disk" } if c.DiskSize == 0 { c.DiskSize = 40000 } if c.DiskAdapterType == "" { // Default is lsilogic c.DiskAdapterType = "lsilogic" } if !c.SkipCompaction { if c.RemoteType == "esx5" { if c.DiskTypeId == "" { c.SkipCompaction = true } } } if c.DiskTypeId == "" { // Default is growable virtual disk split in 2GB files. c.DiskTypeId = "1" if c.RemoteType == "esx5" { c.DiskTypeId = "zeroedthick" } } if c.RemoteType == "esx5" { if c.DiskTypeId != "thin" && !c.SkipCompaction { errs = packer.MultiErrorAppend( errs, fmt.Errorf("skip_compaction must be 'true' for disk_type_id: %s", c.DiskTypeId)) } } if c.GuestOSType == "" { c.GuestOSType = "other" } if c.VMName == "" { c.VMName = fmt.Sprintf("packer-%s", c.PackerBuildName) } if c.Version == "" { c.Version = "9" } if c.VMXTemplatePath != "" { if err := c.validateVMXTemplatePath(); err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("vmx_template_path is invalid: %s", err)) } } else { warn := c.checkForVMXTemplateAndVMXDataCollisions() if warn != "" { warnings = append(warnings, warn) } } if c.HWConfig.Network == "" { c.HWConfig.Network = "nat" } // Remote configuration validation if c.RemoteType != "" { if c.RemoteHost == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("remote_host must be specified")) } if c.RemoteType != "esx5" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("Only 'esx5' value is accepted for remote_type")) } } if c.Format != "" { if c.RemoteType != "esx5" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("format is only valid when RemoteType=esx5")) } } else { c.Format = "ovf" } if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") { errs = packer.MultiErrorAppend(errs, fmt.Errorf("format must be one of ova, ovf, or vmx")) } err = c.DriverConfig.Validate(c.SkipExport) if err != nil { errs = packer.MultiErrorAppend(errs, err) } // Warnings if c.ShutdownCommand == "" { warnings = append(warnings, "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ "will forcibly halt the virtual machine, which may result in data loss.") } if c.Headless && c.DisableVNC { warnings = append(warnings, "Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+ "you won't be able to see any output.") } if errs != nil && len(errs.Errors) > 0 { return warnings, errs } return warnings, nil } func (c *Config) checkForVMXTemplateAndVMXDataCollisions() string { if c.VMXTemplatePath != "" { return "" } var overridden []string tplLines := strings.Split(DefaultVMXTemplate, "\n") tplLines = append(tplLines, fmt.Sprintf("%s0:0.present", strings.ToLower(c.DiskAdapterType)), fmt.Sprintf("%s0:0.fileName", strings.ToLower(c.DiskAdapterType)), fmt.Sprintf("%s0:0.deviceType", strings.ToLower(c.DiskAdapterType)), fmt.Sprintf("%s0:1.present", strings.ToLower(c.DiskAdapterType)), fmt.Sprintf("%s0:1.fileName", strings.ToLower(c.DiskAdapterType)), fmt.Sprintf("%s0:1.deviceType", strings.ToLower(c.DiskAdapterType)), ) for _, line := range tplLines { if strings.Contains(line, `{{`) { key := line[:strings.Index(line, " =")] if _, ok := c.VMXData[key]; ok { overridden = append(overridden, key) } } } if len(overridden) > 0 { warnings := fmt.Sprintf("Your vmx data contains the following "+ "variable(s), which Packer normally sets when it generates its "+ "own default vmx template. This may cause your build to fail or "+ "behave unpredictably: %s", strings.Join(overridden, ", ")) return warnings } return "" } // Make sure custom vmx template exists and that data can be read from it func (c *Config) validateVMXTemplatePath() error { f, err := os.Open(c.VMXTemplatePath) if err != nil { return err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return err } return interpolate.Validate(string(data), &c.ctx) }