Merge pull request #1455 from legal90/prl-tools-flavor

builder/parallels: Detect path to Parallels Tools ISO automatically
This commit is contained in:
Mitchell Hashimoto 2014-09-02 09:09:02 -07:00
commit d9e8676ec5
18 changed files with 567 additions and 308 deletions

View File

@ -27,6 +27,9 @@ type Driver interface {
// Prlctl executes the given Prlctl command // Prlctl executes the given Prlctl command
Prlctl(...string) error Prlctl(...string) error
// Get the path to the Parallels Tools ISO for the given flavor.
ToolsIsoPath(string) (string, error)
// Verify checks to make sure that this driver should function // Verify checks to make sure that this driver should function
// properly. If there is any indication the driver can't function, // properly. If there is any indication the driver can't function,
// this will return an error. // this will return an error.

View File

@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -73,16 +74,21 @@ func getConfigValueFromXpath(path, xpath string) (string, error) {
// Finds an application bundle by identifier (for "darwin" platform only) // Finds an application bundle by identifier (for "darwin" platform only)
func getAppPath(bundleId string) (string, error) { func getAppPath(bundleId string) (string, error) {
var stdout bytes.Buffer
cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId) cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId)
out, err := cmd.Output() cmd.Stdout = &stdout
if err != nil { if err := cmd.Run(); err != nil {
return "", err return "", err
} }
if string(out) == "" {
pathOutput := strings.TrimSpace(stdout.String())
if pathOutput == "" {
return "", fmt.Errorf( return "", fmt.Errorf(
"Could not detect Parallels Desktop! Make sure it is properly installed.") "Could not detect Parallels Desktop! Make sure it is properly installed.")
} }
return string(out), nil
return pathOutput, nil
} }
func (d *Parallels9Driver) IsRunning(name string) (bool, error) { func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
@ -251,3 +257,14 @@ func (d *Parallels9Driver) IpAddress(mac string) (string, error) {
log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac) log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac)
return ip, nil return ip, nil
} }
func (d *Parallels9Driver) ToolsIsoPath(k string) (string, error) {
appPath, err := getAppPath("com.parallels.desktop.console")
if err != nil {
return "", err
}
toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
log.Printf("Parallels Tools path: '%s'", toolsPath)
return toolsPath, nil
}

View File

@ -31,6 +31,11 @@ type DriverMock struct {
SendKeyScanCodesCalls [][]string SendKeyScanCodesCalls [][]string
SendKeyScanCodesErrs []error SendKeyScanCodesErrs []error
ToolsIsoPathCalled bool
ToolsIsoPathFlavor string
ToolsIsoPathResult string
ToolsIsoPathErr error
MacName string MacName string
MacReturn string MacReturn string
MacError error MacError error
@ -98,3 +103,9 @@ func (d *DriverMock) IpAddress(mac string) (string, error) {
d.IpAddressMac = mac d.IpAddressMac = mac
return d.IpAddressReturn, d.IpAddressError return d.IpAddressReturn, d.IpAddressError
} }
func (d *DriverMock) ToolsIsoPath(flavor string) (string, error) {
d.ToolsIsoPathCalled = true
d.ToolsIsoPathFlavor = flavor
return d.ToolsIsoPathResult, d.ToolsIsoPathErr
}

View File

@ -7,18 +7,18 @@ import (
"log" "log"
) )
// This step attaches the Parallels Tools as a inserted CD onto // This step attaches the Parallels Tools as an inserted CD onto
// the virtual machine. // the virtual machine.
// //
// Uses: // Uses:
// driver Driver // driver Driver
// toolsPath string // parallels_tools_path string
// ui packer.Ui // ui packer.Ui
// vmName string // vmName string
// //
// Produces: // Produces:
// attachedToolsIso boolean
type StepAttachParallelsTools struct { type StepAttachParallelsTools struct {
ParallelsToolsHostPath string
ParallelsToolsMode string ParallelsToolsMode string
} }
@ -33,12 +33,15 @@ func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepA
return multistep.ActionContinue return multistep.ActionContinue
} }
// Get the Paralells Tools path on the host machine
parallelsToolsPath := state.Get("parallels_tools_path").(string)
// Attach the guest additions to the computer // Attach the guest additions to the computer
ui.Say("Attaching Parallels Tools ISO onto IDE controller...") ui.Say("Attaching Parallels Tools ISO onto IDE controller...")
command := []string{ command := []string{
"set", vmName, "set", vmName,
"--device-add", "cdrom", "--device-add", "cdrom",
"--image", s.ParallelsToolsHostPath, "--image", parallelsToolsPath,
} }
if err := driver.Prlctl(command...); err != nil { if err := driver.Prlctl(command...); err != nil {
err := fmt.Errorf("Error attaching Parallels Tools: %s", err) err := fmt.Errorf("Error attaching Parallels Tools: %s", err)
@ -59,6 +62,7 @@ func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) {
} }
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string) vmName := state.Get("vmName").(string)
log.Println("Detaching Parallels Tools ISO...") log.Println("Detaching Parallels Tools ISO...")
@ -71,5 +75,8 @@ func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) {
"set", vmName, "set", vmName,
"--device-del", cdDevice, "--device-del", cdDevice,
} }
driver.Prlctl(command...)
if err := driver.Prlctl(command...); err != nil {
ui.Error(fmt.Sprintf("Error detaching Parallels Tools ISO: %s", err))
}
} }

View File

@ -0,0 +1,47 @@
package common
import (
"fmt"
"github.com/mitchellh/multistep"
"os"
)
// This step prepares parameters related to Parallels Tools.
//
// Uses:
// driver Driver
//
// Produces:
// parallels_tools_path string
type StepPrepareParallelsTools struct {
ParallelsToolsFlavor string
ParallelsToolsMode string
}
func (s *StepPrepareParallelsTools) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
if s.ParallelsToolsMode == ParallelsToolsModeDisable {
return multistep.ActionContinue
}
path, err := driver.ToolsIsoPath(s.ParallelsToolsFlavor)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if _, err := os.Stat(path); err != nil {
state.Put("error", fmt.Errorf(
"Couldn't find Parallels Tools for the '%s' flavor! Please, check the\n"+
"value of 'parallels_tools_flavor'. Valid flavors are: 'win', 'lin',\n"+
"'mac', 'os2' and 'other'", s.ParallelsToolsFlavor))
return multistep.ActionHalt
}
state.Put("parallels_tools_path", path)
return multistep.ActionContinue
}
func (s *StepPrepareParallelsTools) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,115 @@
package common
import (
"io/ioutil"
"os"
"testing"
"github.com/mitchellh/multistep"
)
func TestStepPrepareParallelsTools_impl(t *testing.T) {
var _ multistep.Step = new(StepPrepareParallelsTools)
}
func TestStepPrepareParallelsTools(t *testing.T) {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
state := testState(t)
step := &StepPrepareParallelsTools{
ParallelsToolsMode: "",
ParallelsToolsFlavor: "foo",
}
driver := state.Get("driver").(*DriverMock)
// Mock results
driver.ToolsIsoPathResult = tf.Name()
// Test the run
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Test the driver
if !driver.ToolsIsoPathCalled {
t.Fatal("tools iso path should be called")
}
if driver.ToolsIsoPathFlavor != "foo" {
t.Fatalf("bad: %#v", driver.ToolsIsoPathFlavor)
}
// Test the resulting state
path, ok := state.GetOk("parallels_tools_path")
if !ok {
t.Fatal("should have parallels_tools_path")
}
if path != tf.Name() {
t.Fatalf("bad: %#v", path)
}
}
func TestStepPrepareParallelsTools_disabled(t *testing.T) {
state := testState(t)
step := &StepPrepareParallelsTools{
ParallelsToolsFlavor: "foo",
ParallelsToolsMode: ParallelsToolsModeDisable,
}
driver := state.Get("driver").(*DriverMock)
// Test the run
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Test the driver
if driver.ToolsIsoPathCalled {
t.Fatal("tools iso path should NOT be called")
}
}
func TestStepPrepareParallelsTools_nonExist(t *testing.T) {
state := testState(t)
step := &StepPrepareParallelsTools{
ParallelsToolsFlavor: "foo",
ParallelsToolsMode: "",
}
driver := state.Get("driver").(*DriverMock)
// Mock results
driver.ToolsIsoPathResult = "foo"
// Test the run
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
// Test the driver
if !driver.ToolsIsoPathCalled {
t.Fatal("tools iso path should be called")
}
if driver.ToolsIsoPathFlavor != "foo" {
t.Fatalf("bad: %#v", driver.ToolsIsoPathFlavor)
}
// Test the resulting state
if _, ok := state.GetOk("parallels_tools_path"); ok {
t.Fatal("should NOT have parallels_tools_path")
}
}

View File

@ -8,13 +8,21 @@ import (
"os" "os"
) )
// This step uploads the Parallels Tools ISO to the virtual machine.
//
// Uses:
// communicator packer.Communicator
// parallels_tools_path string
// ui packer.Ui
//
// Produces:
type toolsPathTemplate struct { type toolsPathTemplate struct {
Version string Flavor string
} }
// This step uploads the guest additions ISO to the VM. // This step uploads the guest additions ISO to the VM.
type StepUploadParallelsTools struct { type StepUploadParallelsTools struct {
ParallelsToolsHostPath string ParallelsToolsFlavor string
ParallelsToolsGuestPath string ParallelsToolsGuestPath string
ParallelsToolsMode string ParallelsToolsMode string
Tpl *packer.ConfigTemplate Tpl *packer.ConfigTemplate
@ -22,7 +30,6 @@ type StepUploadParallelsTools struct {
func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepAction { func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator) comm := state.Get("communicator").(packer.Communicator)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
// If we're attaching then don't do this, since we attached. // If we're attaching then don't do this, since we attached.
@ -31,20 +38,18 @@ func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepA
return multistep.ActionContinue return multistep.ActionContinue
} }
version, err := driver.Version() // Get the Paralells Tools path on the host machine
if err != nil { parallelsToolsPath := state.Get("parallels_tools_path").(string)
state.Put("error", fmt.Errorf("Error reading version for Parallels Tools upload: %s", err))
return multistep.ActionHalt
}
f, err := os.Open(s.ParallelsToolsHostPath) f, err := os.Open(parallelsToolsPath)
if err != nil { if err != nil {
state.Put("error", fmt.Errorf("Error opening Parallels Tools ISO: %s", err)) state.Put("error", fmt.Errorf("Error opening Parallels Tools ISO: %s", err))
return multistep.ActionHalt return multistep.ActionHalt
} }
defer f.Close()
tplData := &toolsPathTemplate{ tplData := &toolsPathTemplate{
Version: version, Flavor: s.ParallelsToolsFlavor,
} }
s.ParallelsToolsGuestPath, err = s.Tpl.Process(s.ParallelsToolsGuestPath, tplData) s.ParallelsToolsGuestPath, err = s.Tpl.Process(s.ParallelsToolsGuestPath, tplData)
@ -55,9 +60,12 @@ func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepA
return multistep.ActionHalt return multistep.ActionHalt
} }
ui.Say("Uploading Parallels Tools ISO...") ui.Say(fmt.Sprintf("Uploading Parallels Tools for '%s' to path: '%s'",
s.ParallelsToolsFlavor, s.ParallelsToolsGuestPath))
if err := comm.Upload(s.ParallelsToolsGuestPath, f); err != nil { if err := comm.Upload(s.ParallelsToolsGuestPath, f); err != nil {
state.Put("error", fmt.Errorf("Error uploading Parallels Tools: %s", err)) err := fmt.Errorf("Error uploading Parallels Tools: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }

View File

@ -0,0 +1,78 @@
package common
import (
"errors"
"fmt"
"github.com/mitchellh/packer/packer"
"text/template"
)
// These are the different valid mode values for "parallels_tools_mode" which
// determine how guest additions are delivered to the guest.
const (
ParallelsToolsModeDisable string = "disable"
ParallelsToolsModeAttach = "attach"
ParallelsToolsModeUpload = "upload"
)
type ToolsConfig struct {
ParallelsToolsFlavor string `mapstructure:"parallels_tools_flavor"`
ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"`
ParallelsToolsMode string `mapstructure:"parallels_tools_mode"`
}
func (c *ToolsConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.ParallelsToolsMode == "" {
c.ParallelsToolsMode = ParallelsToolsModeUpload
}
if c.ParallelsToolsGuestPath == "" {
c.ParallelsToolsGuestPath = "prl-tools-{{.Flavor}}.iso"
}
templates := map[string]*string{
"parallels_tools_flavor": &c.ParallelsToolsFlavor,
"parallels_tools_mode": &c.ParallelsToolsMode,
}
var err error
errs := make([]error, 0)
for n, ptr := range templates {
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if _, err := template.New("path").Parse(c.ParallelsToolsGuestPath); err != nil {
errs = append(errs, fmt.Errorf("parallels_tools_guest_path invalid: %s", err))
}
validMode := false
validModes := []string{
ParallelsToolsModeDisable,
ParallelsToolsModeAttach,
ParallelsToolsModeUpload,
}
for _, mode := range validModes {
if c.ParallelsToolsMode == mode {
validMode = true
break
}
}
if !validMode {
errs = append(errs,
fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v",
validModes))
}
if c.ParallelsToolsFlavor == "" {
if c.ParallelsToolsMode != ParallelsToolsModeDisable {
errs = append(errs, errors.New("parallels_tools_flavor must be specified."))
}
}
return errs
}

View File

@ -0,0 +1,123 @@
package common
import (
"testing"
)
func testToolsConfig() *ToolsConfig {
return &ToolsConfig{
ParallelsToolsFlavor: "foo",
ParallelsToolsGuestPath: "foo",
ParallelsToolsMode: "attach",
}
}
func TestToolsConfigPrepare(t *testing.T) {
c := testToolsConfig()
errs := c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("bad err: %#v", errs)
}
}
func TestToolsConfigPrepare_ParallelsToolsMode(t *testing.T) {
var c *ToolsConfig
var errs []error
// Test default mode
c = testToolsConfig()
c.ParallelsToolsMode = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if c.ParallelsToolsMode != ParallelsToolsModeUpload {
t.Errorf("bad parallels tools mode: %s", c.ParallelsToolsMode)
}
// Test another mode
c = testToolsConfig()
c.ParallelsToolsMode = "attach"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if c.ParallelsToolsMode != ParallelsToolsModeAttach {
t.Fatalf("bad mode: %s", c.ParallelsToolsMode)
}
// Test invalid mode
c = testToolsConfig()
c.ParallelsToolsMode = "invalid_mode"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
}
func TestToolsConfigPrepare_ParallelsToolsGuestPath(t *testing.T) {
var c *ToolsConfig
var errs []error
// Test default path
c = testToolsConfig()
c.ParallelsToolsGuestPath = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if c.ParallelsToolsGuestPath == "" {
t.Fatal("should not be empty")
}
// Test with a bad value
c = testToolsConfig()
c.ParallelsToolsGuestPath = "{{{nope}"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
// Test with a good one
c = testToolsConfig()
c.ParallelsToolsGuestPath = "foo"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
if c.ParallelsToolsGuestPath != "foo" {
t.Fatalf("bad guest path: %s", c.ParallelsToolsGuestPath)
}
}
func TestToolsConfigPrepare_ParallelsToolsFlavor(t *testing.T) {
var c *ToolsConfig
var errs []error
// Test with a default value
c = testToolsConfig()
c.ParallelsToolsFlavor = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
// Test with an bad value
c = testToolsConfig()
c.ParallelsToolsMode = "attach"
c.ParallelsToolsFlavor = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
// Test with a good one
c = testToolsConfig()
c.ParallelsToolsMode = "disable"
c.ParallelsToolsFlavor = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
}

View File

@ -1,9 +0,0 @@
package common
// These are the different valid mode values for "parallels_tools_mode" which
// determine how guest additions are delivered to the guest.
const (
ParallelsToolsModeDisable string = "disable"
ParallelsToolsModeAttach = "attach"
ParallelsToolsModeUpload = "upload"
)

View File

@ -22,18 +22,16 @@ type config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
parallelscommon.FloppyConfig `mapstructure:",squash"` parallelscommon.FloppyConfig `mapstructure:",squash"`
parallelscommon.OutputConfig `mapstructure:",squash"` parallelscommon.OutputConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
parallelscommon.RunConfig `mapstructure:",squash"` parallelscommon.RunConfig `mapstructure:",squash"`
parallelscommon.ShutdownConfig `mapstructure:",squash"` parallelscommon.ShutdownConfig `mapstructure:",squash"`
parallelscommon.SSHConfig `mapstructure:",squash"` parallelscommon.SSHConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"` parallelscommon.ToolsConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
BootCommand []string `mapstructure:"boot_command"` BootCommand []string `mapstructure:"boot_command"`
DiskSize uint `mapstructure:"disk_size"` DiskSize uint `mapstructure:"disk_size"`
ParallelsToolsMode string `mapstructure:"parallels_tools_mode"` GuestOSType string `mapstructure:"guest_os_type"`
ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"`
ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"`
GuestOSDistribution string `mapstructure:"guest_os_distribution"`
HardDriveInterface string `mapstructure:"hard_drive_interface"` HardDriveInterface string `mapstructure:"hard_drive_interface"`
HostInterfaces []string `mapstructure:"host_interfaces"` HostInterfaces []string `mapstructure:"host_interfaces"`
HTTPDir string `mapstructure:"http_directory"` HTTPDir string `mapstructure:"http_directory"`
@ -47,7 +45,8 @@ type config struct {
RawSingleISOUrl string `mapstructure:"iso_url"` RawSingleISOUrl string `mapstructure:"iso_url"`
// Deprecated parameters // Deprecated parameters
GuestOSType string `mapstructure:"guest_os_type"` GuestOSDistribution string `mapstructure:"guest_os_distribution"`
ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"`
tpl *packer.ConfigTemplate tpl *packer.ConfigTemplate
} }
@ -71,34 +70,33 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(b.config.tpl)...)
warnings := make([]string, 0) warnings := make([]string, 0)
if b.config.DiskSize == 0 { if b.config.DiskSize == 0 {
b.config.DiskSize = 40000 b.config.DiskSize = 40000
} }
if b.config.ParallelsToolsMode == "" {
b.config.ParallelsToolsMode = "upload"
}
if b.config.ParallelsToolsGuestPath == "" {
b.config.ParallelsToolsGuestPath = "prl-tools.iso"
}
if b.config.ParallelsToolsHostPath == "" {
b.config.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso"
}
if b.config.HardDriveInterface == "" { if b.config.HardDriveInterface == "" {
b.config.HardDriveInterface = "sata" b.config.HardDriveInterface = "sata"
} }
if b.config.GuestOSDistribution == "" { if b.config.GuestOSType == "" {
b.config.GuestOSDistribution = "other" b.config.GuestOSType = "other"
}
if b.config.GuestOSDistribution != "" {
// Compatibility with older templates:
// Use value of 'guest_os_distribution' if it is defined.
b.config.GuestOSType = b.config.GuestOSDistribution
warnings = append(warnings,
"A 'guest_os_distribution' has been completely replaced with 'guest_os_type'\n"+
"It is recommended to remove it and assign the previous value to 'guest_os_type'.\n"+
"Run it to see all available values: `prlctl create x -d list` ")
} }
if b.config.HTTPPortMin == 0 { if b.config.HTTPPortMin == 0 {
@ -120,10 +118,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
// Errors // Errors
templates := map[string]*string{ templates := map[string]*string{
"parallels_tools_mode": &b.config.ParallelsToolsMode, "guest_os_type": &b.config.GuestOSType,
"parallels_tools_host_path": &b.config.ParallelsToolsHostPath,
"parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath,
"guest_os_distribution": &b.config.GuestOSDistribution,
"hard_drive_interface": &b.config.HardDriveInterface, "hard_drive_interface": &b.config.HardDriveInterface,
"http_directory": &b.config.HTTPDir, "http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum, "iso_checksum": &b.config.ISOChecksum,
@ -150,17 +145,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
} }
validates := map[string]*string{
"parallels_tools_guest_path": &b.config.ParallelsToolsGuestPath,
}
for n, ptr := range validates {
if err := b.config.tpl.Validate(*ptr); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing %s: %s", n, err))
}
}
for i, command := range b.config.BootCommand { for i, command := range b.config.BootCommand {
if err := b.config.tpl.Validate(command); err != nil { if err := b.config.tpl.Validate(command); err != nil {
errs = packer.MultiErrorAppend(errs, errs = packer.MultiErrorAppend(errs,
@ -217,25 +201,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
} }
validMode := false
validModes := []string{
parallelscommon.ParallelsToolsModeDisable,
parallelscommon.ParallelsToolsModeAttach,
parallelscommon.ParallelsToolsModeUpload,
}
for _, mode := range validModes {
if b.config.ParallelsToolsMode == mode {
validMode = true
break
}
}
if !validMode {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes))
}
// Warnings // Warnings
if b.config.ISOChecksumType == "none" { if b.config.ISOChecksumType == "none" {
warnings = append(warnings, warnings = append(warnings,
@ -249,6 +214,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"will forcibly halt the virtual machine, which may result in data loss.") "will forcibly halt the virtual machine, which may result in data loss.")
} }
if b.config.ParallelsToolsHostPath != "" {
warnings = append(warnings,
"A 'parallels_tools_host_path' has been deprecated and not in use anymore\n"+
"You can remove it from your Packer template.")
}
if errs != nil && len(errs.Errors) > 0 { if errs != nil && len(errs.Errors) > 0 {
return warnings, errs return warnings, errs
} }
@ -264,6 +235,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
steps := []multistep.Step{ steps := []multistep.Step{
&parallelscommon.StepPrepareParallelsTools{
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
ParallelsToolsMode: b.config.ParallelsToolsMode,
},
&common.StepDownload{ &common.StepDownload{
Checksum: b.config.ISOChecksum, Checksum: b.config.ISOChecksum,
ChecksumType: b.config.ISOChecksumType, ChecksumType: b.config.ISOChecksumType,
@ -283,7 +258,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(stepCreateDisk), new(stepCreateDisk),
new(stepAttachISO), new(stepAttachISO),
&parallelscommon.StepAttachParallelsTools{ &parallelscommon.StepAttachParallelsTools{
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode, ParallelsToolsMode: b.config.ParallelsToolsMode,
}, },
new(parallelscommon.StepAttachFloppy), new(parallelscommon.StepAttachFloppy),
@ -310,8 +284,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.PrlctlVersionFile, Path: b.config.PrlctlVersionFile,
}, },
&parallelscommon.StepUploadParallelsTools{ &parallelscommon.StepUploadParallelsTools{
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath,
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode, ParallelsToolsMode: b.config.ParallelsToolsMode,
Tpl: b.config.tpl, Tpl: b.config.tpl,
}, },

View File

@ -1,7 +1,6 @@
package iso package iso
import ( import (
"github.com/mitchellh/packer/builder/parallels/common"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"reflect" "reflect"
"testing" "testing"
@ -14,6 +13,7 @@ func testConfig() map[string]interface{} {
"iso_url": "http://www.google.com/", "iso_url": "http://www.google.com/",
"shutdown_command": "yes", "shutdown_command": "yes",
"ssh_username": "foo", "ssh_username": "foo",
"parallels_tools_flavor": "lin",
packer.BuildNameConfigKey: "foo", packer.BuildNameConfigKey: "foo",
} }
@ -38,12 +38,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Fatalf("should not have error: %s", err) t.Fatalf("should not have error: %s", err)
} }
if b.config.ParallelsToolsMode != common.ParallelsToolsModeUpload { if b.config.GuestOSType != "other" {
t.Errorf("bad parallels tools mode: %s", b.config.ParallelsToolsMode) t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
}
if b.config.GuestOSDistribution != "other" {
t.Errorf("bad guest OS distribution: %s", b.config.GuestOSDistribution)
} }
if b.config.VMName != "packer-foo" { if b.config.VMName != "packer-foo" {
@ -83,104 +79,22 @@ func TestBuilderPrepare_DiskSize(t *testing.T) {
} }
} }
func TestBuilderPrepare_ParallelsToolsMode(t *testing.T) { func TestBuilderPrepare_GuestOSType(t *testing.T) {
var b Builder var b Builder
config := testConfig() config := testConfig()
delete(config, "guest_os_distribution")
// test default mode // Test deprecated parameter
delete(config, "parallels_tools_mode") config["guest_os_distribution"] = "bolgenos"
warns, err := b.Prepare(config) warns, err := b.Prepare(config)
if len(warns) > 0 { if len(warns) == 0 {
t.Fatalf("bad: %#v", warns) t.Fatalf("should have warning")
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
// Test another mode
config["parallels_tools_mode"] = "attach"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
} }
if err != nil { if err != nil {
t.Fatalf("should not have error: %s", err) t.Fatalf("should not have error: %s", err)
} }
if b.config.GuestOSType != "bolgenos" {
if b.config.ParallelsToolsMode != common.ParallelsToolsModeAttach { t.Fatalf("bad: %s", b.config.GuestOSType)
t.Fatalf("bad: %s", b.config.ParallelsToolsMode)
}
// Test bad mode
config["parllels_tools_mode"] = "teleport"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should error")
}
}
func TestBuilderPrepare_ParallelsToolsGuestPath(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "parallesl_tools_guest_path")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
if b.config.ParallelsToolsGuestPath != "prl-tools.iso" {
t.Fatalf("bad: %s", b.config.ParallelsToolsGuestPath)
}
config["parallels_tools_guest_path"] = "foo"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ParallelsToolsGuestPath != "foo" {
t.Fatalf("bad size: %s", b.config.ParallelsToolsGuestPath)
}
}
func TestBuilderPrepare_ParallelsToolsHostPath(t *testing.T) {
var b Builder
config := testConfig()
config["parallels_tools_host_path"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.ParallelsToolsHostPath != "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso" {
t.Fatalf("bad: %s", b.config.ParallelsToolsHostPath)
}
config["parallels_tools_host_path"] = "./prl-tools-lin.iso"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
} }
} }
@ -434,3 +348,19 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
t.Fatalf("bad: %#v", b.config.ISOUrls) t.Fatalf("bad: %#v", b.config.ISOUrls)
} }
} }
func TestBuilderPrepare_ParallelsToolsHostPath(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "parallels_tools_host_path")
// Test that it is deprecated
config["parallels_tools_host_path"] = "/path/to/iso"
warns, err := b.Prepare(config)
if len(warns) == 0 {
t.Fatalf("should have warning")
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}

View File

@ -28,7 +28,7 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
commands := make([][]string, 8) commands := make([][]string, 8)
commands[0] = []string{ commands[0] = []string{
"create", name, "create", name,
"--distribution", config.GuestOSDistribution, "--distribution", config.GuestOSType,
"--dst", path, "--dst", path,
"--vmtype", "vm", "--vmtype", "vm",
} }

View File

@ -47,6 +47,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Build the steps. // Build the steps.
steps := []multistep.Step{ steps := []multistep.Step{
&parallelscommon.StepPrepareParallelsTools{
ParallelsToolsMode: b.config.ParallelsToolsMode,
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
},
&parallelscommon.StepOutputDir{ &parallelscommon.StepOutputDir{
Force: b.config.PackerForce, Force: b.config.PackerForce,
Path: b.config.OutputDir, Path: b.config.OutputDir,
@ -59,7 +63,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SourcePath: b.config.SourcePath, SourcePath: b.config.SourcePath,
}, },
&parallelscommon.StepAttachParallelsTools{ &parallelscommon.StepAttachParallelsTools{
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode, ParallelsToolsMode: b.config.ParallelsToolsMode,
}, },
new(parallelscommon.StepAttachFloppy), new(parallelscommon.StepAttachFloppy),
@ -86,8 +89,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.PrlctlVersionFile, Path: b.config.PrlctlVersionFile,
}, },
&parallelscommon.StepUploadParallelsTools{ &parallelscommon.StepUploadParallelsTools{
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath, ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath,
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode, ParallelsToolsMode: b.config.ParallelsToolsMode,
Tpl: b.config.tpl, Tpl: b.config.tpl,
}, },

View File

@ -13,16 +13,14 @@ type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
parallelscommon.FloppyConfig `mapstructure:",squash"` parallelscommon.FloppyConfig `mapstructure:",squash"`
parallelscommon.OutputConfig `mapstructure:",squash"` parallelscommon.OutputConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
parallelscommon.RunConfig `mapstructure:",squash"` parallelscommon.RunConfig `mapstructure:",squash"`
parallelscommon.SSHConfig `mapstructure:",squash"` parallelscommon.SSHConfig `mapstructure:",squash"`
parallelscommon.ShutdownConfig `mapstructure:",squash"` parallelscommon.ShutdownConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"` parallelscommon.ToolsConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
BootCommand []string `mapstructure:"boot_command"` BootCommand []string `mapstructure:"boot_command"`
ParallelsToolsMode string `mapstructure:"parallels_tools_mode"`
ParallelsToolsGuestPath string `mapstructure:"parallels_tools_guest_path"`
ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"`
SourcePath string `mapstructure:"source_path"` SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"` VMName string `mapstructure:"vm_name"`
@ -42,19 +40,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
} }
c.tpl.UserVars = c.PackerUserVars c.tpl.UserVars = c.PackerUserVars
// Defaults
if c.ParallelsToolsMode == "" {
c.ParallelsToolsMode = "disable"
}
if c.ParallelsToolsGuestPath == "" {
c.ParallelsToolsGuestPath = "prl-tools.iso"
}
if c.ParallelsToolsHostPath == "" {
c.ParallelsToolsHostPath = "/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso"
}
if c.VMName == "" { if c.VMName == "" {
c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName) c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName)
} }
@ -63,16 +48,14 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs := common.CheckUnusedConfig(md) errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, c.PrlctlConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.PrlctlVersionConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.PrlctlConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.PrlctlVersionConfig.Prepare(c.tpl)...)
templates := map[string]*string{ templates := map[string]*string{
"parallels_tools_mode": &c.ParallelsToolsMode,
"parallels_tools_host_paht": &c.ParallelsToolsHostPath,
"parallels_tools_guest_path": &c.ParallelsToolsGuestPath,
"source_path": &c.SourcePath, "source_path": &c.SourcePath,
"vm_name": &c.VMName, "vm_name": &c.VMName,
} }
@ -93,25 +76,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
} }
} }
validMode := false
validModes := []string{
parallelscommon.ParallelsToolsModeDisable,
parallelscommon.ParallelsToolsModeAttach,
parallelscommon.ParallelsToolsModeUpload,
}
for _, mode := range validModes {
if c.ParallelsToolsMode == mode {
validMode = true
break
}
}
if !validMode {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("parallels_tools_mode is invalid. Must be one of: %v", validModes))
}
if c.SourcePath == "" { if c.SourcePath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
} else { } else {

View File

@ -10,6 +10,7 @@ func testConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"ssh_username": "foo", "ssh_username": "foo",
"shutdown_command": "foo", "shutdown_command": "foo",
"parallels_tools_flavor": "lin",
} }
} }

View File

@ -26,10 +26,11 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
<pre class="prettyprint"> <pre class="prettyprint">
{ {
"type": "parallels-iso", "type": "parallels-iso",
"guest_os_type": "Ubuntu_64", "guest_os_type": "ubuntu",
"iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso", "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso",
"iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9", "iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
"iso_checksum_type": "md5", "iso_checksum_type": "md5",
"parallels_tools_flavor": "lin"
"ssh_username": "packer", "ssh_username": "packer",
"ssh_password": "packer", "ssh_password": "packer",
"ssh_wait_timeout": "30s", "ssh_wait_timeout": "30s",
@ -68,6 +69,10 @@ each category, the available options are alphabetized and described.
* `ssh_username` (string) - The username to use to SSH into the machine * `ssh_username` (string) - The username to use to SSH into the machine
once the OS is installed. once the OS is installed.
* `parallels_tools_flavor` (string) - The flavor of the Parallels Tools ISO to
install into the VM. Valid values are "win", "lin", "mac", "os2" and "other".
This can be ommited only if `parallels_tools_mode` is "disable".
### Optional: ### Optional:
* `boot_command` (array of strings) - This is an array of commands to type * `boot_command` (array of strings) - This is an array of commands to type
@ -96,17 +101,10 @@ each category, the available options are alphabetized and described.
characters (*, ?, and []) are allowed. Directory names are also allowed, characters (*, ?, and []) are allowed. Directory names are also allowed,
which will add all the files found in the directory to the floppy. which will add all the files found in the directory to the floppy.
* `guest_os_distribution` (string) - The guest OS distribution being
installed. By default this is "other", but you can get dramatic
performance improvements by setting this to the proper value. To
view all available values for this run `prlctl create x --distribution list`.
Setting the correct value hints to Parallels how to optimize the virtual
hardware to work best with that operating system.
* `guest_os_type` (string) - The guest OS type being installed. By default * `guest_os_type` (string) - The guest OS type being installed. By default
this is "other", but you can get _dramatic_ performance improvements by this is "other", but you can get _dramatic_ performance improvements by
setting this to the proper value. To view all available values for this setting this to the proper value. To view all available values for this
run `prlctl create x --ostype list`. Setting the correct value hints to run `prlctl create x --distribution list`. Setting the correct value hints to
Parallels Desktop how to optimize the virtual hardware to work best with Parallels Desktop how to optimize the virtual hardware to work best with
that operating system. that operating system.
@ -148,18 +146,14 @@ each category, the available options are alphabetized and described.
By default this is "output-BUILDNAME" where "BUILDNAME" is the name By default this is "output-BUILDNAME" where "BUILDNAME" is the name
of the build. of the build.
* `parallels_tools_guest_path` (string) - The path on the guest virtual machine * `parallels_tools_guest_path` (string) - The path in the VM to upload Parallels
where the Parallels tools ISO will be uploaded. By default this is Tools. This only takes effect if `parallels_tools_mode` is not "disable".
"prl-tools.iso" which should upload into the login directory of the user. This is a [configuration template](/docs/templates/configuration-templates.html)
This is a configuration template where the `Version` variable is replaced that has a single valid variable: `Flavor`, which will be the value of
with the prlctl version. `parallels_tools_flavor`. By default the upload path is set to
`prl-tools-{{.Flavor}}.iso`.
* `parallels_tools_host_path` (string) - The path to the Parallels Tools ISO to * `parallels_tools_mode` (string) - The method by which Parallels Tools are
upload. By default the Parallels builder will use the "other" OS tools ISO from
the Parallels installation:
"/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso"
* `parallels_tools_mode` (string) - The method by which Parallels tools are
made available to the guest for installation. Valid options are "upload", made available to the guest for installation. Valid options are "upload",
"attach", or "disable". The functions of each of these should be "attach", or "disable". The functions of each of these should be
self-explanatory. The default value is "upload". self-explanatory. The default value is "upload".
@ -260,20 +254,12 @@ an Ubuntu 12.04 installer:
] ]
</pre> </pre>
## Parallels Tools
After the virtual machine is up and the operating system is installed, Packer
uploads the Parallels Tools into the virtual machine. The path where they are
uploaded is controllable by `parallels_tools_path`, and defaults to
"prl-tools.iso". Without an absolute path, it is uploaded to the home directory
of the SSH user. Parallels Tools ISO's can be found in:
"/Applications/Parallels Desktop.app/Contents/Resources/Tools/"
## prlctl Commands ## prlctl Commands
In order to perform extra customization of the virtual machine, a template can In order to perform extra customization of the virtual machine, a template can
define extra calls to `prlctl` to perform. define extra calls to `prlctl` to perform.
[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/) [prlctl](http://download.parallels.com/desktop/v9/ga/docs/en_US/Parallels%20Command%20Line%20Reference%20Guide.pdf)
is the command-line interface to Parallels. It can be used to do things such as is the command-line interface to Parallels Desktop. It can be used to configure
set RAM, CPUs, etc. the virtual machine, such as set RAM, CPUs, etc.
Extra `prlctl` commands are defined in the template in the `prlctl` section. Extra `prlctl` commands are defined in the template in the `prlctl` section.
An example is shown below that sets the memory and number of CPUs within the An example is shown below that sets the memory and number of CPUs within the

View File

@ -25,6 +25,7 @@ the settings here.
<pre class="prettyprint"> <pre class="prettyprint">
{ {
"type": "parallels-pvm", "type": "parallels-pvm",
"parallels_tools_flavor": "lin"
"source_path": "source.pvm", "source_path": "source.pvm",
"ssh_username": "packer", "ssh_username": "packer",
"ssh_password": "packer", "ssh_password": "packer",
@ -51,6 +52,10 @@ each category, the available options are alphabetized and described.
* `ssh_username` (string) - The username to use to SSH into the machine * `ssh_username` (string) - The username to use to SSH into the machine
once the OS is installed. once the OS is installed.
* `parallels_tools_flavor` (string) - The flavor of the Parallels Tools ISO to
install into the VM. Valid values are "win", "lin", "mac", "os2" and "other".
This can be ommited only if `parallels_tools_mode` is "disable".
### Optional: ### Optional:
* `boot_command` (array of strings) - This is an array of commands to type * `boot_command` (array of strings) - This is an array of commands to type
@ -80,18 +85,14 @@ each category, the available options are alphabetized and described.
By default this is "output-BUILDNAME" where "BUILDNAME" is the name By default this is "output-BUILDNAME" where "BUILDNAME" is the name
of the build. of the build.
* `parallels_tools_guest_path` (string) - The path on the guest virtual machine * `parallels_tools_guest_path` (string) - The path in the VM to upload Parallels
where the Parallels tools ISO will be uploaded. By default this is Tools. This only takes effect if `parallels_tools_mode` is not "disable".
"prl-tools.iso" which should upload into the login directory of the user. This is a [configuration template](/docs/templates/configuration-templates.html)
This is a configuration template where the `Version` variable is replaced that has a single valid variable: `Flavor`, which will be the value of
with the prlctl version. `parallels_tools_flavor`. By default the upload path is set to
`prl-tools-{{.Flavor}}.iso`.
* `parallels_tools_host_path` (string) - The path to the Parallels Tools ISO to * `parallels_tools_mode` (string) - The method by which Parallels Tools are
upload. By default the Parallels builder will use the "other" OS tools ISO from
the Parallels installation:
"/Applications/Parallels Desktop.app/Contents/Resources/Tools/prl-tools-other.iso"
* `parallels_tools_mode` (string) - The method by which Parallels tools are
made available to the guest for installation. Valid options are "upload", made available to the guest for installation. Valid options are "upload",
"attach", or "disable". The functions of each of these should be "attach", or "disable". The functions of each of these should be
self-explanatory. The default value is "upload". self-explanatory. The default value is "upload".
@ -179,9 +180,9 @@ The available variables are:
## prlctl Commands ## prlctl Commands
In order to perform extra customization of the virtual machine, a template can In order to perform extra customization of the virtual machine, a template can
define extra calls to `prlctl` to perform. define extra calls to `prlctl` to perform.
[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/) [prlctl](http://download.parallels.com/desktop/v9/ga/docs/en_US/Parallels%20Command%20Line%20Reference%20Guide.pdf)
is the command-line interface to Parallels. It can be used to do things such as is the command-line interface to Parallels Desktop. It can be used to configure
set RAM, CPUs, etc. the virtual machine, such as set RAM, CPUs, etc.
Extra `prlctl` commands are defined in the template in the `prlctl` section. Extra `prlctl` commands are defined in the template in the `prlctl` section.
An example is shown below that sets the memory and number of CPUs within the An example is shown below that sets the memory and number of CPUs within the