Merge pull request #6977 from hashicorp/fix_6425

Validate username/password for ovftool during prepare.
This commit is contained in:
Megan Marsh 2018-11-12 14:19:11 -08:00 committed by GitHub
commit cf293c3310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 380 additions and 274 deletions

View File

@ -1,22 +1,30 @@
package common package common
import ( import (
"bytes"
"context"
"fmt"
"net/url"
"os" "os"
"os/exec"
"strings"
"time"
"github.com/hashicorp/packer/template/interpolate" "github.com/hashicorp/packer/template/interpolate"
) )
type DriverConfig struct { type DriverConfig struct {
FusionAppPath string `mapstructure:"fusion_app_path"` FusionAppPath string `mapstructure:"fusion_app_path"`
RemoteType string `mapstructure:"remote_type"` RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"` RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"` RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
RemoteCacheDirectory string `mapstructure:"remote_cache_directory"` RemoteCacheDirectory string `mapstructure:"remote_cache_directory"`
RemoteHost string `mapstructure:"remote_host"` RemoteHost string `mapstructure:"remote_host"`
RemotePort uint `mapstructure:"remote_port"` RemotePort uint `mapstructure:"remote_port"`
RemoteUser string `mapstructure:"remote_username"` RemoteUser string `mapstructure:"remote_username"`
RemotePassword string `mapstructure:"remote_password"` RemotePassword string `mapstructure:"remote_password"`
RemotePrivateKey string `mapstructure:"remote_private_key_file"` RemotePrivateKey string `mapstructure:"remote_private_key_file"`
SkipValidateCredentials bool `mapstructure:"skip_validate_credentials"`
} }
func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error { func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
@ -44,3 +52,55 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
return nil return nil
} }
func (c *DriverConfig) Validate(SkipExport bool) error {
if c.RemoteType == "" || SkipExport == true {
return nil
}
if c.RemotePassword == "" {
return fmt.Errorf("exporting the vm (with ovftool) requires that " +
"you set a value for remote_password")
}
if c.SkipValidateCredentials {
return nil
}
// check that password is valid by sending a dummy ovftool command
// now, so that we don't fail for a simple mistake after a long
// build
ovftool := GetOVFTool()
ovfToolArgs := []string{"--verifyOnly", fmt.Sprintf("vi://" +
url.QueryEscape(c.RemoteUser) + ":" +
url.QueryEscape(c.RemotePassword) + "@" +
c.RemoteHost)}
var out bytes.Buffer
cmdCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
cmd := exec.CommandContext(cmdCtx, ovftool, ovfToolArgs...)
cmd.Stdout = &out
// Need to manually close stdin or else the ofvtool call will hang
// forever in a situation where the user has provided an invalid
// password or username
stdin, _ := cmd.StdinPipe()
defer stdin.Close()
if err := cmd.Run(); err != nil {
outString := out.String()
// The command *should* fail with this error, if it
// authenticates properly.
if !strings.Contains(outString, "Found wrong kind of object") {
err := fmt.Errorf("ovftool validation error: %s; %s",
err, outString)
if strings.Contains(outString,
"Enter login information for source") {
err = fmt.Errorf("The username or password you " +
"provided to ovftool is invalid.")
}
return err
}
}
return nil
}

View File

@ -26,6 +26,18 @@ type StepExport struct {
OutputDir string OutputDir string
} }
func GetOVFTool() string {
ovftool := "ovftool"
if runtime.GOOS == "windows" {
ovftool = "ovftool.exe"
}
if _, err := exec.LookPath(ovftool); err != nil {
return ""
}
return ovftool
}
func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassword bool) []string { func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassword bool) []string {
password := url.QueryEscape(c.RemotePassword) password := url.QueryEscape(c.RemotePassword)
if hidePassword { if hidePassword {
@ -57,13 +69,9 @@ func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep.
return multistep.ActionContinue return multistep.ActionContinue
} }
ovftool := "ovftool" ovftool := GetOVFTool()
if runtime.GOOS == "windows" { if ovftool == "" {
ovftool = "ovftool.exe" err := fmt.Errorf("Error %s not found: ", ovftool)
}
if _, err := exec.LookPath(ovftool); err != nil {
err = fmt.Errorf("Error %s not found: %s", ovftool, err)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt

View File

@ -3,20 +3,14 @@ package iso
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os"
"strings"
"time" "time"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common" vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
) )
type Builder struct { type Builder struct {
@ -24,208 +18,15 @@ type Builder struct {
runner multistep.Runner runner multistep.Runner
} }
type Config struct { // Prepare processes the build configuration parameters.
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.OutputConfig `mapstructure:",squash"`
vmwcommon.RunConfig `mapstructure:",squash"`
vmwcommon.ShutdownConfig `mapstructure:",squash"`
vmwcommon.SSHConfig `mapstructure:",squash"`
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
vmwcommon.ExportConfig `mapstructure:",squash"`
// disk drives
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
DiskAdapterType string `mapstructure:"disk_adapter_type"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
Format string `mapstructure:"format"`
// cdrom drive
CdromAdapterType string `mapstructure:"cdrom_adapter_type"`
// platform information
GuestOSType string `mapstructure:"guest_os_type"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"`
// Network adapter and type
NetworkAdapterType string `mapstructure:"network_adapter_type"`
Network string `mapstructure:"network"`
// device presence
Sound bool `mapstructure:"sound"`
USB bool `mapstructure:"usb"`
// communication ports
Serial string `mapstructure:"serial"`
Parallel string `mapstructure:"parallel"`
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
ctx interpolate.Context
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{ c, warnings, errs := NewConfig(raws...)
Interpolate: true, if errs != nil {
InterpolateContext: &b.config.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 := b.config.ISOConfig.Prepare(&b.config.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ExportConfig.Prepare(&b.config.ctx)...)
if b.config.DiskName == "" {
b.config.DiskName = "disk"
}
if b.config.DiskSize == 0 {
b.config.DiskSize = 40000
}
if b.config.DiskAdapterType == "" {
// Default is lsilogic
b.config.DiskAdapterType = "lsilogic"
}
if !b.config.SkipCompaction {
if b.config.RemoteType == "esx5" {
if b.config.DiskTypeId == "" {
b.config.SkipCompaction = true
}
}
}
if b.config.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
b.config.DiskTypeId = "1"
if b.config.RemoteType == "esx5" {
b.config.DiskTypeId = "zeroedthick"
}
}
if b.config.RemoteType == "esx5" {
if b.config.DiskTypeId != "thin" && !b.config.SkipCompaction {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("skip_compaction must be 'true' for disk_type_id: %s", b.config.DiskTypeId))
}
}
if b.config.GuestOSType == "" {
b.config.GuestOSType = "other"
}
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
if b.config.Version == "" {
b.config.Version = "9"
}
if b.config.VMXTemplatePath != "" {
if err := b.validateVMXTemplatePath(); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
}
} else {
warn := b.checkForVMXTemplateAndVMXDataCollisions()
if warn != "" {
warnings = append(warnings, warn)
}
}
if b.config.Network == "" {
b.config.Network = "nat"
}
if !b.config.Sound {
b.config.Sound = false
}
if !b.config.USB {
b.config.USB = false
}
// Remote configuration validation
if b.config.RemoteType != "" {
if b.config.RemoteHost == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("remote_host must be specified"))
}
if b.config.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
}
}
if b.config.Format == "" {
b.config.Format = "ovf"
}
if !(b.config.Format == "ova" || b.config.Format == "ovf" || b.config.Format == "vmx") {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("format must be one of ova, ovf, or vmx"))
}
if b.config.RemoteType == "esx5" && b.config.SkipExport != true && b.config.RemotePassword == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("exporting the vm (with ovftool) requires that you set a value for remote_password"))
}
// Warnings
if b.config.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 b.config.Headless && b.config.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, errs
} }
b.config = *c
return warnings, nil return warnings, nil
} }
@ -406,56 +207,3 @@ func (b *Builder) Cancel() {
b.runner.Cancel() b.runner.Cancel()
} }
} }
// Validate the vmx_data option against the default vmx template to warn
// user if anything is being overridden.
func (b *Builder) checkForVMXTemplateAndVMXDataCollisions() string {
if b.config.VMXTemplatePath != "" {
return ""
}
var overridden []string
tplLines := strings.Split(DefaultVMXTemplate, "\n")
tplLines = append(tplLines,
fmt.Sprintf("%s0:0.present", strings.ToLower(b.config.DiskAdapterType)),
fmt.Sprintf("%s0:0.fileName", strings.ToLower(b.config.DiskAdapterType)),
fmt.Sprintf("%s0:0.deviceType", strings.ToLower(b.config.DiskAdapterType)),
fmt.Sprintf("%s0:1.present", strings.ToLower(b.config.DiskAdapterType)),
fmt.Sprintf("%s0:1.fileName", strings.ToLower(b.config.DiskAdapterType)),
fmt.Sprintf("%s0:1.deviceType", strings.ToLower(b.config.DiskAdapterType)),
)
for _, line := range tplLines {
if strings.Contains(line, `{{`) {
key := line[:strings.Index(line, " =")]
if _, ok := b.config.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 (b *Builder) validateVMXTemplatePath() error {
f, err := os.Open(b.config.VMXTemplatePath)
if err != nil {
return err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return err
}
return interpolate.Validate(string(data), &b.config.ctx)
}

View File

@ -146,6 +146,7 @@ func TestBuilderPrepare_RemoteType(t *testing.T) {
config["format"] = "ovf" config["format"] = "ovf"
config["remote_host"] = "foobar.example.com" config["remote_host"] = "foobar.example.com"
config["remote_password"] = "supersecret" config["remote_password"] = "supersecret"
config["skip_validate_credentials"] = true
// Bad // Bad
config["remote_type"] = "foobar" config["remote_type"] = "foobar"
warns, err := b.Prepare(config) warns, err := b.Prepare(config)
@ -202,6 +203,7 @@ func TestBuilderPrepare_RemoteExport(t *testing.T) {
config["remote_type"] = "esx5" config["remote_type"] = "esx5"
config["remote_host"] = "foobar.example.com" config["remote_host"] = "foobar.example.com"
config["skip_validate_credentials"] = true
// Bad // Bad
config["remote_password"] = "" config["remote_password"] = ""
warns, err := b.Prepare(config) warns, err := b.Prepare(config)

View File

@ -0,0 +1,271 @@
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/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.OutputConfig `mapstructure:",squash"`
vmwcommon.RunConfig `mapstructure:",squash"`
vmwcommon.ShutdownConfig `mapstructure:",squash"`
vmwcommon.SSHConfig `mapstructure:",squash"`
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
vmwcommon.ExportConfig `mapstructure:",squash"`
// disk drives
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
DiskAdapterType string `mapstructure:"disk_adapter_type"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
Format string `mapstructure:"format"`
// cdrom drive
CdromAdapterType string `mapstructure:"cdrom_adapter_type"`
// platform information
GuestOSType string `mapstructure:"guest_os_type"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"`
// Network adapter and type
NetworkAdapterType string `mapstructure:"network_adapter_type"`
Network string `mapstructure:"network"`
// device presence
Sound bool `mapstructure:"sound"`
USB bool `mapstructure:"usb"`
// communication ports
Serial string `mapstructure:"serial"`
Parallel string `mapstructure:"parallel"`
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
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, 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.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.Network == "" {
c.Network = "nat"
}
if !c.Sound {
c.Sound = false
}
if !c.USB {
c.USB = false
}
// 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 == "" {
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 nil, warnings, errs
}
return c, 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)
}

View File

@ -93,6 +93,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
} }
} }
err = c.DriverConfig.Validate(c.SkipExport)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if c.Format == "" { if c.Format == "" {
c.Format = "ovf" c.Format = "ovf"
} }

View File

@ -365,6 +365,12 @@ builder.
Hypervisor](/docs/builders/vmware-iso.html#building-on-a-remote-vsphere-hypervisor) Hypervisor](/docs/builders/vmware-iso.html#building-on-a-remote-vsphere-hypervisor)
section below for more info. section below for more info.
- `skip_validate_credentials` (boolean) - When Packer is preparing to run a
remote esxi build, and export is not disable, by default it runs a no-op
ovftool command to make sure that the remote_username and remote_password
given are valid. If you set this flag to `true`, Packer will skip this
validation. Default: `false`.
- `sound` (boolean) - Enable VMware's virtual soundcard device for the VM. - `sound` (boolean) - Enable VMware's virtual soundcard device for the VM.
- `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to - `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to

View File

@ -132,6 +132,12 @@ builder.
the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the
name of the build. name of the build.
- `skip_validate_credentials` (boolean) - When Packer is preparing to run a
remote esxi build, and export is not disable, by default it runs a no-op
ovftool command to make sure that the remote_username and remote_password
given are valid. If you set this flag to `true`, Packer will skip this
validation. Default: `false`.
- `remote_cache_datastore` (string) - The path to the datastore where - `remote_cache_datastore` (string) - The path to the datastore where
supporting files will be stored during the build on the remote machine. By supporting files will be stored during the build on the remote machine. By
default this is the same as the `remote_datastore` option. This only has an default this is the same as the `remote_datastore` option. This only has an