diff --git a/CHANGELOG.md b/CHANGELOG.md index 52d312039..a51ce760a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ IMPROVEMENTS: * builder/amazon: Tagging now works with all amazon builder types. * builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY 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: An "end" sentinel is outputted in machine-readable output for artifact listing so it is easier to know when it is over. diff --git a/builder/vmware/builder.go b/builder/vmware/builder.go index 8f56e9e23..ad68a067e 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/builder.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" + "io/ioutil" "log" "math/rand" "os" @@ -49,6 +50,7 @@ type config struct { ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` ToolsUploadPath string `mapstructure:"tools_upload_path"` VMXData map[string]string `mapstructure:"vmx_data"` + VMXTemplatePath string `mapstructure:"vmx_template_path"` VNCPortMin uint `mapstructure:"vnc_port_min"` VNCPortMax uint `mapstructure:"vnc_port_max"` @@ -152,6 +154,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { "boot_wait": &b.config.RawBootWait, "shutdown_timeout": &b.config.RawShutdownTimeout, "ssh_wait_timeout": &b.config.RawSSHWaitTimeout, + "vmx_template_path": &b.config.VMXTemplatePath, } 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)) } + 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 { errs = packer.MultiErrorAppend( errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) @@ -414,3 +425,18 @@ func (b *Builder) 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)) +} diff --git a/builder/vmware/builder_test.go b/builder/vmware/builder_test.go index 1e46beaec..20275f24a 100644 --- a/builder/vmware/builder_test.go +++ b/builder/vmware/builder_test.go @@ -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) { var b Builder config := testConfig() diff --git a/builder/vmware/step_create_vmx.go b/builder/vmware/step_create_vmx.go index 8165fb36b..91a2b474b 100644 --- a/builder/vmware/step_create_vmx.go +++ b/builder/vmware/step_create_vmx.go @@ -1,13 +1,13 @@ package vmware import ( - "bytes" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "io/ioutil" "log" + "os" "path/filepath" - "text/template" ) type vmxTemplateData struct { @@ -42,11 +42,37 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction { ISOPath: isoPath, } - var buf bytes.Buffer - t := template.Must(template.New("vmx").Parse(DefaultVMXTemplate)) - t.Execute(&buf, tplData) + vmxTemplate := DefaultVMXTemplate + if config.VMXTemplatePath != "" { + 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 { log.Println("Setting custom VMX data...") for k, v := range config.VMXData { diff --git a/website/source/docs/builders/vmware.html.markdown b/website/source/docs/builders/vmware.html.markdown index aad32fefe..d615d78bd 100644 --- a/website/source/docs/builders/vmware.html.markdown +++ b/website/source/docs/builders/vmware.html.markdown @@ -190,6 +190,13 @@ Optional: 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. +* `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 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>" ] + +## 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. + +
+

+This is an advanced feature. 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. +

+
+ +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.