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(...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
// properly. If there is any indication the driver can't function,
// this will return an error.

View File

@ -6,6 +6,7 @@ import (
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
@ -73,16 +74,21 @@ func getConfigValueFromXpath(path, xpath string) (string, error) {
// Finds an application bundle by identifier (for "darwin" platform only)
func getAppPath(bundleId string) (string, error) {
var stdout bytes.Buffer
cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId)
out, err := cmd.Output()
if err != nil {
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", err
}
if string(out) == "" {
pathOutput := strings.TrimSpace(stdout.String())
if pathOutput == "" {
return "", fmt.Errorf(
"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) {
@ -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)
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
SendKeyScanCodesErrs []error
ToolsIsoPathCalled bool
ToolsIsoPathFlavor string
ToolsIsoPathResult string
ToolsIsoPathErr error
MacName string
MacReturn string
MacError error
@ -98,3 +103,9 @@ func (d *DriverMock) IpAddress(mac string) (string, error) {
d.IpAddressMac = mac
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,19 +7,19 @@ import (
"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.
//
// Uses:
// driver Driver
// toolsPath string
// parallels_tools_path string
// ui packer.Ui
// vmName string
//
// Produces:
// attachedToolsIso boolean
type StepAttachParallelsTools struct {
ParallelsToolsHostPath string
ParallelsToolsMode string
ParallelsToolsMode string
}
func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepAction {
@ -33,12 +33,15 @@ func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepA
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
ui.Say("Attaching Parallels Tools ISO onto IDE controller...")
command := []string{
"set", vmName,
"--device-add", "cdrom",
"--image", s.ParallelsToolsHostPath,
"--image", parallelsToolsPath,
}
if err := driver.Prlctl(command...); err != nil {
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)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
log.Println("Detaching Parallels Tools ISO...")
@ -71,5 +75,8 @@ func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) {
"set", vmName,
"--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"
)
// 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 {
Version string
Flavor string
}
// This step uploads the guest additions ISO to the VM.
type StepUploadParallelsTools struct {
ParallelsToolsHostPath string
ParallelsToolsFlavor string
ParallelsToolsGuestPath string
ParallelsToolsMode string
Tpl *packer.ConfigTemplate
@ -22,7 +30,6 @@ type StepUploadParallelsTools struct {
func (s *StepUploadParallelsTools) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
// 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
}
version, err := driver.Version()
if err != nil {
state.Put("error", fmt.Errorf("Error reading version for Parallels Tools upload: %s", err))
return multistep.ActionHalt
}
// Get the Paralells Tools path on the host machine
parallelsToolsPath := state.Get("parallels_tools_path").(string)
f, err := os.Open(s.ParallelsToolsHostPath)
f, err := os.Open(parallelsToolsPath)
if err != nil {
state.Put("error", fmt.Errorf("Error opening Parallels Tools ISO: %s", err))
return multistep.ActionHalt
}
defer f.Close()
tplData := &toolsPathTemplate{
Version: version,
Flavor: s.ParallelsToolsFlavor,
}
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
}
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 {
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
}

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,32 +22,31 @@ type config struct {
common.PackerConfig `mapstructure:",squash"`
parallelscommon.FloppyConfig `mapstructure:",squash"`
parallelscommon.OutputConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
parallelscommon.RunConfig `mapstructure:",squash"`
parallelscommon.ShutdownConfig `mapstructure:",squash"`
parallelscommon.SSHConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
parallelscommon.ToolsConfig `mapstructure:",squash"`
BootCommand []string `mapstructure:"boot_command"`
DiskSize uint `mapstructure:"disk_size"`
ParallelsToolsMode string `mapstructure:"parallels_tools_mode"`
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"`
HostInterfaces []string `mapstructure:"host_interfaces"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"`
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
DiskSize uint `mapstructure:"disk_size"`
GuestOSType string `mapstructure:"guest_os_type"`
HardDriveInterface string `mapstructure:"hard_drive_interface"`
HostInterfaces []string `mapstructure:"host_interfaces"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"`
VMName string `mapstructure:"vm_name"`
RawSingleISOUrl string `mapstructure:"iso_url"`
// Deprecated parameters
GuestOSType string `mapstructure:"guest_os_type"`
GuestOSDistribution string `mapstructure:"guest_os_distribution"`
ParallelsToolsHostPath string `mapstructure:"parallels_tools_host_path"`
tpl *packer.ConfigTemplate
}
@ -71,34 +70,33 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(
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.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.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)
if b.config.DiskSize == 0 {
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 == "" {
b.config.HardDriveInterface = "sata"
}
if b.config.GuestOSDistribution == "" {
b.config.GuestOSDistribution = "other"
if b.config.GuestOSType == "" {
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 {
@ -120,16 +118,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
// Errors
templates := map[string]*string{
"parallels_tools_mode": &b.config.ParallelsToolsMode,
"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,
"http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType,
"iso_url": &b.config.RawSingleISOUrl,
"vm_name": &b.config.VMName,
"guest_os_type": &b.config.GuestOSType,
"hard_drive_interface": &b.config.HardDriveInterface,
"http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType,
"iso_url": &b.config.RawSingleISOUrl,
"vm_name": &b.config.VMName,
}
for n, ptr := range templates {
@ -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 {
if err := b.config.tpl.Validate(command); err != nil {
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
if b.config.ISOChecksumType == "none" {
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.")
}
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 {
return warnings, errs
}
@ -264,6 +235,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
steps := []multistep.Step{
&parallelscommon.StepPrepareParallelsTools{
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
ParallelsToolsMode: b.config.ParallelsToolsMode,
},
&common.StepDownload{
Checksum: b.config.ISOChecksum,
ChecksumType: b.config.ISOChecksumType,
@ -283,8 +258,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(stepCreateDisk),
new(stepAttachISO),
&parallelscommon.StepAttachParallelsTools{
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode,
ParallelsToolsMode: b.config.ParallelsToolsMode,
},
new(parallelscommon.StepAttachFloppy),
&parallelscommon.StepPrlctl{
@ -310,8 +284,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.PrlctlVersionFile,
},
&parallelscommon.StepUploadParallelsTools{
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath,
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode,
Tpl: b.config.tpl,
},

View File

@ -1,7 +1,6 @@
package iso
import (
"github.com/mitchellh/packer/builder/parallels/common"
"github.com/mitchellh/packer/packer"
"reflect"
"testing"
@ -9,11 +8,12 @@ import (
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
"iso_checksum_type": "md5",
"iso_url": "http://www.google.com/",
"shutdown_command": "yes",
"ssh_username": "foo",
"iso_checksum": "foo",
"iso_checksum_type": "md5",
"iso_url": "http://www.google.com/",
"shutdown_command": "yes",
"ssh_username": "foo",
"parallels_tools_flavor": "lin",
packer.BuildNameConfigKey: "foo",
}
@ -38,12 +38,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Fatalf("should not have error: %s", err)
}
if b.config.ParallelsToolsMode != common.ParallelsToolsModeUpload {
t.Errorf("bad parallels tools mode: %s", b.config.ParallelsToolsMode)
}
if b.config.GuestOSDistribution != "other" {
t.Errorf("bad guest OS distribution: %s", b.config.GuestOSDistribution)
if b.config.GuestOSType != "other" {
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
}
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
config := testConfig()
delete(config, "guest_os_distribution")
// test default mode
delete(config, "parallels_tools_mode")
// Test deprecated parameter
config["guest_os_distribution"] = "bolgenos"
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
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 len(warns) == 0 {
t.Fatalf("should have warning")
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ParallelsToolsMode != common.ParallelsToolsModeAttach {
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)
if b.config.GuestOSType != "bolgenos" {
t.Fatalf("bad: %s", b.config.GuestOSType)
}
}
@ -434,3 +348,19 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
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[0] = []string{
"create", name,
"--distribution", config.GuestOSDistribution,
"--distribution", config.GuestOSType,
"--dst", path,
"--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.
steps := []multistep.Step{
&parallelscommon.StepPrepareParallelsTools{
ParallelsToolsMode: b.config.ParallelsToolsMode,
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
},
&parallelscommon.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
@ -59,8 +63,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SourcePath: b.config.SourcePath,
},
&parallelscommon.StepAttachParallelsTools{
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode,
ParallelsToolsMode: b.config.ParallelsToolsMode,
},
new(parallelscommon.StepAttachFloppy),
&parallelscommon.StepPrlctl{
@ -86,8 +89,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Path: b.config.PrlctlVersionFile,
},
&parallelscommon.StepUploadParallelsTools{
ParallelsToolsFlavor: b.config.ParallelsToolsFlavor,
ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath,
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
ParallelsToolsMode: b.config.ParallelsToolsMode,
Tpl: b.config.tpl,
},

View File

@ -13,18 +13,16 @@ type Config struct {
common.PackerConfig `mapstructure:",squash"`
parallelscommon.FloppyConfig `mapstructure:",squash"`
parallelscommon.OutputConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
parallelscommon.RunConfig `mapstructure:",squash"`
parallelscommon.SSHConfig `mapstructure:",squash"`
parallelscommon.ShutdownConfig `mapstructure:",squash"`
parallelscommon.PrlctlConfig `mapstructure:",squash"`
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
parallelscommon.ToolsConfig `mapstructure:",squash"`
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"`
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"`
tpl *packer.ConfigTemplate
}
@ -42,19 +40,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
}
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 == "" {
c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName)
}
@ -63,18 +48,16 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs := common.CheckUnusedConfig(md)
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.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.ShutdownConfig.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.PrlctlVersionConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(c.tpl)...)
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,
"vm_name": &c.VMName,
"source_path": &c.SourcePath,
"vm_name": &c.VMName,
}
for n, ptr := range templates {
@ -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 == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
} else {

View File

@ -8,8 +8,9 @@ import (
func testConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{
"ssh_username": "foo",
"shutdown_command": "foo",
"ssh_username": "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">
{
"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_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
"iso_checksum_type": "md5",
"parallels_tools_flavor": "lin"
"ssh_username": "packer",
"ssh_password": "packer",
"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
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:
* `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,
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
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 --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
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
of the build.
* `parallels_tools_guest_path` (string) - The path on the guest virtual machine
where the Parallels tools ISO will be uploaded. By default this is
"prl-tools.iso" which should upload into the login directory of the user.
This is a configuration template where the `Version` variable is replaced
with the prlctl version.
* `parallels_tools_guest_path` (string) - The path in the VM to upload Parallels
Tools. This only takes effect if `parallels_tools_mode` is not "disable".
This is a [configuration template](/docs/templates/configuration-templates.html)
that has a single valid variable: `Flavor`, which will be the value of
`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
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
* `parallels_tools_mode` (string) - The method by which Parallels Tools are
made available to the guest for installation. Valid options are "upload",
"attach", or "disable". The functions of each of these should be
self-explanatory. The default value is "upload".
@ -260,20 +254,12 @@ an Ubuntu 12.04 installer:
]
</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
In order to perform extra customization of the virtual machine, a template can
define extra calls to `prlctl` to perform.
[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/)
is the command-line interface to Parallels. It can be used to do things such as
set RAM, CPUs, etc.
[prlctl](http://download.parallels.com/desktop/v9/ga/docs/en_US/Parallels%20Command%20Line%20Reference%20Guide.pdf)
is the command-line interface to Parallels Desktop. It can be used to configure
the virtual machine, such as set RAM, CPUs, etc.
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

View File

@ -25,6 +25,7 @@ the settings here.
<pre class="prettyprint">
{
"type": "parallels-pvm",
"parallels_tools_flavor": "lin"
"source_path": "source.pvm",
"ssh_username": "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
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:
* `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
of the build.
* `parallels_tools_guest_path` (string) - The path on the guest virtual machine
where the Parallels tools ISO will be uploaded. By default this is
"prl-tools.iso" which should upload into the login directory of the user.
This is a configuration template where the `Version` variable is replaced
with the prlctl version.
* `parallels_tools_guest_path` (string) - The path in the VM to upload Parallels
Tools. This only takes effect if `parallels_tools_mode` is not "disable".
This is a [configuration template](/docs/templates/configuration-templates.html)
that has a single valid variable: `Flavor`, which will be the value of
`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
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
* `parallels_tools_mode` (string) - The method by which Parallels Tools are
made available to the guest for installation. Valid options are "upload",
"attach", or "disable". The functions of each of these should be
self-explanatory. The default value is "upload".
@ -179,9 +180,9 @@ The available variables are:
## prlctl Commands
In order to perform extra customization of the virtual machine, a template can
define extra calls to `prlctl` to perform.
[prlctl](http://download.parallels.com/desktop/v4/wl/docs/en/Parallels_Command_Line_Reference_Guide/)
is the command-line interface to Parallels. It can be used to do things such as
set RAM, CPUs, etc.
[prlctl](http://download.parallels.com/desktop/v9/ga/docs/en_US/Parallels%20Command%20Line%20Reference%20Guide.pdf)
is the command-line interface to Parallels Desktop. It can be used to configure
the virtual machine, such as set RAM, CPUs, etc.
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