First working version of virtualbox/vm builder

This commit is contained in:
Thomas Meckel 2019-03-10 15:38:38 +01:00 committed by Thomas Meckel
parent 2a531f8ad6
commit d3202497ae
8 changed files with 195 additions and 39 deletions

View File

@ -69,6 +69,9 @@ type Driver interface {
//
SnapshotExists(string, string) (bool, error)
//
GetParentSnapshot(string, string) (string, error)
}
func NewDriver() (Driver, error) {

View File

@ -12,7 +12,8 @@ import (
"time"
versionUtil "github.com/hashicorp/go-version"
"github.com/hashicorp/packer/common/retry"
packer "github.com/hashicorp/packer/common"
)
type VBox42Driver struct {
@ -241,10 +242,14 @@ func (d *VBox42Driver) Version() (string, error) {
}
func (d *VBox42Driver) CreateSnapshot(vmname string, snapshotName string) error {
log.Printf("Executing CreateSnapshot: VM: %s, SnapshotName %s", vmname, snapshotName)
return d.VBoxManage("snapshot", vmname, "take", snapshotName)
}
func (d *VBox42Driver) HasSnapshots(vmname string) (bool, error) {
log.Printf("Executing HasSnapshots: VM: %s", vmname)
var stdout, stderr bytes.Buffer
var hasSnapshots = false
@ -268,6 +273,8 @@ func (d *VBox42Driver) HasSnapshots(vmname string) (bool, error) {
}
func (d *VBox42Driver) GetCurrentSnapshot(vmname string) (string, error) {
log.Printf("Executing GetCurrentSnapshot: VM: %s", vmname)
var stdout, stderr bytes.Buffer
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
@ -299,6 +306,8 @@ func (d *VBox42Driver) GetCurrentSnapshot(vmname string) (string, error) {
}
func (d *VBox42Driver) SetSnapshot(vmname string, snapshotName string) error {
log.Printf("Executing SetSnapshot: VM: %s, SnapshotName %s", vmname, snapshotName)
var err error
if snapshotName == "" {
err = d.VBoxManage("snapshot", vmname, "restorecurrent")
@ -313,12 +322,18 @@ func (d *VBox42Driver) DeleteSnapshot(vmname string, snapshotName string) error
}
func (d *VBox42Driver) SnapshotExists(vmname string, snapshotName string) (bool, error) {
var stdout bytes.Buffer
log.Printf("Executing SnapshotExists: VM %s, SnapshotName %s", vmname, snapshotName)
var stdout, stderr bytes.Buffer
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return false, err
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))
}
SnapshotNameRe := regexp.MustCompile(fmt.Sprintf("SnapshotName[^=]*=[^\"]*\"%s\"", snapshotName))
@ -331,3 +346,64 @@ func (d *VBox42Driver) SnapshotExists(vmname string, snapshotName string) (bool,
return false, nil
}
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
}

View File

@ -70,12 +70,28 @@ func (s *StepForwardSSH) Run(ctx context.Context, state multistep.StateBag) mult
"--natpf1",
fmt.Sprintf("packercomm,tcp,127.0.0.1,%d,,%d", sshHostPort, guestPort),
}
retried := false
retry:
if err := driver.VBoxManage(command...); err != nil {
if !strings.Contains(err.Error(), "A NAT rule of this name already exists") {
if !strings.Contains(err.Error(), "A NAT rule of this name already exists") || retried {
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else {
log.Printf("A packer NAT rule already exists. Trying to delete ...")
delcommand := []string{
"modifyvm", vmName,
"--natpf1",
"delete", "packercomm",
}
if err := driver.VBoxManage(delcommand...); err != nil {
err := fmt.Errorf("Error deleting packer NAT forwarding rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
goto retry
}
}
}

View File

@ -50,10 +50,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Build the steps.
steps := []multistep.Step{
&common.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
new(vboxcommon.StepSuppressMessages),
&common.StepCreateFloppy{
Files: b.config.FloppyConfig.FloppyFiles,
@ -62,6 +58,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepSetSnapshot{
Name: b.config.VMName,
AttachSnapshot: b.config.AttachSnapshot,
KeepRegistered: b.config.KeepRegistered,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
@ -105,6 +102,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName,
Ctx: b.config.ctx,
GroupInterval: b.config.BootConfig.BootGroupInterval,
Comm: &b.config.Comm,
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
@ -147,6 +145,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
}
if !b.config.SkipExport {
steps = append(steps, nil)
copy(steps[1:], steps)
steps[0] = &common.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
}
}
// Run the steps.
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(state)
@ -165,7 +171,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, errors.New("Build was halted.")
}
return vboxcommon.NewArtifact(b.config.OutputDir)
if b.config.SkipExport {
return nil, nil
} else {
return vboxcommon.NewArtifact(b.config.OutputDir)
}
}
// Cancel.

View File

@ -131,10 +131,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
if c.AttachSnapshot != "" {
snapshotExists, err := driver.SnapshotExists(c.VMName, c.AttachSnapshot)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to check for snapshot: %s", err))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to check for snapshot: %s with VM %s ; Error: %s", c.AttachSnapshot, c.VMName, err))
} else {
if !snapshotExists {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Snapshot does not exist: %s", c.AttachSnapshot))
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Snapshot %s does not exist on with VM %s", c.AttachSnapshot, c.VMName))
}
}
}
@ -144,10 +144,35 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to check for snapshot: %s", err))
} else {
if snapshotExists {
warnings = append(warnings, fmt.Sprintf("Target snapshot already exists: %s.", c.TargetSnapshot))
parent, err := driver.GetParentSnapshot(c.VMName, c.TargetSnapshot)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to get parent for snapshot %s: %s", c.TargetSnapshot, err))
return nil, warnings, errs
} else {
var selfSnapshotName string
if "" != c.AttachSnapshot {
selfSnapshotName = c.AttachSnapshot
} else {
currentSnapshot, err := driver.GetCurrentSnapshot(c.VMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to get current snapshot for VM %s: %s", c.VMName, err))
return nil, warnings, errs
}
selfSnapshotName = currentSnapshot
}
selfSnapshotParent, err := driver.GetParentSnapshot(c.VMName, selfSnapshotName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed to get parent for snapshot %s: %s", selfSnapshotName, err))
} else if parent != selfSnapshotName {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Target snapshot %s already exists and is not a direct child of %s", c.TargetSnapshot, selfSnapshotParent))
}
}
}
}
}
if c.AttachSnapshot != "" && c.TargetSnapshot != "" && c.AttachSnapshot == c.TargetSnapshot {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Attach snapshot %s and target snapshot %s cannot be the same", c.AttachSnapshot, c.TargetSnapshot))
}
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {

View File

@ -3,6 +3,7 @@ package vm
import (
"context"
"fmt"
"log"
"time"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
@ -21,7 +22,23 @@ func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) mu
if s.TargetSnapshot != "" {
time.Sleep(10 * time.Second) // Wait after the Vm has been shutdown, otherwise creating the snapshot might make the VM unstartable
ui.Say(fmt.Sprintf("Creating snapshot %s on virtual machine %s", s.TargetSnapshot, s.Name))
err := driver.CreateSnapshot(s.Name, s.TargetSnapshot)
snapshotExists, err := driver.SnapshotExists(s.Name, s.TargetSnapshot)
if err != nil {
err = fmt.Errorf("Failed to check for snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else if snapshotExists {
log.Printf("Deleting existing target snapshot %s", s.TargetSnapshot)
err = driver.DeleteSnapshot(s.Name, s.TargetSnapshot)
if nil != err {
err = fmt.Errorf("Unable to delete snapshot %s from VM %s: %s", s.TargetSnapshot, s.Name, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
err = driver.CreateSnapshot(s.Name, s.TargetSnapshot)
if err != nil {
err := fmt.Errorf("Error creating snaphot VM: %s", err)
state.Put("error", err)

View File

@ -12,26 +12,20 @@ import (
type StepSetSnapshot struct {
Name string
AttachSnapshot string
KeepRegistered bool
revertToSnapshot string
}
func (s *StepSetSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(vboxcommon.Driver)
if s.AttachSnapshot != "" {
ui := state.Get("ui").(packer.Ui)
hasSnapshots, err := driver.HasSnapshots(s.Name)
if err != nil {
err := fmt.Errorf("Error checking for snapshots VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if !hasSnapshots {
err := fmt.Errorf("Unable to attach snapshot on VM %s when no snapshots exist", s.Name)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui := state.Get("ui").(packer.Ui)
hasSnapshots, err := driver.HasSnapshots(s.Name)
if err != nil {
err := fmt.Errorf("Error checking for snapshots VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else if hasSnapshots {
currentSnapshot, err := driver.GetCurrentSnapshot(s.Name)
if err != nil {
err := fmt.Errorf("Unable to get current snapshot for VM: %s", err)
@ -39,6 +33,15 @@ func (s *StepSetSnapshot) Run(_ context.Context, state multistep.StateBag) multi
ui.Error(err.Error())
return multistep.ActionHalt
}
s.revertToSnapshot = currentSnapshot
}
if s.AttachSnapshot != "" {
if !hasSnapshots {
err := fmt.Errorf("Unable to attach snapshot on VM %s when no snapshots exist", s.Name)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Attaching snapshot %s on virtual machine %s", s.AttachSnapshot, s.Name))
err = driver.SetSnapshot(s.Name, s.AttachSnapshot)
if err != nil {
@ -47,7 +50,6 @@ func (s *StepSetSnapshot) Run(_ context.Context, state multistep.StateBag) multi
ui.Error(err.Error())
return multistep.ActionHalt
}
s.revertToSnapshot = currentSnapshot
}
return multistep.ActionContinue
}
@ -56,13 +58,18 @@ func (s *StepSetSnapshot) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(vboxcommon.Driver)
if s.revertToSnapshot != "" {
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Reverting to snapshot %s on virtual machine %s", s.revertToSnapshot, s.Name))
err := driver.SetSnapshot(s.Name, s.revertToSnapshot)
if err != nil {
err := fmt.Errorf("Unable to set snapshot for VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
if s.KeepRegistered {
ui.Say("Keeping virtual machine state (keep_registered = true)")
return
} else {
ui.Say(fmt.Sprintf("Reverting to snapshot %s on virtual machine %s", s.revertToSnapshot, s.Name))
err := driver.SetSnapshot(s.Name, s.revertToSnapshot)
if err != nil {
err := fmt.Errorf("Unable to set snapshot for VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
}
}
}

View File

@ -241,9 +241,11 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, args []string) int {
errors.Unlock()
} else {
ui.Say(fmt.Sprintf("Build '%s' finished.", name))
artifacts.Lock()
artifacts.m[name] = runArtifacts
artifacts.Unlock()
if nil != runArtifacts {
artifacts.Lock()
artifacts.m[name] = runArtifacts
artifacts.Unlock()
}
}
}()