packer-cn/builder/virtualbox/iso/builder.go

428 lines
16 KiB
Go
Raw Normal View History

//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
2013-12-21 17:27:00 -05:00
package iso
2013-06-11 18:12:45 -04:00
import (
"context"
2013-06-11 23:00:30 -04:00
"errors"
"fmt"
"strings"
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
"github.com/hashicorp/hcl/v2/hcldec"
2017-04-04 16:39:01 -04:00
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
2013-06-11 18:12:45 -04:00
)
const BuilderId = "mitchellh.virtualbox"
type Builder struct {
config Config
2013-06-11 18:12:45 -04:00
runner multistep.Runner
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
2016-07-26 15:42:04 -04:00
common.FloppyConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
vboxcommon.ExportConfig `mapstructure:",squash"`
vboxcommon.OutputConfig `mapstructure:",squash"`
vboxcommon.RunConfig `mapstructure:",squash"`
vboxcommon.ShutdownConfig `mapstructure:",squash"`
vboxcommon.CommConfig `mapstructure:",squash"`
vboxcommon.HWConfig `mapstructure:",squash"`
vboxcommon.VBoxManageConfig `mapstructure:",squash"`
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
vboxcommon.VBoxBundleConfig `mapstructure:",squash"`
vboxcommon.GuestAdditionsConfig `mapstructure:",squash"`
2019-06-17 10:42:49 -04:00
// The size, in megabytes, of the hard disk to create for the VM. By
// default, this is 40000 (about 40 GB).
2019-06-06 10:29:25 -04:00
DiskSize uint `mapstructure:"disk_size" required:"false"`
2019-06-17 10:42:49 -04:00
// The method by which guest additions are made available to the guest for
// installation. Valid options are upload, attach, or disable. If the mode
// is attach the guest additions ISO will be attached as a CD device to the
// virtual machine. If the mode is upload the guest additions ISO will be
// uploaded to the path specified by guest_additions_path. The default
// value is upload. If disable is used, guest additions won't be
// downloaded, either.
2019-06-06 10:29:25 -04:00
GuestAdditionsMode string `mapstructure:"guest_additions_mode" required:"false"`
2019-06-17 10:42:49 -04:00
// 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 where the Version variable is
// replaced with the VirtualBox version.
2019-06-06 10:29:25 -04:00
GuestAdditionsPath string `mapstructure:"guest_additions_path" required:"false"`
2019-06-17 10:42:49 -04:00
// 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.
2019-06-06 10:29:25 -04:00
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256" required:"false"`
2019-06-17 10:42:49 -04:00
// 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 attempt to find the guest additions ISO on the local file system.
// If it is not available locally, the builder will download the proper
// guest additions ISO from the internet.
2019-06-06 10:29:25 -04:00
GuestAdditionsURL string `mapstructure:"guest_additions_url" required:"false"`
2019-06-17 10:42:49 -04:00
// The interface type to use to mount guest additions when
// guest_additions_mode is set to attach. Will default to the value set in
// iso_interface, if iso_interface is set. Will default to "ide", if
// iso_interface is not set. Options are "ide" and "sata".
GuestAdditionsInterface string `mapstructure:"guest_additions_interface" required:"false"`
2019-06-17 10:42:49 -04:00
// 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.
2019-06-06 10:29:25 -04:00
GuestOSType string `mapstructure:"guest_os_type" required:"false"`
2019-06-17 10:42:49 -04:00
// When this value is set to true, a VDI image will be shrunk in response
// to the trim command from the guest OS. The size of the cleared area must
// be at least 1MB. Also set hard_drive_nonrotational to true to enable
// TRIM support.
2019-06-06 10:29:25 -04:00
HardDriveDiscard bool `mapstructure:"hard_drive_discard" required:"false"`
2019-06-17 10:42:49 -04:00
// 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. When set to scsi, the drive is attached to an LsiLogic SCSI
// controller. When set to pcie, the drive is attached to an NVMe
// controller. Please note that when you use "pcie", you'll need to have
// Virtualbox 6, install an [extension
// pack](https://www.virtualbox.org/wiki/Downloads#VirtualBox6.0.14OracleVMVirtualBoxExtensionPack)
// and you will need to enable EFI mode for nvme to work, ex:
// "vboxmanage": [
// [ "modifyvm", "{{.Name}}", "--firmware", "EFI" ],
// ]
2019-06-06 10:29:25 -04:00
HardDriveInterface string `mapstructure:"hard_drive_interface" required:"false"`
2019-06-17 10:42:49 -04:00
// The number of ports available on any SATA controller created, defaults
// to 1. VirtualBox supports up to 30 ports on a maximum of 1 SATA
// controller. Increasing this value can be useful if you want to attach
// additional drives.
2019-06-06 10:29:25 -04:00
SATAPortCount int `mapstructure:"sata_port_count" required:"false"`
// The number of ports available on any NVMe controller created, defaults
// to 1. VirtualBox supports up to 255 ports on a maximum of 1 NVMe
// controller. Increasing this value can be useful if you want to attach
// additional drives.
NVMePortCount int `mapstructure:"nvme_port_count" required:"false"`
2019-06-17 10:42:49 -04:00
// Forces some guests (i.e. Windows 7+) to treat disks as SSDs and stops
// them from performing disk fragmentation. Also set hard_drive_discard to
// true to enable TRIM support.
2019-06-06 10:29:25 -04:00
HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational" required:"false"`
2019-06-17 10:42:49 -04:00
// The type of controller that the ISO is attached to, defaults to ide.
// When set to sata, the drive is attached to an AHCI SATA controller.
2019-06-06 10:29:25 -04:00
ISOInterface string `mapstructure:"iso_interface" required:"false"`
2019-06-17 10:42:49 -04:00
// Set this to true if you would like to keep the VM registered with
// virtualbox. Defaults to false.
2019-06-06 10:29:25 -04:00
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
2019-06-17 10:42:49 -04:00
// Defaults to false. When enabled, Packer will not export the VM. Useful
// if the build output is not the resultant image, but created inside the
// VM.
2019-06-06 10:29:25 -04:00
SkipExport bool `mapstructure:"skip_export" required:"false"`
2019-06-17 10:42:49 -04:00
// 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.
2019-06-06 10:29:25 -04:00
VMName string `mapstructure:"vm_name" required:"false"`
2013-06-13 13:24:10 -04:00
ctx interpolate.Context
2013-06-11 18:12:45 -04:00
}
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []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, 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.ExportConfig.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.CommConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.HWConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VBoxBundleConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VBoxManageConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VBoxVersionConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.GuestAdditionsConfig.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.GuestAdditionsInterface == "" {
b.config.GuestAdditionsInterface = b.config.ISOInterface
}
2015-06-22 12:25:15 -04:00
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf(
"packer-%s-%d", b.config.PackerBuildName, interpolate.InitTime.Unix())
}
switch b.config.HardDriveInterface {
case "ide", "sata", "scsi", "pcie":
// do nothing
default:
errs = packer.MultiErrorAppend(
errs, errors.New("hard_drive_interface can only be ide, sata, pcie or scsi"))
}
if b.config.SATAPortCount == 0 {
b.config.SATAPortCount = 1
}
if b.config.SATAPortCount > 30 {
errs = packer.MultiErrorAppend(
errs, errors.New("sata_port_count cannot be greater than 30"))
}
if b.config.NVMePortCount == 0 {
b.config.NVMePortCount = 1
}
if b.config.NVMePortCount > 255 {
errs = packer.MultiErrorAppend(
errs, errors.New("nvme_port_count cannot be greater than 255"))
}
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)
}
// 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.")
}
2013-07-19 19:08:25 -04:00
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs
}
return nil, warnings, nil
2013-06-11 18:12:45 -04:00
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (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: b.config.TargetExtension,
ResultKey: "iso_path",
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
},
&common.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
&common.StepCreateFloppy{
2016-10-01 03:04:50 -04:00
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
2019-09-12 08:25:22 -04:00
Label: b.config.FloppyConfig.FloppyLabel,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&vboxcommon.StepSshKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
Comm: &b.config.Comm,
},
new(vboxcommon.StepSuppressMessages),
2013-06-11 19:12:19 -04:00
new(stepCreateVM),
new(stepCreateDisk),
2013-06-11 23:07:11 -04:00
new(stepAttachISO),
&vboxcommon.StepAttachGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
},
&vboxcommon.StepConfigureVRDP{
VRDPBindAddress: b.config.VRDPBindAddress,
VRDPPortMin: b.config.VRDPPortMin,
VRDPPortMax: b.config.VRDPPortMax,
},
2013-12-22 11:10:11 -05:00
new(vboxcommon.StepAttachFloppy),
&vboxcommon.StepPortForwarding{
CommConfig: &b.config.CommConfig.Comm,
HostPortMin: b.config.HostPortMin,
HostPortMax: b.config.HostPortMax,
SkipNatMapping: b.config.SkipNatMapping,
2013-12-22 12:08:09 -05:00
},
2013-12-22 12:24:29 -05:00
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManage,
Ctx: b.config.ctx,
2013-12-22 12:24:29 -05:00
},
2013-12-22 13:30:12 -05:00
&vboxcommon.StepRun{
Headless: b.config.Headless,
},
&vboxcommon.StepTypeBootCommand{
BootWait: b.config.BootWait,
BootCommand: b.config.FlatBootCommand(),
VMName: b.config.VMName,
Ctx: b.config.ctx,
GroupInterval: b.config.BootConfig.BootGroupInterval,
Comm: &b.config.Comm,
},
&communicator.StepConnect{
Config: &b.config.CommConfig.Comm,
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.SSHHost),
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
SSHPort: vboxcommon.CommPort,
WinRMPort: vboxcommon.CommPort,
},
2013-12-22 14:50:29 -05:00
&vboxcommon.StepUploadVersion{
Path: *b.config.VBoxVersionFile,
2013-12-22 14:50:29 -05:00
},
&vboxcommon.StepUploadGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsPath: b.config.GuestAdditionsPath,
Ctx: b.config.ctx,
},
new(common.StepProvision),
&common.StepCleanupTempKeys{
Comm: &b.config.CommConfig.Comm,
},
2013-12-22 12:37:27 -05:00
&vboxcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
Delay: b.config.PostShutdownDelay,
DisableShutdown: b.config.DisableShutdown,
ACPIShutdown: b.config.ACPIShutdown,
2013-12-22 12:37:27 -05:00
},
&vboxcommon.StepRemoveDevices{
Bundling: b.config.VBoxBundleConfig,
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
},
&vboxcommon.StepVBoxManage{
Commands: b.config.VBoxManagePost,
Ctx: b.config.ctx,
},
2013-12-22 13:40:39 -05:00
&vboxcommon.StepExport{
Format: b.config.Format,
OutputDir: b.config.OutputDir,
ExportOpts: b.config.ExportConfig.ExportOpts,
Bundling: b.config.VBoxBundleConfig,
SkipNatMapping: b.config.SkipNatMapping,
SkipExport: b.config.SkipExport,
2013-12-22 13:40:39 -05:00
},
}
// Setup the state bag
2013-08-31 15:44:58 -04:00
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("debug", b.config.PackerDebug)
2013-08-31 15:44:58 -04:00
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Run
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(ctx, state)
// If there was an error, return that
2013-08-31 15:44:58 -04:00
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
2013-08-31 15:44:58 -04:00
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
2013-08-31 15:44:58 -04:00
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
return vboxcommon.NewArtifact(b.config.OutputDir)
2013-06-11 18:12:45 -04:00
}