Merged changes from local branch

This commit is contained in:
Thomas Meckel 2019-02-16 14:32:22 +01:00 committed by Thomas Meckel
parent d21f4eb888
commit 2a531f8ad6
9 changed files with 595 additions and 4 deletions

View File

@ -51,6 +51,24 @@ type Driver interface {
// Version reads the version of VirtualBox that is installed.
Version() (string, error)
//
CreateSnapshot(string, string) error
//
HasSnapshots(string) (bool, error)
//
GetCurrentSnapshot(string) (string, error)
//
SetSnapshot(string, string) error
//
DeleteSnapshot(string, string) error
//
SnapshotExists(string, string) (bool, error)
}
func NewDriver() (Driver, error) {

View File

@ -239,3 +239,95 @@ func (d *VBox42Driver) Version() (string, error) {
log.Printf("VirtualBox version: %s", matches[0][1])
return matches[0][1], nil
}
func (d *VBox42Driver) CreateSnapshot(vmname string, snapshotName string) error {
return d.VBoxManage("snapshot", vmname, "take", snapshotName)
}
func (d *VBox42Driver) HasSnapshots(vmname string) (bool, error) {
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) {
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 {
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) {
var stdout bytes.Buffer
cmd := exec.Command(d.VBoxManagePath, "snapshot", vmname, "list", "--machinereadable")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return false, err
}
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
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/packer/common/net"
"github.com/hashicorp/packer/helper/communicator"
@ -70,10 +71,12 @@ func (s *StepForwardSSH) Run(ctx context.Context, state multistep.StateBag) mult
fmt.Sprintf("packercomm,tcp,127.0.0.1,%d,,%d", sshHostPort, guestPort),
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
if !strings.Contains(err.Error(), "A NAT rule of this name already exists") {
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}

View File

@ -0,0 +1,177 @@
package vm
import (
"errors"
"fmt"
"log"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// Builder implements packer.Builder and builds the actual VirtualBox
// 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 VirtualBox 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 VirtualBox
driver, err := vboxcommon.NewDriver()
if err != nil {
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("debug", b.config.PackerDebug)
state.Put("driver", driver)
state.Put("cache", cache)
state.Put("hook", hook)
state.Put("ui", ui)
// 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,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&StepSetSnapshot{
Name: b.config.VMName,
AttachSnapshot: b.config.AttachSnapshot,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&vboxcommon.StepDownloadGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsURL: b.config.GuestAdditionsURL,
GuestAdditionsSHA256: b.config.GuestAdditionsSHA256,
Ctx: b.config.ctx,
},
&StepImport{
Name: b.config.VMName,
},
&vboxcommon.StepAttachGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
},
&vboxcommon.StepConfigureVRDP{
VRDPBindAddress: b.config.VRDPBindAddress,
VRDPPortMin: b.config.VRDPPortMin,
VRDPPortMax: b.config.VRDPPortMax,
},
new(vboxcommon.StepAttachFloppy),
&vboxcommon.StepForwardSSH{
CommConfig: &b.config.SSHConfig.Comm,
HostPortMin: b.config.SSHHostPortMin,
HostPortMax: b.config.SSHHostPortMax,
SkipNatMapping: b.config.SSHSkipNatMapping,
},
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManage,
Ctx: b.config.ctx,
},
&vboxcommon.StepRun{
Headless: b.config.Headless,
},
&vboxcommon.StepTypeBootCommand{
BootWait: b.config.BootWait,
BootCommand: b.config.FlatBootCommand(),
VMName: b.config.VMName,
Ctx: b.config.ctx,
GroupInterval: b.config.BootConfig.BootGroupInterval,
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: vboxcommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
SSHPort: vboxcommon.SSHPort,
WinRMPort: vboxcommon.SSHPort,
},
&vboxcommon.StepUploadVersion{
Path: *b.config.VBoxVersionFile,
},
&vboxcommon.StepUploadGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsPath: b.config.GuestAdditionsPath,
Ctx: b.config.ctx,
},
new(common.StepProvision),
&common.StepCleanupTempKeys{
Comm: &b.config.SSHConfig.Comm,
},
&vboxcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
Delay: b.config.PostShutdownDelay,
},
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManagePost,
Ctx: b.config.ctx,
},
&StepCreateSnapshot{
Name: b.config.VMName,
TargetSnapshot: b.config.TargetSnapshot,
},
&vboxcommon.StepExport{
Format: b.config.Format,
OutputDir: b.config.OutputDir,
ExportOpts: b.config.ExportOpts.ExportOpts,
SkipNatMapping: b.config.SSHSkipNatMapping,
SkipExport: b.config.SkipExport,
},
}
// Run the steps.
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
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 vboxcommon.NewArtifact(b.config.OutputDir)
}
// Cancel.
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}

View File

@ -0,0 +1,158 @@
package vm
import (
"fmt"
"strings"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// Config is the configuration structure for the builder.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
vboxcommon.ExportConfig `mapstructure:",squash"`
vboxcommon.ExportOpts `mapstructure:",squash"`
vboxcommon.OutputConfig `mapstructure:",squash"`
vboxcommon.RunConfig `mapstructure:",squash"`
vboxcommon.SSHConfig `mapstructure:",squash"`
vboxcommon.ShutdownConfig `mapstructure:",squash"`
vboxcommon.VBoxManageConfig `mapstructure:",squash"`
vboxcommon.VBoxManagePostConfig `mapstructure:",squash"`
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
VMName string `mapstructure:"vm_name"`
AttachSnapshot string `mapstructure:"attach_snapshot"`
TargetSnapshot string `mapstructure:"target_snapshot"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipExport bool `mapstructure:"skip_export"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
"guest_additions_path",
"guest_additions_url",
"vboxmanage",
"vboxmanage_post",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
// Defaults
if c.GuestAdditionsMode == "" {
c.GuestAdditionsMode = "upload"
}
if c.GuestAdditionsPath == "" {
c.GuestAdditionsPath = "VBoxGuestAdditions.iso"
}
// Prepare the errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportOpts.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VBoxManagePostConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
if c.VMName == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("vm_name is required"))
}
if c.TargetSnapshot == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("target_snapshot is required"))
}
validMode := false
validModes := []string{
vboxcommon.GuestAdditionsModeDisable,
vboxcommon.GuestAdditionsModeAttach,
vboxcommon.GuestAdditionsModeUpload,
}
for _, mode := range validModes {
if c.GuestAdditionsMode == mode {
validMode = true
break
}
}
if !validMode {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("guest_additions_mode is invalid. Must be one of: %v", validModes))
}
if c.GuestAdditionsSHA256 != "" {
c.GuestAdditionsSHA256 = strings.ToLower(c.GuestAdditionsSHA256)
}
// 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.")
}
driver, err := vboxcommon.NewDriver()
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed creating VirtualBox driver: %s", err))
} else {
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))
} else {
if !snapshotExists {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Snapshot does not exist: %s", c.AttachSnapshot))
}
}
}
if c.TargetSnapshot != "" {
snapshotExists, err := driver.SnapshotExists(c.VMName, c.TargetSnapshot)
if err != nil {
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))
}
}
}
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs
}
return c, warnings, nil
}

View File

@ -0,0 +1,53 @@
package vm
import (
"context"
"fmt"
"time"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCreateSnapshot struct {
Name string
TargetSnapshot string
}
func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
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)
if err != nil {
err := fmt.Errorf("Error creating snaphot VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
ui.Say("No target snapshot defined...")
}
return multistep.ActionContinue
}
func (s *StepCreateSnapshot) Cleanup(state multistep.StateBag) {
/*
driver := state.Get("driver").(vboxcommon.Driver)
if s.TargetSnapshot != "" {
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Deleting snapshot %s on virtual machine %s", s.TargetSnapshot, s.Name))
err := driver.DeleteSnapshot(s.Name, s.TargetSnapshot)
if err != nil {
err := fmt.Errorf("Error cleaning up created snaphot VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
}
*/
}

View File

@ -0,0 +1,20 @@
package vm
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
)
// This step imports an OVF VM into VirtualBox.
type StepImport struct {
Name string
}
func (s *StepImport) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
state.Put("vmName", s.Name)
return multistep.ActionContinue
}
func (s *StepImport) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,68 @@
package vm
import (
"context"
"fmt"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepSetSnapshot struct {
Name string
AttachSnapshot string
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
}
currentSnapshot, err := driver.GetCurrentSnapshot(s.Name)
if err != nil {
err := fmt.Errorf("Unable to get current snapshot for VM: %s", err)
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 {
err := fmt.Errorf("Unable to set snapshot for VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.revertToSnapshot = currentSnapshot
}
return multistep.ActionContinue
}
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())
return
}
}
}

View File

@ -54,6 +54,7 @@ import (
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
virtualboxvmbuilder "github.com/hashicorp/packer/builder/virtualbox/vm"
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
vmwarevmxbuilder "github.com/hashicorp/packer/builder/vmware/vmx"
yandexbuilder "github.com/hashicorp/packer/builder/yandex"
@ -141,6 +142,7 @@ var Builders = map[string]packer.Builder{
"vagrant": new(vagrantbuilder.Builder),
"virtualbox-iso": new(virtualboxisobuilder.Builder),
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
"virtualbox-vm": new(virtualboxvmbuilder.Builder),
"vmware-iso": new(vmwareisobuilder.Builder),
"vmware-vmx": new(vmwarevmxbuilder.Builder),
"yandex": new(yandexbuilder.Builder),