Commit 573a2a23 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #743 from mitchellh/f-virtualbox-ovf

VirtualBox-OVF builder
parents 8e4ce2c5 c7bf38b6
......@@ -4,6 +4,7 @@
/src
/website/.sass-cache
/website/build
.DS_Store
.vagrant
Vagrantfile
test/.env
package virtualbox
import (
"fmt"
"os"
)
// Artifact is the result of running the VirtualBox builder, namely a set
// of files associated with the resulting machine.
type Artifact struct {
dir string
f []string
}
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)
}
package common
import (
"fmt"
"os"
"path/filepath"
"github.com/mitchellh/packer/packer"
)
// This is the common builder ID to all of these artifacts.
const BuilderId = "mitchellh.virtualbox"
// Artifact is the result of running the VirtualBox builder, namely a set
// of files associated with the resulting machine.
type artifact struct {
dir string
f []string
}
// NewArtifact returns a VirtualBox 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 {
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)
}
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()))
}
}
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
}
package common
import (
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
// A driver is able to talk to VirtualBox 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 VirtualBox builder for Packer, and to abstract differences in
// versions out of the builder steps, so sometimes the methods are
// extremely specific.
type Driver interface {
// Create a SATA controller.
CreateSATAController(vm string, controller string) error
// Delete a VM by name
Delete(string) error
// Import a VM
Import(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
// SuppressMessages should do what needs to be done in order to
// suppress any annoying popups from VirtualBox.
SuppressMessages() error
// VBoxManage executes the given VBoxManage command
VBoxManage(...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 VirtualBox that is installed.
Version() (string, error)
}
func NewDriver() (Driver, error) {
var vboxmanagePath string
// On Windows, we check VBOX_INSTALL_PATH env var for the path
if runtime.GOOS == "windows" {
if installPath := os.Getenv("VBOX_INSTALL_PATH"); installPath != "" {
log.Printf("[DEBUG] builder/virtualbox: VBOX_INSTALL_PATH: %s",
installPath)
for _, path := range strings.Split(installPath, ";") {
path = filepath.Join(path, "VBoxManage.exe")
if _, err := os.Stat(path); err == nil {
vboxmanagePath = path
break
}
}
}
}
if vboxmanagePath == "" {
var err error
vboxmanagePath, err = exec.LookPath("VBoxManage")
if err != nil {
return nil, err
}
}
log.Printf("VBoxManage path: %s", vboxmanagePath)
driver := &VBox42Driver{vboxmanagePath}
if err := driver.Verify(); err != nil {
return nil, err
}
return driver, nil
}
package virtualbox
package common
import (
"bytes"
......@@ -10,38 +10,6 @@ import (
"time"
)
// A driver is able to talk to VirtualBox 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 VirtualBox builder for Packer, and to abstract differences in
// versions out of the builder steps, so sometimes the methods are
// extremely specific.
type Driver interface {
// Create a SATA controller.
CreateSATAController(vm string, controller 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
// SuppressMessages should do what needs to be done in order to
// suppress any annoying popups from VirtualBox.
SuppressMessages() error
// VBoxManage executes the given VBoxManage command
VBoxManage(...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 VirtualBox that is installed.
Version() (string, error)
}
type VBox42Driver struct {
// This is the path to the "VBoxManage" application.
VBoxManagePath string
......@@ -68,6 +36,20 @@ func (d *VBox42Driver) CreateSATAController(vmName string, name string) error {
return d.VBoxManage(command...)
}
func (d *VBox42Driver) Delete(name string) error {
return d.VBoxManage("unregistervm", name, "--delete")
}
func (d *VBox42Driver) Import(name, path string) error {
args := []string{
"import", path,
"--vsys", "0",
"--vmname", name,
}
return d.VBoxManage(args...)
}
func (d *VBox42Driver) IsRunning(name string) (bool, error) {
var stdout bytes.Buffer
......
package common
import (
"testing"
)
func TestVBox42Driver_impl(t *testing.T) {
var _ Driver = new(VBox42Driver)
}
package common
import "sync"
type DriverMock struct {
sync.Mutex
CreateSATAControllerVM string
CreateSATAControllerController string
CreateSATAControllerErr error
DeleteCalled bool
DeleteName string
DeleteErr error
ImportCalled bool
ImportName string
ImportPath string
ImportErr error
IsRunningName string
IsRunningReturn bool
IsRunningErr error
StopName string
StopErr error
SuppressMessagesCalled bool
SuppressMessagesErr error
VBoxManageCalls [][]string
VBoxManageErrs []error
VerifyCalled bool
VerifyErr error
VersionCalled bool
VersionResult string
VersionErr error
}
func (d *DriverMock) CreateSATAController(vm string, controller string) error {
d.CreateSATAControllerVM = vm
d.CreateSATAControllerController = vm
return d.CreateSATAControllerErr
}
func (d *DriverMock) Delete(name string) error {
d.DeleteCalled = true
d.DeleteName = name
return d.DeleteErr
}
func (d *DriverMock) Import(name, path string) error {
d.ImportCalled = true
d.ImportName = name
d.ImportPath = path
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) SuppressMessages() error {
d.SuppressMessagesCalled = true
return d.SuppressMessagesErr
}
func (d *DriverMock) VBoxManage(args ...string) error {
d.VBoxManageCalls = append(d.VBoxManageCalls, args)
if len(d.VBoxManageErrs) >= len(d.VBoxManageCalls) {
return d.VBoxManageErrs[len(d.VBoxManageCalls)-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
}
package common
import (
"errors"
"fmt"
"github.com/mitchellh/packer/packer"
)
type ExportConfig struct {
Format string `mapstruture:"format"`
}
func (c *ExportConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.Format == "" {
c.Format = "ovf"
}
templates := map[string]*string{
"format": &c.Format,
}
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.Format != "ovf" && c.Format != "ova" {
errs = append(errs,
errors.New("invalid format, only 'ovf' or 'ova' are allowed"))
}
return errs
}
package common
import (
"testing"
)
func TestExportConfigPrepare_BootWait(t *testing.T) {
var c *ExportConfig
var errs []error
// Bad
c = new(ExportConfig)
c.Format = "illega"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatalf("bad: %#v", errs)
}
// Good
c = new(ExportConfig)
c.Format = "ova"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
// Good
c = new(ExportConfig)
c.Format = "ovf"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
}
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
}
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")
}
}
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
}
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")
}
}
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
}
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)
}
}
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
}
package common
import (
"testing"
)
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)
}
}
package virtualbox
package common
import (
gossh "code.google.com/p/go.crypto/ssh"
......@@ -9,33 +9,33 @@ import (
"os"
)
func sshAddress(state multistep.StateBag) (string, error) {
func SSHAddress(state multistep.StateBag) (string, error) {
sshHostPort := state.Get("sshHostPort").(uint)
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
}
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*config)
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
auth = append(auth, gossh.ClientAuthKeyring(keyring))
}
auth = append(auth, gossh.ClientAuthKeyring(keyring))
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: auth,
}, nil
}
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: auth,
}, nil
}
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
......
package common
import (
"errors"
"fmt"
"github.com/mitchellh/packer/packer"
"os"
"time"
)
type SSHConfig struct {
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
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.SSHHostPortMin == 0 {
c.SSHHostPortMin = 2222
}
if c.SSHHostPortMax == 0 {
c.SSHHostPortMax = 4444
}
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 := sshKeyToKeyring(c.SSHKeyPath); err != nil {
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if c.SSHHostPortMin > c.SSHHostPortMax {
errs = append(errs,
errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
}
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
}
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.SSHHostPortMin != 2222 {
t.Errorf("bad min ssh host port: %d", c.SSHHostPortMin)
}
if c.SSHHostPortMax != 4444 {
t.Errorf("bad max ssh host port: %d", c.SSHHostPortMax)
}
if c.SSHPort != 22 {
t.Errorf("bad ssh port: %d", c.SSHPort)
}
}
func TestSSHConfigPrepare_SSHHostPort(t *testing.T) {
var c *SSHConfig
var errs []error
// Bad
c = testSSHConfig()
c.SSHHostPortMin = 1000
c.SSHHostPortMax = 500
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatalf("bad: %#v", errs)
}
// Good
c = testSSHConfig()
c.SSHHostPortMin = 50
c.SSHHostPortMax = 500
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
}
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-----
`
package virtualbox
package common
import (
"fmt"
......@@ -14,13 +14,16 @@ import (
// This step attaches the ISO to the virtual machine.
//
// Uses:
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
type stepAttachFloppy struct {
type StepAttachFloppy struct {
floppyPath string
}
func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
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 {
......@@ -76,7 +79,7 @@ func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepAttachFloppy) Cleanup(state multistep.StateBag) {
func (s *StepAttachFloppy) Cleanup(state multistep.StateBag) {
if s.floppyPath == "" {
return
}
......@@ -100,7 +103,7 @@ func (s *stepAttachFloppy) Cleanup(state multistep.StateBag) {
}
}
func (s *stepAttachFloppy) copyFloppy(path string) (string, error) {
func (s *StepAttachFloppy) copyFloppy(path string) (string, error) {
tempdir, err := ioutil.TempDir("", "packer")
if err != nil {
return "", err
......
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.VBoxManageCalls) != 2 {
t.Fatal("not enough calls to VBoxManage")
}
if driver.VBoxManageCalls[0][0] != "storagectl" {
t.Fatal("bad call")
}
if driver.VBoxManageCalls[1][0] != "storageattach" {
t.Fatal("bad call")
}
// Test the cleanup
step.Cleanup(state)
if driver.VBoxManageCalls[2][0] != "storageattach" {
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.VBoxManageCalls) > 0 {
t.Fatal("should not call vboxmanage")
}
}
package virtualbox
package common
import (
"fmt"
......@@ -15,10 +15,12 @@ import (
//
// Produces:
// exportPath string - The path to the resulting export.
type stepExport struct{}
type StepExport struct {
Format string
OutputDir string
}
func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
......@@ -41,7 +43,7 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
}
// Export the VM to an OVF
outputPath := filepath.Join(config.OutputDir, vmName+"."+config.Format)
outputPath := filepath.Join(s.OutputDir, vmName+"."+s.Format)
command = []string{
"export",
......@@ -64,4 +66,4 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepExport) Cleanup(state multistep.StateBag) {}
func (s *StepExport) Cleanup(state multistep.StateBag) {}
package common
import (
"github.com/mitchellh/multistep"
"testing"
)
func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport)
}
func TestStepExport(t *testing.T) {
state := testState(t)
step := new(StepExport)
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 output state
if _, ok := state.GetOk("exportPath"); !ok {
t.Fatal("should set exportPath")
}
// Test driver
if len(driver.VBoxManageCalls) != 2 {
t.Fatal("should call vboxmanage")
}
if driver.VBoxManageCalls[0][0] != "modifyvm" {
t.Fatal("bad")
}
if driver.VBoxManageCalls[1][0] != "export" {
t.Fatal("bad")
}
}
package virtualbox
package common
import (
"fmt"
......@@ -13,30 +13,37 @@ import (
// on the guest machine.
//
// Uses:
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
type stepForwardSSH struct{}
type StepForwardSSH struct {
GuestPort uint
HostPortMin uint
HostPortMax uint
}
func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
log.Printf("Looking for available SSH port between %d and %d",
s.HostPortMin, s.HostPortMax)
var sshHostPort uint
var offset uint = 0
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
portRange := int(s.HostPortMax - s.HostPortMin)
if portRange > 0 {
// Have to check if > 0 to avoid a panic
offset = uint(rand.Intn(portRange))
}
for {
sshHostPort = offset + config.SSHHostPortMin
sshHostPort = offset + s.HostPortMin
log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
if err == nil {
defer l.Close()
break
......@@ -48,7 +55,7 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
command := []string{
"modifyvm", vmName,
"--natpf1",
fmt.Sprintf("packerssh,tcp,127.0.0.1,%d,,%d", sshHostPort, config.SSHPort),
fmt.Sprintf("packerssh,tcp,127.0.0.1,%d,,%d", sshHostPort, s.GuestPort),
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
......@@ -63,4 +70,4 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepForwardSSH) Cleanup(state multistep.StateBag) {}
func (s *StepForwardSSH) Cleanup(state multistep.StateBag) {}
package virtualbox
package common
import (
"fmt"
......@@ -11,25 +11,30 @@ import (
"github.com/mitchellh/packer/packer"
)
type stepPrepareOutputDir struct{}
// 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
}
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
if _, err := os.Stat(s.Path); err == nil && s.Force {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
os.RemoveAll(s.Path)
}
// Create the directory
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
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(config.OutputDir, "_packer_perm_check"))
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)
......@@ -41,17 +46,16 @@ func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
func (s *StepOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir)
err := os.RemoveAll(s.Path)
if err == nil {
break
}
......
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")
}
}
package virtualbox
package common
import (
"fmt"
......@@ -10,11 +10,14 @@ import (
// machine that we may have added.
//
// Uses:
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
type stepRemoveDevices struct{}
type StepRemoveDevices struct{}
func (s *stepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction {
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)
......@@ -37,23 +40,25 @@ func (s *stepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction {
}
}
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "0",
"--device", "1",
"--medium", "none",
}
if _, ok := state.GetOk("attachedIso"); ok {
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "0",
"--device", "1",
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error detaching ISO: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
if err := driver.VBoxManage(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) {
func (s *StepRemoveDevices) Cleanup(state multistep.StateBag) {
}
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.VBoxManageCalls) != 0 {
t.Fatalf("bad: %#v", driver.VBoxManageCalls)
}
}
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.VBoxManageCalls) != 1 {
t.Fatalf("bad: %#v", driver.VBoxManageCalls)
}
if driver.VBoxManageCalls[0][3] != "IDE Controller" {
t.Fatalf("bad: %#v", driver.VBoxManageCalls)
}
}
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.VBoxManageCalls) != 1 {
t.Fatalf("bad: %#v", driver.VBoxManageCalls)
}
if driver.VBoxManageCalls[0][3] != "Floppy Controller" {
t.Fatalf("bad: %#v", driver.VBoxManageCalls)
}
}
package virtualbox
package common
import (
"fmt"
......@@ -10,21 +10,26 @@ import (
// This step starts the virtual machine.
//
// Uses:
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
type stepRun struct {
type StepRun struct {
BootWait time.Duration
Headless bool
vmName string
}
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
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 config.Headless == true {
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.")
......@@ -40,9 +45,9 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
s.vmName = vmName
if int64(config.bootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
wait := time.After(config.bootWait)
if int64(s.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait))
wait := time.After(s.BootWait)
WAITLOOP:
for {
select {
......@@ -59,7 +64,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepRun) Cleanup(state multistep.StateBag) {
func (s *StepRun) Cleanup(state multistep.StateBag) {
if s.vmName == "" {
return
}
......
package virtualbox
package common
import (
"errors"
......@@ -14,26 +14,27 @@ import (
//
// Uses:
// communicator packer.Communicator
// config *config
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
// <nothing>
type stepShutdown struct{}
type StepShutdown struct {
Command string
Timeout time.Duration
}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if config.ShutdownCommand != "" {
if s.Command != "" {
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
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)
......@@ -42,8 +43,8 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
}
// Wait for the machine to actually shut down
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
shutdownTimer := time.After(config.shutdownTimeout)
log.Printf("Waiting max %s for shutdown to complete", s.Timeout)
shutdownTimer := time.After(s.Timeout)
for {
running, _ := driver.IsRunning(vmName)
if !running {
......@@ -57,7 +58,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
ui.Error(err.Error())
return multistep.ActionHalt
default:
time.Sleep(1 * time.Second)
time.Sleep(500 * time.Millisecond)
}
}
} else {
......@@ -74,4 +75,4 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}
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")
}
}
package virtualbox
package common
import (
"fmt"
......@@ -9,9 +9,9 @@ import (
// This step sets some variables in VirtualBox so that annoying
// pop-up messages don't exist.
type stepSuppressMessages struct{}
type StepSuppressMessages struct{}
func (stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
func (StepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
......@@ -26,4 +26,4 @@ func (stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (stepSuppressMessages) Cleanup(multistep.StateBag) {}
func (StepSuppressMessages) Cleanup(multistep.StateBag) {}
package common
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
)
func TestStepSuppressMessages_impl(t *testing.T) {
var _ multistep.Step = new(StepSuppressMessages)
}
func TestStepSuppressMessages(t *testing.T) {
state := testState(t)
step := new(StepSuppressMessages)
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 !driver.SuppressMessagesCalled {
t.Fatal("should call suppressmessages")
}
}
func TestStepSuppressMessages_error(t *testing.T) {
state := testState(t)
step := new(StepSuppressMessages)
driver := state.Get("driver").(*DriverMock)
driver.SuppressMessagesErr = errors.New("foo")
// Test the run
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
if !driver.SuppressMessagesCalled {
t.Fatal("should call suppressmessages")
}
}
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
}
package virtualbox
package common
import (
"bytes"
......@@ -10,15 +10,16 @@ import (
// This step uploads a file containing the VirtualBox version, which
// can be useful for various provisioning reasons.
type stepUploadVersion struct{}
type StepUploadVersion struct {
Path string
}
func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
func (s *StepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if config.VBoxVersionFile == "" {
if s.Path == "" {
log.Println("VBoxVersionFile is empty. Not uploading.")
return multistep.ActionContinue
}
......@@ -32,7 +33,7 @@ func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("Uploading VirtualBox version info (%s)", version))
var data bytes.Buffer
data.WriteString(version)
if err := comm.Upload(config.VBoxVersionFile, &data); err != nil {
if err := comm.Upload(s.Path, &data); err != nil {
state.Put("error", fmt.Errorf("Error uploading VirtualBox version: %s", err))
return multistep.ActionHalt
}
......@@ -40,4 +41,4 @@ func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepUploadVersion) Cleanup(state multistep.StateBag) {}
func (s *StepUploadVersion) Cleanup(state multistep.StateBag) {}
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")
}
}
package virtualbox
package common
import (
"fmt"
......@@ -15,17 +15,22 @@ type commandTemplate struct {
// template.
//
// Uses:
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
type stepVBoxManage struct{}
type StepVBoxManage struct {
Commands [][]string
Tpl *packer.ConfigTemplate
}
func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
func (s *StepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if len(config.VBoxManage) > 0 {
if len(s.Commands) > 0 {
ui.Say("Executing custom VBoxManage commands...")
}
......@@ -33,13 +38,13 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
Name: vmName,
}
for _, originalCommand := range config.VBoxManage {
for _, originalCommand := range s.Commands {
command := make([]string, len(originalCommand))
copy(command, originalCommand)
for i, arg := range command {
var err error
command[i], err = config.tpl.Process(arg, tplData)
command[i], err = s.Tpl.Process(arg, tplData)
if err != nil {
err := fmt.Errorf("Error preparing vboxmanage command: %s", err)
state.Put("error", err)
......@@ -60,4 +65,4 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepVBoxManage) Cleanup(state multistep.StateBag) {}
func (s *StepVBoxManage) Cleanup(state multistep.StateBag) {}
package common
import (
"fmt"
"github.com/mitchellh/packer/packer"
)
type VBoxVersionConfig struct {
VBoxVersionFile string `mapstructure:"virtualbox_version_file"`
}
func (c *VBoxVersionConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.VBoxVersionFile == "" {
c.VBoxVersionFile = ".vbox_version"
}
templates := map[string]*string{
"virtualbox_version_file": &c.VBoxVersionFile,
}
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
}
package common
import (
"testing"
)
func TestVBoxVersionConfigPrepare_BootWait(t *testing.T) {
var c *VBoxVersionConfig
var errs []error
// Test empty
c = new(VBoxVersionConfig)
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
if c.VBoxVersionFile != ".vbox_version" {
t.Fatalf("bad value: %s", c.VBoxVersionFile)
}
// Test with a good one
c = new(VBoxVersionConfig)
c.VBoxVersionFile = "foo"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
if c.VBoxVersionFile != "foo" {
t.Fatalf("bad value: %s", c.VBoxVersionFile)
}
}
package common
import (
"fmt"
"github.com/mitchellh/packer/packer"
)
type VBoxManageConfig struct {
VBoxManage [][]string `mapstructure:"vboxmanage"`
}
func (c *VBoxManageConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.VBoxManage == nil {
c.VBoxManage = make([][]string, 0)
}
errs := make([]error, 0)
for i, args := range c.VBoxManage {
for j, arg := range args {
if err := t.Validate(arg); err != nil {
errs = append(errs,
fmt.Errorf("Error processing vboxmanage[%d][%d]: %s", i, j, err))
}
}
}
return errs
}
package common
import (
"reflect"
"testing"
)
func TestVBoxManageConfigPrepare_VBoxManage(t *testing.T) {
// Test with empty
c := new(VBoxManageConfig)
errs := c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if !reflect.DeepEqual(c.VBoxManage, [][]string{}) {
t.Fatalf("bad: %#v", c.VBoxManage)
}
// Test with a good one
c = new(VBoxManageConfig)
c.VBoxManage = [][]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.VBoxManage, expected) {
t.Fatalf("bad: %#v", c.VBoxManage)
}
}
package virtualbox
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
"log"
)
......@@ -24,7 +25,7 @@ type stepAttachGuestAdditions struct {
func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
......@@ -65,7 +66,7 @@ func (s *stepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
return
}
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
......
package virtualbox
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
)
......@@ -16,7 +17,7 @@ type stepAttachISO struct {
}
func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
......@@ -40,6 +41,9 @@ func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
// Track the path so that we can unregister it from VirtualBox later
s.diskPath = isoPath
// Set some state so we know to remove
state.Put("attachedIso", true)
return multistep.ActionContinue
}
......@@ -48,7 +52,7 @@ func (s *stepAttachISO) Cleanup(state multistep.StateBag) {
return
}
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
vmName := state.Get("vmName").(string)
command := []string{
......
package virtualbox
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
"path/filepath"
"strconv"
......@@ -15,7 +16,7 @@ type stepCreateDisk struct{}
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
......
package virtualbox
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
)
......@@ -16,7 +17,7 @@ type stepCreateVM struct {
func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
name := config.VMName
......@@ -60,7 +61,7 @@ func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
return
}
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unregistering and deleting virtual machine...")
......
package virtualbox
package iso
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io"
......@@ -31,7 +32,7 @@ type stepDownloadGuestAdditions struct{}
func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
var action multistep.StepAction
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*config)
......
package virtualbox
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
"log"
"strings"
......@@ -34,7 +35,7 @@ type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
......
package virtualbox
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
"log"
"os"
......@@ -18,7 +19,7 @@ type stepUploadGuestAdditions struct{}
func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
// If we're attaching then don't do this, since we attached.
......
package ovf
import (
"errors"
"fmt"
"log"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/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("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{
&vboxcommon.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
new(vboxcommon.StepSuppressMessages),
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
},
&StepImport{
Name: b.config.VMName,
SourcePath: b.config.SourcePath,
},
/*
new(stepAttachGuestAdditions),
*/
new(vboxcommon.StepAttachFloppy),
&vboxcommon.StepForwardSSH{
GuestPort: b.config.SSHPort,
HostPortMin: b.config.SSHHostPortMin,
HostPortMax: b.config.SSHHostPortMax,
},
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManage,
Tpl: b.config.tpl,
},
&vboxcommon.StepRun{
BootWait: b.config.BootWait,
Headless: b.config.Headless,
},
&common.StepConnectSSH{
SSHAddress: vboxcommon.SSHAddress,
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
SSHWaitTimeout: b.config.SSHWaitTimeout,
},
&vboxcommon.StepUploadVersion{
Path: b.config.VBoxVersionFile,
},
/*
new(stepUploadGuestAdditions),
*/
new(common.StepProvision),
&vboxcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
},
new(vboxcommon.StepRemoveDevices),
&vboxcommon.StepExport{
Format: b.config.Format,
OutputDir: b.config.OutputDir,
},
}
// 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 vboxcommon.NewArtifact(b.config.OutputDir)
}
// Cancel.
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
package ovf
import (
"fmt"
"os"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
)
// Config is the configuration structure for the builder.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
vboxcommon.ExportConfig `mapstructure:",squash"`
vboxcommon.FloppyConfig `mapstructure:",squash"`
vboxcommon.OutputConfig `mapstructure:",squash"`
vboxcommon.RunConfig `mapstructure:",squash"`
vboxcommon.SSHConfig `mapstructure:",squash"`
vboxcommon.ShutdownConfig `mapstructure:",squash"`
vboxcommon.VBoxManageConfig `mapstructure:",squash"`
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
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.VMName == "" {
c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName)
}
// Prepare the errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(c.tpl)...)
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.SSHConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(c.tpl)...)
errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(c.tpl)...)
templates := map[string]*string{
"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))
}
}
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
}
package ovf
import (
"testing"
"io/ioutil"
"os"
)
func testConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{
"ssh_username": "foo",
"shutdown_command": "foo",
}
}
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, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
c = testConfig(t)
c["source_path"] = tf.Name()
_, warns, errs = NewConfig(c)
testConfigOk(t, warns, errs)
}
package ovf
import (
"fmt"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
)
// This step imports an OVF VM into VirtualBox.
type StepImport struct {
Name string
SourcePath string
vmName string
}
func (s *StepImport) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Importing VM: %s", s.SourcePath))
if err := driver.Import(s.Name, s.SourcePath); 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").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unregistering and deleting imported VM...")
if err := driver.Delete(s.vmName); err != nil {
ui.Error(fmt.Sprintf("Error deleting VM: %s", err))
}
}
package ovf
import (
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"testing"
)
func TestStepImport_impl(t *testing.T) {
var _ multistep.Step = new(StepImport)
}
func TestStepImport(t *testing.T) {
state := testState(t)
step := new(StepImport)
step.Name = "bar"
step.SourcePath = "foo"
driver := state.Get("driver").(*vboxcommon.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 driver
if !driver.ImportCalled {
t.Fatal("import should be called")
}
if driver.ImportName != step.Name {
t.Fatalf("bad: %#v", driver.ImportName)
}
if driver.ImportPath != step.SourcePath {
t.Fatalf("bad: %#v", driver.ImportPath)
}
// Test output state
if name, ok := state.GetOk("vmName"); !ok {
t.Fatal("vmName should be set")
} else if name != "bar" {
t.Fatalf("bad: %#v", name)
}
// Test cleanup
step.Cleanup(state)
if !driver.DeleteCalled {
t.Fatal("delete should be called")
}
if driver.DeleteName != "bar" {
t.Fatalf("bad: %#v", driver.DeleteName)
}
}
package ovf
import (
"bytes"
"github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/packer"
"testing"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("driver", new(vboxcommon.DriverMock))
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
......@@ -24,6 +24,7 @@ func init() {
"createtime": new(FixerCreateTime),
"pp-vagrant-override": new(FixerVagrantPPOverride),
"virtualbox-gaattach": new(FixerVirtualBoxGAAttach),
"virtualbox-rename": new(FixerVirtualBoxRename),
}
FixerOrder = []string{
......@@ -31,5 +32,6 @@ func init() {
"createtime",
"virtualbox-gaattach",
"pp-vagrant-override",
"virtualbox-rename",
}
}
package fix
import (
"github.com/mitchellh/mapstructure"
)
// FixerVirtualBoxRename changes "virtualbox" builders to "virtualbox-iso"
type FixerVirtualBoxRename struct{}
func (FixerVirtualBoxRename) Fix(input map[string]interface{}) (map[string]interface{}, error) {
// The type we'll decode into; we only care about builders
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
for _, builder := range tpl.Builders {
builderTypeRaw, ok := builder["type"]
if !ok {
continue
}
builderType, ok := builderTypeRaw.(string)
if !ok {
continue
}
if builderType != "virtualbox" {
continue
}
builder["type"] = "virtualbox-iso"
}
input["builders"] = tpl.Builders
return input, nil
}
func (FixerVirtualBoxRename) Synopsis() string {
return `Updates "virtualbox" builders to "virtualbox-iso"`
}
package fix
import (
"reflect"
"testing"
)
func TestFixerVirtualBoxRename_impl(t *testing.T) {
var _ Fixer = new(FixerVirtualBoxRename)
}
func TestFixerVirtualBoxRename_Fix(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Expected map[string]interface{}
}{
// No attach field
{
Input: map[string]interface{}{
"type": "virtualbox",
},
Expected: map[string]interface{}{
"type": "virtualbox-iso",
},
},
}
for _, tc := range cases {
var f FixerVirtualBoxRename
input := map[string]interface{}{
"builders": []map[string]interface{}{tc.Input},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{tc.Expected},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(output, expected) {
t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected)
}
}
}
......@@ -17,5 +17,6 @@ Fixes that are run:
to use "guest_additions_mode"
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
post-processor to new-style as of Packer 0.5.0.
virtualbox-rename Updates "virtualbox" builders to "virtualbox-iso"
`
......@@ -27,7 +27,8 @@ const defaultConfig = `
"googlecompute": "packer-builder-googlecompute",
"openstack": "packer-builder-openstack",
"qemu": "packer-builder-qemu",
"virtualbox": "packer-builder-virtualbox",
"virtualbox-iso": "packer-builder-virtualbox-iso",
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
"vmware": "packer-builder-vmware"
},
......
package main
import (
"github.com/mitchellh/packer/builder/virtualbox"
"github.com/mitchellh/packer/builder/virtualbox/iso"
"github.com/mitchellh/packer/packer/plugin"
)
......@@ -10,6 +10,6 @@ func main() {
if err != nil {
panic(err)
}
server.RegisterBuilder(new(virtualbox.Builder))
server.RegisterBuilder(new(iso.Builder))
server.Serve()
}
package main
import (
"github.com/mitchellh/packer/builder/virtualbox/ovf"
"github.com/mitchellh/packer/packer/plugin"
)
func main() {
server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(ovf.Builder))
server.Serve()
}
This diff is collapsed.
---
layout: "docs"
page_title: "VirtualBox Builder (from an OVF/OVA)"
---
# VirtualBox Builder (from an OVF/OVA)
Type: `virtualbox-ovf`
This VirtualBox builder is able to create [VirtualBox](https://www.virtualbox.org/)
virtual machines and export them in the OVF format, starting from an
existing OVF/OVA (exported virtual machine image).
The builder builds a virtual machine by importing an existing OVF or OVA
file. It then boots this image, runs provisioners on this new VM, and
exports that VM to create the image. The imported machine is deleted prior
to finishing the build.
## Basic Example
Here is a basic example. This example is functional if you have an OVF matching
the settings here.
<pre class="prettyprint">
{
"type": "virtualbox-ovf",
"source_path": "source.ovf",
"ssh_username": "packer",
"ssh_password": "packer",
"ssh_wait_timeout": "30s",
"shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
}
</pre>
It is important to add a `shutdown_command`. By default Packer halts the
virtual machine and the file system may not be sync'd. Thus, changes made in a
provisioner might not be saved.
## Configuration Reference
There are many configuration options available for the VirtualBox builder.
They are organized below into two categories: required and optional. Within
each category, the available options are alphabetized and described.
Required:
* `source_path` (string) - The path to an OVF or OVA file that acts as
the source of this build.
* `ssh_username` (string) - The username to use to SSH into the machine
once the OS is installed.
Optional:
* `floppy_files` (array of strings) - A list of files to put onto a floppy
disk that is attached when the VM is booted for the first time. This is
most useful for unattended Windows installs, which look for an
`Autounattend.xml` file on removable media. By default no floppy will
be attached. The files listed in this configuration will all be put
into the root directory of the floppy disk; sub-directories are not supported.
* `format` (string) - Either "ovf" or "ova", this specifies the output
format of the exported virtual machine. This defaults to "ovf".
* `guest_additions_mode` (string) - The method by which guest additions
are made available to the guest for installation. Valid options are
"upload", "attach", or "disable". The functions of each of these should be
self-explanatory. The default value is "upload". If "disable" is used,
guest additions won't be downloaded, either.
* `guest_additions_path` (string) - The path on the guest virtual machine
where the VirtualBox guest additions ISO will be uploaded. By default this
is "VBoxGuestAdditions.iso" which should upload into the login directory
of the user. This is a [configuration template](/docs/templates/configuration-templates.html)
where the `Version` variable is replaced with the VirtualBox version.
* `guest_additions_sha256` (string) - The SHA256 checksum of the guest
additions ISO that will be uploaded to the guest VM. By default the
checksums will be downloaded from the VirtualBox website, so this only
needs to be set if you want to be explicit about the checksum.
* `guest_additions_url` (string) - The URL to the guest additions ISO
to upload. This can also be a file URL if the ISO is at a local path.
By default the VirtualBox builder will go and download the proper
guest additions ISO from the internet.
* `headless` (bool) - Packer defaults to building VirtualBox
virtual machines by launching a GUI that shows the console of the
machine being built. When this value is set to true, the machine will
start without a console.
* `output_directory` (string) - This is the path to the directory where the
resulting virtual machine will be created. This may be relative or absolute.
If relative, the path is relative to the working directory when `packer`
is executed. This directory must not exist or be empty prior to running the builder.
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
of the build.
* `shutdown_command` (string) - The command to use to gracefully shut down
the machine once all the provisioning is done. By default this is an empty
string, which tells Packer to just forcefully shut down the machine.
* `shutdown_timeout` (string) - The amount of time to wait after executing
the `shutdown_command` for the virtual machine to actually shut down.
If it doesn't shut down in this time, it is an error. By default, the timeout
is "5m", or five minutes.
* `ssh_host_port_min` and `ssh_host_port_max` (uint) - The minimum and
maximum port to use for the SSH port on the host machine which is forwarded
to the SSH port on the guest machine. Because Packer often runs in parallel,
Packer will choose a randomly available port in this range to use as the
host port.
* `ssh_key_path` (string) - Path to a private key to use for authenticating
with SSH. By default this is not set (key-based auth won't be used).
The associated public key is expected to already be configured on the
VM being prepared by some other process (kickstart, etc.).
* `ssh_password` (string) - The password for `ssh_username` to use to
authenticate with SSH. By default this is the empty string.
* `ssh_port` (int) - The port that SSH will be listening on in the guest
virtual machine. By default this is 22.
* `ssh_wait_timeout` (string) - The duration to wait for SSH to become
available. By default this is "20m", or 20 minutes. Note that this should
be quite long since the timer begins as soon as the virtual machine is booted.
* `vboxmanage` (array of array of strings) - Custom `VBoxManage` commands to
execute in order to further customize the virtual machine being created.
The value of this is an array of commands to execute. The commands are executed
in the order defined in the template. For each command, the command is
defined itself as an array of strings, where each string represents a single
argument on the command-line to `VBoxManage` (but excluding `VBoxManage`
itself). Each arg is treated as a [configuration template](/docs/templates/configuration-templates.html),
where the `Name` variable is replaced with the VM name. More details on how
to use `VBoxManage` are below.
* `virtualbox_version_file` (string) - The path within the virtual machine
to upload a file that contains the VirtualBox version that was used to
create the machine. This information can be useful for provisioning.
By default this is ".vbox_version", which will generally upload it into
the home directory.
* `vm_name` (string) - This is the name of the virtual machine when it is
imported as well as the name of the OVF file when the virtual machine is
exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is
the name of the build.
## Guest Additions
Packer will automatically download the proper guest additions for the
version of VirtualBox that is running and upload those guest additions into
the virtual machine so that provisioners can easily install them.
Packer downloads the guest additions from the official VirtualBox website,
and verifies the file with the official checksums released by VirtualBox.
After the virtual machine is up and the operating system is installed,
Packer uploads the guest additions into the virtual machine. The path where
they are uploaded is controllable by `guest_additions_path`, and defaults
to "VBoxGuestAdditions.iso". Without an absolute path, it is uploaded to the
home directory of the SSH user.
## VBoxManage Commands
In order to perform extra customization of the virtual machine, a template
can define extra calls to `VBoxManage` to perform. [VBoxManage](http://www.virtualbox.org/manual/ch08.html)
is the command-line interface to VirtualBox where you can completely control
VirtualBox. It can be used to do things such as set RAM, CPUs, etc.
Extra VBoxManage commands are defined in the template in the `vboxmanage` section.
An example is shown below that sets the memory and number of CPUs within the
virtual machine:
<pre class="prettyprint">
{
"vboxmanage": [
["modifyvm", "{{.Name}}", "--memory", "1024"],
["modifyvm", "{{.Name}}", "--cpus", "2"]
]
}
</pre>
The value of `vboxmanage` is an array of commands to execute. These commands
are executed in the order defined. So in the above example, the memory will be
set followed by the CPUs.
Each command itself is an array of strings, where each string is an argument
to `VBoxManage`. Each argument is treated as a
[configuration template](/docs/templates/configuration-templates.html).
The only available variable is `Name` which is replaced with the unique
name of the VM, which is required for many VBoxManage calls.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment