2013-12-21 18:00:48 -05:00
|
|
|
package common
|
2013-06-11 18:45:52 -04:00
|
|
|
|
|
|
|
import (
|
2013-06-11 19:48:01 -04:00
|
|
|
"bytes"
|
2019-04-08 11:57:27 -04:00
|
|
|
"context"
|
2013-06-11 18:52:46 -04:00
|
|
|
"fmt"
|
2013-06-11 18:45:52 -04:00
|
|
|
"log"
|
|
|
|
"os/exec"
|
2013-06-24 01:44:58 -04:00
|
|
|
"regexp"
|
2017-03-22 08:09:25 -04:00
|
|
|
"strconv"
|
2013-06-11 19:48:01 -04:00
|
|
|
"strings"
|
2013-06-11 18:52:46 -04:00
|
|
|
"time"
|
2017-10-25 13:17:08 -04:00
|
|
|
|
2019-03-22 09:53:28 -04:00
|
|
|
versionUtil "github.com/hashicorp/go-version"
|
2019-03-10 10:38:38 -04:00
|
|
|
|
|
|
|
packer "github.com/hashicorp/packer/common"
|
2013-06-11 18:45:52 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type VBox42Driver struct {
|
|
|
|
// This is the path to the "VBoxManage" application.
|
|
|
|
VBoxManagePath string
|
2013-10-20 18:54:34 -04:00
|
|
|
}
|
|
|
|
|
2017-03-22 08:09:25 -04:00
|
|
|
func (d *VBox42Driver) CreateSATAController(vmName string, name string, portcount int) error {
|
2013-10-20 18:54:34 -04:00
|
|
|
version, err := d.Version()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-08 11:15:24 -05:00
|
|
|
portCountArg := "--portcount"
|
2019-01-09 03:45:01 -05:00
|
|
|
|
2019-01-09 11:30:54 -05:00
|
|
|
currentVersion, err := versionUtil.NewVersion(version)
|
2019-01-09 11:22:33 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-09 11:30:54 -05:00
|
|
|
firstVersionUsingPortCount, err := versionUtil.NewVersion("4.3")
|
2019-01-09 11:22:33 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-09 11:30:54 -05:00
|
|
|
if currentVersion.LessThan(firstVersionUsingPortCount) {
|
2019-01-09 11:22:33 -05:00
|
|
|
portCountArg = "--sataportcount"
|
2013-10-20 18:54:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
command := []string{
|
|
|
|
"storagectl", vmName,
|
|
|
|
"--name", name,
|
|
|
|
"--add", "sata",
|
2017-03-22 08:09:25 -04:00
|
|
|
portCountArg, strconv.Itoa(portcount),
|
2013-10-20 18:54:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return d.VBoxManage(command...)
|
2013-06-11 18:45:52 -04:00
|
|
|
}
|
|
|
|
|
2015-01-15 20:53:01 -05:00
|
|
|
func (d *VBox42Driver) CreateSCSIController(vmName string, name string) error {
|
|
|
|
|
|
|
|
command := []string{
|
|
|
|
"storagectl", vmName,
|
|
|
|
"--name", name,
|
|
|
|
"--add", "scsi",
|
2015-01-16 11:34:12 -05:00
|
|
|
"--controller", "LSILogic",
|
2015-01-15 20:53:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return d.VBoxManage(command...)
|
|
|
|
}
|
|
|
|
|
2013-12-22 18:19:19 -05:00
|
|
|
func (d *VBox42Driver) Delete(name string) error {
|
2019-04-08 11:57:27 -04:00
|
|
|
ctx := context.TODO()
|
|
|
|
return retry.Config{
|
|
|
|
Tries: 5,
|
|
|
|
RetryDelay: (&retry.Backoff{InitialBackoff: 1 * time.Second, MaxBackoff: 1 * time.Second, Multiplier: 2}).Linear,
|
|
|
|
}.Run(ctx, func(ctx context.Context) error {
|
|
|
|
err := d.VBoxManage("unregistervm", name, "--delete")
|
|
|
|
return err
|
2017-10-25 13:17:08 -04:00
|
|
|
})
|
2013-12-22 18:19:19 -05:00
|
|
|
}
|
|
|
|
|
2014-05-06 18:37:49 -04:00
|
|
|
func (d *VBox42Driver) Iso() (string, error) {
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "list", "systemproperties")
|
|
|
|
cmd.Stdout = &stdout
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2014-09-17 21:51:20 -04:00
|
|
|
DefaultGuestAdditionsRe := regexp.MustCompile("Default Guest Additions ISO:(.+)")
|
2014-05-06 18:37:49 -04:00
|
|
|
|
|
|
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
|
|
|
// Need to trim off CR character when running in windows
|
2014-09-17 21:51:20 -04:00
|
|
|
// Trimming whitespaces at this point helps to filter out empty value
|
|
|
|
line = strings.TrimRight(line, " \r")
|
2014-05-06 18:37:49 -04:00
|
|
|
|
|
|
|
matches := DefaultGuestAdditionsRe.FindStringSubmatch(line)
|
|
|
|
if matches == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
isoname := strings.Trim(matches[1], " \r\n")
|
|
|
|
log.Printf("Found Default Guest Additions ISO: %s", isoname)
|
|
|
|
|
|
|
|
return isoname, nil
|
|
|
|
}
|
|
|
|
|
2014-09-17 21:51:20 -04:00
|
|
|
return "", fmt.Errorf("Cannot find \"Default Guest Additions ISO\" in vboxmanage output (or it is empty)")
|
2014-05-06 18:37:49 -04:00
|
|
|
}
|
|
|
|
|
2014-09-05 13:23:37 -04:00
|
|
|
func (d *VBox42Driver) Import(name string, path string, flags []string) error {
|
2013-12-22 18:19:19 -05:00
|
|
|
args := []string{
|
|
|
|
"import", path,
|
|
|
|
"--vsys", "0",
|
|
|
|
"--vmname", name,
|
|
|
|
}
|
2014-09-05 13:23:37 -04:00
|
|
|
args = append(args, flags...)
|
2013-12-22 18:19:19 -05:00
|
|
|
|
|
|
|
return d.VBoxManage(args...)
|
|
|
|
}
|
|
|
|
|
2013-06-12 21:02:42 -04:00
|
|
|
func (d *VBox42Driver) IsRunning(name string) (bool, error) {
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "showvminfo", name, "--machinereadable")
|
|
|
|
cmd.Stdout = &stdout
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
2013-07-25 12:28:04 -04:00
|
|
|
// Need to trim off CR character when running in windows
|
2013-07-25 21:49:39 -04:00
|
|
|
line = strings.TrimRight(line, "\r")
|
2013-07-25 12:28:04 -04:00
|
|
|
|
2013-06-12 21:02:42 -04:00
|
|
|
if line == `VMState="running"` {
|
|
|
|
return true, nil
|
|
|
|
}
|
2013-06-24 12:32:08 -04:00
|
|
|
|
|
|
|
// We consider "stopping" to still be running. We wait for it to
|
|
|
|
// be completely stopped or some other state.
|
|
|
|
if line == `VMState="stopping"` {
|
|
|
|
return true, nil
|
|
|
|
}
|
2013-07-09 15:39:28 -04:00
|
|
|
|
|
|
|
// We consider "paused" to still be running. We wait for it to
|
|
|
|
// be completely stopped or some other state.
|
|
|
|
if line == `VMState="paused"` {
|
|
|
|
return true, nil
|
|
|
|
}
|
2013-06-12 21:02:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *VBox42Driver) Stop(name string) error {
|
|
|
|
if err := d.VBoxManage("controlvm", name, "poweroff"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-12-19 11:49:23 -05:00
|
|
|
// We sleep here for a little bit to let the session "unlock"
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
|
2013-06-12 21:02:42 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-06-11 18:45:52 -04:00
|
|
|
func (d *VBox42Driver) SuppressMessages() error {
|
|
|
|
extraData := map[string]string{
|
|
|
|
"GUI/RegistrationData": "triesLeft=0",
|
|
|
|
"GUI/SuppressMessages": "confirmInputCapture,remindAboutAutoCapture,remindAboutMouseIntegrationOff,remindAboutMouseIntegrationOn,remindAboutWrongColorDepth",
|
2013-06-11 23:07:11 -04:00
|
|
|
"GUI/UpdateDate": fmt.Sprintf("1 d, %d-01-01, stable", time.Now().Year()+1),
|
2013-06-11 18:52:46 -04:00
|
|
|
"GUI/UpdateCheckCount": "60",
|
2013-06-11 18:45:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range extraData {
|
2013-06-11 19:12:19 -04:00
|
|
|
if err := d.VBoxManage("setextradata", "global", k, v); err != nil {
|
2013-06-11 18:45:52 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-06-11 19:12:19 -04:00
|
|
|
func (d *VBox42Driver) VBoxManage(args ...string) error {
|
2013-06-11 19:48:01 -04:00
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
|
2013-06-11 18:45:52 -04:00
|
|
|
log.Printf("Executing VBoxManage: %#v", args)
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, args...)
|
2013-06-11 19:48:01 -04:00
|
|
|
cmd.Stdout = &stdout
|
|
|
|
cmd.Stderr = &stderr
|
|
|
|
err := cmd.Run()
|
2013-06-11 18:45:52 -04:00
|
|
|
|
2013-06-24 00:19:41 -04:00
|
|
|
stdoutString := strings.TrimSpace(stdout.String())
|
|
|
|
stderrString := strings.TrimSpace(stderr.String())
|
|
|
|
|
|
|
|
if _, ok := err.(*exec.ExitError); ok {
|
|
|
|
err = fmt.Errorf("VBoxManage error: %s", stderrString)
|
|
|
|
}
|
|
|
|
|
2014-09-05 12:57:30 -04:00
|
|
|
if err == nil {
|
2014-09-05 12:59:39 -04:00
|
|
|
// Sometimes VBoxManage gives us an error with a zero exit code,
|
|
|
|
// so we also regexp match an error string.
|
2014-09-05 12:57:30 -04:00
|
|
|
m, _ := regexp.MatchString("VBoxManage([.a-z]+?): error:", stderrString)
|
|
|
|
if m {
|
|
|
|
err = fmt.Errorf("VBoxManage error: %s", stderrString)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-24 00:19:41 -04:00
|
|
|
log.Printf("stdout: %s", stdoutString)
|
|
|
|
log.Printf("stderr: %s", stderrString)
|
2013-06-11 19:48:01 -04:00
|
|
|
|
|
|
|
return err
|
2013-06-11 18:45:52 -04:00
|
|
|
}
|
2013-06-11 19:12:19 -04:00
|
|
|
|
|
|
|
func (d *VBox42Driver) Verify() error {
|
|
|
|
return nil
|
|
|
|
}
|
2013-06-24 01:44:58 -04:00
|
|
|
|
|
|
|
func (d *VBox42Driver) Version() (string, error) {
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "--version")
|
|
|
|
cmd.Stdout = &stdout
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2013-07-01 13:59:04 -04:00
|
|
|
versionOutput := strings.TrimSpace(stdout.String())
|
|
|
|
log.Printf("VBoxManage --version output: %s", versionOutput)
|
2013-10-02 20:11:42 -04:00
|
|
|
|
2013-10-02 20:13:22 -04:00
|
|
|
// If the "--version" output contains vboxdrv, then this is indicative
|
|
|
|
// of problems with the VirtualBox setup and we shouldn't really continue,
|
|
|
|
// whether or not we can read the version.
|
|
|
|
if strings.Contains(versionOutput, "vboxdrv") {
|
|
|
|
return "", fmt.Errorf("VirtualBox is not properly setup: %s", versionOutput)
|
|
|
|
}
|
|
|
|
|
2014-10-09 04:45:03 -04:00
|
|
|
versionRe := regexp.MustCompile("^([.0-9]+)(?:_(?:RC|OSEr)[0-9]+)?")
|
|
|
|
matches := versionRe.FindAllStringSubmatch(versionOutput, 1)
|
|
|
|
if matches == nil || len(matches[0]) != 2 {
|
2013-07-01 13:59:04 -04:00
|
|
|
return "", fmt.Errorf("No version found: %s", versionOutput)
|
2013-06-24 01:44:58 -04:00
|
|
|
}
|
|
|
|
|
2014-10-09 04:45:03 -04:00
|
|
|
log.Printf("VirtualBox version: %s", matches[0][1])
|
|
|
|
return matches[0][1], nil
|
2013-06-24 01:44:58 -04:00
|
|
|
}
|
2019-02-16 08:32:22 -05:00
|
|
|
|
|
|
|
func (d *VBox42Driver) CreateSnapshot(vmname string, snapshotName string) error {
|
2019-03-10 10:38:38 -04:00
|
|
|
log.Printf("Executing CreateSnapshot: VM: %s, SnapshotName %s", vmname, snapshotName)
|
|
|
|
|
2019-02-16 08:32:22 -05:00
|
|
|
return d.VBoxManage("snapshot", vmname, "take", snapshotName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *VBox42Driver) HasSnapshots(vmname string) (bool, error) {
|
2019-03-10 10:38:38 -04:00
|
|
|
log.Printf("Executing HasSnapshots: VM: %s", vmname)
|
|
|
|
|
2019-02-16 08:32:22 -05:00
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
var hasSnapshots = false
|
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
|
|
|
|
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 {
|
|
|
|
if stdoutString != "This machine does not have any snapshots" {
|
|
|
|
err = fmt.Errorf("VBoxManage error: %s", stderrString)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hasSnapshots = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasSnapshots, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *VBox42Driver) GetCurrentSnapshot(vmname string) (string, error) {
|
2019-03-10 10:38:38 -04:00
|
|
|
log.Printf("Executing GetCurrentSnapshot: VM: %s", vmname)
|
|
|
|
|
2019-02-16 08:32:22 -05:00
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
|
|
|
|
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 {
|
|
|
|
if stdoutString == "This machine does not have any snapshots" {
|
|
|
|
return "", nil
|
|
|
|
} else {
|
|
|
|
return "", (fmt.Errorf("VBoxManage error: %s", stderrString))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CurrentSnapshotNameRe := regexp.MustCompile("CurrentSnapshotName=\"(?P<snapshotName>[^\"]*)\"")
|
|
|
|
|
|
|
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
|
|
|
result := CurrentSnapshotNameRe.FindStringSubmatch(line)
|
|
|
|
if len(result) > 1 {
|
|
|
|
return result[1], nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", (fmt.Errorf("VBoxManage unable to find current snapshot name"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *VBox42Driver) SetSnapshot(vmname string, snapshotName string) error {
|
2019-03-10 10:38:38 -04:00
|
|
|
log.Printf("Executing SetSnapshot: VM: %s, SnapshotName %s", vmname, snapshotName)
|
|
|
|
|
2019-02-16 08:32:22 -05:00
|
|
|
var err error
|
|
|
|
if snapshotName == "" {
|
|
|
|
err = d.VBoxManage("snapshot", vmname, "restorecurrent")
|
|
|
|
} else {
|
|
|
|
err = d.VBoxManage("snapshot", vmname, "restore", snapshotName)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *VBox42Driver) DeleteSnapshot(vmname string, snapshotName string) error {
|
|
|
|
return d.VBoxManage("snapshot", vmname, "delete", snapshotName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *VBox42Driver) SnapshotExists(vmname string, snapshotName string) (bool, error) {
|
2019-03-10 10:38:38 -04:00
|
|
|
log.Printf("Executing SnapshotExists: VM %s, SnapshotName %s", vmname, snapshotName)
|
|
|
|
|
|
|
|
var stdout, stderr bytes.Buffer
|
2019-02-16 08:32:22 -05:00
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
|
|
|
|
cmd.Stdout = &stdout
|
2019-03-10 10:38:38 -04:00
|
|
|
cmd.Stderr = &stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
|
|
|
|
if _, ok := err.(*exec.ExitError); ok {
|
|
|
|
stderrString := strings.TrimSpace(stderr.String())
|
|
|
|
return false, (fmt.Errorf("VBoxManage error: %s", stderrString))
|
2019-02-16 08:32:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
SnapshotNameRe := regexp.MustCompile(fmt.Sprintf("SnapshotName[^=]*=[^\"]*\"%s\"", snapshotName))
|
|
|
|
|
|
|
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
|
|
|
if SnapshotNameRe.MatchString(line) {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
2019-03-10 10:38:38 -04:00
|
|
|
|
|
|
|
func (d *VBox42Driver) GetParentSnapshot(vmname string, snapshotName string) (string, error) {
|
|
|
|
log.Printf("Executing GetParentSnapshot: VM %s, SnapshotName %s", vmname, snapshotName)
|
|
|
|
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
|
|
|
|
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
|
|
|
|
cmd.Stdout = &stdout
|
|
|
|
cmd.Stderr = &stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
|
|
|
|
if _, ok := err.(*exec.ExitError); ok {
|
|
|
|
stderrString := strings.TrimSpace(stderr.String())
|
|
|
|
return "", (fmt.Errorf("VBoxManage error: %s", stderrString))
|
|
|
|
}
|
|
|
|
|
|
|
|
SnapshotNameRe := regexp.MustCompile(fmt.Sprintf("SnapshotName[^=]*=[^\"]*\"%s\"", snapshotName))
|
|
|
|
|
|
|
|
var snapshot string
|
|
|
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
|
|
|
if SnapshotNameRe.MatchString(line) {
|
|
|
|
snapshot = line
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if snapshot == "" {
|
|
|
|
return "", (fmt.Errorf("Snapshot %s does not exist", snapshotName))
|
|
|
|
}
|
|
|
|
|
|
|
|
SnapshotNamePartsRe := regexp.MustCompile("SnapshotName(?P<Path>(-[1-9]+)*)")
|
|
|
|
matches := SnapshotNamePartsRe.FindStringSubmatch(snapshot)
|
|
|
|
log.Printf("************ Snapshot %s name parts", snapshot)
|
|
|
|
log.Printf("Matches %#v\n", matches)
|
|
|
|
log.Printf("Node %s\n", matches[0])
|
|
|
|
log.Printf("Path %s\n", matches[1])
|
|
|
|
log.Printf("Leaf %s\n", matches[2])
|
|
|
|
leaf := matches[2]
|
|
|
|
node := matches[0]
|
|
|
|
if node == "" {
|
|
|
|
return "", (fmt.Errorf("Unsupported format for snapshot %s", snapshot))
|
|
|
|
}
|
|
|
|
if leaf != "" && node != "" {
|
|
|
|
SnapshotNodeRe := regexp.MustCompile("^(?P<Node>SnapshotName[^=]*)=[^\"]*\"(?P<Name>[^\"]+)\"")
|
|
|
|
parentNode := node[:len(node)-len(leaf)]
|
|
|
|
log.Printf("Parent node %s\n", parentNode)
|
|
|
|
var parentName string
|
|
|
|
for _, line := range strings.Split(stdout.String(), "\n") {
|
|
|
|
if matches := SnapshotNodeRe.FindStringSubmatch(line); len(matches) > 1 && parentNode == matches[1] {
|
|
|
|
parentName = matches[2]
|
|
|
|
log.Printf("Parent Snapshot name %s\n", parentName)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if parentName == "" {
|
|
|
|
return "", (fmt.Errorf("Internal error: Unable to find name for snapshot node %s", parentNode))
|
|
|
|
}
|
|
|
|
return parentName, nil
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|