Merge pull request #743 from mitchellh/f-virtualbox-ovf
VirtualBox-OVF builder
This commit is contained in:
commit
573a2a23f2
|
@ -4,6 +4,7 @@
|
||||||
/src
|
/src
|
||||||
/website/.sass-cache
|
/website/.sass-cache
|
||||||
/website/build
|
/website/build
|
||||||
|
.DS_Store
|
||||||
.vagrant
|
.vagrant
|
||||||
Vagrantfile
|
Vagrantfile
|
||||||
test/.env
|
test/.env
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,522 +0,0 @@
|
||||||
package virtualbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"github.com/mitchellh/packer/common"
|
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const BuilderId = "mitchellh.virtualbox"
|
|
||||||
|
|
||||||
// These are the different valid mode values for "guest_additions_mode" which
|
|
||||||
// determine how guest additions are delivered to the guest.
|
|
||||||
const (
|
|
||||||
GuestAdditionsModeDisable string = "disable"
|
|
||||||
GuestAdditionsModeAttach = "attach"
|
|
||||||
GuestAdditionsModeUpload = "upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
config config
|
|
||||||
runner multistep.Runner
|
|
||||||
}
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
|
||||||
|
|
||||||
BootCommand []string `mapstructure:"boot_command"`
|
|
||||||
DiskSize uint `mapstructure:"disk_size"`
|
|
||||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
|
||||||
Format string `mapstructure:"format"`
|
|
||||||
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
|
|
||||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
|
||||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
|
||||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
|
||||||
GuestOSType string `mapstructure:"guest_os_type"`
|
|
||||||
HardDriveInterface string `mapstructure:"hard_drive_interface"`
|
|
||||||
Headless bool `mapstructure:"headless"`
|
|
||||||
HTTPDir string `mapstructure:"http_directory"`
|
|
||||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
|
||||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
|
||||||
ISOChecksum string `mapstructure:"iso_checksum"`
|
|
||||||
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
|
||||||
ISOUrls []string `mapstructure:"iso_urls"`
|
|
||||||
OutputDir string `mapstructure:"output_directory"`
|
|
||||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
|
||||||
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"`
|
|
||||||
VBoxVersionFile string `mapstructure:"virtualbox_version_file"`
|
|
||||||
VBoxManage [][]string `mapstructure:"vboxmanage"`
|
|
||||||
VMName string `mapstructure:"vm_name"`
|
|
||||||
|
|
||||||
RawBootWait string `mapstructure:"boot_wait"`
|
|
||||||
RawSingleISOUrl string `mapstructure:"iso_url"`
|
|
||||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
|
||||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
|
||||||
|
|
||||||
bootWait time.Duration ``
|
|
||||||
shutdownTimeout time.Duration ``
|
|
||||||
sshWaitTimeout time.Duration ``
|
|
||||||
tpl *packer.ConfigTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|
||||||
md, err := common.DecodeConfig(&b.config, raws...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.config.tpl, err = packer.NewConfigTemplate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.config.tpl.UserVars = b.config.PackerUserVars
|
|
||||||
|
|
||||||
// Accumulate any errors and warnings
|
|
||||||
errs := common.CheckUnusedConfig(md)
|
|
||||||
warnings := make([]string, 0)
|
|
||||||
|
|
||||||
if b.config.DiskSize == 0 {
|
|
||||||
b.config.DiskSize = 40000
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.FloppyFiles == nil {
|
|
||||||
b.config.FloppyFiles = make([]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsMode == "" {
|
|
||||||
b.config.GuestAdditionsMode = "upload"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsPath == "" {
|
|
||||||
b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.HardDriveInterface == "" {
|
|
||||||
b.config.HardDriveInterface = "ide"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestOSType == "" {
|
|
||||||
b.config.GuestOSType = "Other"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.HTTPPortMin == 0 {
|
|
||||||
b.config.HTTPPortMin = 8000
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.HTTPPortMax == 0 {
|
|
||||||
b.config.HTTPPortMax = 9000
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.OutputDir == "" {
|
|
||||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RawBootWait == "" {
|
|
||||||
b.config.RawBootWait = "10s"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHHostPortMin == 0 {
|
|
||||||
b.config.SSHHostPortMin = 2222
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHHostPortMax == 0 {
|
|
||||||
b.config.SSHHostPortMax = 4444
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHPort == 0 {
|
|
||||||
b.config.SSHPort = 22
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VBoxManage == nil {
|
|
||||||
b.config.VBoxManage = make([][]string, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VBoxVersionFile == "" {
|
|
||||||
b.config.VBoxVersionFile = ".vbox_version"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VMName == "" {
|
|
||||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.Format == "" {
|
|
||||||
b.config.Format = "ovf"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
templates := map[string]*string{
|
|
||||||
"guest_additions_mode": &b.config.GuestAdditionsMode,
|
|
||||||
"guest_additions_sha256": &b.config.GuestAdditionsSHA256,
|
|
||||||
"guest_os_type": &b.config.GuestOSType,
|
|
||||||
"hard_drive_interface": &b.config.HardDriveInterface,
|
|
||||||
"http_directory": &b.config.HTTPDir,
|
|
||||||
"iso_checksum": &b.config.ISOChecksum,
|
|
||||||
"iso_checksum_type": &b.config.ISOChecksumType,
|
|
||||||
"iso_url": &b.config.RawSingleISOUrl,
|
|
||||||
"output_directory": &b.config.OutputDir,
|
|
||||||
"shutdown_command": &b.config.ShutdownCommand,
|
|
||||||
"ssh_key_path": &b.config.SSHKeyPath,
|
|
||||||
"ssh_password": &b.config.SSHPassword,
|
|
||||||
"ssh_username": &b.config.SSHUser,
|
|
||||||
"virtualbox_version_file": &b.config.VBoxVersionFile,
|
|
||||||
"vm_name": &b.config.VMName,
|
|
||||||
"format": &b.config.Format,
|
|
||||||
"boot_wait": &b.config.RawBootWait,
|
|
||||||
"shutdown_timeout": &b.config.RawShutdownTimeout,
|
|
||||||
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
for n, ptr := range templates {
|
|
||||||
var err error
|
|
||||||
*ptr, err = b.config.tpl.Process(*ptr, nil)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, url := range b.config.ISOUrls {
|
|
||||||
var err error
|
|
||||||
b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validates := map[string]*string{
|
|
||||||
"guest_additions_path": &b.config.GuestAdditionsPath,
|
|
||||||
"guest_additions_url": &b.config.GuestAdditionsURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
for n, ptr := range validates {
|
|
||||||
if err := b.config.tpl.Validate(*ptr); err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, command := range b.config.BootCommand {
|
|
||||||
if err := b.config.tpl.Validate(command); err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, file := range b.config.FloppyFiles {
|
|
||||||
var err error
|
|
||||||
b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("Error processing floppy_files[%d]: %s",
|
|
||||||
i, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(b.config.Format == "ovf" || b.config.Format == "ova") {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("invalid format, only 'ovf' or 'ova' are allowed"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("hard_drive_interface can only be ide or sata"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.ISOChecksum == "" {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
|
||||||
} else {
|
|
||||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.ISOChecksumType == "" {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("The iso_checksum_type must be specified."))
|
|
||||||
} else {
|
|
||||||
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
|
||||||
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs,
|
|
||||||
fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("One of iso_url or iso_urls must be specified."))
|
|
||||||
} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
|
|
||||||
} else if b.config.RawSingleISOUrl != "" {
|
|
||||||
b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, url := range b.config.ISOUrls {
|
|
||||||
b.config.ISOUrls[i], err = common.DownloadableURL(url)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validMode := false
|
|
||||||
validModes := []string{
|
|
||||||
GuestAdditionsModeDisable,
|
|
||||||
GuestAdditionsModeAttach,
|
|
||||||
GuestAdditionsModeUpload,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mode := range validModes {
|
|
||||||
if b.config.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 b.config.GuestAdditionsSHA256 != "" {
|
|
||||||
b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !b.config.PackerForce {
|
|
||||||
if _, err := os.Stat(b.config.OutputDir); err == nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs,
|
|
||||||
fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RawShutdownTimeout == "" {
|
|
||||||
b.config.RawShutdownTimeout = "5m"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RawSSHWaitTimeout == "" {
|
|
||||||
b.config.RawSSHWaitTimeout = "20m"
|
|
||||||
}
|
|
||||||
|
|
||||||
b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHKeyPath != "" {
|
|
||||||
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
|
||||||
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHUser == "" {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, errors.New("An ssh_username must be specified."))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
|
|
||||||
if err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(
|
|
||||||
errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, args := range b.config.VBoxManage {
|
|
||||||
for j, arg := range args {
|
|
||||||
if err := b.config.tpl.Validate(arg); err != nil {
|
|
||||||
errs = packer.MultiErrorAppend(errs,
|
|
||||||
fmt.Errorf("Error processing vboxmanage[%d][%d]: %s", i, j, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnings
|
|
||||||
if b.config.ShutdownCommand == "" {
|
|
||||||
warnings = append(warnings,
|
|
||||||
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
|
|
||||||
"will forcibly halt the virtual machine, which may result in data loss.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs != nil && len(errs.Errors) > 0 {
|
|
||||||
return warnings, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
return warnings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
|
||||||
// Create the driver that we'll use to communicate with VirtualBox
|
|
||||||
driver, err := b.newDriver()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
steps := []multistep.Step{
|
|
||||||
new(stepDownloadGuestAdditions),
|
|
||||||
&common.StepDownload{
|
|
||||||
Checksum: b.config.ISOChecksum,
|
|
||||||
ChecksumType: b.config.ISOChecksumType,
|
|
||||||
Description: "ISO",
|
|
||||||
ResultKey: "iso_path",
|
|
||||||
Url: b.config.ISOUrls,
|
|
||||||
},
|
|
||||||
new(stepPrepareOutputDir),
|
|
||||||
&common.StepCreateFloppy{
|
|
||||||
Files: b.config.FloppyFiles,
|
|
||||||
},
|
|
||||||
new(stepHTTPServer),
|
|
||||||
new(stepSuppressMessages),
|
|
||||||
new(stepCreateVM),
|
|
||||||
new(stepCreateDisk),
|
|
||||||
new(stepAttachISO),
|
|
||||||
new(stepAttachGuestAdditions),
|
|
||||||
new(stepAttachFloppy),
|
|
||||||
new(stepForwardSSH),
|
|
||||||
new(stepVBoxManage),
|
|
||||||
new(stepRun),
|
|
||||||
new(stepTypeBootCommand),
|
|
||||||
&common.StepConnectSSH{
|
|
||||||
SSHAddress: sshAddress,
|
|
||||||
SSHConfig: sshConfig,
|
|
||||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
|
||||||
},
|
|
||||||
new(stepUploadVersion),
|
|
||||||
new(stepUploadGuestAdditions),
|
|
||||||
new(common.StepProvision),
|
|
||||||
new(stepShutdown),
|
|
||||||
new(stepRemoveDevices),
|
|
||||||
new(stepExport),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the state bag
|
|
||||||
state := new(multistep.BasicStateBag)
|
|
||||||
state.Put("cache", cache)
|
|
||||||
state.Put("config", &b.config)
|
|
||||||
state.Put("driver", driver)
|
|
||||||
state.Put("hook", hook)
|
|
||||||
state.Put("ui", ui)
|
|
||||||
|
|
||||||
// Run
|
|
||||||
if b.config.PackerDebug {
|
|
||||||
b.runner = &multistep.DebugRunner{
|
|
||||||
Steps: steps,
|
|
||||||
PauseFn: common.MultistepDebugFn(ui),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.runner.Run(state)
|
|
||||||
|
|
||||||
// If there was an error, return that
|
|
||||||
if rawErr, ok := state.GetOk("error"); ok {
|
|
||||||
return nil, rawErr.(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we were interrupted or cancelled, then just exit.
|
|
||||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
||||||
return nil, errors.New("Build was cancelled.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
|
||||||
return nil, errors.New("Build was halted.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the artifact list
|
|
||||||
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(b.config.OutputDir, visit); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
artifact := &Artifact{
|
|
||||||
dir: b.config.OutputDir,
|
|
||||||
f: files,
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifact, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) Cancel() {
|
|
||||||
if b.runner != nil {
|
|
||||||
log.Println("Cancelling the step runner...")
|
|
||||||
b.runner.Cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Builder) newDriver() (Driver, error) {
|
|
||||||
var vboxmanagePath string
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// On Windows, we check VBOX_INSTALL_PATH env var for the path
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,930 +0,0 @@
|
||||||
package virtualbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var 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-----
|
|
||||||
`
|
|
||||||
|
|
||||||
func testConfig() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"iso_checksum": "foo",
|
|
||||||
"iso_checksum_type": "md5",
|
|
||||||
"iso_url": "http://www.google.com/",
|
|
||||||
"shutdown_command": "yes",
|
|
||||||
"ssh_username": "foo",
|
|
||||||
|
|
||||||
packer.BuildNameConfigKey: "foo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &Builder{}
|
|
||||||
if _, ok := raw.(packer.Builder); !ok {
|
|
||||||
t.Error("Builder must implement builder.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Defaults(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsMode != GuestAdditionsModeUpload {
|
|
||||||
t.Errorf("bad guest additions mode: %s", b.config.GuestAdditionsMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestOSType != "Other" {
|
|
||||||
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.OutputDir != "output-foo" {
|
|
||||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHHostPortMin != 2222 {
|
|
||||||
t.Errorf("bad min ssh host port: %d", b.config.SSHHostPortMin)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHHostPortMax != 4444 {
|
|
||||||
t.Errorf("bad max ssh host port: %d", b.config.SSHHostPortMax)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.SSHPort != 22 {
|
|
||||||
t.Errorf("bad ssh port: %d", b.config.SSHPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VMName != "packer-foo" {
|
|
||||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.Format != "ovf" {
|
|
||||||
t.Errorf("bad format: %s", b.config.Format)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_BootWait(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test a default boot_wait
|
|
||||||
delete(config, "boot_wait")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RawBootWait != "10s" {
|
|
||||||
t.Fatalf("bad value: %s", b.config.RawBootWait)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a bad boot_wait
|
|
||||||
config["boot_wait"] = "this is not good"
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good one
|
|
||||||
config["boot_wait"] = "5s"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
delete(config, "disk_size")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.DiskSize != 40000 {
|
|
||||||
t.Fatalf("bad size: %d", b.config.DiskSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["disk_size"] = 60000
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.DiskSize != 60000 {
|
|
||||||
t.Fatalf("bad size: %s", b.config.DiskSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
delete(config, "floppy_files")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b.config.FloppyFiles) != 0 {
|
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["floppy_files"] = []string{"foo", "bar"}
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{"foo", "bar"}
|
|
||||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
|
||||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// test default mode
|
|
||||||
delete(config, "guest_additions_mode")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test another mode
|
|
||||||
config["guest_additions_mode"] = "attach"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsMode != GuestAdditionsModeAttach {
|
|
||||||
t.Fatalf("bad: %s", b.config.GuestAdditionsMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test bad mode
|
|
||||||
config["guest_additions_mode"] = "teleport"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
delete(config, "guest_additions_path")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsPath != "VBoxGuestAdditions.iso" {
|
|
||||||
t.Fatalf("bad: %s", b.config.GuestAdditionsPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["guest_additions_path"] = "foo"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsPath != "foo" {
|
|
||||||
t.Fatalf("bad size: %s", b.config.GuestAdditionsPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_GuestAdditionsSHA256(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
delete(config, "guest_additions_sha256")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsSHA256 != "" {
|
|
||||||
t.Fatalf("bad: %s", b.config.GuestAdditionsSHA256)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["guest_additions_sha256"] = "FOO"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsSHA256 != "foo" {
|
|
||||||
t.Fatalf("bad size: %s", b.config.GuestAdditionsSHA256)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
config["guest_additions_url"] = ""
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.GuestAdditionsURL != "" {
|
|
||||||
t.Fatalf("should be empty: %s", b.config.GuestAdditionsURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["guest_additions_url"] = "http://www.packer.io"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_HardDriveInterface(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test a default boot_wait
|
|
||||||
delete(config, "hard_drive_interface")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.HardDriveInterface != "ide" {
|
|
||||||
t.Fatalf("bad: %s", b.config.HardDriveInterface)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a bad
|
|
||||||
config["hard_drive_interface"] = "fake"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good
|
|
||||||
config["hard_drive_interface"] = "sata"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Bad
|
|
||||||
config["http_port_min"] = 1000
|
|
||||||
config["http_port_max"] = 500
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bad
|
|
||||||
config["http_port_min"] = -500
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good
|
|
||||||
config["http_port_min"] = 500
|
|
||||||
config["http_port_max"] = 1000
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_Format(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Bad
|
|
||||||
config["format"] = "illegal value"
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good
|
|
||||||
config["format"] = "ova"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good
|
|
||||||
config["format"] = "ovf"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Add a random key
|
|
||||||
config["i_should_not_be_valid"] = true
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test bad
|
|
||||||
config["iso_checksum"] = ""
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test good
|
|
||||||
config["iso_checksum"] = "FOo"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.ISOChecksum != "foo" {
|
|
||||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test bad
|
|
||||||
config["iso_checksum_type"] = ""
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test good
|
|
||||||
config["iso_checksum_type"] = "mD5"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.ISOChecksumType != "md5" {
|
|
||||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test unknown
|
|
||||||
config["iso_checksum_type"] = "fake"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
delete(config, "iso_url")
|
|
||||||
delete(config, "iso_urls")
|
|
||||||
|
|
||||||
// Test both epty
|
|
||||||
config["iso_url"] = ""
|
|
||||||
b = Builder{}
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test iso_url set
|
|
||||||
config["iso_url"] = "http://www.packer.io"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{"http://www.packer.io"}
|
|
||||||
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
|
|
||||||
t.Fatalf("bad: %#v", b.config.ISOUrls)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both set
|
|
||||||
config["iso_url"] = "http://www.packer.io"
|
|
||||||
config["iso_urls"] = []string{"http://www.packer.io"}
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test just iso_urls set
|
|
||||||
delete(config, "iso_url")
|
|
||||||
config["iso_urls"] = []string{
|
|
||||||
"http://www.packer.io",
|
|
||||||
"http://www.hashicorp.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected = []string{
|
|
||||||
"http://www.packer.io",
|
|
||||||
"http://www.hashicorp.com",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
|
|
||||||
t.Fatalf("bad: %#v", b.config.ISOUrls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_OutputDir(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with existing dir
|
|
||||||
dir, err := ioutil.TempDir("", "packer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
config["output_directory"] = dir
|
|
||||||
b = Builder{}
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good one
|
|
||||||
config["output_directory"] = "i-hope-i-dont-exist"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ShutdownCommand(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
delete(config, "shutdown_command")
|
|
||||||
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("bad: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(warns) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with a bad value
|
|
||||||
config["shutdown_timeout"] = "this is not good"
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good one
|
|
||||||
config["shutdown_timeout"] = "5s"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_SSHHostPort(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Bad
|
|
||||||
config["ssh_host_port_min"] = 1000
|
|
||||||
config["ssh_host_port_max"] = 500
|
|
||||||
b = Builder{}
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bad
|
|
||||||
config["ssh_host_port_min"] = -500
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good
|
|
||||||
config["ssh_host_port_min"] = 500
|
|
||||||
config["ssh_host_port_max"] = 1000
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
config["ssh_key_path"] = ""
|
|
||||||
b = Builder{}
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["ssh_key_path"] = "/i/dont/exist"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
config["ssh_key_path"] = tf.Name()
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test good contents
|
|
||||||
tf.Seek(0, 0)
|
|
||||||
tf.Truncate(0)
|
|
||||||
tf.Write([]byte(testPem))
|
|
||||||
config["ssh_key_path"] = tf.Name()
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_SSHUser(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
config["ssh_username"] = ""
|
|
||||||
b = Builder{}
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
config["ssh_username"] = "exists"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test a default boot_wait
|
|
||||||
delete(config, "ssh_wait_timeout")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RawSSHWaitTimeout != "20m" {
|
|
||||||
t.Fatalf("bad value: %s", b.config.RawSSHWaitTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a bad value
|
|
||||||
config["ssh_wait_timeout"] = "this is not good"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good one
|
|
||||||
config["ssh_wait_timeout"] = "5s"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_VBoxManage(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test with empty
|
|
||||||
delete(config, "vboxmanage")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(b.config.VBoxManage, [][]string{}) {
|
|
||||||
t.Fatalf("bad: %#v", b.config.VBoxManage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good one
|
|
||||||
config["vboxmanage"] = [][]interface{}{
|
|
||||||
[]interface{}{"foo", "bar", "baz"},
|
|
||||||
}
|
|
||||||
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := [][]string{
|
|
||||||
[]string{"foo", "bar", "baz"},
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(b.config.VBoxManage, expected) {
|
|
||||||
t.Fatalf("bad: %#v", b.config.VBoxManage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuilderPrepare_VBoxVersionFile(t *testing.T) {
|
|
||||||
var b Builder
|
|
||||||
config := testConfig()
|
|
||||||
|
|
||||||
// Test empty
|
|
||||||
delete(config, "virtualbox_version_file")
|
|
||||||
warns, err := b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VBoxVersionFile != ".vbox_version" {
|
|
||||||
t.Fatalf("bad value: %s", b.config.VBoxVersionFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with a good one
|
|
||||||
config["virtualbox_version_file"] = "foo"
|
|
||||||
b = Builder{}
|
|
||||||
warns, err = b.Prepare(config)
|
|
||||||
if len(warns) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", warns)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("should not have error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VBoxVersionFile != "foo" {
|
|
||||||
t.Fatalf("bad value: %s", b.config.VBoxVersionFile)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArtifact_impl(t *testing.T) {
|
||||||
|
var _ packer.Artifact = new(artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewArtifact(t *testing.T) {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := NewArtifact(td)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.BuilderId() != BuilderId {
|
||||||
|
t.Fatalf("bad: %#v", a.BuilderId())
|
||||||
|
}
|
||||||
|
if len(a.Files()) != 1 {
|
||||||
|
t.Fatalf("should length 1: %d", len(a.Files()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfigTemplate(t *testing.T) *packer.ConfigTemplate {
|
||||||
|
result, err := packer.NewConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -10,38 +10,6 @@ import (
|
||||||
"time"
|
"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 {
|
type VBox42Driver struct {
|
||||||
// This is the path to the "VBoxManage" application.
|
// This is the path to the "VBoxManage" application.
|
||||||
VBoxManagePath string
|
VBoxManagePath string
|
||||||
|
@ -68,6 +36,20 @@ func (d *VBox42Driver) CreateSATAController(vmName string, name string) error {
|
||||||
return d.VBoxManage(command...)
|
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) {
|
func (d *VBox42Driver) IsRunning(name string) (bool, error) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVBox42Driver_impl(t *testing.T) {
|
||||||
|
var _ Driver = new(VBox42Driver)
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FloppyConfig is configuration related to created floppy disks and attaching
|
||||||
|
// them to a VirtualBox machine.
|
||||||
|
type FloppyConfig struct {
|
||||||
|
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FloppyConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.FloppyFiles == nil {
|
||||||
|
c.FloppyFiles = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for i, file := range c.FloppyFiles {
|
||||||
|
var err error
|
||||||
|
c.FloppyFiles[i], err = t.Process(file, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"Error processing floppy_files[%d]: %s", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloppyConfigPrepare(t *testing.T) {
|
||||||
|
c := new(FloppyConfig)
|
||||||
|
|
||||||
|
errs := c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.FloppyFiles) > 0 {
|
||||||
|
t.Fatal("should not have floppy files")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutputConfig struct {
|
||||||
|
OutputDir string `mapstructure:"output_directory"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OutputConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig) []error {
|
||||||
|
if c.OutputDir == "" {
|
||||||
|
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"output_directory": &c.OutputDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pc.PackerForce {
|
||||||
|
if _, err := os.Stat(c.OutputDir); err == nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"Output directory '%s' already exists. It must not exist.", c.OutputDir))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOutputConfigPrepare(t *testing.T) {
|
||||||
|
c := new(OutputConfig)
|
||||||
|
if c.OutputDir != "" {
|
||||||
|
t.Fatalf("what: %s", c.OutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := &common.PackerConfig{PackerBuildName: "foo"}
|
||||||
|
errs := c.Prepare(testConfigTemplate(t), pc)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OutputDir == "" {
|
||||||
|
t.Fatal("should have output dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputConfigPrepare_exists(t *testing.T) {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
c := new(OutputConfig)
|
||||||
|
c.OutputDir = td
|
||||||
|
|
||||||
|
pc := &common.PackerConfig{
|
||||||
|
PackerBuildName: "foo",
|
||||||
|
PackerForce: false,
|
||||||
|
}
|
||||||
|
errs := c.Prepare(testConfigTemplate(t), pc)
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatal("should have errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputConfigPrepare_forceExists(t *testing.T) {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
c := new(OutputConfig)
|
||||||
|
c.OutputDir = td
|
||||||
|
|
||||||
|
pc := &common.PackerConfig{
|
||||||
|
PackerBuildName: "foo",
|
||||||
|
PackerForce: true,
|
||||||
|
}
|
||||||
|
errs := c.Prepare(testConfigTemplate(t), pc)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatal("should not have errors")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunConfig struct {
|
||||||
|
Headless bool `mapstructure:"headless"`
|
||||||
|
RawBootWait string `mapstructure:"boot_wait"`
|
||||||
|
|
||||||
|
BootWait time.Duration ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.RawBootWait == "" {
|
||||||
|
c.RawBootWait = "10s"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"boot_wait": &c.RawBootWait,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c.BootWait, err = time.ParseDuration(c.RawBootWait)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_BootWait(t *testing.T) {
|
||||||
|
var c *RunConfig
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Test a default boot_wait
|
||||||
|
c = new(RunConfig)
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %s", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.RawBootWait != "10s" {
|
||||||
|
t.Fatalf("bad value: %s", c.RawBootWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a bad boot_wait
|
||||||
|
c = new(RunConfig)
|
||||||
|
c.RawBootWait = "this is not good"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Fatalf("bad: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good one
|
||||||
|
c = new(RunConfig)
|
||||||
|
c.RawBootWait = "5s"
|
||||||
|
errs = c.Prepare(testConfigTemplate(t))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("should not have error: %s", errs)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShutdownConfig struct {
|
||||||
|
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||||
|
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||||
|
|
||||||
|
ShutdownTimeout time.Duration ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||||
|
if c.RawShutdownTimeout == "" {
|
||||||
|
c.RawShutdownTimeout = "5m"
|
||||||
|
}
|
||||||
|
|
||||||
|
templates := map[string]*string{
|
||||||
|
"shutdown_command": &c.ShutdownCommand,
|
||||||
|
"shutdown_timeout": &c.RawShutdownTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = t.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
gossh "code.google.com/p/go.crypto/ssh"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/communicator/ssh"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SSHAddress(state multistep.StateBag) (string, error) {
|
||||||
|
sshHostPort := state.Get("sshHostPort").(uint)
|
||||||
|
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SSHKeyPath != "" {
|
||||||
|
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = append(auth, gossh.ClientAuthKeyring(keyring))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gossh.ClientConfig{
|
||||||
|
User: config.SSHUser,
|
||||||
|
Auth: auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
keyBytes, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyring := new(ssh.SimpleKeychain)
|
||||||
|
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyring, nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
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-----
|
||||||
|
`
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -14,13 +14,16 @@ import (
|
||||||
// This step attaches the ISO to the virtual machine.
|
// This step attaches the ISO to the virtual machine.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
type stepAttachFloppy struct {
|
type StepAttachFloppy struct {
|
||||||
floppyPath string
|
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
|
// Determine if we even have a floppy disk to attach
|
||||||
var floppyPath string
|
var floppyPath string
|
||||||
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
|
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
|
||||||
|
@ -76,7 +79,7 @@ func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepAttachFloppy) Cleanup(state multistep.StateBag) {
|
func (s *StepAttachFloppy) Cleanup(state multistep.StateBag) {
|
||||||
if s.floppyPath == "" {
|
if s.floppyPath == "" {
|
||||||
return
|
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")
|
tempdir, err := ioutil.TempDir("", "packer")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
|
@ -0,0 +1,73 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -15,10 +15,12 @@ import (
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// exportPath string - The path to the resulting export.
|
// 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 {
|
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
@ -41,7 +43,7 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export the VM to an OVF
|
// Export the VM to an OVF
|
||||||
outputPath := filepath.Join(config.OutputDir, vmName+"."+config.Format)
|
outputPath := filepath.Join(s.OutputDir, vmName+"."+s.Format)
|
||||||
|
|
||||||
command = []string{
|
command = []string{
|
||||||
"export",
|
"export",
|
||||||
|
@ -64,4 +66,4 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepExport) Cleanup(state multistep.StateBag) {}
|
func (s *StepExport) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,43 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -13,30 +13,37 @@ import (
|
||||||
// on the guest machine.
|
// on the guest machine.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
type stepForwardSSH struct{}
|
type StepForwardSSH struct {
|
||||||
|
GuestPort uint
|
||||||
|
HostPortMin uint
|
||||||
|
HostPortMax uint
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
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 sshHostPort uint
|
||||||
var offset uint = 0
|
var offset uint = 0
|
||||||
|
|
||||||
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
|
portRange := int(s.HostPortMax - s.HostPortMin)
|
||||||
if portRange > 0 {
|
if portRange > 0 {
|
||||||
// Have to check if > 0 to avoid a panic
|
// Have to check if > 0 to avoid a panic
|
||||||
offset = uint(rand.Intn(portRange))
|
offset = uint(rand.Intn(portRange))
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
sshHostPort = offset + config.SSHHostPortMin
|
sshHostPort = offset + s.HostPortMin
|
||||||
log.Printf("Trying port: %d", sshHostPort)
|
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 {
|
if err == nil {
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
break
|
break
|
||||||
|
@ -48,7 +55,7 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
command := []string{
|
command := []string{
|
||||||
"modifyvm", vmName,
|
"modifyvm", vmName,
|
||||||
"--natpf1",
|
"--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 {
|
if err := driver.VBoxManage(command...); err != nil {
|
||||||
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
|
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
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepForwardSSH) Cleanup(state multistep.StateBag) {}
|
func (s *StepForwardSSH) Cleanup(state multistep.StateBag) {}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,25 +11,30 @@ import (
|
||||||
"github.com/mitchellh/packer/packer"
|
"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 {
|
func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
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...")
|
ui.Say("Deleting previous output directory...")
|
||||||
os.RemoveAll(config.OutputDir)
|
os.RemoveAll(s.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the directory
|
// 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)
|
state.Put("error", err)
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we can write in the directory
|
// 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 {
|
if err != nil {
|
||||||
err = fmt.Errorf("Couldn't write to output directory: %s", err)
|
err = fmt.Errorf("Couldn't write to output directory: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -41,17 +46,16 @@ func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
|
func (s *StepOutputDir) Cleanup(state multistep.StateBag) {
|
||||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||||
_, halted := state.GetOk(multistep.StateHalted)
|
_, halted := state.GetOk(multistep.StateHalted)
|
||||||
|
|
||||||
if cancelled || halted {
|
if cancelled || halted {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Deleting output directory...")
|
ui.Say("Deleting output directory...")
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
err := os.RemoveAll(config.OutputDir)
|
err := os.RemoveAll(s.Path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testStepOutputDir(t *testing.T) *StepOutputDir {
|
||||||
|
td, err := ioutil.TempDir("", "packer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(td); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StepOutputDir{Force: false, Path: td}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepOutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := testStepOutputDir(t)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir_cancelled(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := testStepOutputDir(t)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark
|
||||||
|
state.Put(multistep.StateCancelled, true)
|
||||||
|
|
||||||
|
// Test the cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
if _, err := os.Stat(step.Path); err == nil {
|
||||||
|
t.Fatal("should not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepOutputDir_halted(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := testStepOutputDir(t)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(step.Path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark
|
||||||
|
state.Put(multistep.StateHalted, true)
|
||||||
|
|
||||||
|
// Test the cleanup
|
||||||
|
step.Cleanup(state)
|
||||||
|
if _, err := os.Stat(step.Path); err == nil {
|
||||||
|
t.Fatal("should not exist")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -10,11 +10,14 @@ import (
|
||||||
// machine that we may have added.
|
// machine that we may have added.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
//
|
//
|
||||||
// Produces:
|
// 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)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
@ -37,23 +40,25 @@ func (s *stepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command := []string{
|
if _, ok := state.GetOk("attachedIso"); ok {
|
||||||
"storageattach", vmName,
|
command := []string{
|
||||||
"--storagectl", "IDE Controller",
|
"storageattach", vmName,
|
||||||
"--port", "0",
|
"--storagectl", "IDE Controller",
|
||||||
"--device", "1",
|
"--port", "0",
|
||||||
"--medium", "none",
|
"--device", "1",
|
||||||
}
|
"--medium", "none",
|
||||||
|
}
|
||||||
|
|
||||||
if err := driver.VBoxManage(command...); err != nil {
|
if err := driver.VBoxManage(command...); err != nil {
|
||||||
err := fmt.Errorf("Error detaching ISO: %s", err)
|
err := fmt.Errorf("Error detaching ISO: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRemoveDevices) Cleanup(state multistep.StateBag) {
|
func (s *StepRemoveDevices) Cleanup(state multistep.StateBag) {
|
||||||
}
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -10,21 +10,26 @@ import (
|
||||||
// This step starts the virtual machine.
|
// This step starts the virtual machine.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
type stepRun struct {
|
type StepRun struct {
|
||||||
|
BootWait time.Duration
|
||||||
|
Headless bool
|
||||||
|
|
||||||
vmName string
|
vmName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
ui.Say("Starting the virtual machine...")
|
ui.Say("Starting the virtual machine...")
|
||||||
guiArgument := "gui"
|
guiArgument := "gui"
|
||||||
if config.Headless == true {
|
if s.Headless == true {
|
||||||
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
|
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" +
|
"In headless mode, errors during the boot sequence or OS setup\n" +
|
||||||
"won't be easily visible. Use at your own discretion.")
|
"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
|
s.vmName = vmName
|
||||||
|
|
||||||
if int64(config.bootWait) > 0 {
|
if int64(s.BootWait) > 0 {
|
||||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
|
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait))
|
||||||
wait := time.After(config.bootWait)
|
wait := time.After(s.BootWait)
|
||||||
WAITLOOP:
|
WAITLOOP:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -59,7 +64,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRun) Cleanup(state multistep.StateBag) {
|
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||||
if s.vmName == "" {
|
if s.vmName == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -14,26 +14,27 @@ import (
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
// communicator packer.Communicator
|
// communicator packer.Communicator
|
||||||
// config *config
|
|
||||||
// driver Driver
|
// driver Driver
|
||||||
// ui packer.Ui
|
// ui packer.Ui
|
||||||
// vmName string
|
// vmName string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <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)
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
if config.ShutdownCommand != "" {
|
if s.Command != "" {
|
||||||
ui.Say("Gracefully halting virtual machine...")
|
ui.Say("Gracefully halting virtual machine...")
|
||||||
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
|
log.Printf("Executing shutdown command: %s", s.Command)
|
||||||
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
|
cmd := &packer.RemoteCmd{Command: s.Command}
|
||||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||||
err := fmt.Errorf("Failed to send shutdown command: %s", err)
|
err := fmt.Errorf("Failed to send shutdown command: %s", err)
|
||||||
state.Put("error", 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
|
// Wait for the machine to actually shut down
|
||||||
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
|
log.Printf("Waiting max %s for shutdown to complete", s.Timeout)
|
||||||
shutdownTimer := time.After(config.shutdownTimeout)
|
shutdownTimer := time.After(s.Timeout)
|
||||||
for {
|
for {
|
||||||
running, _ := driver.IsRunning(vmName)
|
running, _ := driver.IsRunning(vmName)
|
||||||
if !running {
|
if !running {
|
||||||
|
@ -57,7 +58,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
default:
|
default:
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,4 +75,4 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}
|
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepShutdown_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepShutdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepShutdown_noShutdownCommand(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepShutdown)
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that Stop was just called
|
||||||
|
if driver.StopName != "foo" {
|
||||||
|
t.Fatal("should call stop")
|
||||||
|
}
|
||||||
|
if comm.StartCalled {
|
||||||
|
t.Fatal("comm start should not be called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepShutdown_shutdownCommand(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepShutdown)
|
||||||
|
step.Command = "poweroff"
|
||||||
|
step.Timeout = 1 * time.Second
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.IsRunningReturn = true
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
driver.Lock()
|
||||||
|
defer driver.Unlock()
|
||||||
|
driver.IsRunningReturn = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that Stop was just called
|
||||||
|
if driver.StopName != "" {
|
||||||
|
t.Fatal("should not call stop")
|
||||||
|
}
|
||||||
|
if comm.StartCmd.Command != step.Command {
|
||||||
|
t.Fatal("comm start should be called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepShutdown_shutdownTimeout(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepShutdown)
|
||||||
|
step.Command = "poweroff"
|
||||||
|
step.Timeout = 1 * time.Second
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
state.Put("vmName", "foo")
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.IsRunningReturn = true
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
driver.Lock()
|
||||||
|
defer driver.Unlock()
|
||||||
|
driver.IsRunningReturn = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionHalt {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); !ok {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -9,9 +9,9 @@ import (
|
||||||
|
|
||||||
// This step sets some variables in VirtualBox so that annoying
|
// This step sets some variables in VirtualBox so that annoying
|
||||||
// pop-up messages don't exist.
|
// 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)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
@ -26,4 +26,4 @@ func (stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stepSuppressMessages) Cleanup(multistep.StateBag) {}
|
func (StepSuppressMessages) Cleanup(multistep.StateBag) {}
|
|
@ -0,0 +1,50 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testState(t *testing.T) multistep.StateBag {
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("driver", new(DriverMock))
|
||||||
|
state.Put("ui", &packer.BasicUi{
|
||||||
|
Reader: new(bytes.Buffer),
|
||||||
|
Writer: new(bytes.Buffer),
|
||||||
|
})
|
||||||
|
return state
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -10,15 +10,16 @@ import (
|
||||||
|
|
||||||
// This step uploads a file containing the VirtualBox version, which
|
// This step uploads a file containing the VirtualBox version, which
|
||||||
// can be useful for various provisioning reasons.
|
// 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)
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
if config.VBoxVersionFile == "" {
|
if s.Path == "" {
|
||||||
log.Println("VBoxVersionFile is empty. Not uploading.")
|
log.Println("VBoxVersionFile is empty. Not uploading.")
|
||||||
return multistep.ActionContinue
|
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))
|
ui.Say(fmt.Sprintf("Uploading VirtualBox version info (%s)", version))
|
||||||
var data bytes.Buffer
|
var data bytes.Buffer
|
||||||
data.WriteString(version)
|
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))
|
state.Put("error", fmt.Errorf("Error uploading VirtualBox version: %s", err))
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
@ -40,4 +41,4 @@ func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepUploadVersion) Cleanup(state multistep.StateBag) {}
|
func (s *StepUploadVersion) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStepUploadVersion_impl(t *testing.T) {
|
||||||
|
var _ multistep.Step = new(StepUploadVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepUploadVersion(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepUploadVersion)
|
||||||
|
step.Path = "foopath"
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
|
||||||
|
driver := state.Get("driver").(*DriverMock)
|
||||||
|
driver.VersionResult = "foo"
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if comm.UploadPath != "foopath" {
|
||||||
|
t.Fatalf("bad: %#v", comm.UploadPath)
|
||||||
|
}
|
||||||
|
if comm.UploadData != "foo" {
|
||||||
|
t.Fatalf("upload data bad: %#v", comm.UploadData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepUploadVersion_noPath(t *testing.T) {
|
||||||
|
state := testState(t)
|
||||||
|
step := new(StepUploadVersion)
|
||||||
|
step.Path = ""
|
||||||
|
|
||||||
|
comm := new(packer.MockCommunicator)
|
||||||
|
state.Put("communicator", comm)
|
||||||
|
|
||||||
|
// Test the run
|
||||||
|
if action := step.Run(state); action != multistep.ActionContinue {
|
||||||
|
t.Fatalf("bad action: %#v", action)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetOk("error"); ok {
|
||||||
|
t.Fatal("should NOT have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if comm.UploadCalled {
|
||||||
|
t.Fatal("bad")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -15,17 +15,22 @@ type commandTemplate struct {
|
||||||
// template.
|
// template.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
|
// driver Driver
|
||||||
|
// ui packer.Ui
|
||||||
|
// vmName string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
type stepVBoxManage struct{}
|
type StepVBoxManage struct {
|
||||||
|
Commands [][]string
|
||||||
|
Tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
if len(config.VBoxManage) > 0 {
|
if len(s.Commands) > 0 {
|
||||||
ui.Say("Executing custom VBoxManage commands...")
|
ui.Say("Executing custom VBoxManage commands...")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,13 +38,13 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
Name: vmName,
|
Name: vmName,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, originalCommand := range config.VBoxManage {
|
for _, originalCommand := range s.Commands {
|
||||||
command := make([]string, len(originalCommand))
|
command := make([]string, len(originalCommand))
|
||||||
copy(command, originalCommand)
|
copy(command, originalCommand)
|
||||||
|
|
||||||
for i, arg := range command {
|
for i, arg := range command {
|
||||||
var err error
|
var err error
|
||||||
command[i], err = config.tpl.Process(arg, tplData)
|
command[i], err = s.Tpl.Process(arg, tplData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error preparing vboxmanage command: %s", err)
|
err := fmt.Errorf("Error preparing vboxmanage command: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -60,4 +65,4 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepVBoxManage) Cleanup(state multistep.StateBag) {}
|
func (s *StepVBoxManage) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,31 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,358 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BuilderId = "mitchellh.virtualbox"
|
||||||
|
|
||||||
|
// These are the different valid mode values for "guest_additions_mode" which
|
||||||
|
// determine how guest additions are delivered to the guest.
|
||||||
|
const (
|
||||||
|
GuestAdditionsModeDisable string = "disable"
|
||||||
|
GuestAdditionsModeAttach = "attach"
|
||||||
|
GuestAdditionsModeUpload = "upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.ExportConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.FloppyConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.OutputConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.RunConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.ShutdownConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.SSHConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.VBoxManageConfig `mapstructure:",squash"`
|
||||||
|
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
|
BootCommand []string `mapstructure:"boot_command"`
|
||||||
|
DiskSize uint `mapstructure:"disk_size"`
|
||||||
|
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
|
||||||
|
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||||
|
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||||
|
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||||
|
GuestOSType string `mapstructure:"guest_os_type"`
|
||||||
|
HardDriveInterface string `mapstructure:"hard_drive_interface"`
|
||||||
|
HTTPDir string `mapstructure:"http_directory"`
|
||||||
|
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||||
|
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||||
|
ISOChecksum string `mapstructure:"iso_checksum"`
|
||||||
|
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
||||||
|
ISOUrls []string `mapstructure:"iso_urls"`
|
||||||
|
VMName string `mapstructure:"vm_name"`
|
||||||
|
|
||||||
|
RawSingleISOUrl string `mapstructure:"iso_url"`
|
||||||
|
|
||||||
|
tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
md, err := common.DecodeConfig(&b.config, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.config.tpl, err = packer.NewConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||||
|
|
||||||
|
// Accumulate any errors and warnings
|
||||||
|
errs := common.CheckUnusedConfig(md)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.ExportConfig.Prepare(b.config.tpl)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(b.config.tpl)...)
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.VBoxManageConfig.Prepare(b.config.tpl)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.VBoxVersionConfig.Prepare(b.config.tpl)...)
|
||||||
|
warnings := make([]string, 0)
|
||||||
|
|
||||||
|
if b.config.DiskSize == 0 {
|
||||||
|
b.config.DiskSize = 40000
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsMode == "" {
|
||||||
|
b.config.GuestAdditionsMode = "upload"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsPath == "" {
|
||||||
|
b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HardDriveInterface == "" {
|
||||||
|
b.config.HardDriveInterface = "ide"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestOSType == "" {
|
||||||
|
b.config.GuestOSType = "Other"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HTTPPortMin == 0 {
|
||||||
|
b.config.HTTPPortMin = 8000
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HTTPPortMax == 0 {
|
||||||
|
b.config.HTTPPortMax = 9000
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.VMName == "" {
|
||||||
|
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
templates := map[string]*string{
|
||||||
|
"guest_additions_mode": &b.config.GuestAdditionsMode,
|
||||||
|
"guest_additions_sha256": &b.config.GuestAdditionsSHA256,
|
||||||
|
"guest_os_type": &b.config.GuestOSType,
|
||||||
|
"hard_drive_interface": &b.config.HardDriveInterface,
|
||||||
|
"http_directory": &b.config.HTTPDir,
|
||||||
|
"iso_checksum": &b.config.ISOChecksum,
|
||||||
|
"iso_checksum_type": &b.config.ISOChecksumType,
|
||||||
|
"iso_url": &b.config.RawSingleISOUrl,
|
||||||
|
"vm_name": &b.config.VMName,
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, ptr := range templates {
|
||||||
|
var err error
|
||||||
|
*ptr, err = b.config.tpl.Process(*ptr, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, url := range b.config.ISOUrls {
|
||||||
|
var err error
|
||||||
|
b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validates := map[string]*string{
|
||||||
|
"guest_additions_path": &b.config.GuestAdditionsPath,
|
||||||
|
"guest_additions_url": &b.config.GuestAdditionsURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, ptr := range validates {
|
||||||
|
if err := b.config.tpl.Validate(*ptr); err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, command := range b.config.BootCommand {
|
||||||
|
if err := b.config.tpl.Validate(command); err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(errs,
|
||||||
|
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("hard_drive_interface can only be ide or sata"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ISOChecksum == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||||
|
} else {
|
||||||
|
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ISOChecksumType == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("The iso_checksum_type must be specified."))
|
||||||
|
} else {
|
||||||
|
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
||||||
|
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs,
|
||||||
|
fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("One of iso_url or iso_urls must be specified."))
|
||||||
|
} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
|
||||||
|
} else if b.config.RawSingleISOUrl != "" {
|
||||||
|
b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, url := range b.config.ISOUrls {
|
||||||
|
b.config.ISOUrls[i], err = common.DownloadableURL(url)
|
||||||
|
if err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validMode := false
|
||||||
|
validModes := []string{
|
||||||
|
GuestAdditionsModeDisable,
|
||||||
|
GuestAdditionsModeAttach,
|
||||||
|
GuestAdditionsModeUpload,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mode := range validModes {
|
||||||
|
if b.config.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 b.config.GuestAdditionsSHA256 != "" {
|
||||||
|
b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnings
|
||||||
|
if b.config.ShutdownCommand == "" {
|
||||||
|
warnings = append(warnings,
|
||||||
|
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
|
||||||
|
"will forcibly halt the virtual machine, which may result in data loss.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
// Create the driver that we'll use to communicate with VirtualBox
|
||||||
|
driver, err := vboxcommon.NewDriver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []multistep.Step{
|
||||||
|
new(stepDownloadGuestAdditions),
|
||||||
|
&common.StepDownload{
|
||||||
|
Checksum: b.config.ISOChecksum,
|
||||||
|
ChecksumType: b.config.ISOChecksumType,
|
||||||
|
Description: "ISO",
|
||||||
|
ResultKey: "iso_path",
|
||||||
|
Url: b.config.ISOUrls,
|
||||||
|
},
|
||||||
|
&vboxcommon.StepOutputDir{
|
||||||
|
Force: b.config.PackerForce,
|
||||||
|
Path: b.config.OutputDir,
|
||||||
|
},
|
||||||
|
&common.StepCreateFloppy{
|
||||||
|
Files: b.config.FloppyFiles,
|
||||||
|
},
|
||||||
|
new(stepHTTPServer),
|
||||||
|
new(vboxcommon.StepSuppressMessages),
|
||||||
|
new(stepCreateVM),
|
||||||
|
new(stepCreateDisk),
|
||||||
|
new(stepAttachISO),
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
new(stepTypeBootCommand),
|
||||||
|
&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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the state bag
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("cache", cache)
|
||||||
|
state.Put("config", &b.config)
|
||||||
|
state.Put("driver", driver)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
// Run
|
||||||
|
if b.config.PackerDebug {
|
||||||
|
b.runner = &multistep.DebugRunner{
|
||||||
|
Steps: steps,
|
||||||
|
PauseFn: common.MultistepDebugFn(ui),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were interrupted or cancelled, then just exit.
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return nil, errors.New("Build was cancelled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
||||||
|
return nil, errors.New("Build was halted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return vboxcommon.NewArtifact(b.config.OutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,456 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"iso_checksum": "foo",
|
||||||
|
"iso_checksum_type": "md5",
|
||||||
|
"iso_url": "http://www.google.com/",
|
||||||
|
"shutdown_command": "yes",
|
||||||
|
"ssh_username": "foo",
|
||||||
|
|
||||||
|
packer.BuildNameConfigKey: "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Builder{}
|
||||||
|
if _, ok := raw.(packer.Builder); !ok {
|
||||||
|
t.Error("Builder must implement builder.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsMode != GuestAdditionsModeUpload {
|
||||||
|
t.Errorf("bad guest additions mode: %s", b.config.GuestAdditionsMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestOSType != "Other" {
|
||||||
|
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.VMName != "packer-foo" {
|
||||||
|
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.Format != "ovf" {
|
||||||
|
t.Errorf("bad format: %s", b.config.Format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "disk_size")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.DiskSize != 40000 {
|
||||||
|
t.Fatalf("bad size: %d", b.config.DiskSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["disk_size"] = 60000
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.DiskSize != 60000 {
|
||||||
|
t.Fatalf("bad size: %s", b.config.DiskSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_GuestAdditionsMode(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// test default mode
|
||||||
|
delete(config, "guest_additions_mode")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test another mode
|
||||||
|
config["guest_additions_mode"] = "attach"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsMode != GuestAdditionsModeAttach {
|
||||||
|
t.Fatalf("bad: %s", b.config.GuestAdditionsMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad mode
|
||||||
|
config["guest_additions_mode"] = "teleport"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "guest_additions_path")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsPath != "VBoxGuestAdditions.iso" {
|
||||||
|
t.Fatalf("bad: %s", b.config.GuestAdditionsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["guest_additions_path"] = "foo"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsPath != "foo" {
|
||||||
|
t.Fatalf("bad size: %s", b.config.GuestAdditionsPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_GuestAdditionsSHA256(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "guest_additions_sha256")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsSHA256 != "" {
|
||||||
|
t.Fatalf("bad: %s", b.config.GuestAdditionsSHA256)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["guest_additions_sha256"] = "FOO"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsSHA256 != "foo" {
|
||||||
|
t.Fatalf("bad size: %s", b.config.GuestAdditionsSHA256)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["guest_additions_url"] = ""
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.GuestAdditionsURL != "" {
|
||||||
|
t.Fatalf("should be empty: %s", b.config.GuestAdditionsURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["guest_additions_url"] = "http://www.packer.io"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_HardDriveInterface(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test a default boot_wait
|
||||||
|
delete(config, "hard_drive_interface")
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.HardDriveInterface != "ide" {
|
||||||
|
t.Fatalf("bad: %s", b.config.HardDriveInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a bad
|
||||||
|
config["hard_drive_interface"] = "fake"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with a good
|
||||||
|
config["hard_drive_interface"] = "sata"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Bad
|
||||||
|
config["http_port_min"] = 1000
|
||||||
|
config["http_port_max"] = 500
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad
|
||||||
|
config["http_port_min"] = -500
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good
|
||||||
|
config["http_port_min"] = 500
|
||||||
|
config["http_port_max"] = 1000
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Add a random key
|
||||||
|
config["i_should_not_be_valid"] = true
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["iso_checksum"] = ""
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test good
|
||||||
|
config["iso_checksum"] = "FOo"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ISOChecksum != "foo" {
|
||||||
|
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["iso_checksum_type"] = ""
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test good
|
||||||
|
config["iso_checksum_type"] = "mD5"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ISOChecksumType != "md5" {
|
||||||
|
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test unknown
|
||||||
|
config["iso_checksum_type"] = "fake"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
delete(config, "iso_url")
|
||||||
|
delete(config, "iso_urls")
|
||||||
|
|
||||||
|
// Test both epty
|
||||||
|
config["iso_url"] = ""
|
||||||
|
b = Builder{}
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test iso_url set
|
||||||
|
config["iso_url"] = "http://www.packer.io"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"http://www.packer.io"}
|
||||||
|
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.ISOUrls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test both set
|
||||||
|
config["iso_url"] = "http://www.packer.io"
|
||||||
|
config["iso_urls"] = []string{"http://www.packer.io"}
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test just iso_urls set
|
||||||
|
delete(config, "iso_url")
|
||||||
|
config["iso_urls"] = []string{
|
||||||
|
"http://www.packer.io",
|
||||||
|
"http://www.hashicorp.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = []string{
|
||||||
|
"http://www.packer.io",
|
||||||
|
"http://www.hashicorp.com",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
|
||||||
|
t.Fatalf("bad: %#v", b.config.ISOUrls)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
@ -24,7 +25,7 @@ type stepAttachGuestAdditions struct {
|
||||||
|
|
||||||
func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ func (s *stepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ type stepAttachISO struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
|
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)
|
isoPath := state.Get("iso_path").(string)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
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
|
// Track the path so that we can unregister it from VirtualBox later
|
||||||
s.diskPath = isoPath
|
s.diskPath = isoPath
|
||||||
|
|
||||||
|
// Set some state so we know to remove
|
||||||
|
state.Put("attachedIso", true)
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +52,7 @@ func (s *stepAttachISO) Cleanup(state multistep.StateBag) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
||||||
command := []string{
|
command := []string{
|
|
@ -1,8 +1,9 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -15,7 +16,7 @@ type stepCreateDisk struct{}
|
||||||
|
|
||||||
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ type stepCreateVM struct {
|
||||||
|
|
||||||
func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
name := config.VMName
|
name := config.VMName
|
||||||
|
@ -60,7 +61,7 @@ func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Unregistering and deleting virtual machine...")
|
ui.Say("Unregistering and deleting virtual machine...")
|
|
@ -1,9 +1,10 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/common"
|
"github.com/mitchellh/packer/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"io"
|
"io"
|
||||||
|
@ -31,7 +32,7 @@ type stepDownloadGuestAdditions struct{}
|
||||||
|
|
||||||
func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
var action multistep.StepAction
|
var action multistep.StepAction
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,8 +1,9 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -34,7 +35,7 @@ type stepTypeBootCommand struct{}
|
||||||
|
|
||||||
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
httpPort := state.Get("http_port").(uint)
|
httpPort := state.Get("http_port").(uint)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
|
@ -1,8 +1,9 @@
|
||||||
package virtualbox
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -18,7 +19,7 @@ type stepUploadGuestAdditions struct{}
|
||||||
func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
comm := state.Get("communicator").(packer.Communicator)
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vboxcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
// If we're attaching then don't do this, since we attached.
|
// If we're attaching then don't do this, since we attached.
|
|
@ -0,0 +1,136 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
package virtualbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
gossh "code.google.com/p/go.crypto/ssh"
|
|
||||||
"fmt"
|
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"github.com/mitchellh/packer/communicator/ssh"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
auth = append(auth, gossh.ClientAuthKeyring(keyring))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gossh.ClientConfig{
|
|
||||||
User: config.SSHUser,
|
|
||||||
Auth: auth,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
keyBytes, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keyring := new(ssh.SimpleKeychain)
|
|
||||||
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyring, nil
|
|
||||||
}
|
|
|
@ -24,6 +24,7 @@ func init() {
|
||||||
"createtime": new(FixerCreateTime),
|
"createtime": new(FixerCreateTime),
|
||||||
"pp-vagrant-override": new(FixerVagrantPPOverride),
|
"pp-vagrant-override": new(FixerVagrantPPOverride),
|
||||||
"virtualbox-gaattach": new(FixerVirtualBoxGAAttach),
|
"virtualbox-gaattach": new(FixerVirtualBoxGAAttach),
|
||||||
|
"virtualbox-rename": new(FixerVirtualBoxRename),
|
||||||
}
|
}
|
||||||
|
|
||||||
FixerOrder = []string{
|
FixerOrder = []string{
|
||||||
|
@ -31,5 +32,6 @@ func init() {
|
||||||
"createtime",
|
"createtime",
|
||||||
"virtualbox-gaattach",
|
"virtualbox-gaattach",
|
||||||
"pp-vagrant-override",
|
"pp-vagrant-override",
|
||||||
|
"virtualbox-rename",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
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"`
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
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"
|
to use "guest_additions_mode"
|
||||||
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
|
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
|
||||||
post-processor to new-style as of Packer 0.5.0.
|
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",
|
"googlecompute": "packer-builder-googlecompute",
|
||||||
"openstack": "packer-builder-openstack",
|
"openstack": "packer-builder-openstack",
|
||||||
"qemu": "packer-builder-qemu",
|
"qemu": "packer-builder-qemu",
|
||||||
"virtualbox": "packer-builder-virtualbox",
|
"virtualbox-iso": "packer-builder-virtualbox-iso",
|
||||||
|
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
||||||
"vmware": "packer-builder-vmware"
|
"vmware": "packer-builder-vmware"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/builder/virtualbox"
|
"github.com/mitchellh/packer/builder/virtualbox/iso"
|
||||||
"github.com/mitchellh/packer/packer/plugin"
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,6 +10,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
server.RegisterBuilder(new(virtualbox.Builder))
|
server.RegisterBuilder(new(iso.Builder))
|
||||||
server.Serve()
|
server.Serve()
|
||||||
}
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,305 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "VirtualBox Builder (from an ISO)"
|
||||||
|
---
|
||||||
|
|
||||||
|
# VirtualBox Builder (from an ISO)
|
||||||
|
|
||||||
|
Type: `virtualbox`
|
||||||
|
|
||||||
|
The VirtualBox builder is able to create [VirtualBox](https://www.virtualbox.org/)
|
||||||
|
virtual machines and export them in the OVF format, starting from an
|
||||||
|
ISO image.
|
||||||
|
|
||||||
|
The builder builds a virtual machine by creating a new virtual machine
|
||||||
|
from scratch, booting it, installing an OS, provisioning software within
|
||||||
|
the OS, then shutting it down. The result of the VirtualBox builder is a directory
|
||||||
|
containing all the files necessary to run the virtual machine portably.
|
||||||
|
|
||||||
|
## Basic Example
|
||||||
|
|
||||||
|
Here is a basic example. This example is not functional. It will start the
|
||||||
|
OS installer but then fail because we don't provide the preseed file for
|
||||||
|
Ubuntu to self-install. Still, the example serves to show the basic configuration:
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
{
|
||||||
|
"type": "virtualbox",
|
||||||
|
"guest_os_type": "Ubuntu_64",
|
||||||
|
"iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso",
|
||||||
|
"iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
|
||||||
|
"iso_checksum_type": "md5",
|
||||||
|
"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:
|
||||||
|
|
||||||
|
* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO
|
||||||
|
files are so large, this is required and Packer will verify it prior
|
||||||
|
to booting a virtual machine with the ISO attached. The type of the
|
||||||
|
checksum is specified with `iso_checksum_type`, documented below.
|
||||||
|
|
||||||
|
* `iso_checksum_type` (string) - The type of the checksum specified in
|
||||||
|
`iso_checksum`. Valid values are "md5", "sha1", "sha256", or "sha512" currently.
|
||||||
|
|
||||||
|
* `iso_url` (string) - A URL to the ISO containing the installation image.
|
||||||
|
This URL can be either an HTTP URL or a file URL (or path to a file).
|
||||||
|
If this is an HTTP URL, Packer will download it and cache it between
|
||||||
|
runs.
|
||||||
|
|
||||||
|
* `ssh_username` (string) - The username to use to SSH into the machine
|
||||||
|
once the OS is installed.
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
* `boot_command` (array of strings) - This is an array of commands to type
|
||||||
|
when the virtual machine is first booted. The goal of these commands should
|
||||||
|
be to type just enough to initialize the operating system installer. Special
|
||||||
|
keys can be typed as well, and are covered in the section below on the boot
|
||||||
|
command. If this is not specified, it is assumed the installer will start
|
||||||
|
itself.
|
||||||
|
|
||||||
|
* `boot_wait` (string) - The time to wait after booting the initial virtual
|
||||||
|
machine before typing the `boot_command`. The value of this should be
|
||||||
|
a duration. Examples are "5s" and "1m30s" which will cause Packer to wait
|
||||||
|
five seconds and one minute 30 seconds, respectively. If this isn't specified,
|
||||||
|
the default is 10 seconds.
|
||||||
|
|
||||||
|
* `disk_size` (int) - The size, in megabytes, of the hard disk to create
|
||||||
|
for the VM. By default, this is 40000 (40 GB).
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
* `guest_os_type` (string) - The guest OS type being installed. By default
|
||||||
|
this is "other", but you can get _dramatic_ performance improvements by
|
||||||
|
setting this to the proper value. To view all available values for this
|
||||||
|
run `VBoxManage list ostypes`. Setting the correct value hints to VirtualBox
|
||||||
|
how to optimize the virtual hardware to work best with that operating
|
||||||
|
system.
|
||||||
|
|
||||||
|
* `hard_drive_interface` (string) - The type of controller that the primary
|
||||||
|
hard drive is attached to, defaults to "ide". When set to "sata", the
|
||||||
|
drive is attached to an AHCI SATA controller.
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
* `http_directory` (string) - Path to a directory to serve using an HTTP
|
||||||
|
server. The files in this directory will be available over HTTP that will
|
||||||
|
be requestable from the virtual machine. This is useful for hosting
|
||||||
|
kickstart files and so on. By default this is "", which means no HTTP
|
||||||
|
server will be started. The address and port of the HTTP server will be
|
||||||
|
available as variables in `boot_command`. This is covered in more detail
|
||||||
|
below.
|
||||||
|
|
||||||
|
* `http_port_min` and `http_port_max` (int) - These are the minimum and
|
||||||
|
maximum port to use for the HTTP server started to serve the `http_directory`.
|
||||||
|
Because Packer often runs in parallel, Packer will choose a randomly available
|
||||||
|
port in this range to run the HTTP server. If you want to force the HTTP
|
||||||
|
server to be on one port, make this minimum and maximum port the same.
|
||||||
|
By default the values are 8000 and 9000, respectively.
|
||||||
|
|
||||||
|
* `iso_urls` (array of strings) - Multiple URLs for the ISO to download.
|
||||||
|
Packer will try these in order. If anything goes wrong attempting to download
|
||||||
|
or while downloading a single URL, it will move on to the next. All URLs
|
||||||
|
must point to the same file (same checksum). By default this is empty
|
||||||
|
and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified.
|
||||||
|
|
||||||
|
* `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 OVF file for the new virtual
|
||||||
|
machine, without the file extension. By default this is "packer-BUILDNAME",
|
||||||
|
where "BUILDNAME" is the name of the build.
|
||||||
|
|
||||||
|
## Boot Command
|
||||||
|
|
||||||
|
The `boot_command` configuration is very important: it specifies the keys
|
||||||
|
to type when the virtual machine is first booted in order to start the
|
||||||
|
OS installer. This command is typed after `boot_wait`, which gives the
|
||||||
|
virtual machine some time to actually load the ISO.
|
||||||
|
|
||||||
|
As documented above, the `boot_command` is an array of strings. The
|
||||||
|
strings are all typed in sequence. It is an array only to improve readability
|
||||||
|
within the template.
|
||||||
|
|
||||||
|
The boot command is "typed" character for character over a VNC connection
|
||||||
|
to the machine, simulating a human actually typing the keyboard. There are
|
||||||
|
a set of special keys available. If these are in your boot command, they
|
||||||
|
will be replaced by the proper key:
|
||||||
|
|
||||||
|
* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress.
|
||||||
|
|
||||||
|
* `<esc>` - Simulates pressing the escape key.
|
||||||
|
|
||||||
|
* `<tab>` - Simulates pressing the tab key.
|
||||||
|
|
||||||
|
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This
|
||||||
|
is useful if you have to generally wait for the UI to update before typing more.
|
||||||
|
|
||||||
|
In addition to the special keys, each command to type is treated as a
|
||||||
|
[configuration template](/docs/templates/configuration-templates.html).
|
||||||
|
The available variables are:
|
||||||
|
|
||||||
|
* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server
|
||||||
|
that is started serving the directory specified by the `http_directory`
|
||||||
|
configuration parameter. If `http_directory` isn't specified, these will
|
||||||
|
be blank!
|
||||||
|
|
||||||
|
Example boot command. This is actually a working boot command used to start
|
||||||
|
an Ubuntu 12.04 installer:
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
[
|
||||||
|
"<esc><esc><enter><wait>",
|
||||||
|
"/install/vmlinuz noapic ",
|
||||||
|
"preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
|
||||||
|
"debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
|
||||||
|
"hostname={{ .Name }} ",
|
||||||
|
"fb=false debconf/frontend=noninteractive ",
|
||||||
|
"keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
|
||||||
|
"keyboard-configuration/variant=USA console-setup/ask_detect=false ",
|
||||||
|
"initrd=/install/initrd.gz -- <enter>"
|
||||||
|
]
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## 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.
|
|
@ -0,0 +1,193 @@
|
||||||
|
---
|
||||||
|
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.
|
|
@ -1,303 +1,25 @@
|
||||||
---
|
---
|
||||||
layout: "docs"
|
layout: "docs"
|
||||||
|
page_title: "Amazon AMI Builder"
|
||||||
---
|
---
|
||||||
|
|
||||||
# VirtualBox Builder
|
# VirtualBox Builder
|
||||||
|
|
||||||
Type: `virtualbox`
|
The VirtualBox builder is able to create [VirtualBox](http://www.virtualbox.org)
|
||||||
|
virtual machines and export them in the OVA or OVF format.
|
||||||
|
|
||||||
The VirtualBox builder is able to create [VirtualBox](https://www.virtualbox.org/)
|
Packer actually comes with multiple builders able to create VirtualBox
|
||||||
virtual machines and export them in the OVF format.
|
machines, depending on the strategy you want to use to build the image.
|
||||||
|
Packer supports the following VirtualBox builders:
|
||||||
|
|
||||||
The builder builds a virtual machine by creating a new virtual machine
|
* [virtualbox-iso](/docs/builders/virtualbox-iso.html) - Starts from
|
||||||
from scratch, booting it, installing an OS, provisioning software within
|
an ISO file, creates a brand new VirtualBox VM, installs an OS,
|
||||||
the OS, then shutting it down. The result of the VirtualBox builder is a directory
|
provisions software within the OS, then exports that machine to create
|
||||||
containing all the files necessary to run the virtual machine portably.
|
an image. This is best for people who want to start from scratch.
|
||||||
|
|
||||||
## Basic Example
|
* [virtualbox-ovf](/docs/builders/virtualbox-ovf.html) - This builder
|
||||||
|
imports an existing OVF/OVA file, runs provisioners on top of that VM,
|
||||||
Here is a basic example. This example is not functional. It will start the
|
and exports that machine to create an image. This is best if you have
|
||||||
OS installer but then fail because we don't provide the preseed file for
|
an existing VirtualBox VM export you want to use as the source. As an
|
||||||
Ubuntu to self-install. Still, the example serves to show the basic configuration:
|
additional benefit, you can feed the artifact of this builder back into
|
||||||
|
itself to iterate on a machine.
|
||||||
<pre class="prettyprint">
|
|
||||||
{
|
|
||||||
"type": "virtualbox",
|
|
||||||
"guest_os_type": "Ubuntu_64",
|
|
||||||
"iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.3-server-amd64.iso",
|
|
||||||
"iso_checksum": "2cbe868812a871242cdcdd8f2fd6feb9",
|
|
||||||
"iso_checksum_type": "md5",
|
|
||||||
"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:
|
|
||||||
|
|
||||||
* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO
|
|
||||||
files are so large, this is required and Packer will verify it prior
|
|
||||||
to booting a virtual machine with the ISO attached. The type of the
|
|
||||||
checksum is specified with `iso_checksum_type`, documented below.
|
|
||||||
|
|
||||||
* `iso_checksum_type` (string) - The type of the checksum specified in
|
|
||||||
`iso_checksum`. Valid values are "md5", "sha1", "sha256", or "sha512" currently.
|
|
||||||
|
|
||||||
* `iso_url` (string) - A URL to the ISO containing the installation image.
|
|
||||||
This URL can be either an HTTP URL or a file URL (or path to a file).
|
|
||||||
If this is an HTTP URL, Packer will download it and cache it between
|
|
||||||
runs.
|
|
||||||
|
|
||||||
* `ssh_username` (string) - The username to use to SSH into the machine
|
|
||||||
once the OS is installed.
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
|
|
||||||
* `boot_command` (array of strings) - This is an array of commands to type
|
|
||||||
when the virtual machine is first booted. The goal of these commands should
|
|
||||||
be to type just enough to initialize the operating system installer. Special
|
|
||||||
keys can be typed as well, and are covered in the section below on the boot
|
|
||||||
command. If this is not specified, it is assumed the installer will start
|
|
||||||
itself.
|
|
||||||
|
|
||||||
* `boot_wait` (string) - The time to wait after booting the initial virtual
|
|
||||||
machine before typing the `boot_command`. The value of this should be
|
|
||||||
a duration. Examples are "5s" and "1m30s" which will cause Packer to wait
|
|
||||||
five seconds and one minute 30 seconds, respectively. If this isn't specified,
|
|
||||||
the default is 10 seconds.
|
|
||||||
|
|
||||||
* `disk_size` (int) - The size, in megabytes, of the hard disk to create
|
|
||||||
for the VM. By default, this is 40000 (40 GB).
|
|
||||||
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
* `guest_os_type` (string) - The guest OS type being installed. By default
|
|
||||||
this is "other", but you can get _dramatic_ performance improvements by
|
|
||||||
setting this to the proper value. To view all available values for this
|
|
||||||
run `VBoxManage list ostypes`. Setting the correct value hints to VirtualBox
|
|
||||||
how to optimize the virtual hardware to work best with that operating
|
|
||||||
system.
|
|
||||||
|
|
||||||
* `hard_drive_interface` (string) - The type of controller that the primary
|
|
||||||
hard drive is attached to, defaults to "ide". When set to "sata", the
|
|
||||||
drive is attached to an AHCI SATA controller.
|
|
||||||
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
* `http_directory` (string) - Path to a directory to serve using an HTTP
|
|
||||||
server. The files in this directory will be available over HTTP that will
|
|
||||||
be requestable from the virtual machine. This is useful for hosting
|
|
||||||
kickstart files and so on. By default this is "", which means no HTTP
|
|
||||||
server will be started. The address and port of the HTTP server will be
|
|
||||||
available as variables in `boot_command`. This is covered in more detail
|
|
||||||
below.
|
|
||||||
|
|
||||||
* `http_port_min` and `http_port_max` (int) - These are the minimum and
|
|
||||||
maximum port to use for the HTTP server started to serve the `http_directory`.
|
|
||||||
Because Packer often runs in parallel, Packer will choose a randomly available
|
|
||||||
port in this range to run the HTTP server. If you want to force the HTTP
|
|
||||||
server to be on one port, make this minimum and maximum port the same.
|
|
||||||
By default the values are 8000 and 9000, respectively.
|
|
||||||
|
|
||||||
* `iso_urls` (array of strings) - Multiple URLs for the ISO to download.
|
|
||||||
Packer will try these in order. If anything goes wrong attempting to download
|
|
||||||
or while downloading a single URL, it will move on to the next. All URLs
|
|
||||||
must point to the same file (same checksum). By default this is empty
|
|
||||||
and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified.
|
|
||||||
|
|
||||||
* `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 OVF file for the new virtual
|
|
||||||
machine, without the file extension. By default this is "packer-BUILDNAME",
|
|
||||||
where "BUILDNAME" is the name of the build.
|
|
||||||
|
|
||||||
## Boot Command
|
|
||||||
|
|
||||||
The `boot_command` configuration is very important: it specifies the keys
|
|
||||||
to type when the virtual machine is first booted in order to start the
|
|
||||||
OS installer. This command is typed after `boot_wait`, which gives the
|
|
||||||
virtual machine some time to actually load the ISO.
|
|
||||||
|
|
||||||
As documented above, the `boot_command` is an array of strings. The
|
|
||||||
strings are all typed in sequence. It is an array only to improve readability
|
|
||||||
within the template.
|
|
||||||
|
|
||||||
The boot command is "typed" character for character over a VNC connection
|
|
||||||
to the machine, simulating a human actually typing the keyboard. There are
|
|
||||||
a set of special keys available. If these are in your boot command, they
|
|
||||||
will be replaced by the proper key:
|
|
||||||
|
|
||||||
* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress.
|
|
||||||
|
|
||||||
* `<esc>` - Simulates pressing the escape key.
|
|
||||||
|
|
||||||
* `<tab>` - Simulates pressing the tab key.
|
|
||||||
|
|
||||||
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This
|
|
||||||
is useful if you have to generally wait for the UI to update before typing more.
|
|
||||||
|
|
||||||
In addition to the special keys, each command to type is treated as a
|
|
||||||
[configuration template](/docs/templates/configuration-templates.html).
|
|
||||||
The available variables are:
|
|
||||||
|
|
||||||
* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server
|
|
||||||
that is started serving the directory specified by the `http_directory`
|
|
||||||
configuration parameter. If `http_directory` isn't specified, these will
|
|
||||||
be blank!
|
|
||||||
|
|
||||||
Example boot command. This is actually a working boot command used to start
|
|
||||||
an Ubuntu 12.04 installer:
|
|
||||||
|
|
||||||
<pre class="prettyprint">
|
|
||||||
[
|
|
||||||
"<esc><esc><enter><wait>",
|
|
||||||
"/install/vmlinuz noapic ",
|
|
||||||
"preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
|
|
||||||
"debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
|
|
||||||
"hostname={{ .Name }} ",
|
|
||||||
"fb=false debconf/frontend=noninteractive ",
|
|
||||||
"keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
|
|
||||||
"keyboard-configuration/variant=USA console-setup/ask_detect=false ",
|
|
||||||
"initrd=/install/initrd.gz -- <enter>"
|
|
||||||
]
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
Loading…
Reference in New Issue