Merge pull request #6448 from DanHam/fix-esxi-displayName
ESXi: Fix failure to discover IP or export VM when 'displayName' differs from 'vm_name'
This commit is contained in:
commit
8e852af1eb
|
@ -16,6 +16,9 @@ import (
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
// vmx_path string
|
// vmx_path string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// display_name string - Value of the displayName key set in the VMX file
|
||||||
type StepConfigureVMX struct {
|
type StepConfigureVMX struct {
|
||||||
CustomData map[string]string
|
CustomData map[string]string
|
||||||
SkipFloppy bool
|
SkipFloppy bool
|
||||||
|
@ -73,6 +76,19 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the build is taking place on a remote ESX server, the displayName
|
||||||
|
// will be needed for discovery of the VM's IP address and for export
|
||||||
|
// of the VM. The displayName key should always be set in the VMX file,
|
||||||
|
// so error if we don't find it
|
||||||
|
if displayName, ok := vmxData["displayname"]; !ok { // Packer converts key names to lowercase!
|
||||||
|
err := fmt.Errorf("Error: Could not get value of displayName from VMX data")
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
} else {
|
||||||
|
state.Put("display_name", displayName)
|
||||||
|
}
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ func testVMXFile(t *testing.T) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// displayName must always be set
|
||||||
|
err = WriteVMX(tf.Name(), map[string]string{"displayName": "PackerBuild"})
|
||||||
tf.Close()
|
tf.Close()
|
||||||
|
|
||||||
return tf.Name()
|
return tf.Name()
|
||||||
|
@ -132,12 +135,29 @@ func TestStepConfigureVMX_generatedAddresses(t *testing.T) {
|
||||||
vmxPath := testVMXFile(t)
|
vmxPath := testVMXFile(t)
|
||||||
defer os.Remove(vmxPath)
|
defer os.Remove(vmxPath)
|
||||||
|
|
||||||
err := WriteVMX(vmxPath, map[string]string{
|
additionalTestVmxData := []struct {
|
||||||
"foo": "bar",
|
Key string
|
||||||
"ethernet0.generatedAddress": "foo",
|
Value string
|
||||||
"ethernet1.generatedAddress": "foo",
|
}{
|
||||||
"ethernet1.generatedAddressOffset": "foo",
|
{"foo", "bar"},
|
||||||
})
|
{"ethernet0.generatedaddress", "foo"},
|
||||||
|
{"ethernet1.generatedaddress", "foo"},
|
||||||
|
{"ethernet1.generatedaddressoffset", "foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get any existing VMX data from the VMX file
|
||||||
|
vmxData, err := ReadVMX(vmxPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the additional key/value pairs we need for this test to the existing VMX data
|
||||||
|
for _, data := range additionalTestVmxData {
|
||||||
|
vmxData[data.Key] = data.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate the VMX file so it includes all the data needed for this test
|
||||||
|
err = WriteVMX(vmxPath, vmxData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +177,7 @@ func TestStepConfigureVMX_generatedAddresses(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
vmxData := ParseVMX(string(vmxContents))
|
vmxData = ParseVMX(string(vmxContents))
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Key string
|
Key string
|
||||||
|
@ -180,5 +200,71 @@ func TestStepConfigureVMX_generatedAddresses(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should fail if the displayName key is not found in the VMX
|
||||||
|
func TestStepConfigureVMX_displayNameMissing(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepConfigureVMX)
|
||||||
|
|
||||||
|
// testVMXFile adds displayName key/value pair to the VMX
|
||||||
|
vmxPath := testVMXFile(t)
|
||||||
|
defer os.Remove(vmxPath)
|
||||||
|
|
||||||
|
// Bad: Delete displayName from the VMX/Create an empty VMX file
|
||||||
|
err := WriteVMX(vmxPath, map[string]string{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put("vmx_path", vmxPath)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||||
|
t.Fatalf("bad action: %#v. Should halt when displayName key is missing from VMX", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); !ok {
|
||||||
|
t.Fatal("should store error in state when displayName key is missing from VMX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should store the value of displayName in the statebag
|
||||||
|
func TestStepConfigureVMX_displayNameStore(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepConfigureVMX)
|
||||||
|
|
||||||
|
// testVMXFile adds displayName key/value pair to the VMX
|
||||||
|
vmxPath := testVMXFile(t)
|
||||||
|
defer os.Remove(vmxPath)
|
||||||
|
|
||||||
|
state.Put("vmx_path", vmxPath)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The value of displayName must be stored in the statebag
|
||||||
|
if _, ok := state.GetOk("display_name"); !ok {
|
||||||
|
t.Fatalf("displayName should be stored in the statebag as 'display_name'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepConfigureVMX_vmxPathBad(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepConfigureVMX)
|
||||||
|
|
||||||
|
state.Put("vmx_path", "some_bad_path")
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
|
||||||
|
t.Fatalf("bad action: %#v. Should halt when vmxPath is bad", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); !ok {
|
||||||
|
t.Fatal("should store error in state when vmxPath is bad")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -389,7 +389,13 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err := r.find("Name", config.VMName)
|
// The value in the Name field returned by 'esxcli network vm list'
|
||||||
|
// corresponds directly to the value of displayName set in the VMX file
|
||||||
|
var displayName string
|
||||||
|
if v, ok := state.GetOk("display_name"); ok {
|
||||||
|
displayName = v.(string)
|
||||||
|
}
|
||||||
|
record, err := r.find("Name", displayName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,17 @@ import (
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This step exports a VM built on ESXi using ovftool
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// display_name string
|
||||||
type StepExport struct {
|
type StepExport struct {
|
||||||
Format string
|
Format string
|
||||||
SkipExport bool
|
SkipExport bool
|
||||||
OutputDir string
|
OutputDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepExport) generateArgs(c *Config, hidePassword bool) []string {
|
func (s *StepExport) generateArgs(c *Config, displayName string, hidePassword bool) []string {
|
||||||
password := url.QueryEscape(c.RemotePassword)
|
password := url.QueryEscape(c.RemotePassword)
|
||||||
if hidePassword {
|
if hidePassword {
|
||||||
password = "****"
|
password = "****"
|
||||||
|
@ -29,7 +33,7 @@ func (s *StepExport) generateArgs(c *Config, hidePassword bool) []string {
|
||||||
"--noSSLVerify=true",
|
"--noSSLVerify=true",
|
||||||
"--skipManifestCheck",
|
"--skipManifestCheck",
|
||||||
"-tt=" + s.Format,
|
"-tt=" + s.Format,
|
||||||
"vi://" + c.RemoteUser + ":" + password + "@" + c.RemoteHost + "/" + c.VMName,
|
"vi://" + c.RemoteUser + ":" + password + "@" + c.RemoteHost + "/" + displayName,
|
||||||
s.OutputDir,
|
s.OutputDir,
|
||||||
}
|
}
|
||||||
return append(c.OVFToolOptions, args...)
|
return append(c.OVFToolOptions, args...)
|
||||||
|
@ -72,9 +76,13 @@ func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep.
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Say("Exporting virtual machine...")
|
ui.Say("Exporting virtual machine...")
|
||||||
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(s.generateArgs(c, true), " ")))
|
var displayName string
|
||||||
|
if v, ok := state.GetOk("display_name"); ok {
|
||||||
|
displayName = v.(string)
|
||||||
|
}
|
||||||
|
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(s.generateArgs(c, displayName, true), " ")))
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd := exec.Command(ovftool, s.generateArgs(c, false)...)
|
cmd := exec.Command(ovftool, s.generateArgs(c, displayName, false)...)
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String())
|
err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String())
|
||||||
|
|
Loading…
Reference in New Issue