mirror of
https://github.com/hashicorp/packer.git
synced 2026-10-23 22:52:11 -04:00
Prevalidates hardware resources on Linux platforms for Virtualbox and VMware builders. This is currently only available on Linux, as enabling for both Darwin and Windows platforms, relies on cgo bindings that would prevent effective cross-compilation. Packer will now fail to build and validate templates if the template is requesting that the VM to be created would allocate more system resources than the host system has available. This _however_ doesn't catch parallel builds that overflow the hosts resources, will probably still need a better error message for VM's failing to boot in that case. Example Outputs: ``` $ $GOPATH/bin/packer build -debug ./vmware-iso.json Debug mode enabled. Builds will not be parallelized. vmware-iso output will be in this color. 2 error(s) occurred: * Unavailable Resources: RAM - Requested - 204800000MB - Available 21721MB * Unavailable Resources: Disk - Requested - 4000000000MB - Available 76701MB ``` ``` $ $GOPATH/bin/packer build -debug ./vbox-iso.json Debug mode enabled. Builds will not be parallelized. virtualbox-iso output will be in this color. 2 error(s) occurred: * Unavailable Resources: RAM - Requested - 10240000MB - Available 21721MB * Unavailable Resources: Disk - Requested - 1000000000MB - Available 76701MB ```
312 lines
9.4 KiB
Go
312 lines
9.4 KiB
Go
package iso
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/mitchellh/multistep"
|
|
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
|
"github.com/mitchellh/packer/common"
|
|
"github.com/mitchellh/packer/helper/communicator"
|
|
"github.com/mitchellh/packer/helper/config"
|
|
"github.com/mitchellh/packer/packer"
|
|
"github.com/mitchellh/packer/template/interpolate"
|
|
)
|
|
|
|
const BuilderId = "mitchellh.virtualbox"
|
|
|
|
type Builder struct {
|
|
config Config
|
|
runner multistep.Runner
|
|
}
|
|
|
|
type Config struct {
|
|
common.PackerConfig `mapstructure:",squash"`
|
|
common.HTTPConfig `mapstructure:",squash"`
|
|
common.ISOConfig `mapstructure:",squash"`
|
|
vboxcommon.ExportConfig `mapstructure:",squash"`
|
|
vboxcommon.ExportOpts `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.VBoxManagePostConfig `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"`
|
|
ISOInterface string `mapstructure:"iso_interface"`
|
|
VMName string `mapstructure:"vm_name"`
|
|
|
|
ctx interpolate.Context
|
|
}
|
|
|
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
|
Interpolate: true,
|
|
InterpolateContext: &b.config.ctx,
|
|
InterpolateFilter: &interpolate.RenderFilter{
|
|
Exclude: []string{
|
|
"boot_command",
|
|
"guest_additions_path",
|
|
"guest_additions_url",
|
|
"vboxmanage",
|
|
"vboxmanage_post",
|
|
},
|
|
},
|
|
}, raws...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Accumulate any errors and warnings
|
|
var errs *packer.MultiError
|
|
warnings := make([]string, 0)
|
|
|
|
isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx)
|
|
warnings = append(warnings, isoWarnings...)
|
|
errs = packer.MultiErrorAppend(errs, isoErrs...)
|
|
|
|
errs = packer.MultiErrorAppend(errs, b.config.ExportConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.ExportOpts.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(
|
|
errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.VBoxManageConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.VBoxManagePostConfig.Prepare(&b.config.ctx)...)
|
|
errs = packer.MultiErrorAppend(errs, b.config.VBoxVersionConfig.Prepare(&b.config.ctx)...)
|
|
|
|
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.ISOInterface == "" {
|
|
b.config.ISOInterface = "ide"
|
|
}
|
|
|
|
if b.config.VMName == "" {
|
|
b.config.VMName = fmt.Sprintf(
|
|
"packer-%s-%d", b.config.PackerBuildName, interpolate.InitTime.Unix())
|
|
}
|
|
|
|
if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" && b.config.HardDriveInterface != "scsi" {
|
|
errs = packer.MultiErrorAppend(
|
|
errs, errors.New("hard_drive_interface can only be ide, sata, or scsi"))
|
|
}
|
|
|
|
if b.config.ISOInterface != "ide" && b.config.ISOInterface != "sata" {
|
|
errs = packer.MultiErrorAppend(
|
|
errs, errors.New("iso_interface can only be ide or sata"))
|
|
}
|
|
|
|
validMode := false
|
|
validModes := []string{
|
|
vboxcommon.GuestAdditionsModeDisable,
|
|
vboxcommon.GuestAdditionsModeAttach,
|
|
vboxcommon.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)
|
|
}
|
|
|
|
// Determine if DiskSize is able to be allocated
|
|
if err = common.AvailableDisk(uint64(b.config.DiskSize)); err != nil {
|
|
errs = packer.MultiErrorAppend(errs,
|
|
fmt.Errorf("Unavailable Resources: %s", 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 := vboxcommon.NewDriver()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
|
|
}
|
|
|
|
steps := []multistep.Step{
|
|
&vboxcommon.StepDownloadGuestAdditions{
|
|
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
|
GuestAdditionsURL: b.config.GuestAdditionsURL,
|
|
GuestAdditionsSHA256: b.config.GuestAdditionsSHA256,
|
|
Ctx: b.config.ctx,
|
|
},
|
|
&common.StepDownload{
|
|
Checksum: b.config.ISOChecksum,
|
|
ChecksumType: b.config.ISOChecksumType,
|
|
Description: "ISO",
|
|
Extension: "iso",
|
|
ResultKey: "iso_path",
|
|
TargetPath: b.config.TargetPath,
|
|
Url: b.config.ISOUrls,
|
|
},
|
|
&vboxcommon.StepOutputDir{
|
|
Force: b.config.PackerForce,
|
|
Path: b.config.OutputDir,
|
|
},
|
|
&common.StepCreateFloppy{
|
|
Files: b.config.FloppyFiles,
|
|
},
|
|
&common.StepHTTPServer{
|
|
HTTPDir: b.config.HTTPDir,
|
|
HTTPPortMin: b.config.HTTPPortMin,
|
|
HTTPPortMax: b.config.HTTPPortMax,
|
|
},
|
|
new(vboxcommon.StepSuppressMessages),
|
|
new(stepCreateVM),
|
|
new(stepCreateDisk),
|
|
new(stepAttachISO),
|
|
&vboxcommon.StepAttachGuestAdditions{
|
|
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
|
},
|
|
new(vboxcommon.StepAttachFloppy),
|
|
&vboxcommon.StepForwardSSH{
|
|
CommConfig: &b.config.SSHConfig.Comm,
|
|
HostPortMin: b.config.SSHHostPortMin,
|
|
HostPortMax: b.config.SSHHostPortMax,
|
|
SkipNatMapping: b.config.SSHSkipNatMapping,
|
|
},
|
|
&vboxcommon.StepVBoxManage{
|
|
Commands: b.config.VBoxManage,
|
|
Ctx: b.config.ctx,
|
|
},
|
|
&vboxcommon.StepRun{
|
|
BootWait: b.config.BootWait,
|
|
Headless: b.config.Headless,
|
|
},
|
|
&vboxcommon.StepTypeBootCommand{
|
|
BootCommand: b.config.BootCommand,
|
|
VMName: b.config.VMName,
|
|
Ctx: b.config.ctx,
|
|
},
|
|
&communicator.StepConnect{
|
|
Config: &b.config.SSHConfig.Comm,
|
|
Host: vboxcommon.CommHost,
|
|
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
|
|
SSHPort: vboxcommon.SSHPort,
|
|
},
|
|
&vboxcommon.StepUploadVersion{
|
|
Path: b.config.VBoxVersionFile,
|
|
},
|
|
&vboxcommon.StepUploadGuestAdditions{
|
|
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
|
GuestAdditionsPath: b.config.GuestAdditionsPath,
|
|
Ctx: b.config.ctx,
|
|
},
|
|
new(common.StepProvision),
|
|
&vboxcommon.StepShutdown{
|
|
Command: b.config.ShutdownCommand,
|
|
Timeout: b.config.ShutdownTimeout,
|
|
},
|
|
new(vboxcommon.StepRemoveDevices),
|
|
&vboxcommon.StepVBoxManage{
|
|
Commands: b.config.VBoxManagePost,
|
|
Ctx: b.config.ctx,
|
|
},
|
|
&vboxcommon.StepExport{
|
|
Format: b.config.Format,
|
|
OutputDir: b.config.OutputDir,
|
|
ExportOpts: b.config.ExportOpts.ExportOpts,
|
|
SkipNatMapping: b.config.SSHSkipNatMapping,
|
|
},
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|