builder/vmware: support vmx_template_path [GH-270]

This commit is contained in:
Mitchell Hashimoto 2013-08-27 17:23:22 -07:00
parent c753946c84
commit 1566b4d858
5 changed files with 143 additions and 6 deletions

View File

@ -16,6 +16,8 @@ IMPROVEMENTS:
* builder/amazon: Tagging now works with all amazon builder types. * builder/amazon: Tagging now works with all amazon builder types.
* builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY * builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY
for the SSH connection. [GH-270] for the SSH connection. [GH-270]
* builder/vmware: Specify a `vmx_template_path` in order to customize
the generated VMX. [GH-270]
* command/build: Machine-readable output now contains build errors, if any. * command/build: Machine-readable output now contains build errors, if any.
* command/build: An "end" sentinel is outputted in machine-readable output * command/build: An "end" sentinel is outputted in machine-readable output
for artifact listing so it is easier to know when it is over. for artifact listing so it is easier to know when it is over.

View File

@ -6,6 +6,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io/ioutil"
"log" "log"
"math/rand" "math/rand"
"os" "os"
@ -49,6 +50,7 @@ type config struct {
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
ToolsUploadPath string `mapstructure:"tools_upload_path"` ToolsUploadPath string `mapstructure:"tools_upload_path"`
VMXData map[string]string `mapstructure:"vmx_data"` VMXData map[string]string `mapstructure:"vmx_data"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
VNCPortMin uint `mapstructure:"vnc_port_min"` VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"` VNCPortMax uint `mapstructure:"vnc_port_max"`
@ -152,6 +154,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"boot_wait": &b.config.RawBootWait, "boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout, "shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout, "ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
"vmx_template_path": &b.config.VMXTemplatePath,
} }
for n, ptr := range templates { for n, ptr := range templates {
@ -298,6 +301,14 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs, fmt.Errorf("tools_upload_path invalid: %s", err)) errs, fmt.Errorf("tools_upload_path invalid: %s", err))
} }
if b.config.VMXTemplatePath != "" {
if err := b.validateVMXTemplatePath(); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
}
}
if b.config.VNCPortMin > b.config.VNCPortMax { if b.config.VNCPortMin > b.config.VNCPortMax {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
@ -414,3 +425,18 @@ func (b *Builder) Cancel() {
b.runner.Cancel() b.runner.Cancel()
} }
} }
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 b.config.tpl.Validate(string(data))
}

View File

@ -437,6 +437,56 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
} }
} }
func TestBuilderPrepare_VMXTemplatePath(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["vmx_template_path"] = "/i/dont/exist/forreal"
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["vmx_template_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Bad template
tf2, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf2.Name())
defer tf2.Close()
if _, err := tf2.Write([]byte("{{foo}")); err != nil {
t.Fatalf("err: %s", err)
}
config["vmx_template_path"] = tf2.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_VNCPort(t *testing.T) { func TestBuilderPrepare_VNCPort(t *testing.T) {
var b Builder var b Builder
config := testConfig() config := testConfig()

View File

@ -1,13 +1,13 @@
package vmware package vmware
import ( import (
"bytes"
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"io/ioutil"
"log" "log"
"os"
"path/filepath" "path/filepath"
"text/template"
) )
type vmxTemplateData struct { type vmxTemplateData struct {
@ -42,11 +42,37 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
ISOPath: isoPath, ISOPath: isoPath,
} }
var buf bytes.Buffer vmxTemplate := DefaultVMXTemplate
t := template.Must(template.New("vmx").Parse(DefaultVMXTemplate)) if config.VMXTemplatePath != "" {
t.Execute(&buf, tplData) f, err := os.Open(config.VMXTemplatePath)
if err != nil {
err := fmt.Errorf("Error reading VMX template: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
defer f.Close()
vmxData := ParseVMX(buf.String()) rawBytes, err := ioutil.ReadAll(f)
if err != nil {
err := fmt.Errorf("Error reading VMX template: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
vmxTemplate = string(rawBytes)
}
vmxContents, err := config.tpl.Process(vmxTemplate, tplData)
if err != nil {
err := fmt.Errorf("Error procesing VMX template: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
vmxData := ParseVMX(vmxContents)
if config.VMXData != nil { if config.VMXData != nil {
log.Println("Setting custom VMX data...") log.Println("Setting custom VMX data...")
for k, v := range config.VMXData { for k, v := range config.VMXData {

View File

@ -190,6 +190,13 @@ Optional:
uses a randomly chosen port in this range that appears available. By default uses a randomly chosen port in this range that appears available. By default
this is 5900 to 6000. The minimum and maximum ports are inclusive. this is 5900 to 6000. The minimum and maximum ports are inclusive.
* `vmx_template_path` (string) - Path to a
[configuration template](/docs/templates/configuration-templates.html) that
defines the contents of the virtual machine VMX file for VMware. 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.
## Boot Command ## Boot Command
The `boot_command` configuration is very important: it specifies the keys The `boot_command` configuration is very important: it specifies the keys
@ -240,3 +247,29 @@ an Ubuntu 12.04 installer:
"initrd=/install/initrd.gz -- <enter>" "initrd=/install/initrd.gz -- <enter>"
] ]
</pre> </pre>
## VMX Template
The heart of a VMware machine is the "vmx" file. This contains all the
virtual hardware metadata necessary for the VM to function. Packer by default
uses a [safe, flexible VMX file](https://github.com/mitchellh/packer/blob/20541a7eda085aa5cf35bfed5069592ca49d106e/builder/vmware/step_create_vmx.go#L84).
But for advanced users, this template can be customized. This allows
Packer to build virtual machines of effectively any guest operating system
type.
<div class="alert alert-block alert-warn">
<p>
<strong>This is an advanced feature.</strong> Modifying the VMX template
can easily cause your virtual machine to not boot properly. Please only
modify the template if you know what you're doing.
</p>
</div>
Within the template, a handful of variables are available so that your
template can continue working with the rest of the Packer machinery. Using
these variables isn't required, however.
* `Name` - The name of the virtual machine.
* `GuestOS` - The VMware-valid guest OS type.
* `DiskName` - The filename (without the suffix) of the main virtual disk.
* `ISOPath` - The path to the ISO to use for the OS installation.