Added support for Parallels Desktop for Mac [GH-223]
Added builder plugins: builder-parallels-iso and builder-parallels-pvm.
This commit is contained in:
parent
2010bf184f
commit
cdaa9d5a8e
|
@ -0,0 +1,71 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the common builder ID to all of these artifacts.
|
||||||
|
const BuilderId = "rickard-von-essen.parallels"
|
||||||
|
|
||||||
|
// These are the extensions of files and directories that are unnecessary for the function
|
||||||
|
// of a Parallels virtual machine.
|
||||||
|
var unnecessaryFiles = []string{"\\.log$", "\\.backup$", "\\.Backup$", "\\.app"}
|
||||||
|
|
||||||
|
// Artifact is the result of running the parallels builder, namely a set
|
||||||
|
// of files associated with the resulting machine.
|
||||||
|
type artifact struct {
|
||||||
|
dir string
|
||||||
|
f []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArtifact returns a Parallels artifact containing the files
|
||||||
|
// in the given directory.
|
||||||
|
func NewArtifact(dir string) (packer.Artifact, error) {
|
||||||
|
files := make([]string, 0, 5)
|
||||||
|
visit := func(path string, info os.FileInfo, err error) error {
|
||||||
|
for _, unnecessaryFile := range unnecessaryFiles {
|
||||||
|
if unnecessary, _ := regexp.MatchString(unnecessaryFile, path); unnecessary {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := filepath.Walk(dir, visit); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &artifact{
|
||||||
|
dir: dir,
|
||||||
|
f: files,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*artifact) BuilderId() string {
|
||||||
|
return BuilderId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artifact) Files() []string {
|
||||||
|
return a.f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*artifact) Id() string {
|
||||||
|
return "VM"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artifact) String() string {
|
||||||
|
return fmt.Sprintf("VM files in directory: %s", a.dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *artifact) Destroy() error {
|
||||||
|
return os.RemoveAll(a.dir)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArtifact_impl(t *testing.T) {
|
||||||
|
var _ packer.Artifact = new(artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewArtifact(t *testing.T) {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := NewArtifact(td)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.BuilderId() != BuilderId {
|
||||||
|
t.Fatalf("bad: %#v", a.BuilderId())
|
||||||
|
}
|
||||||
|
if len(a.Files()) != 1 {
|
||||||
|
t.Fatalf("should length 1: %d", len(a.Files()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfigTemplate(t *testing.T) *packer.ConfigTemplate {
|
||||||
|
result, err := packer.NewConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A driver is able to talk to Parallels and perform certain
|
||||||
|
// operations with it. Some of the operations on here may seem overly
|
||||||
|
// specific, but they were built specifically in mind to handle features
|
||||||
|
// of the Parallels builder for Packer, and to abstract differences in
|
||||||
|
// versions out of the builder steps, so sometimes the methods are
|
||||||
|
// extremely specific.
|
||||||
|
type Driver interface {
|
||||||
|
// Import a VM
|
||||||
|
Import(string, string, string) error
|
||||||
|
|
||||||
|
// Checks if the VM with the given name is running.
|
||||||
|
IsRunning(string) (bool, error)
|
||||||
|
|
||||||
|
// Stop stops a running machine, forcefully.
|
||||||
|
Stop(string) error
|
||||||
|
|
||||||
|
// Prlctl executes the given Prlctl command
|
||||||
|
Prlctl(...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.
|
||||||
|
Verify() error
|
||||||
|
|
||||||
|
// Version reads the version of Parallels that is installed.
|
||||||
|
Version() (string, error)
|
||||||
|
|
||||||
|
// Send scancodes to the vm using the prltype tool.
|
||||||
|
SendKeyScanCodes(string, ...string) error
|
||||||
|
|
||||||
|
// Finds the MAC address of the NIC nic0
|
||||||
|
Mac(string) (string, error)
|
||||||
|
|
||||||
|
// Finds the IP address of a VM connected that uses DHCP by its MAC address
|
||||||
|
IpAddress(string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDriver() (Driver, error) {
|
||||||
|
var prlctlPath string
|
||||||
|
|
||||||
|
if prlctlPath == "" {
|
||||||
|
var err error
|
||||||
|
prlctlPath, err = exec.LookPath("prlctl")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("prlctl path: %s", prlctlPath)
|
||||||
|
driver := &Parallels9Driver{prlctlPath}
|
||||||
|
if err := driver.Verify(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, nil
|
||||||
|
}
|
|
@ -0,0 +1,241 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/going/toolkit/xmlpath"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parallels9Driver struct {
|
||||||
|
// This is the path to the "prlctl" application.
|
||||||
|
PrlctlPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) Import(name, srcPath, dstDir string) error {
|
||||||
|
|
||||||
|
err := d.Prlctl("register", srcPath, "--preserve-uuid")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srcId, err := getVmId(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srcMac, err := getFirtsMacAddress(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Prlctl("clone", srcId, "--name", name, "--dst", dstDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Prlctl("unregister", srcId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMac)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVmId(path string) (string, error) {
|
||||||
|
return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFirtsMacAddress(path string) (string, error) {
|
||||||
|
return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigValueFromXpath(path, xpath string) (string, error) {
|
||||||
|
file, err := os.Open(path + "/config.pvs")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
xpathComp := xmlpath.MustCompile(xpath)
|
||||||
|
root, err := xmlpath.Parse(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
value, _ := xpathComp.String(root)
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
|
||||||
|
cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
||||||
|
if line == "running" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == "suspended" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if line == "paused" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) Stop(name string) error {
|
||||||
|
if err := d.Prlctl("stop", name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We sleep here for a little bit to let the session "unlock"
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) Prlctl(args ...string) error {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
log.Printf("Executing prlctl: %#v", args)
|
||||||
|
cmd := exec.Command(d.PrlctlPath, args...)
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
stdoutString := strings.TrimSpace(stdout.String())
|
||||||
|
stderrString := strings.TrimSpace(stderr.String())
|
||||||
|
|
||||||
|
if _, ok := err.(*exec.ExitError); ok {
|
||||||
|
err = fmt.Errorf("prlctl error: %s", stderrString)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("stdout: %s", stdoutString)
|
||||||
|
log.Printf("stderr: %s", stderrString)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) Verify() error {
|
||||||
|
version, _ := d.Version()
|
||||||
|
if !strings.HasPrefix(version, "9.") {
|
||||||
|
return fmt.Errorf("The packer-parallels builder plugin only supports Parallels Desktop v. 9. You have: %s!\n", version)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) Version() (string, error) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
|
||||||
|
cmd := exec.Command(d.PrlctlPath, "--version")
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
versionOutput := strings.TrimSpace(stdout.String())
|
||||||
|
re := regexp.MustCompile("prlctl version ([0-9\\.]+)")
|
||||||
|
verMatch := re.FindAllStringSubmatch(versionOutput, 1)
|
||||||
|
|
||||||
|
if len(verMatch) != 1 {
|
||||||
|
return "", fmt.Errorf("prlctl version not found!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
version := verMatch[0][1]
|
||||||
|
log.Printf("prlctl version: %s\n", version)
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
args := prepend(vmName, codes)
|
||||||
|
cmd := exec.Command("prltype", args...)
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
stdoutString := strings.TrimSpace(stdout.String())
|
||||||
|
stderrString := strings.TrimSpace(stderr.String())
|
||||||
|
|
||||||
|
if _, ok := err.(*exec.ExitError); ok {
|
||||||
|
err = fmt.Errorf("prltype error: %s", stderrString)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("stdout: %s", stdoutString)
|
||||||
|
log.Printf("stderr: %s", stderrString)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepend(head string, tail []string) []string {
|
||||||
|
tmp := make([]string, len(tail)+1)
|
||||||
|
for i := 0; i < len(tail); i++ {
|
||||||
|
tmp[i+1] = tail[i]
|
||||||
|
}
|
||||||
|
tmp[0] = head
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Parallels9Driver) Mac(vmName string) (string, error) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
|
||||||
|
cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutString := strings.TrimSpace(stdout.String())
|
||||||
|
re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
|
||||||
|
macMatch := re.FindAllStringSubmatch(stdoutString, 1)
|
||||||
|
|
||||||
|
if len(macMatch) != 1 {
|
||||||
|
return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := macMatch[0][1]
|
||||||
|
log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
|
||||||
|
return mac, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds the IP address of a VM connected that uses DHCP by its MAC address
|
||||||
|
func (d *Parallels9Driver) IpAddress(mac string) (string, error) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
dhcp_lease_file := "/Library/Preferences/Parallels/parallels_dhcp_leases"
|
||||||
|
|
||||||
|
if len(mac) != 12 {
|
||||||
|
return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("grep", "-i", mac, dhcp_lease_file)
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutString := strings.TrimSpace(stdout.String())
|
||||||
|
re := regexp.MustCompile("(.*)=.*")
|
||||||
|
ipMatch := re.FindAllStringSubmatch(stdoutString, 1)
|
||||||
|
|
||||||
|
if len(ipMatch) != 1 {
|
||||||
|
return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, dhcp_lease_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := ipMatch[0][1]
|
||||||
|
log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac)
|
||||||
|
return ip, nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParallels9Driver_impl(t *testing.T) {
|
||||||
|
var _ Driver = new(Parallels9Driver)
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type DriverMock struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
ImportCalled bool
|
||||||
|
ImportName string
|
||||||
|
ImportSrcPath string
|
||||||
|
ImportDstPath string
|
||||||
|
ImportErr error
|
||||||
|
|
||||||
|
IsRunningName string
|
||||||
|
IsRunningReturn bool
|
||||||
|
IsRunningErr error
|
||||||
|
|
||||||
|
StopName string
|
||||||
|
StopErr error
|
||||||
|
|
||||||
|
PrlctlCalls [][]string
|
||||||
|
PrlctlErrs []error
|
||||||
|
|
||||||
|
VerifyCalled bool
|
||||||
|
VerifyErr error
|
||||||
|
|
||||||
|
VersionCalled bool
|
||||||
|
VersionResult string
|
||||||
|
VersionErr error
|
||||||
|
|
||||||
|
SendKeyScanCodesCalls [][]string
|
||||||
|
SendKeyScanCodesErrs []error
|
||||||
|
|
||||||
|
MacName string
|
||||||
|
MacReturn string
|
||||||
|
MacError error
|
||||||
|
|
||||||
|
IpAddressMac string
|
||||||
|
IpAddressReturn string
|
||||||
|
IpAddressError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Import(name, srcPath, dstPath string) error {
|
||||||
|
d.ImportCalled = true
|
||||||
|
d.ImportName = name
|
||||||
|
d.ImportSrcPath = srcPath
|
||||||
|
d.ImportDstPath = dstPath
|
||||||
|
return d.ImportErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) IsRunning(name string) (bool, error) {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
|
||||||
|
d.IsRunningName = name
|
||||||
|
return d.IsRunningReturn, d.IsRunningErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Stop(name string) error {
|
||||||
|
d.StopName = name
|
||||||
|
return d.StopErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Prlctl(args ...string) error {
|
||||||
|
d.PrlctlCalls = append(d.PrlctlCalls, args)
|
||||||
|
|
||||||
|
if len(d.PrlctlErrs) >= len(d.PrlctlCalls) {
|
||||||
|
return d.PrlctlErrs[len(d.PrlctlCalls)-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Verify() error {
|
||||||
|
d.VerifyCalled = true
|
||||||
|
return d.VerifyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Version() (string, error) {
|
||||||
|
d.VersionCalled = true
|
||||||
|
return d.VersionResult, d.VersionErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) SendKeyScanCodes(name string, scancodes ...string) error {
|
||||||
|
d.SendKeyScanCodesCalls = append(d.SendKeyScanCodesCalls, scancodes)
|
||||||
|
|
||||||
|
if len(d.SendKeyScanCodesErrs) >= len(d.SendKeyScanCodesCalls) {
|
||||||
|
return d.SendKeyScanCodesErrs[len(d.SendKeyScanCodesCalls)-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) Mac(name string) (string, error) {
|
||||||
|
d.MacName = name
|
||||||
|
return d.MacReturn, d.MacError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) IpAddress(mac string) (string, error) {
|
||||||
|
d.IpAddressMac = mac
|
||||||
|
return d.IpAddressReturn, d.IpAddressError
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FloppyConfig is configuration related to created floppy disks and attaching
|
||||||
|
// them to a VirtualBox machine.
|
||||||
|
type FloppyConfig struct {
|
||||||
|
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FloppyConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.FloppyFiles == nil {
|
||||||
|
c.FloppyFiles = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for i, file := range c.FloppyFiles {
|
||||||
|
var err error
|
||||||
|
c.FloppyFiles[i], err = t.Process(file, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"Error processing floppy_files[%d]: %s", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloppyConfigPrepare(t *testing.T) {
|
||||||
|
c := new(FloppyConfig)
|
||||||
|
|
||||||
|
errs := c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.FloppyFiles) > 0 {
|
||||||
|
t.Fatal("should not have floppy files")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutputConfig struct {
|
||||||
|
OutputDir string `mapstructure:"output_directory"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OutputConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig) []error {
|
||||||
|
if c.OutputDir == "" {
|
||||||
|
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"output_directory": &c.OutputDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pc.PackerForce {
|
||||||
|
if _, err := os.Stat(c.OutputDir); err == nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"Output directory '%s' already exists. It must not exist.", c.OutputDir))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOutputConfigPrepare(t *testing.T) {
|
||||||
|
c := new(OutputConfig)
|
||||||
|
if c.OutputDir != "" {
|
||||||
|
t.Fatalf("what: %s", c.OutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := &common.PackerConfig{PackerBuildName: "foo"}
|
||||||
|
errs := c.Prepare(testConfigTemplate(t), pc)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OutputDir == "" {
|
||||||
|
t.Fatal("should have output dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputConfigPrepare_exists(t *testing.T) {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
c := new(OutputConfig)
|
||||||
|
c.OutputDir = td
|
||||||
|
|
||||||
|
pc := &common.PackerConfig{
|
||||||
|
PackerBuildName: "foo",
|
||||||
|
PackerForce: false,
|
||||||
|
}
|
||||||
|
errs := c.Prepare(testConfigTemplate(t), pc)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("should have errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputConfigPrepare_forceExists(t *testing.T) {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
c := new(OutputConfig)
|
||||||
|
c.OutputDir = td
|
||||||
|
|
||||||
|
pc := &common.PackerConfig{
|
||||||
|
PackerBuildName: "foo",
|
||||||
|
PackerForce: true,
|
||||||
|
}
|
||||||
|
errs := c.Prepare(testConfigTemplate(t), pc)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatal("should not have errors")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrlctlConfig struct {
|
||||||
|
Prlctl [][]string `mapstructure:"prlctl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PrlctlConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.Prlctl == nil {
|
||||||
|
c.Prlctl = make([][]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for i, args := range c.Prlctl {
|
||||||
|
for j, arg := range args {
|
||||||
|
if err := t.Validate(arg); err != nil {
|
||||||
|
errs = append(errs,
|
||||||
|
fmt.Errorf("Error processing prlctl[%d][%d]: %s", i, j, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrlctlConfigPrepare_Prlctl(t *testing.T) {
|
||||||
|
// Test with empty
|
||||||
|
c := new(PrlctlConfig)
|
||||||
|
errs := c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(c.Prlctl, [][]string{}) {
|
||||||
|
t.Fatalf("bad: %#v", c.Prlctl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
c = new(PrlctlConfig)
|
||||||
|
c.Prlctl = [][]string{
|
||||||
|
{"foo", "bar", "baz"},
|
||||||
|
}
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := [][]string{
|
||||||
|
[]string{"foo", "bar", "baz"},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(c.Prlctl, expected) {
|
||||||
|
t.Fatalf("bad: %#v", c.Prlctl)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrlctlVersionConfig struct {
|
||||||
|
PrlctlVersionFile string `mapstructure:"prlctl_version_file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PrlctlVersionConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.PrlctlVersionFile == "" {
|
||||||
|
c.PrlctlVersionFile = ".prlctl_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"prlctl_version_file": &c.PrlctlVersionFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrlctlVersionConfigPrepare_BootWait(t *testing.T) {
|
||||||
|
var c *PrlctlVersionConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Test empty
|
||||||
|
c = new(PrlctlVersionConfig)
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %s", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.PrlctlVersionFile != ".prlctl_version" {
|
||||||
|
t.Fatalf("bad value: %s", c.PrlctlVersionFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
c = new(PrlctlVersionConfig)
|
||||||
|
c.PrlctlVersionFile = "foo"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %s", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.PrlctlVersionFile != "foo" {
|
||||||
|
t.Fatalf("bad value: %s", c.PrlctlVersionFile)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunConfig struct {
|
||||||
|
Headless bool `mapstructure:"headless"`
|
||||||
|
RawBootWait string `mapstructure:"boot_wait"`
|
||||||
|
|
||||||
|
BootWait time.Duration ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.RawBootWait == "" {
|
||||||
|
c.RawBootWait = "10s"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"boot_wait": &c.RawBootWait,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c.BootWait, err = time.ParseDuration(c.RawBootWait)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_BootWait(t *testing.T) {
|
||||||
|
var c *RunConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Test a default boot_wait
|
||||||
|
c = new(RunConfig)
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %s", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.RawBootWait != "10s" {
|
||||||
|
t.Fatalf("bad value: %s", c.RawBootWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a bad boot_wait
|
||||||
|
c = new(RunConfig)
|
||||||
|
c.RawBootWait = "this is not good"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatalf("bad: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
c = new(RunConfig)
|
||||||
|
c.RawBootWait = "5s"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %s", errs)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShutdownConfig struct {
|
||||||
|
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||||
|
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||||
|
|
||||||
|
ShutdownTimeout time.Duration ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.RawShutdownTimeout == "" {
|
||||||
|
c.RawShutdownTimeout = "5m"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"shutdown_command": &c.ShutdownCommand,
|
||||||
|
"shutdown_timeout": &c.RawShutdownTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testShutdownConfig() *ShutdownConfig {
|
||||||
|
return &ShutdownConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShutdownConfigPrepare_ShutdownCommand(t *testing.T) {
|
||||||
|
var c *ShutdownConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
c = testShutdownConfig()
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) {
|
||||||
|
var c *ShutdownConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Test with a bad value
|
||||||
|
c = testShutdownConfig()
|
||||||
|
c.RawShutdownTimeout = "this is not good"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatalf("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
c = testShutdownConfig()
|
||||||
|
c.RawShutdownTimeout = "5s"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
if c.ShutdownTimeout != 5*time.Second {
|
||||||
|
t.Fatalf("bad: %s", c.ShutdownTimeout)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/go.crypto/ssh"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
packerssh "github.com/mitchellh/packer/communicator/ssh"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SSHAddress(state multistep.StateBag) (string, error) {
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
|
||||||
|
mac, err := driver.Mac(vmName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := driver.IpAddress(mac)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s:22", ip), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||||
|
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||||
|
auth := []ssh.AuthMethod{
|
||||||
|
ssh.Password(config.SSHPassword),
|
||||||
|
ssh.KeyboardInteractive(
|
||||||
|
packerssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SSHKeyPath != "" {
|
||||||
|
signer, err := sshKeyToSigner(config.SSHKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = append(auth, ssh.PublicKeys(signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ssh.ClientConfig{
|
||||||
|
User: config.SSHUser,
|
||||||
|
Auth: auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshKeyToSigner(path string) (ssh.Signer, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
keyBytes, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := ssh.ParsePrivateKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signer, nil
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SSHConfig struct {
|
||||||
|
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||||
|
SSHPassword string `mapstructure:"ssh_password"`
|
||||||
|
SSHPort uint `mapstructure:"ssh_port"`
|
||||||
|
SSHUser string `mapstructure:"ssh_username"`
|
||||||
|
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||||
|
|
||||||
|
SSHWaitTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.SSHPort == 0 {
|
||||||
|
c.SSHPort = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.RawSSHWaitTimeout == "" {
|
||||||
|
c.RawSSHWaitTimeout = "20m"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"ssh_key_path": &c.SSHKeyPath,
|
||||||
|
"ssh_password": &c.SSHPassword,
|
||||||
|
"ssh_username": &c.SSHUser,
|
||||||
|
"ssh_wait_timeout": &c.RawSSHWaitTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHKeyPath != "" {
|
||||||
|
if _, err := os.Stat(c.SSHKeyPath); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||||
|
} else if _, err := sshKeyToSigner(c.SSHKeyPath); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHUser == "" {
|
||||||
|
errs = append(errs, errors.New("An ssh_username must be specified."))
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSSHConfig() *SSHConfig {
|
||||||
|
return &SSHConfig{
|
||||||
|
SSHUser: "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHConfigPrepare(t *testing.T) {
|
||||||
|
c := testSSHConfig()
|
||||||
|
errs := c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPort != 22 {
|
||||||
|
t.Errorf("bad ssh port: %d", c.SSHPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHConfigPrepare_SSHKeyPath(t *testing.T) {
|
||||||
|
var c *SSHConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.SSHKeyPath = ""
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.SSHKeyPath = "/i/dont/exist"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad contents
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.SSHKeyPath = tf.Name()
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test good contents
|
||||||
|
tf.Seek(0, 0)
|
||||||
|
tf.Truncate(0)
|
||||||
|
tf.Write([]byte(testPem))
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.SSHKeyPath = tf.Name()
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %#v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHConfigPrepare_SSHUser(t *testing.T) {
|
||||||
|
var c *SSHConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.SSHUser = ""
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatalf("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.SSHUser = "exists"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %#v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) {
|
||||||
|
var c *SSHConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.RawSSHWaitTimeout = ""
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %#v", errs)
|
||||||
|
}
|
||||||
|
if c.RawSSHWaitTimeout != "20m" {
|
||||||
|
t.Fatalf("bad value: %s", c.RawSSHWaitTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a bad value
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.RawSSHWaitTimeout = "this is not good"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
c = testSSHConfig()
|
||||||
|
c.RawSSHWaitTimeout = "5s"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %#v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testPem = `
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
|
||||||
|
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
|
||||||
|
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
|
||||||
|
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
|
||||||
|
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
|
||||||
|
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
|
||||||
|
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
|
||||||
|
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
|
||||||
|
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
|
||||||
|
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
|
||||||
|
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
|
||||||
|
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
|
||||||
|
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
|
||||||
|
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
|
||||||
|
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
|
||||||
|
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
|
||||||
|
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
|
||||||
|
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
|
||||||
|
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
|
||||||
|
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
|
||||||
|
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
|
||||||
|
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
|
||||||
|
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
|
||||||
|
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
|
||||||
|
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
|
@ -0,0 +1,55 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step attaches a floppy to the virtual machine.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
type StepAttachFloppy struct {
|
||||||
|
floppyPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
// Determine if we even have a floppy disk to attach
|
||||||
|
var floppyPath string
|
||||||
|
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
|
||||||
|
floppyPath = floppyPathRaw.(string)
|
||||||
|
} else {
|
||||||
|
log.Println("No floppy disk, not attaching.")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
ui.Say("Attaching floppy disk...")
|
||||||
|
|
||||||
|
// Create the floppy disk controller
|
||||||
|
command := []string{
|
||||||
|
"set", vmName,
|
||||||
|
"--device-add", "fdd",
|
||||||
|
"--image", floppyPath,
|
||||||
|
}
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error adding floppy: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the path so that we can unregister it from Parallels later
|
||||||
|
s.floppyPath = floppyPath
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepAttachFloppy) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepAttachFloppy_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepAttachFloppy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAttachFloppy(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepAttachFloppy)
|
||||||
|
|
||||||
|
// Create a temporary file for our floppy file
|
||||||
|
tf, err := ioutil.TempFile("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
tf.Close()
|
||||||
|
defer os.Remove(tf.Name())
|
||||||
|
|
||||||
|
state.Put("floppy_path", tf.Name())
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(driver.PrlctlCalls) != 1 {
|
||||||
|
t.Fatal("not enough calls to prlctl")
|
||||||
|
}
|
||||||
|
|
||||||
|
if driver.PrlctlCalls[0][0] != "set" {
|
||||||
|
t.Fatal("bad call")
|
||||||
|
}
|
||||||
|
if driver.PrlctlCalls[0][2] != "--device-add" {
|
||||||
|
t.Fatal("bad call")
|
||||||
|
}
|
||||||
|
if driver.PrlctlCalls[0][3] != "fdd" {
|
||||||
|
t.Fatal("bad call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepAttachFloppy_noFloppy(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepAttachFloppy)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(driver.PrlctlCalls) > 0 {
|
||||||
|
t.Fatal("should not call vboxmanage")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step attaches the Parallels Tools as a inserted CD onto
|
||||||
|
// the virtual machine.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// toolsPath string
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
type StepAttachParallelsTools struct {
|
||||||
|
ParallelsToolsHostPath string
|
||||||
|
ParallelsToolsMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepAttachParallelsTools) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
// If we're not attaching the guest additions then just return
|
||||||
|
if s.ParallelsToolsMode != ParallelsToolsModeAttach {
|
||||||
|
log.Println("Not attaching parallels tools since we're uploading.")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the guest additions to the computer
|
||||||
|
log.Println("Attaching Parallels Tools ISO onto IDE controller...")
|
||||||
|
command := []string{
|
||||||
|
"set", vmName,
|
||||||
|
"--device-add", "cdrom",
|
||||||
|
"--image", s.ParallelsToolsHostPath,
|
||||||
|
}
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error attaching Parallels Tools: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set some state so we know to remove
|
||||||
|
state.Put("attachedToolsIso", true)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepAttachParallelsTools) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StepOutputDir sets up the output directory by creating it if it does
|
||||||
|
// not exist, deleting it if it does exist and we're forcing, and cleaning
|
||||||
|
// it up when we're done with it.
|
||||||
|
type StepOutputDir struct {
|
||||||
|
Force bool
|
||||||
|
Path string
|
||||||
|
success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if _, err := os.Stat(s.Path); err == nil && s.Force {
|
||||||
|
ui.Say("Deleting previous output directory...")
|
||||||
|
os.RemoveAll(s.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the directory
|
||||||
|
if err := os.MkdirAll(s.Path, 0755); err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we can write in the directory
|
||||||
|
f, err := os.Create(filepath.Join(s.Path, "_packer_perm_check"))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Couldn't write to output directory: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
os.Remove(f.Name())
|
||||||
|
|
||||||
|
s.success = true
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepOutputDir) Cleanup(state multistep.StateBag) {
|
||||||
|
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||||
|
_, halted := state.GetOk(multistep.StateHalted)
|
||||||
|
|
||||||
|
if !s.success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cancelled || halted {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
ui.Say("Deleting output directory...")
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
err := os.RemoveAll(s.Path)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Error removing output dir: %s", err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testStepOutputDir(t *testing.T) *StepOutputDir {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(td); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StepOutputDir{Force: false, Path: td}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepOutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := testStepOutputDir(t)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir_cancelled(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := testStepOutputDir(t)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark
|
||||||
|
state.Put(multistep.StateCancelled, true)
|
||||||
|
|
||||||
|
// Test the cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
if _, err := os.Stat(step.Path); err == nil {
|
||||||
|
t.Fatal("should not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir_halted(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := testStepOutputDir(t)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark
|
||||||
|
state.Put(multistep.StateHalted, true)
|
||||||
|
|
||||||
|
// Test the cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
if _, err := os.Stat(step.Path); err == nil {
|
||||||
|
t.Fatal("should not exist")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commandTemplate struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This step executes additional prlctl commands as specified by the
|
||||||
|
// template.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
type StepPrlctl struct {
|
||||||
|
Commands [][]string
|
||||||
|
Tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepPrlctl) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
if len(s.Commands) > 0 {
|
||||||
|
ui.Say("Executing custom prlctl commands...")
|
||||||
|
}
|
||||||
|
|
||||||
|
tplData := &commandTemplate{
|
||||||
|
Name: vmName,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, originalCommand := range s.Commands {
|
||||||
|
command := make([]string, len(originalCommand))
|
||||||
|
copy(command, originalCommand)
|
||||||
|
|
||||||
|
for i, arg := range command {
|
||||||
|
var err error
|
||||||
|
command[i], err = s.Tpl.Process(arg, tplData)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error preparing prlctl command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Message(fmt.Sprintf("Executing: prlctl %s", strings.Join(command, " ")))
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error executing command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepPrlctl) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step removes any devices (floppy disks, ISOs, etc.) from the
|
||||||
|
// machine that we may have added.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
type StepRemoveDevices struct{}
|
||||||
|
|
||||||
|
func (s *StepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
// Remove the attached floppy disk, if it exists
|
||||||
|
if _, ok := state.GetOk("floppy_path"); ok {
|
||||||
|
ui.Message("Removing floppy drive...")
|
||||||
|
command := []string{"set", vmName, "--device-del", "fdd0"}
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error removing floppy: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk("attachedIso"); ok {
|
||||||
|
command := []string{"set", vmName, "--device-del", "cdrom0"}
|
||||||
|
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error detaching ISO: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk("attachedToolsIso"); ok {
|
||||||
|
command := []string{"set", vmName, "--device-del", "cdrom1"}
|
||||||
|
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error detaching ISO: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepRemoveDevices) Cleanup(state multistep.StateBag) {
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepRemoveDevices_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepRemoveDevices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRemoveDevices(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepRemoveDevices)
|
||||||
|
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
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 that ISO was removed
|
||||||
|
if len(driver.PrlctlCalls) != 0 {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRemoveDevices_attachedIso(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepRemoveDevices)
|
||||||
|
|
||||||
|
state.Put("attachedIso", true)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
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 that ISO was removed
|
||||||
|
if len(driver.PrlctlCalls) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
if driver.PrlctlCalls[0][2] != "--device-del" {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
if driver.PrlctlCalls[0][3] != "cdrom0" {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepRemoveDevices_floppyPath(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepRemoveDevices)
|
||||||
|
|
||||||
|
state.Put("floppy_path", "foo")
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
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 that both were removed
|
||||||
|
if len(driver.PrlctlCalls) != 1 {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
if driver.PrlctlCalls[0][2] != "--device-del" {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
if driver.PrlctlCalls[0][3] != "fdd0" {
|
||||||
|
t.Fatalf("bad: %#v", driver.PrlctlCalls)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step starts the virtual machine.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
type StepRun struct {
|
||||||
|
BootWait time.Duration
|
||||||
|
Headless bool
|
||||||
|
|
||||||
|
vmName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
ui.Say("Starting the virtual machine...")
|
||||||
|
//guiArgument := "gui"
|
||||||
|
if s.Headless == true {
|
||||||
|
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
|
||||||
|
"In headless mode, errors during the boot sequence or OS setup\n" +
|
||||||
|
"won't be easily visible. Use at your own discretion.")
|
||||||
|
//guiArgument = "headless"
|
||||||
|
}
|
||||||
|
command := []string{"start", vmName}
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error starting VM: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.vmName = vmName
|
||||||
|
|
||||||
|
if int64(s.BootWait) > 0 {
|
||||||
|
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait))
|
||||||
|
wait := time.After(s.BootWait)
|
||||||
|
WAITLOOP:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-wait:
|
||||||
|
break WAITLOOP
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||||
|
if s.vmName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if running, _ := driver.IsRunning(s.vmName); running {
|
||||||
|
if err := driver.Prlctl("stop", s.vmName); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error stopping VM: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step shuts down the machine. It first attempts to do so gracefully,
|
||||||
|
// but ultimately forcefully shuts it down if that fails.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// communicator packer.Communicator
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// <nothing>
|
||||||
|
type StepShutdown struct {
|
||||||
|
Command string
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
if s.Command != "" {
|
||||||
|
ui.Say("Gracefully halting virtual machine...")
|
||||||
|
log.Printf("Executing shutdown command: %s", s.Command)
|
||||||
|
cmd := &packer.RemoteCmd{Command: s.Command}
|
||||||
|
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||||
|
err := fmt.Errorf("Failed to send shutdown command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the machine to actually shut down
|
||||||
|
log.Printf("Waiting max %s for shutdown to complete", s.Timeout)
|
||||||
|
shutdownTimer := time.After(s.Timeout)
|
||||||
|
for {
|
||||||
|
running, _ := driver.IsRunning(vmName)
|
||||||
|
if !running {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-shutdownTimer:
|
||||||
|
err := errors.New("Timeout while waiting for machine to shut down.")
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
default:
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.Say("Halting the virtual machine...")
|
||||||
|
if err := driver.Stop(vmName); err != nil {
|
||||||
|
err := fmt.Errorf("Error stopping VM: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("VM shut down.")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepShutdown_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepShutdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepShutdown_noShutdownCommand(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepShutdown)
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
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 that Stop was just called
|
||||||
|
if driver.StopName != "foo" {
|
||||||
|
t.Fatal("should call stop")
|
||||||
|
}
|
||||||
|
if comm.StartCalled {
|
||||||
|
t.Fatal("comm start should not be called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepShutdown_shutdownCommand(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepShutdown)
|
||||||
|
step.Command = "poweroff"
|
||||||
|
step.Timeout = 1 * time.Second
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.IsRunningReturn = true
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
driver.Lock()
|
||||||
|
defer driver.Unlock()
|
||||||
|
driver.IsRunningReturn = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 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 that Stop was just called
|
||||||
|
if driver.StopName != "" {
|
||||||
|
t.Fatal("should not call stop")
|
||||||
|
}
|
||||||
|
if comm.StartCmd.Command != step.Command {
|
||||||
|
t.Fatal("comm start should be called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepShutdown_shutdownTimeout(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepShutdown)
|
||||||
|
step.Command = "poweroff"
|
||||||
|
step.Timeout = 1 * time.Second
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.IsRunningReturn = true
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
driver.Lock()
|
||||||
|
defer driver.Unlock()
|
||||||
|
driver.IsRunningReturn = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testState(t *testing.T) multistep.StateBag {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("driver", new(DriverMock))
|
||||||
|
state.Put("ui", &packer.BasicUi{
|
||||||
|
Reader: new(bytes.Buffer),
|
||||||
|
Writer: new(bytes.Buffer),
|
||||||
|
})
|
||||||
|
return state
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type toolsPathTemplate struct {
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This step uploads the guest additions ISO to the VM.
|
||||||
|
type StepUploadParallelsTools struct {
|
||||||
|
ParallelsToolsHostPath string
|
||||||
|
ParallelsToolsGuestPath string
|
||||||
|
ParallelsToolsMode string
|
||||||
|
Tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
if s.ParallelsToolsMode != ParallelsToolsModeUpload {
|
||||||
|
log.Println("Not uploading Parallels Tools since mode is not upload")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(s.ParallelsToolsHostPath)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error opening Parallels Tools ISO: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
tplData := &toolsPathTemplate{
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ParallelsToolsGuestPath, err = s.Tpl.Process(s.ParallelsToolsGuestPath, tplData)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error preparing Parallels Tools path: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Uploading Parallels Tools ISO...")
|
||||||
|
if err := comm.Upload(s.ParallelsToolsGuestPath, f); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error uploading Parallels Tools: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepUploadParallelsTools) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step uploads a file containing the Parallels version, which
|
||||||
|
// can be useful for various provisioning reasons.
|
||||||
|
type StepUploadVersion struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if s.Path == "" {
|
||||||
|
log.Println("ParallelsVersionFile is empty. Not uploading.")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := driver.Version()
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error reading version for metadata upload: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Uploading Parallels version info (%s)", version))
|
||||||
|
var data bytes.Buffer
|
||||||
|
data.WriteString(version)
|
||||||
|
if err := comm.Upload(s.Path, &data); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error uploading Parallels version: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepUploadVersion) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepUploadVersion_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepUploadVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepUploadVersion(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepUploadVersion)
|
||||||
|
step.Path = "foopath"
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.VersionResult = "foo"
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if comm.UploadPath != "foopath" {
|
||||||
|
t.Fatalf("bad: %#v", comm.UploadPath)
|
||||||
|
}
|
||||||
|
if comm.UploadData != "foo" {
|
||||||
|
t.Fatalf("upload data bad: %#v", comm.UploadData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepUploadVersion_noPath(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepUploadVersion)
|
||||||
|
step.Path = ""
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if comm.UploadCalled {
|
||||||
|
t.Fatal("bad")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
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"
|
||||||
|
)
|
|
@ -0,0 +1,360 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BuilderId = "rickard-von-essen.parallels"
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.FloppyConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.OutputConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.RunConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.ShutdownConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.SSHConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.PrlctlVersionConfig `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"`
|
||||||
|
GuestOSType string `mapstructure:"guest_os_type"`
|
||||||
|
GuestOSDistribution string `mapstructure:"guest_os_distribution"`
|
||||||
|
HardDriveInterface string `mapstructure:"hard_drive_interface"`
|
||||||
|
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"`
|
||||||
|
|
||||||
|
tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
|
md, err := common.DecodeConfig(&b.config, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.config.tpl, err = packer.NewConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||||
|
|
||||||
|
// Accumulate any errors and warnings
|
||||||
|
errs := common.CheckUnusedConfig(md)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(b.config.tpl)...)
|
||||||
|
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)...)
|
||||||
|
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.GuestOSType == "" {
|
||||||
|
b.config.GuestOSType = "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestOSDistribution == "" {
|
||||||
|
b.config.GuestOSDistribution = "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HTTPPortMin == 0 {
|
||||||
|
b.config.HTTPPortMin = 8000
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HTTPPortMax == 0 {
|
||||||
|
b.config.HTTPPortMax = 9000
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.VMName == "" {
|
||||||
|
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_type": &b.config.GuestOSType,
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = b.config.tpl.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, url := range b.config.ISOUrls {
|
||||||
|
var err error
|
||||||
|
b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" && b.config.HardDriveInterface != "scsi" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("hard_drive_interface can only be ide, sata, or scsi"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ISOChecksumType == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("The iso_checksum_type must be specified."))
|
||||||
|
} else {
|
||||||
|
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
||||||
|
if b.config.ISOChecksumType != "none" {
|
||||||
|
if b.config.ISOChecksum == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||||
|
} else {
|
||||||
|
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs,
|
||||||
|
fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("One of iso_url or iso_urls must be specified."))
|
||||||
|
} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
|
||||||
|
} else if b.config.RawSingleISOUrl != "" {
|
||||||
|
b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, url := range b.config.ISOUrls {
|
||||||
|
b.config.ISOUrls[i], err = common.DownloadableURL(url)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
"A checksum type of 'none' was specified. Since ISO files are so big,\n"+
|
||||||
|
"a checksum is highly recommended.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ShutdownCommand == "" {
|
||||||
|
warnings = append(warnings,
|
||||||
|
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
|
||||||
|
"will forcibly halt the virtual machine, which may result in data loss.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
// Create the driver that we'll use to communicate with Parallels
|
||||||
|
driver, err := parallelscommon.NewDriver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed creating Parallels driver: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []multistep.Step{
|
||||||
|
&common.StepDownload{
|
||||||
|
Checksum: b.config.ISOChecksum,
|
||||||
|
ChecksumType: b.config.ISOChecksumType,
|
||||||
|
Description: "ISO",
|
||||||
|
ResultKey: "iso_path",
|
||||||
|
Url: b.config.ISOUrls,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepOutputDir{
|
||||||
|
Force: b.config.PackerForce,
|
||||||
|
Path: b.config.OutputDir,
|
||||||
|
},
|
||||||
|
&common.StepCreateFloppy{
|
||||||
|
Files: b.config.FloppyFiles,
|
||||||
|
},
|
||||||
|
new(stepHTTPServer),
|
||||||
|
new(stepCreateVM),
|
||||||
|
new(stepCreateDisk),
|
||||||
|
new(stepAttachISO),
|
||||||
|
¶llelscommon.StepAttachParallelsTools{
|
||||||
|
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
|
||||||
|
ParallelsToolsMode: b.config.ParallelsToolsMode,
|
||||||
|
},
|
||||||
|
new(parallelscommon.StepAttachFloppy),
|
||||||
|
¶llelscommon.StepPrlctl{
|
||||||
|
Commands: b.config.Prlctl,
|
||||||
|
Tpl: b.config.tpl,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepRun{
|
||||||
|
BootWait: b.config.BootWait,
|
||||||
|
Headless: b.config.Headless, // TODO: migth work on Enterprise Ed.
|
||||||
|
},
|
||||||
|
new(stepTypeBootCommand),
|
||||||
|
&common.StepConnectSSH{
|
||||||
|
SSHAddress: parallelscommon.SSHAddress,
|
||||||
|
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
|
||||||
|
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepUploadVersion{
|
||||||
|
Path: b.config.PrlctlVersionFile,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepUploadParallelsTools{
|
||||||
|
ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath,
|
||||||
|
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
|
||||||
|
ParallelsToolsMode: b.config.ParallelsToolsMode,
|
||||||
|
Tpl: b.config.tpl,
|
||||||
|
},
|
||||||
|
new(common.StepProvision),
|
||||||
|
¶llelscommon.StepShutdown{
|
||||||
|
Command: b.config.ShutdownCommand,
|
||||||
|
Timeout: b.config.ShutdownTimeout,
|
||||||
|
},
|
||||||
|
new(parallelscommon.StepRemoveDevices),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the state bag
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("cache", cache)
|
||||||
|
state.Put("config", &b.config)
|
||||||
|
state.Put("driver", driver)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
// Run
|
||||||
|
if b.config.PackerDebug {
|
||||||
|
b.runner = &multistep.DebugRunner{
|
||||||
|
Steps: steps,
|
||||||
|
PauseFn: common.MultistepDebugFn(ui),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were interrupted or cancelled, then just exit.
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return nil, errors.New("Build was cancelled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
||||||
|
return nil, errors.New("Build was halted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelscommon.NewArtifact(b.config.OutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,436 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
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",
|
||||||
|
|
||||||
|
packer.BuildNameConfigKey: "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Builder{}
|
||||||
|
if _, ok := raw.(packer.Builder); !ok {
|
||||||
|
t.Error("Builder must implement builder.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
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.ParallelsToolsMode != common.ParallelsToolsModeUpload {
|
||||||
|
t.Errorf("bad parallels tools mode: %s", b.config.ParallelsToolsMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestOSType != "other" {
|
||||||
|
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.VMName != "packer-foo" {
|
||||||
|
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "disk_size")
|
||||||
|
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.DiskSize != 40000 {
|
||||||
|
t.Fatalf("bad size: %d", b.config.DiskSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["disk_size"] = 60000
|
||||||
|
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.DiskSize != 60000 {
|
||||||
|
t.Fatalf("bad size: %s", b.config.DiskSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ParallelsToolsMode(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// test default mode
|
||||||
|
delete(config, "parallels_tools_mode")
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_HardDriveInterface(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test a default boot_wait
|
||||||
|
delete(config, "hard_drive_interface")
|
||||||
|
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.HardDriveInterface != "sata" {
|
||||||
|
t.Fatalf("bad: %s", b.config.HardDriveInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a bad
|
||||||
|
config["hard_drive_interface"] = "fake"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good
|
||||||
|
config["hard_drive_interface"] = "scsi"
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Bad
|
||||||
|
config["http_port_min"] = 1000
|
||||||
|
config["http_port_max"] = 500
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad
|
||||||
|
config["http_port_min"] = -500
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good
|
||||||
|
config["http_port_min"] = 500
|
||||||
|
config["http_port_max"] = 1000
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Add a random key
|
||||||
|
config["i_should_not_be_valid"] = true
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["iso_checksum"] = ""
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test good
|
||||||
|
config["iso_checksum"] = "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.ISOChecksum != "foo" {
|
||||||
|
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["iso_checksum_type"] = ""
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test good
|
||||||
|
config["iso_checksum_type"] = "mD5"
|
||||||
|
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.ISOChecksumType != "md5" {
|
||||||
|
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test unknown
|
||||||
|
config["iso_checksum_type"] = "fake"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test none
|
||||||
|
config["iso_checksum_type"] = "none"
|
||||||
|
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.ISOChecksumType != "none" {
|
||||||
|
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
delete(config, "iso_url")
|
||||||
|
delete(config, "iso_urls")
|
||||||
|
|
||||||
|
// Test both epty
|
||||||
|
config["iso_url"] = ""
|
||||||
|
b = Builder{}
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test iso_url set
|
||||||
|
config["iso_url"] = "http://www.packer.io"
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"http://www.packer.io"}
|
||||||
|
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.ISOUrls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test both set
|
||||||
|
config["iso_url"] = "http://www.packer.io"
|
||||||
|
config["iso_urls"] = []string{"http://www.packer.io"}
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test just iso_urls set
|
||||||
|
delete(config, "iso_url")
|
||||||
|
config["iso_urls"] = []string{
|
||||||
|
"http://www.packer.io",
|
||||||
|
"http://www.hashicorp.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = []string{
|
||||||
|
"http://www.packer.io",
|
||||||
|
"http://www.hashicorp.com",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.ISOUrls)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
// Interface to help find the host IP that is available from within
|
||||||
|
// the VMware virtual machines.
|
||||||
|
type HostIPFinder interface {
|
||||||
|
HostIP() (string, error)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IfconfigIPFinder finds the host IP based on the output of `ifconfig`.
|
||||||
|
type IfconfigIPFinder struct {
|
||||||
|
Devices []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IfconfigIPFinder) HostIP() (string, error) {
|
||||||
|
var ifconfigPath string
|
||||||
|
|
||||||
|
// On some systems, ifconfig is in /sbin which is generally not
|
||||||
|
// on the PATH for a standard user, so we just check that first.
|
||||||
|
if _, err := os.Stat("/sbin/ifconfig"); err == nil {
|
||||||
|
ifconfigPath = "/sbin/ifconfig"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ifconfigPath == "" {
|
||||||
|
var err error
|
||||||
|
ifconfigPath, err = exec.LookPath("ifconfig")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, device := range f.Devices {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
|
||||||
|
cmd := exec.Command(ifconfigPath, device)
|
||||||
|
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||||
|
|
||||||
|
// Force LANG=C so that the output is what we expect it to be
|
||||||
|
// despite the locale.
|
||||||
|
cmd.Env = append(cmd.Env, "LANG=C")
|
||||||
|
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err == nil {
|
||||||
|
re := regexp.MustCompile(`inet\s+(?:addr:)?(.+?)\s`)
|
||||||
|
matches := re.FindStringSubmatch(stdout.String())
|
||||||
|
if matches != nil {
|
||||||
|
return matches[1], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("IP not found in ifconfig output...")
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIfconfigIPFinder_Impl(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &IfconfigIPFinder{}
|
||||||
|
if _, ok := raw.(HostIPFinder); !ok {
|
||||||
|
t.Fatalf("IfconfigIPFinder is not a host IP finder")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step attaches the ISO to the virtual machine.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
type stepAttachISO struct {
|
||||||
|
diskPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
isoPath := state.Get("iso_path").(string)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
// Attach the disk to the controller
|
||||||
|
command := []string{
|
||||||
|
"set", vmName,
|
||||||
|
"--device-set", "cdrom0",
|
||||||
|
"--image", isoPath,
|
||||||
|
"--enable", "--connect",
|
||||||
|
}
|
||||||
|
if err := driver.Prlctl(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error attaching ISO: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set some state so we know to remove
|
||||||
|
state.Put("attachedIso", true)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepAttachISO) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step creates the virtual disk that will be used as the
|
||||||
|
// hard drive for the virtual machine.
|
||||||
|
type stepCreateDisk struct{}
|
||||||
|
|
||||||
|
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*config)
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
command := []string{
|
||||||
|
"set", vmName,
|
||||||
|
"--device-set", "hdd0",
|
||||||
|
"--size", strconv.FormatUint(uint64(config.DiskSize), 10),
|
||||||
|
"--iface", config.HardDriveInterface,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating hard drive...")
|
||||||
|
err := driver.Prlctl(command...)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating hard drive: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step creates the actual virtual machine.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// vmName string - The name of the VM
|
||||||
|
type stepCreateVM struct {
|
||||||
|
vmName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
|
||||||
|
config := state.Get("config").(*config)
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
name := config.VMName
|
||||||
|
path := filepath.Join(".", config.OutputDir)
|
||||||
|
|
||||||
|
commands := make([][]string, 9)
|
||||||
|
commands[0] = []string{
|
||||||
|
"create", name,
|
||||||
|
"--ostype", config.GuestOSType,
|
||||||
|
"--distribution", config.GuestOSDistribution,
|
||||||
|
"--dst", path,
|
||||||
|
"--vmtype", "vm",
|
||||||
|
}
|
||||||
|
commands[1] = []string{"set", name, "--cpus", "1"}
|
||||||
|
commands[2] = []string{"set", name, "--memsize", "512"}
|
||||||
|
commands[3] = []string{"set", name, "--startup-view", "same"}
|
||||||
|
commands[4] = []string{"set", name, "--on-shutdown", "close"}
|
||||||
|
commands[5] = []string{"set", name, "--on-window-close", "keep-running"}
|
||||||
|
commands[6] = []string{"set", name, "--auto-share-camera", "off"}
|
||||||
|
commands[7] = []string{"set", name, "--device-del", "sound0"}
|
||||||
|
commands[8] = []string{"set", name, "--smart-guard", "off"}
|
||||||
|
|
||||||
|
ui.Say("Creating virtual machine...")
|
||||||
|
for _, command := range commands {
|
||||||
|
err := driver.Prlctl(command...)
|
||||||
|
ui.Say(fmt.Sprintf("Executing: prlctl %s", command))
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating VM: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the VM name property on the first command
|
||||||
|
if s.vmName == "" {
|
||||||
|
s.vmName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the final name in the state bag so others can use it
|
||||||
|
state.Put("vmName", s.vmName)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
|
||||||
|
if s.vmName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
ui.Say("Unregistering virtual machine...")
|
||||||
|
if err := driver.Prlctl("unregister", s.vmName); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error unregistering virtual machine: %s", err))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step creates and runs the HTTP server that is serving the files
|
||||||
|
// specified by the 'http_files` configuration parameter in the template.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// config *config
|
||||||
|
// ui packer.Ui
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// http_port int - The port the HTTP server started on.
|
||||||
|
type stepHTTPServer struct {
|
||||||
|
l net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
var httpPort uint = 0
|
||||||
|
if config.HTTPDir == "" {
|
||||||
|
state.Put("http_port", httpPort)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an available TCP port for our HTTP server
|
||||||
|
var httpAddr string
|
||||||
|
portRange := int(config.HTTPPortMax - config.HTTPPortMin)
|
||||||
|
for {
|
||||||
|
var err error
|
||||||
|
var offset uint = 0
|
||||||
|
|
||||||
|
if portRange > 0 {
|
||||||
|
// Intn will panic if portRange == 0, so we do a check.
|
||||||
|
offset = uint(rand.Intn(portRange))
|
||||||
|
}
|
||||||
|
|
||||||
|
httpPort = offset + config.HTTPPortMin
|
||||||
|
httpAddr = fmt.Sprintf(":%d", httpPort)
|
||||||
|
log.Printf("Trying port: %d", httpPort)
|
||||||
|
s.l, err = net.Listen("tcp", httpAddr)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
|
||||||
|
|
||||||
|
// Start the HTTP server and run it in the background
|
||||||
|
fileServer := http.FileServer(http.Dir(config.HTTPDir))
|
||||||
|
server := &http.Server{Addr: httpAddr, Handler: fileServer}
|
||||||
|
go server.Serve(s.l)
|
||||||
|
|
||||||
|
// Save the address into the state so it can be accessed in the future
|
||||||
|
state.Put("http_port", httpPort)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
|
||||||
|
if s.l != nil {
|
||||||
|
// Close the listener so that the HTTP server stops
|
||||||
|
s.l.Close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const KeyLeftShift uint32 = 0xFFE1
|
||||||
|
|
||||||
|
type bootCommandTemplateData struct {
|
||||||
|
HTTPIP string
|
||||||
|
HTTPPort uint
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This step "types" the boot command into the VM via prltype, built on the
|
||||||
|
// Parallels Virtualization SDK - C API.
|
||||||
|
//
|
||||||
|
// Uses:
|
||||||
|
// config *config
|
||||||
|
// driver Driver
|
||||||
|
// http_port int
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// <nothing>
|
||||||
|
type stepTypeBootCommand struct{}
|
||||||
|
|
||||||
|
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*config)
|
||||||
|
httpPort := state.Get("http_port").(uint)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
vmName := state.Get("vmName").(string)
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
|
||||||
|
// Determine the host IP
|
||||||
|
ipFinder := &IfconfigIPFinder{Devices: []string{"en0", "en1", "en2", "en3", "en4", "en5", "en6", "en7", "en8", "en9"}}
|
||||||
|
|
||||||
|
hostIp, err := ipFinder.HostIP()
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error detecting host IP: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Host IP for the Parallels machine: %s", hostIp))
|
||||||
|
|
||||||
|
tplData := &bootCommandTemplateData{
|
||||||
|
hostIp,
|
||||||
|
httpPort,
|
||||||
|
config.VMName,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Typing the boot command...")
|
||||||
|
for _, command := range config.BootCommand {
|
||||||
|
command, err := config.tpl.Process(command, tplData)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error preparing boot command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
codes := []string{}
|
||||||
|
for _, code := range scancodes(command) {
|
||||||
|
if code == "wait" {
|
||||||
|
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
|
||||||
|
err := fmt.Errorf("Error sending boot command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
codes = []string{}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if code == "wait5" {
|
||||||
|
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
|
||||||
|
err := fmt.Errorf("Error sending boot command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
codes = []string{}
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if code == "wait10" {
|
||||||
|
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
|
||||||
|
err := fmt.Errorf("Error sending boot command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
codes = []string{}
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since typing is sometimes so slow, we check for an interrupt
|
||||||
|
// in between each character.
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
codes = append(codes, code)
|
||||||
|
}
|
||||||
|
log.Printf("Sending scancodes: %#v", codes)
|
||||||
|
if err := driver.SendKeyScanCodes(vmName, codes...); err != nil {
|
||||||
|
err := fmt.Errorf("Error sending boot command: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||||
|
|
||||||
|
func scancodes(message string) []string {
|
||||||
|
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
|
||||||
|
//
|
||||||
|
// Scancodes represent raw keyboard output and are fed to the VM by the
|
||||||
|
// Parallels Virtualization SDK - C API, PrlDevKeyboard_SendKeyEvent
|
||||||
|
//
|
||||||
|
// Scancodes are recorded here in pairs. The first entry represents
|
||||||
|
// the key press and the second entry represents the key release and is
|
||||||
|
// derived from the first by the addition of 0x80.
|
||||||
|
special := make(map[string][]string)
|
||||||
|
special["<bs>"] = []string{"0e", "8e"}
|
||||||
|
special["<del>"] = []string{"53", "d3"}
|
||||||
|
special["<enter>"] = []string{"1c", "9c"}
|
||||||
|
special["<esc>"] = []string{"01", "81"}
|
||||||
|
special["<f1>"] = []string{"3b", "bb"}
|
||||||
|
special["<f2>"] = []string{"3c", "bc"}
|
||||||
|
special["<f3>"] = []string{"3d", "bd"}
|
||||||
|
special["<f4>"] = []string{"3e", "be"}
|
||||||
|
special["<f5>"] = []string{"3f", "bf"}
|
||||||
|
special["<f6>"] = []string{"40", "c0"}
|
||||||
|
special["<f7>"] = []string{"41", "c1"}
|
||||||
|
special["<f8>"] = []string{"42", "c2"}
|
||||||
|
special["<f9>"] = []string{"43", "c3"}
|
||||||
|
special["<f10>"] = []string{"44", "c4"}
|
||||||
|
special["<return>"] = []string{"1c", "9c"}
|
||||||
|
special["<tab>"] = []string{"0f", "8f"}
|
||||||
|
|
||||||
|
shiftedChars := "!@#$%^&*()_+{}:\"~|<>?"
|
||||||
|
|
||||||
|
scancodeIndex := make(map[string]uint)
|
||||||
|
scancodeIndex["1234567890-="] = 0x02
|
||||||
|
scancodeIndex["!@#$%^&*()_+"] = 0x02
|
||||||
|
scancodeIndex["qwertyuiop[]"] = 0x10
|
||||||
|
scancodeIndex["QWERTYUIOP{}"] = 0x10
|
||||||
|
scancodeIndex["asdfghjkl;´`"] = 0x1e
|
||||||
|
scancodeIndex[`ASDFGHJKL:"~`] = 0x1e
|
||||||
|
scancodeIndex["\\zxcvbnm,./"] = 0x2b
|
||||||
|
scancodeIndex["|ZXCVBNM<>?"] = 0x2b
|
||||||
|
scancodeIndex[" "] = 0x39
|
||||||
|
|
||||||
|
scancodeMap := make(map[rune]uint)
|
||||||
|
for chars, start := range scancodeIndex {
|
||||||
|
var i uint = 0
|
||||||
|
for len(chars) > 0 {
|
||||||
|
r, size := utf8.DecodeRuneInString(chars)
|
||||||
|
chars = chars[size:]
|
||||||
|
scancodeMap[r] = start + i
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, 0, len(message)*2)
|
||||||
|
for len(message) > 0 {
|
||||||
|
var scancode []string
|
||||||
|
|
||||||
|
if strings.HasPrefix(message, "<wait>") {
|
||||||
|
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
||||||
|
scancode = []string{"wait"}
|
||||||
|
message = message[len("<wait>"):]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(message, "<wait5>") {
|
||||||
|
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
|
||||||
|
scancode = []string{"wait5"}
|
||||||
|
message = message[len("<wait5>"):]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(message, "<wait10>") {
|
||||||
|
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
|
||||||
|
scancode = []string{"wait10"}
|
||||||
|
message = message[len("<wait10>"):]
|
||||||
|
}
|
||||||
|
|
||||||
|
if scancode == nil {
|
||||||
|
for specialCode, specialValue := range special {
|
||||||
|
if strings.HasPrefix(message, specialCode) {
|
||||||
|
log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue)
|
||||||
|
scancode = specialValue
|
||||||
|
message = message[len(specialCode):]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if scancode == nil {
|
||||||
|
r, size := utf8.DecodeRuneInString(message)
|
||||||
|
message = message[size:]
|
||||||
|
scancodeInt := scancodeMap[r]
|
||||||
|
keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||||
|
|
||||||
|
scancode = make([]string, 0, 4)
|
||||||
|
if keyShift {
|
||||||
|
scancode = append(scancode, "2a")
|
||||||
|
}
|
||||||
|
|
||||||
|
scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt))
|
||||||
|
scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80))
|
||||||
|
|
||||||
|
if keyShift {
|
||||||
|
scancode = append(scancode, "aa")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, scancode...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package pvm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder implements packer.Builder and builds the actual Parallels
|
||||||
|
// images.
|
||||||
|
type Builder struct {
|
||||||
|
config *Config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare processes the build configuration parameters.
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
c, warnings, errs := NewConfig(raws...)
|
||||||
|
if errs != nil {
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
b.config = c
|
||||||
|
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes a Packer build and returns a packer.Artifact representing
|
||||||
|
// a Parallels appliance.
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
// Create the driver that we'll use to communicate with Parallels
|
||||||
|
driver, err := parallelscommon.NewDriver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed creating Paralles driver: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the state.
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("config", b.config)
|
||||||
|
state.Put("driver", driver)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
// Build the steps.
|
||||||
|
steps := []multistep.Step{
|
||||||
|
¶llelscommon.StepOutputDir{
|
||||||
|
Force: b.config.PackerForce,
|
||||||
|
Path: b.config.OutputDir,
|
||||||
|
},
|
||||||
|
&common.StepCreateFloppy{
|
||||||
|
Files: b.config.FloppyFiles,
|
||||||
|
},
|
||||||
|
&StepImport{
|
||||||
|
Name: b.config.VMName,
|
||||||
|
SourcePath: b.config.SourcePath,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepAttachParallelsTools{
|
||||||
|
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
|
||||||
|
ParallelsToolsMode: b.config.ParallelsToolsMode,
|
||||||
|
},
|
||||||
|
new(parallelscommon.StepAttachFloppy),
|
||||||
|
¶llelscommon.StepPrlctl{
|
||||||
|
Commands: b.config.Prlctl,
|
||||||
|
Tpl: b.config.tpl,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepRun{
|
||||||
|
BootWait: b.config.BootWait,
|
||||||
|
Headless: b.config.Headless,
|
||||||
|
},
|
||||||
|
&common.StepConnectSSH{
|
||||||
|
SSHAddress: parallelscommon.SSHAddress,
|
||||||
|
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
|
||||||
|
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepUploadVersion{
|
||||||
|
Path: b.config.PrlctlVersionFile,
|
||||||
|
},
|
||||||
|
¶llelscommon.StepUploadParallelsTools{
|
||||||
|
ParallelsToolsGuestPath: b.config.ParallelsToolsGuestPath,
|
||||||
|
ParallelsToolsHostPath: b.config.ParallelsToolsHostPath,
|
||||||
|
ParallelsToolsMode: b.config.ParallelsToolsMode,
|
||||||
|
Tpl: b.config.tpl,
|
||||||
|
},
|
||||||
|
new(common.StepProvision),
|
||||||
|
¶llelscommon.StepShutdown{
|
||||||
|
Command: b.config.ShutdownCommand,
|
||||||
|
Timeout: b.config.ShutdownTimeout,
|
||||||
|
},
|
||||||
|
new(parallelscommon.StepRemoveDevices),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the steps.
|
||||||
|
if b.config.PackerDebug {
|
||||||
|
b.runner = &multistep.DebugRunner{
|
||||||
|
Steps: steps,
|
||||||
|
PauseFn: common.MultistepDebugFn(ui),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||||
|
}
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// Report any errors.
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were interrupted or cancelled, then just exit.
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return nil, errors.New("Build was cancelled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
||||||
|
return nil, errors.New("Build was halted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parallelscommon.NewArtifact(b.config.OutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel.
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package pvm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the configuration structure for the builder.
|
||||||
|
type Config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.FloppyConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.OutputConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.RunConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.SSHConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.ShutdownConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
||||||
|
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
|
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"`
|
||||||
|
|
||||||
|
tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
|
c := new(Config)
|
||||||
|
md, err := common.DecodeConfig(c, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.tpl, err = packer.NewConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the errors
|
||||||
|
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.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)...)
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = c.tpl.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if _, err := os.Stat(c.SourcePath); err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(errs,
|
||||||
|
fmt.Errorf("source_path is invalid: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnings
|
||||||
|
var warnings []string
|
||||||
|
if c.ShutdownCommand == "" {
|
||||||
|
warnings = append(warnings,
|
||||||
|
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
|
||||||
|
"will forcibly halt the virtual machine, which may result in data loss.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any errors.
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, warnings, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, warnings, nil
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package pvm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig(t *testing.T) map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"ssh_username": "foo",
|
||||||
|
"shutdown_command": "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTempFile(t *testing.T) *os.File {
|
||||||
|
tf, err := ioutil.TempFile("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
tf.Close()
|
||||||
|
|
||||||
|
// don't forget to cleanup the file downstream:
|
||||||
|
// defer os.Remove(tf.Name())
|
||||||
|
|
||||||
|
return tf
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigErr(t *testing.T, warns []string, err error) {
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfig_sourcePath(t *testing.T) {
|
||||||
|
// Bad
|
||||||
|
c := testConfig(t)
|
||||||
|
delete(c, "source_path")
|
||||||
|
_, warns, errs := NewConfig(c)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
|
||||||
|
// Bad
|
||||||
|
c = testConfig(t)
|
||||||
|
c["source_path"] = "/i/dont/exist"
|
||||||
|
_, warns, errs = NewConfig(c)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
|
||||||
|
// Good
|
||||||
|
tf := getTempFile(t)
|
||||||
|
defer os.Remove(tf.Name())
|
||||||
|
|
||||||
|
c = testConfig(t)
|
||||||
|
c["source_path"] = tf.Name()
|
||||||
|
_, warns, errs = NewConfig(c)
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfig_shutdown_timeout(t *testing.T) {
|
||||||
|
c := testConfig(t)
|
||||||
|
tf := getTempFile(t)
|
||||||
|
defer os.Remove(tf.Name())
|
||||||
|
|
||||||
|
// Expect this to fail
|
||||||
|
c["source_path"] = tf.Name()
|
||||||
|
c["shutdown_timeout"] = "NaN"
|
||||||
|
_, warns, errs := NewConfig(c)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
|
||||||
|
// Passes when given a valid time duration
|
||||||
|
c["shutdown_timeout"] = "10s"
|
||||||
|
_, warns, errs = NewConfig(c)
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package pvm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This step imports an PVM VM into Parallels.
|
||||||
|
type StepImport struct {
|
||||||
|
Name string
|
||||||
|
SourcePath string
|
||||||
|
vmName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepImport) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Importing VM: %s", s.SourcePath))
|
||||||
|
if err := driver.Import(s.Name, s.SourcePath, config.OutputDir); err != nil {
|
||||||
|
err := fmt.Errorf("Error importing VM: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.vmName = s.Name
|
||||||
|
state.Put("vmName", s.Name)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepImport) Cleanup(state multistep.StateBag) {
|
||||||
|
|
||||||
|
if s.vmName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := state.Get("driver").(parallelscommon.Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
ui.Say("Unregistering virtual machine...")
|
||||||
|
if err := driver.Prlctl("unregister", s.vmName); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error unregistering virtual machine: %s", err))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package pvm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testState(t *testing.T) multistep.StateBag {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("driver", new(parallelscommon.DriverMock))
|
||||||
|
state.Put("ui", &packer.BasicUi{
|
||||||
|
Reader: new(bytes.Buffer),
|
||||||
|
Writer: new(bytes.Buffer),
|
||||||
|
})
|
||||||
|
return state
|
||||||
|
}
|
|
@ -31,6 +31,8 @@ const defaultConfig = `
|
||||||
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
||||||
"vmware-iso": "packer-builder-vmware-iso",
|
"vmware-iso": "packer-builder-vmware-iso",
|
||||||
"vmware-vmx": "packer-builder-vmware-vmx",
|
"vmware-vmx": "packer-builder-vmware-vmx",
|
||||||
|
"parallels-iso": "packer-builder-parallels-iso",
|
||||||
|
"parallels-pvm": "packer-builder-parallels-pvm",
|
||||||
"null": "packer-builder-null"
|
"null": "packer-builder-null"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/builder/parallels/iso"
|
||||||
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server, err := plugin.Server()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
server.RegisterBuilder(new(iso.Builder))
|
||||||
|
server.Serve()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/builder/parallels/pvm"
|
||||||
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server, err := plugin.Server()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
server.RegisterBuilder(new(pvm.Builder))
|
||||||
|
server.Serve()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
Loading…
Reference in New Issue