Merge pull request #9944 from hashicorp/qemu_cleanup_and_tests
Qemu cleanup and tests
This commit is contained in:
commit
32b22ab5d7
|
@ -1,6 +1,3 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package qemu
|
||||
|
||||
import (
|
||||
|
@ -11,577 +8,26 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/common/shutdowncommand"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const BuilderId = "transcend.qemu"
|
||||
|
||||
var accels = map[string]struct{}{
|
||||
"none": {},
|
||||
"kvm": {},
|
||||
"tcg": {},
|
||||
"xen": {},
|
||||
"hax": {},
|
||||
"hvf": {},
|
||||
"whpx": {},
|
||||
}
|
||||
|
||||
var diskInterface = map[string]bool{
|
||||
"ide": true,
|
||||
"scsi": true,
|
||||
"virtio": true,
|
||||
"virtio-scsi": true,
|
||||
}
|
||||
|
||||
var diskCache = map[string]bool{
|
||||
"writethrough": true,
|
||||
"writeback": true,
|
||||
"none": true,
|
||||
"unsafe": true,
|
||||
"directsync": true,
|
||||
}
|
||||
|
||||
var diskDiscard = map[string]bool{
|
||||
"unmap": true,
|
||||
"ignore": true,
|
||||
}
|
||||
|
||||
var diskDZeroes = map[string]bool{
|
||||
"unmap": true,
|
||||
"on": true,
|
||||
"off": true,
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
common.HTTPConfig `mapstructure:",squash"`
|
||||
common.ISOConfig `mapstructure:",squash"`
|
||||
bootcommand.VNCConfig `mapstructure:",squash"`
|
||||
shutdowncommand.ShutdownConfig `mapstructure:",squash"`
|
||||
CommConfig CommConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
common.CDConfig `mapstructure:",squash"`
|
||||
// Use iso from provided url. Qemu must support
|
||||
// curl block device. This defaults to `false`.
|
||||
ISOSkipCache bool `mapstructure:"iso_skip_cache" required:"false"`
|
||||
// The accelerator type to use when running the VM.
|
||||
// This may be `none`, `kvm`, `tcg`, `hax`, `hvf`, `whpx`, or `xen`. The appropriate
|
||||
// software must have already been installed on your build machine to use the
|
||||
// accelerator you specified. When no accelerator is specified, Packer will try
|
||||
// to use `kvm` if it is available but will default to `tcg` otherwise.
|
||||
//
|
||||
// ~> The `hax` accelerator has issues attaching CDROM ISOs. This is an
|
||||
// upstream issue which can be tracked
|
||||
// [here](https://github.com/intel/haxm/issues/20).
|
||||
//
|
||||
// ~> The `hvf` and `whpx` accelerator are new and experimental as of
|
||||
// [QEMU 2.12.0](https://wiki.qemu.org/ChangeLog/2.12#Host_support).
|
||||
// You may encounter issues unrelated to Packer when using these. You may need to
|
||||
// add [ "-global", "virtio-pci.disable-modern=on" ] to `qemuargs` depending on the
|
||||
// guest operating system.
|
||||
//
|
||||
// ~> For `whpx`, note that [Stefan Weil's QEMU for Windows distribution](https://qemu.weilnetz.de/w64/)
|
||||
// does not include WHPX support and users may need to compile or source a
|
||||
// build of QEMU for Windows themselves with WHPX support.
|
||||
Accelerator string `mapstructure:"accelerator" required:"false"`
|
||||
// Additional disks to create. Uses `vm_name` as the disk name template and
|
||||
// appends `-#` where `#` is the position in the array. `#` starts at 1 since 0
|
||||
// is the default disk. Each string represents the disk image size in bytes.
|
||||
// Optional suffixes 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G'
|
||||
// (gigabyte, 1024M), 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E'
|
||||
// (exabyte, 1024P) are supported. 'b' is ignored. Per qemu-img documentation.
|
||||
// Each additional disk uses the same disk parameters as the default disk.
|
||||
// Unset by default.
|
||||
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false"`
|
||||
// The number of cpus to use when building the VM.
|
||||
// The default is `1` CPU.
|
||||
CpuCount int `mapstructure:"cpus" required:"false"`
|
||||
// The interface to use for the disk. Allowed values include any of `ide`,
|
||||
// `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot commands
|
||||
// or kickstart type scripts must have proper adjustments for resulting
|
||||
// device names. The Qemu builder uses `virtio` by default.
|
||||
//
|
||||
// ^\* Please be aware that use of the `scsi` disk interface has been
|
||||
// disabled by Red Hat due to a bug described
|
||||
// [here](https://bugzilla.redhat.com/show_bug.cgi?id=1019220). If you are
|
||||
// running Qemu on RHEL or a RHEL variant such as CentOS, you *must* choose
|
||||
// one of the other listed interfaces. Using the `scsi` interface under
|
||||
// these circumstances will cause the build to fail.
|
||||
DiskInterface string `mapstructure:"disk_interface" required:"false"`
|
||||
// The size in bytes of the hard disk of the VM. Suffix with the first
|
||||
// letter of common byte types. Use "k" or "K" for kilobytes, "M" for
|
||||
// megabytes, G for gigabytes, and T for terabytes. If no value is provided
|
||||
// for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size
|
||||
// number is provided with no units, Packer will default to Megabytes.
|
||||
DiskSize string `mapstructure:"disk_size" required:"false"`
|
||||
// Packer resizes the QCOW2 image using
|
||||
// qemu-img resize. Set this option to true to disable resizing.
|
||||
// Defaults to false.
|
||||
SkipResizeDisk bool `mapstructure:"skip_resize_disk" required:"false"`
|
||||
// The cache mode to use for disk. Allowed values include any of
|
||||
// `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By
|
||||
// default, this is set to `writeback`.
|
||||
DiskCache string `mapstructure:"disk_cache" required:"false"`
|
||||
// The discard mode to use for disk. Allowed values
|
||||
// include any of unmap or ignore. By default, this is set to ignore.
|
||||
DiskDiscard string `mapstructure:"disk_discard" required:"false"`
|
||||
// The detect-zeroes mode to use for disk.
|
||||
// Allowed values include any of unmap, on or off. Defaults to off.
|
||||
// When the value is "off" we don't set the flag in the qemu command, so that
|
||||
// Packer still works with old versions of QEMU that don't have this option.
|
||||
DetectZeroes string `mapstructure:"disk_detect_zeroes" required:"false"`
|
||||
// Packer compacts the QCOW2 image using
|
||||
// qemu-img convert. Set this option to true to disable compacting.
|
||||
// Defaults to false.
|
||||
SkipCompaction bool `mapstructure:"skip_compaction" required:"false"`
|
||||
// Apply compression to the QCOW2 disk file
|
||||
// using qemu-img convert. Defaults to false.
|
||||
DiskCompression bool `mapstructure:"disk_compression" required:"false"`
|
||||
// Either `qcow2` or `raw`, this specifies the output format of the virtual
|
||||
// machine image. This defaults to `qcow2`.
|
||||
Format string `mapstructure:"format" required:"false"`
|
||||
// Packer defaults to building QEMU 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.
|
||||
//
|
||||
// You can still see the console if you make a note of the VNC display
|
||||
// number chosen, and then connect using `vncviewer -Shared <host>:<display>`
|
||||
Headless bool `mapstructure:"headless" required:"false"`
|
||||
// Packer defaults to building from an ISO file, this parameter controls
|
||||
// whether the ISO URL supplied is actually a bootable QEMU image. When
|
||||
// this value is set to `true`, the machine will either clone the source or
|
||||
// use it as a backing file (if `use_backing_file` is `true`); then, it
|
||||
// will resize the image according to `disk_size` and boot it.
|
||||
DiskImage bool `mapstructure:"disk_image" required:"false"`
|
||||
// Only applicable when disk_image is true
|
||||
// and format is qcow2, set this option to true to create a new QCOW2
|
||||
// file that uses the file located at iso_url as a backing file. The new file
|
||||
// will only contain blocks that have changed compared to the backing file, so
|
||||
// enabling this option can significantly reduce disk usage. If true, Packer
|
||||
// will force the `skip_compaction` also to be true as well to skip disk
|
||||
// conversion which would render the backing file feature useless.
|
||||
UseBackingFile bool `mapstructure:"use_backing_file" required:"false"`
|
||||
// The type of machine emulation to use. Run your qemu binary with the
|
||||
// flags `-machine help` to list available types for your system. This
|
||||
// defaults to `pc`.
|
||||
MachineType string `mapstructure:"machine_type" required:"false"`
|
||||
// The amount of memory to use when building the VM
|
||||
// in megabytes. This defaults to 512 megabytes.
|
||||
MemorySize int `mapstructure:"memory" required:"false"`
|
||||
// The driver to use for the network interface. Allowed values `ne2k_pci`,
|
||||
// `i82551`, `i82557b`, `i82559er`, `rtl8139`, `e1000`, `pcnet`, `virtio`,
|
||||
// `virtio-net`, `virtio-net-pci`, `usb-net`, `i82559a`, `i82559b`,
|
||||
// `i82559c`, `i82550`, `i82562`, `i82557a`, `i82557c`, `i82801`,
|
||||
// `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by
|
||||
// default.
|
||||
NetDevice string `mapstructure:"net_device" required:"false"`
|
||||
// Connects the network to this bridge instead of using the user mode
|
||||
// networking.
|
||||
//
|
||||
// **NB** This bridge must already exist. You can use the `virbr0` bridge
|
||||
// as created by vagrant-libvirt.
|
||||
//
|
||||
// **NB** This will automatically enable the QMP socket (see QMPEnable).
|
||||
//
|
||||
// **NB** This only works in Linux based OSes.
|
||||
NetBridge string `mapstructure:"net_bridge" required:"false"`
|
||||
// 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.
|
||||
OutputDir string `mapstructure:"output_directory" required:"false"`
|
||||
// Allows complete control over the qemu command line (though not, at this
|
||||
// time, qemu-img). Each array of strings makes up a command line switch
|
||||
// that overrides matching default switch/value pairs. Any value specified
|
||||
// as an empty string is ignored. All values after the switch are
|
||||
// concatenated with no separator.
|
||||
//
|
||||
// ~> **Warning:** The qemu command line allows extreme flexibility, so
|
||||
// beware of conflicting arguments causing failures of your run. For
|
||||
// instance, using --no-acpi could break the ability to send power signal
|
||||
// type commands (e.g., shutdown -P now) to the virtual machine, thus
|
||||
// preventing proper shutdown. To see the defaults, look in the packer.log
|
||||
// file and search for the qemu-system-x86 command. The arguments are all
|
||||
// printed for review.
|
||||
//
|
||||
// The following shows a sample usage:
|
||||
//
|
||||
// In JSON:
|
||||
// ```json
|
||||
// "qemuargs": [
|
||||
// [ "-m", "1024M" ],
|
||||
// [ "--no-acpi", "" ],
|
||||
// [
|
||||
// "-netdev",
|
||||
// "user,id=mynet0,",
|
||||
// "hostfwd=hostip:hostport-guestip:guestport",
|
||||
// ""
|
||||
// ],
|
||||
// [ "-device", "virtio-net,netdev=mynet0" ]
|
||||
// ]
|
||||
// ```
|
||||
//
|
||||
// In HCL2:
|
||||
// ```hcl
|
||||
// qemuargs = [
|
||||
// [ "-m", "1024M" ],
|
||||
// [ "--no-acpi", "" ],
|
||||
// [
|
||||
// "-netdev",
|
||||
// "user,id=mynet0,",
|
||||
// "hostfwd=hostip:hostport-guestip:guestport",
|
||||
// ""
|
||||
// ],
|
||||
// [ "-device", "virtio-net,netdev=mynet0" ]
|
||||
// ]
|
||||
// ```
|
||||
//
|
||||
// would produce the following (not including other defaults supplied by
|
||||
// the builder and not otherwise conflicting with the qemuargs):
|
||||
//
|
||||
// ```text
|
||||
// qemu-system-x86 -m 1024m --no-acpi -netdev
|
||||
// user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device
|
||||
// virtio-net,netdev=mynet0"
|
||||
// ```
|
||||
//
|
||||
// ~> **Windows Users:** [QEMU for Windows](https://qemu.weilnetz.de/)
|
||||
// builds are available though an environmental variable does need to be
|
||||
// set for QEMU for Windows to redirect stdout to the console instead of
|
||||
// stdout.txt.
|
||||
//
|
||||
// The following shows the environment variable that needs to be set for
|
||||
// Windows QEMU support:
|
||||
//
|
||||
// ```text
|
||||
// setx SDL_STDIO_REDIRECT=0
|
||||
// ```
|
||||
//
|
||||
// You can also use the `SSHHostPort` template variable to produce a packer
|
||||
// template that can be invoked by `make` in parallel:
|
||||
//
|
||||
// In JSON:
|
||||
// ```json
|
||||
// "qemuargs": [
|
||||
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
|
||||
// [ "-device", "virtio-net,netdev=forward,id=net0"]
|
||||
// ]
|
||||
// ```
|
||||
//
|
||||
// In HCL2:
|
||||
// ```hcl
|
||||
// qemuargs = [
|
||||
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
|
||||
// [ "-device", "virtio-net,netdev=forward,id=net0"]
|
||||
// ]
|
||||
//
|
||||
// `make -j 3 my-awesome-packer-templates` spawns 3 packer processes, each
|
||||
// of which will bind to their own SSH port as determined by each process.
|
||||
// This will also work with WinRM, just change the port forward in
|
||||
// `qemuargs` to map to WinRM's default port of `5985` or whatever value
|
||||
// you have the service set to listen on.
|
||||
//
|
||||
// This is a template engine and allows access to the following variables:
|
||||
// `{{ .HTTPIP }}`, `{{ .HTTPPort }}`, `{{ .HTTPDir }}`,
|
||||
// `{{ .OutputDir }}`, `{{ .Name }}`, and `{{ .SSHHostPort }}`
|
||||
QemuArgs [][]string `mapstructure:"qemuargs" required:"false"`
|
||||
// The name of the Qemu binary to look for. This
|
||||
// defaults to qemu-system-x86_64, but may need to be changed for
|
||||
// some platforms. For example qemu-kvm, or qemu-system-i386 may be a
|
||||
// better choice for some systems.
|
||||
QemuBinary string `mapstructure:"qemu_binary" required:"false"`
|
||||
// Enable QMP socket. Location is specified by `qmp_socket_path`. Defaults
|
||||
// to false.
|
||||
QMPEnable bool `mapstructure:"qmp_enable" required:"false"`
|
||||
// QMP Socket Path when `qmp_enable` is true. Defaults to
|
||||
// `output_directory`/`vm_name`.monitor.
|
||||
QMPSocketPath string `mapstructure:"qmp_socket_path" required:"false"`
|
||||
// If true, do not pass a -display option
|
||||
// to qemu, allowing it to choose the default. This may be needed when running
|
||||
// under macOS, and getting errors about sdl not being available.
|
||||
UseDefaultDisplay bool `mapstructure:"use_default_display" required:"false"`
|
||||
// What QEMU -display option to use. Defaults to gtk, use none to not pass the
|
||||
// -display option allowing QEMU to choose the default. This may be needed when
|
||||
// running under macOS, and getting errors about sdl not being available.
|
||||
Display string `mapstructure:"display" required:"false"`
|
||||
// The IP address that should be
|
||||
// binded to for VNC. By default packer will use 127.0.0.1 for this. If you
|
||||
// wish to bind to all interfaces use 0.0.0.0.
|
||||
VNCBindAddress string `mapstructure:"vnc_bind_address" required:"false"`
|
||||
// Whether or not to set a password on the VNC server. This option
|
||||
// automatically enables the QMP socket. See `qmp_socket_path`. Defaults to
|
||||
// `false`.
|
||||
VNCUsePassword bool `mapstructure:"vnc_use_password" required:"false"`
|
||||
// The minimum and maximum port
|
||||
// to use for VNC access to the virtual machine. The builder uses VNC to type
|
||||
// the initial boot_command. Because Packer generally runs in parallel,
|
||||
// Packer uses a randomly chosen port in this range that appears available. By
|
||||
// default this is 5900 to 6000. The minimum and maximum ports are inclusive.
|
||||
VNCPortMin int `mapstructure:"vnc_port_min" required:"false"`
|
||||
VNCPortMax int `mapstructure:"vnc_port_max"`
|
||||
// This is the name of the image (QCOW2 or IMG) file for
|
||||
// the new virtual machine. By default this is packer-BUILDNAME, where
|
||||
// "BUILDNAME" is the name of the build. Currently, no file extension will be
|
||||
// used unless it is specified in this option.
|
||||
VMName string `mapstructure:"vm_name" required:"false"`
|
||||
// The interface to use for the CDROM device which contains the ISO image.
|
||||
// Allowed values include any of `ide`, `scsi`, `virtio` or
|
||||
// `virtio-scsi`. The Qemu builder uses `virtio` by default.
|
||||
// Some ARM64 images require `virtio-scsi`.
|
||||
CDROMInterface string `mapstructure:"cdrom_interface" required:"false"`
|
||||
|
||||
// TODO(mitchellh): deprecate
|
||||
RunOnce bool `mapstructure:"run_once"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
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",
|
||||
"qemuargs",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
warnings := make([]string, 0)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.DiskSize == "" || b.config.DiskSize == "0" {
|
||||
b.config.DiskSize = "40960M"
|
||||
} else {
|
||||
// Make sure supplied disk size is valid
|
||||
// (digits, plus an optional valid unit character). e.g. 5000, 40G, 1t
|
||||
re := regexp.MustCompile(`^[\d]+(b|k|m|g|t){0,1}$`)
|
||||
matched := re.MatchString(strings.ToLower(b.config.DiskSize))
|
||||
if !matched {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid disk size."))
|
||||
} else {
|
||||
// Okay, it's valid -- if it doesn't alreay have a suffix, then
|
||||
// append "M" as the default unit.
|
||||
re = regexp.MustCompile(`^[\d]+$`)
|
||||
matched = re.MatchString(strings.ToLower(b.config.DiskSize))
|
||||
if matched {
|
||||
// Needs M added.
|
||||
b.config.DiskSize = fmt.Sprintf("%sM", b.config.DiskSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.DiskCache == "" {
|
||||
b.config.DiskCache = "writeback"
|
||||
}
|
||||
|
||||
if b.config.DiskDiscard == "" {
|
||||
b.config.DiskDiscard = "ignore"
|
||||
}
|
||||
|
||||
if b.config.DetectZeroes == "" {
|
||||
b.config.DetectZeroes = "off"
|
||||
}
|
||||
|
||||
if b.config.Accelerator == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
b.config.Accelerator = "tcg"
|
||||
} else {
|
||||
// /dev/kvm is a kernel module that may be loaded if kvm is
|
||||
// installed and the host supports VT-x extensions. To make sure
|
||||
// this will actually work we need to os.Open() it. If os.Open fails
|
||||
// the kernel module was not installed or loaded correctly.
|
||||
if fp, err := os.Open("/dev/kvm"); err != nil {
|
||||
b.config.Accelerator = "tcg"
|
||||
} else {
|
||||
fp.Close()
|
||||
b.config.Accelerator = "kvm"
|
||||
}
|
||||
}
|
||||
log.Printf("use detected accelerator: %s", b.config.Accelerator)
|
||||
} else {
|
||||
log.Printf("use specified accelerator: %s", b.config.Accelerator)
|
||||
}
|
||||
|
||||
if b.config.MachineType == "" {
|
||||
b.config.MachineType = "pc"
|
||||
}
|
||||
|
||||
if b.config.OutputDir == "" {
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.QemuBinary == "" {
|
||||
b.config.QemuBinary = "qemu-system-x86_64"
|
||||
}
|
||||
|
||||
if b.config.MemorySize < 10 {
|
||||
log.Printf("MemorySize %d is too small, using default: 512", b.config.MemorySize)
|
||||
b.config.MemorySize = 512
|
||||
}
|
||||
|
||||
if b.config.CpuCount < 1 {
|
||||
log.Printf("CpuCount %d too small, using default: 1", b.config.CpuCount)
|
||||
b.config.CpuCount = 1
|
||||
}
|
||||
|
||||
if b.config.VNCBindAddress == "" {
|
||||
b.config.VNCBindAddress = "127.0.0.1"
|
||||
}
|
||||
|
||||
if b.config.VNCPortMin == 0 {
|
||||
b.config.VNCPortMin = 5900
|
||||
}
|
||||
|
||||
if b.config.VNCPortMax == 0 {
|
||||
b.config.VNCPortMax = 6000
|
||||
}
|
||||
|
||||
if b.config.VMName == "" {
|
||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.Format == "" {
|
||||
b.config.Format = "qcow2"
|
||||
}
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.CDConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.NetDevice == "" {
|
||||
b.config.NetDevice = "virtio-net"
|
||||
}
|
||||
|
||||
if b.config.DiskInterface == "" {
|
||||
b.config.DiskInterface = "virtio"
|
||||
}
|
||||
|
||||
if b.config.ISOSkipCache {
|
||||
b.config.ISOChecksum = "none"
|
||||
}
|
||||
isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx)
|
||||
warnings = append(warnings, isoWarnings...)
|
||||
errs = packer.MultiErrorAppend(errs, isoErrs...)
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...)
|
||||
commConfigWarnings, es := b.config.CommConfig.Prepare(&b.config.ctx)
|
||||
if len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
warnings = append(warnings, commConfigWarnings...)
|
||||
|
||||
if !(b.config.Format == "qcow2" || b.config.Format == "raw") {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
|
||||
}
|
||||
|
||||
if b.config.Format != "qcow2" {
|
||||
b.config.SkipCompaction = true
|
||||
b.config.DiskCompression = false
|
||||
}
|
||||
|
||||
if b.config.UseBackingFile {
|
||||
b.config.SkipCompaction = true
|
||||
if !(b.config.DiskImage && b.config.Format == "qcow2") {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true"))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.DiskImage && len(b.config.AdditionalDiskSize) > 0 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("disk_additional_size can only be used when disk_image is false"))
|
||||
}
|
||||
|
||||
if b.config.SkipResizeDisk && !(b.config.DiskImage) {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("skip_resize_disk can only be used when disk_image is true"))
|
||||
}
|
||||
|
||||
if _, ok := accels[b.config.Accelerator]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
|
||||
}
|
||||
|
||||
if _, ok := diskInterface[b.config.DiskInterface]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk interface type"))
|
||||
}
|
||||
|
||||
if _, ok := diskCache[b.config.DiskCache]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk cache type"))
|
||||
}
|
||||
|
||||
if _, ok := diskDiscard[b.config.DiskDiscard]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk discard type"))
|
||||
}
|
||||
|
||||
if _, ok := diskDZeroes[b.config.DetectZeroes]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk detect zeroes setting"))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.VNCPortMin > b.config.VNCPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
}
|
||||
|
||||
if b.config.NetBridge != "" && runtime.GOOS != "linux" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("net_bridge is only supported in Linux based OSes"))
|
||||
}
|
||||
|
||||
if b.config.NetBridge != "" || b.config.VNCUsePassword {
|
||||
b.config.QMPEnable = true
|
||||
}
|
||||
|
||||
if b.config.QMPEnable && b.config.QMPSocketPath == "" {
|
||||
socketName := fmt.Sprintf("%s.monitor", b.config.VMName)
|
||||
b.config.QMPSocketPath = filepath.Join(b.config.OutputDir, socketName)
|
||||
}
|
||||
|
||||
if b.config.QemuArgs == nil {
|
||||
b.config.QemuArgs = make([][]string, 0)
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
warnings, errs := b.config.Prepare(raws...)
|
||||
if errs != nil {
|
||||
return nil, warnings, errs
|
||||
}
|
||||
|
||||
|
@ -595,15 +41,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
return nil, fmt.Errorf("Failed creating Qemu driver: %s", err)
|
||||
}
|
||||
|
||||
steprun := &stepRun{}
|
||||
if !b.config.DiskImage {
|
||||
steprun.BootDrive = "once=d"
|
||||
steprun.Message = "Starting VM, booting from CD-ROM"
|
||||
} else {
|
||||
steprun.BootDrive = "c"
|
||||
steprun.Message = "Starting VM, booting disk image"
|
||||
}
|
||||
|
||||
steps := []multistep.Step{}
|
||||
if !b.config.ISOSkipCache {
|
||||
steps = append(steps, &common.StepDownload{
|
||||
|
@ -613,14 +50,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
ResultKey: "iso_path",
|
||||
TargetPath: b.config.TargetPath,
|
||||
Url: b.config.ISOUrls,
|
||||
},
|
||||
)
|
||||
})
|
||||
} else {
|
||||
steps = append(steps, &stepSetISO{
|
||||
ResultKey: "iso_path",
|
||||
Url: b.config.ISOUrls,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
steps = append(steps, new(stepPrepareOutputDir),
|
||||
|
@ -643,57 +78,35 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
HTTPPortMax: b.config.HTTPPortMax,
|
||||
HTTPAddress: b.config.HTTPAddress,
|
||||
},
|
||||
)
|
||||
|
||||
if b.config.CommConfig.Comm.Type != "none" && b.config.NetBridge == "" {
|
||||
steps = append(steps,
|
||||
new(stepPortForward),
|
||||
)
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&stepPortForward{
|
||||
CommunicatorType: b.config.CommConfig.Comm.Type,
|
||||
NetBridge: b.config.NetBridge,
|
||||
},
|
||||
new(stepConfigureVNC),
|
||||
steprun,
|
||||
&stepRun{
|
||||
DiskImage: b.config.DiskImage,
|
||||
},
|
||||
&stepConfigureQMP{
|
||||
QMPSocketPath: b.config.QMPSocketPath,
|
||||
},
|
||||
&stepTypeBootCommand{},
|
||||
)
|
||||
|
||||
if b.config.CommConfig.Comm.Type != "none" && b.config.NetBridge != "" {
|
||||
steps = append(steps,
|
||||
&stepWaitGuestAddress{
|
||||
timeout: b.config.CommConfig.Comm.SSHTimeout,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if b.config.CommConfig.Comm.Type != "none" {
|
||||
steps = append(steps,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.CommConfig.Comm,
|
||||
Host: commHost(b.config.CommConfig.Comm.Host()),
|
||||
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
|
||||
SSHPort: commPort,
|
||||
WinRMPort: commPort,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
steps = append(steps,
|
||||
&stepWaitGuestAddress{
|
||||
CommunicatorType: b.config.CommConfig.Comm.Type,
|
||||
NetBridge: b.config.NetBridge,
|
||||
timeout: b.config.CommConfig.Comm.SSHTimeout,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.CommConfig.Comm,
|
||||
Host: commHost(b.config.CommConfig.Comm.Host()),
|
||||
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
|
||||
SSHPort: commPort,
|
||||
WinRMPort: commPort,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
)
|
||||
|
||||
steps = append(steps,
|
||||
&common.StepCleanupTempKeys{
|
||||
Comm: &b.config.CommConfig.Comm,
|
||||
},
|
||||
)
|
||||
steps = append(steps,
|
||||
new(stepShutdown),
|
||||
)
|
||||
|
||||
steps = append(steps,
|
||||
new(stepConvertDisk),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,56 +1,11 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
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": "md5:0B0F137F17AC10944716020B018F8126",
|
||||
"iso_url": "http://www.google.com/",
|
||||
"ssh_username": "foo",
|
||||
packer.BuildNameConfigKey: "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
|
@ -58,616 +13,3 @@ func TestBuilder_ImplementsBuilder(t *testing.T) {
|
|||
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.OutputDir != "output-foo" {
|
||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
||||
}
|
||||
|
||||
if b.config.CommConfig.HostPortMin != 2222 {
|
||||
t.Errorf("bad min ssh host port: %d", b.config.CommConfig.HostPortMin)
|
||||
}
|
||||
|
||||
if b.config.CommConfig.HostPortMax != 4444 {
|
||||
t.Errorf("bad max ssh host port: %d", b.config.CommConfig.HostPortMax)
|
||||
}
|
||||
|
||||
if b.config.CommConfig.Comm.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", b.config.CommConfig.Comm.SSHPort)
|
||||
}
|
||||
|
||||
if b.config.VMName != "packer-foo" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
|
||||
if b.config.Format != "qcow2" {
|
||||
t.Errorf("bad format: %s", b.config.Format)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCBindAddress(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test a default boot_wait
|
||||
delete(config, "vnc_bind_address")
|
||||
_, 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.VNCBindAddress != "127.0.0.1" {
|
||||
t.Fatalf("bad value: %s", b.config.VNCBindAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_DiskCompaction(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Bad
|
||||
config["skip_compaction"] = false
|
||||
config["disk_compression"] = true
|
||||
config["format"] = "img"
|
||||
_, warns, err := b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if b.config.SkipCompaction != true {
|
||||
t.Fatalf("SkipCompaction should be true")
|
||||
}
|
||||
if b.config.DiskCompression != false {
|
||||
t.Fatalf("DiskCompression should be false")
|
||||
}
|
||||
|
||||
// Good
|
||||
config["skip_compaction"] = false
|
||||
config["disk_compression"] = true
|
||||
config["format"] = "qcow2"
|
||||
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.SkipCompaction != false {
|
||||
t.Fatalf("SkipCompaction should be false")
|
||||
}
|
||||
if b.config.DiskCompression != true {
|
||||
t.Fatalf("DiskCompression should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||
type testcase struct {
|
||||
InputSize string
|
||||
OutputSize string
|
||||
ErrExpected bool
|
||||
}
|
||||
|
||||
testCases := []testcase{
|
||||
{"", "40960M", false}, // not provided
|
||||
{"12345", "12345M", false}, // no unit given, defaults to M
|
||||
{"12345x", "12345x", true}, // invalid unit
|
||||
{"12345T", "12345T", false}, // terabytes
|
||||
{"12345b", "12345b", false}, // bytes get preserved when set.
|
||||
{"60000M", "60000M", false}, // Original test case
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
// Set input disk size
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
delete(config, "disk_size")
|
||||
config["disk_size"] = tc.InputSize
|
||||
|
||||
_, warns, err := b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if (err == nil) == tc.ErrExpected {
|
||||
t.Fatalf("bad: error when providing disk size %s; Err expected: %t; err recieved: %v", tc.InputSize, tc.ErrExpected, err)
|
||||
}
|
||||
|
||||
if b.config.DiskSize != tc.OutputSize {
|
||||
t.Fatalf("bad size: received: %s but expected %s", b.config.DiskSize, tc.OutputSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AdditionalDiskSize(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["disk_additional_size"] = []string{"1M"}
|
||||
config["disk_image"] = true
|
||||
_, warns, err := b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
delete(config, "disk_image")
|
||||
config["disk_additional_size"] = []string{"1M"}
|
||||
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.AdditionalDiskSize[0] != "1M" {
|
||||
t.Fatalf("bad size: %s", b.config.AdditionalDiskSize)
|
||||
}
|
||||
}
|
||||
|
||||
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"] = "qcow2"
|
||||
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"] = "raw"
|
||||
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_UseBackingFile(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["use_backing_file"] = true
|
||||
|
||||
// Bad: iso_url is not a disk_image
|
||||
config["disk_image"] = false
|
||||
config["format"] = "qcow2"
|
||||
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: format is not 'qcow2'
|
||||
config["disk_image"] = true
|
||||
config["format"] = "raw"
|
||||
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: iso_url is a disk image and format is 'qcow2'
|
||||
config["disk_image"] = true
|
||||
config["format"] = "qcow2"
|
||||
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_SkipResizeDisk(t *testing.T) {
|
||||
config := testConfig()
|
||||
config["skip_resize_disk"] = true
|
||||
config["disk_image"] = false
|
||||
|
||||
b := Builder{}
|
||||
_, warns, err := b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Errorf("unexpected warns when calling prepare with skip_resize_disk set to true: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("setting skip_resize_disk to true when disk_image is false should have error")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
floppies_path := "../../common/test-fixtures/floppies"
|
||||
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_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)
|
||||
}
|
||||
|
||||
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
|
||||
b = Builder{}
|
||||
_, _, errs := b.Prepare(config)
|
||||
if errs == nil {
|
||||
t.Fatalf("Nonexistent floppies should trigger multierror")
|
||||
}
|
||||
|
||||
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||
t.Fatalf("Multierror should work and report 2 errors")
|
||||
}
|
||||
}
|
||||
|
||||
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_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_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["host_port_min"] = 1000
|
||||
config["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["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["host_port_min"] = 500
|
||||
config["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_SSHPrivateKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["ssh_private_key_file"] = ""
|
||||
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_private_key_file"] = "/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_private_key_file"] = 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_private_key_file"] = 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_SSHWaitTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test a default boot_wait
|
||||
delete(config, "ssh_timeout")
|
||||
_, warns, err := b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a bad value
|
||||
config["ssh_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_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_QemuArgs(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test with empty
|
||||
delete(config, "qemuargs")
|
||||
_, 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.QemuArgs, [][]string{}) {
|
||||
t.Fatalf("bad: %#v", b.config.QemuArgs)
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
config["qemuargs"] = [][]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{
|
||||
{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(b.config.QemuArgs, expected) {
|
||||
t.Fatalf("bad: %#v", b.config.QemuArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCPassword(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["vnc_use_password"] = true
|
||||
config["output_directory"] = "not-a-real-directory"
|
||||
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 := filepath.Join("not-a-real-directory", "packer-foo.monitor")
|
||||
if !reflect.DeepEqual(b.config.QMPSocketPath, expected) {
|
||||
t.Fatalf("Bad QMP socket Path: %s", b.config.QMPSocketPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommConfigPrepare_BackwardsCompatibility(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
hostPortMin := 1234
|
||||
hostPortMax := 4321
|
||||
sshTimeout := 2 * time.Minute
|
||||
|
||||
config["ssh_wait_timeout"] = sshTimeout
|
||||
config["ssh_host_port_min"] = hostPortMin
|
||||
config["ssh_host_port_max"] = hostPortMax
|
||||
|
||||
_, warns, err := b.Prepare(config)
|
||||
if len(warns) == 0 {
|
||||
t.Fatalf("should have deprecation warn")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.CommConfig.Comm.SSHTimeout != sshTimeout {
|
||||
t.Fatalf("SSHTimeout should be %s for backwards compatibility, but it was %s", sshTimeout.String(), b.config.CommConfig.Comm.SSHTimeout.String())
|
||||
}
|
||||
|
||||
if b.config.CommConfig.HostPortMin != hostPortMin {
|
||||
t.Fatalf("HostPortMin should be %d for backwards compatibility, but it was %d", hostPortMin, b.config.CommConfig.HostPortMin)
|
||||
}
|
||||
|
||||
if b.config.CommConfig.HostPortMax != hostPortMax {
|
||||
t.Fatalf("HostPortMax should be %d for backwards compatibility, but it was %d", hostPortMax, b.config.CommConfig.HostPortMax)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,578 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/common/shutdowncommand"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
var accels = map[string]struct{}{
|
||||
"none": {},
|
||||
"kvm": {},
|
||||
"tcg": {},
|
||||
"xen": {},
|
||||
"hax": {},
|
||||
"hvf": {},
|
||||
"whpx": {},
|
||||
}
|
||||
|
||||
var diskInterface = map[string]bool{
|
||||
"ide": true,
|
||||
"scsi": true,
|
||||
"virtio": true,
|
||||
"virtio-scsi": true,
|
||||
}
|
||||
|
||||
var diskCache = map[string]bool{
|
||||
"writethrough": true,
|
||||
"writeback": true,
|
||||
"none": true,
|
||||
"unsafe": true,
|
||||
"directsync": true,
|
||||
}
|
||||
|
||||
var diskDiscard = map[string]bool{
|
||||
"unmap": true,
|
||||
"ignore": true,
|
||||
}
|
||||
|
||||
var diskDZeroes = map[string]bool{
|
||||
"unmap": true,
|
||||
"on": true,
|
||||
"off": true,
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
common.HTTPConfig `mapstructure:",squash"`
|
||||
common.ISOConfig `mapstructure:",squash"`
|
||||
bootcommand.VNCConfig `mapstructure:",squash"`
|
||||
shutdowncommand.ShutdownConfig `mapstructure:",squash"`
|
||||
CommConfig CommConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
common.CDConfig `mapstructure:",squash"`
|
||||
// Use iso from provided url. Qemu must support
|
||||
// curl block device. This defaults to `false`.
|
||||
ISOSkipCache bool `mapstructure:"iso_skip_cache" required:"false"`
|
||||
// The accelerator type to use when running the VM.
|
||||
// This may be `none`, `kvm`, `tcg`, `hax`, `hvf`, `whpx`, or `xen`. The appropriate
|
||||
// software must have already been installed on your build machine to use the
|
||||
// accelerator you specified. When no accelerator is specified, Packer will try
|
||||
// to use `kvm` if it is available but will default to `tcg` otherwise.
|
||||
//
|
||||
// ~> The `hax` accelerator has issues attaching CDROM ISOs. This is an
|
||||
// upstream issue which can be tracked
|
||||
// [here](https://github.com/intel/haxm/issues/20).
|
||||
//
|
||||
// ~> The `hvf` and `whpx` accelerator are new and experimental as of
|
||||
// [QEMU 2.12.0](https://wiki.qemu.org/ChangeLog/2.12#Host_support).
|
||||
// You may encounter issues unrelated to Packer when using these. You may need to
|
||||
// add [ "-global", "virtio-pci.disable-modern=on" ] to `qemuargs` depending on the
|
||||
// guest operating system.
|
||||
//
|
||||
// ~> For `whpx`, note that [Stefan Weil's QEMU for Windows distribution](https://qemu.weilnetz.de/w64/)
|
||||
// does not include WHPX support and users may need to compile or source a
|
||||
// build of QEMU for Windows themselves with WHPX support.
|
||||
Accelerator string `mapstructure:"accelerator" required:"false"`
|
||||
// Additional disks to create. Uses `vm_name` as the disk name template and
|
||||
// appends `-#` where `#` is the position in the array. `#` starts at 1 since 0
|
||||
// is the default disk. Each string represents the disk image size in bytes.
|
||||
// Optional suffixes 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G'
|
||||
// (gigabyte, 1024M), 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E'
|
||||
// (exabyte, 1024P) are supported. 'b' is ignored. Per qemu-img documentation.
|
||||
// Each additional disk uses the same disk parameters as the default disk.
|
||||
// Unset by default.
|
||||
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false"`
|
||||
// The number of cpus to use when building the VM.
|
||||
// The default is `1` CPU.
|
||||
CpuCount int `mapstructure:"cpus" required:"false"`
|
||||
// The interface to use for the disk. Allowed values include any of `ide`,
|
||||
// `scsi`, `virtio` or `virtio-scsi`^\*. Note also that any boot commands
|
||||
// or kickstart type scripts must have proper adjustments for resulting
|
||||
// device names. The Qemu builder uses `virtio` by default.
|
||||
//
|
||||
// ^\* Please be aware that use of the `scsi` disk interface has been
|
||||
// disabled by Red Hat due to a bug described
|
||||
// [here](https://bugzilla.redhat.com/show_bug.cgi?id=1019220). If you are
|
||||
// running Qemu on RHEL or a RHEL variant such as CentOS, you *must* choose
|
||||
// one of the other listed interfaces. Using the `scsi` interface under
|
||||
// these circumstances will cause the build to fail.
|
||||
DiskInterface string `mapstructure:"disk_interface" required:"false"`
|
||||
// The size in bytes of the hard disk of the VM. Suffix with the first
|
||||
// letter of common byte types. Use "k" or "K" for kilobytes, "M" for
|
||||
// megabytes, G for gigabytes, and T for terabytes. If no value is provided
|
||||
// for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size
|
||||
// number is provided with no units, Packer will default to Megabytes.
|
||||
DiskSize string `mapstructure:"disk_size" required:"false"`
|
||||
// Packer resizes the QCOW2 image using
|
||||
// qemu-img resize. Set this option to true to disable resizing.
|
||||
// Defaults to false.
|
||||
SkipResizeDisk bool `mapstructure:"skip_resize_disk" required:"false"`
|
||||
// The cache mode to use for disk. Allowed values include any of
|
||||
// `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By
|
||||
// default, this is set to `writeback`.
|
||||
DiskCache string `mapstructure:"disk_cache" required:"false"`
|
||||
// The discard mode to use for disk. Allowed values
|
||||
// include any of unmap or ignore. By default, this is set to ignore.
|
||||
DiskDiscard string `mapstructure:"disk_discard" required:"false"`
|
||||
// The detect-zeroes mode to use for disk.
|
||||
// Allowed values include any of unmap, on or off. Defaults to off.
|
||||
// When the value is "off" we don't set the flag in the qemu command, so that
|
||||
// Packer still works with old versions of QEMU that don't have this option.
|
||||
DetectZeroes string `mapstructure:"disk_detect_zeroes" required:"false"`
|
||||
// Packer compacts the QCOW2 image using
|
||||
// qemu-img convert. Set this option to true to disable compacting.
|
||||
// Defaults to false.
|
||||
SkipCompaction bool `mapstructure:"skip_compaction" required:"false"`
|
||||
// Apply compression to the QCOW2 disk file
|
||||
// using qemu-img convert. Defaults to false.
|
||||
DiskCompression bool `mapstructure:"disk_compression" required:"false"`
|
||||
// Either `qcow2` or `raw`, this specifies the output format of the virtual
|
||||
// machine image. This defaults to `qcow2`.
|
||||
Format string `mapstructure:"format" required:"false"`
|
||||
// Packer defaults to building QEMU 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.
|
||||
//
|
||||
// You can still see the console if you make a note of the VNC display
|
||||
// number chosen, and then connect using `vncviewer -Shared <host>:<display>`
|
||||
Headless bool `mapstructure:"headless" required:"false"`
|
||||
// Packer defaults to building from an ISO file, this parameter controls
|
||||
// whether the ISO URL supplied is actually a bootable QEMU image. When
|
||||
// this value is set to `true`, the machine will either clone the source or
|
||||
// use it as a backing file (if `use_backing_file` is `true`); then, it
|
||||
// will resize the image according to `disk_size` and boot it.
|
||||
DiskImage bool `mapstructure:"disk_image" required:"false"`
|
||||
// Only applicable when disk_image is true
|
||||
// and format is qcow2, set this option to true to create a new QCOW2
|
||||
// file that uses the file located at iso_url as a backing file. The new file
|
||||
// will only contain blocks that have changed compared to the backing file, so
|
||||
// enabling this option can significantly reduce disk usage. If true, Packer
|
||||
// will force the `skip_compaction` also to be true as well to skip disk
|
||||
// conversion which would render the backing file feature useless.
|
||||
UseBackingFile bool `mapstructure:"use_backing_file" required:"false"`
|
||||
// The type of machine emulation to use. Run your qemu binary with the
|
||||
// flags `-machine help` to list available types for your system. This
|
||||
// defaults to `pc`.
|
||||
MachineType string `mapstructure:"machine_type" required:"false"`
|
||||
// The amount of memory to use when building the VM
|
||||
// in megabytes. This defaults to 512 megabytes.
|
||||
MemorySize int `mapstructure:"memory" required:"false"`
|
||||
// The driver to use for the network interface. Allowed values `ne2k_pci`,
|
||||
// `i82551`, `i82557b`, `i82559er`, `rtl8139`, `e1000`, `pcnet`, `virtio`,
|
||||
// `virtio-net`, `virtio-net-pci`, `usb-net`, `i82559a`, `i82559b`,
|
||||
// `i82559c`, `i82550`, `i82562`, `i82557a`, `i82557c`, `i82801`,
|
||||
// `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by
|
||||
// default.
|
||||
NetDevice string `mapstructure:"net_device" required:"false"`
|
||||
// Connects the network to this bridge instead of using the user mode
|
||||
// networking.
|
||||
//
|
||||
// **NB** This bridge must already exist. You can use the `virbr0` bridge
|
||||
// as created by vagrant-libvirt.
|
||||
//
|
||||
// **NB** This will automatically enable the QMP socket (see QMPEnable).
|
||||
//
|
||||
// **NB** This only works in Linux based OSes.
|
||||
NetBridge string `mapstructure:"net_bridge" required:"false"`
|
||||
// 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.
|
||||
OutputDir string `mapstructure:"output_directory" required:"false"`
|
||||
// Allows complete control over the qemu command line (though not, at this
|
||||
// time, qemu-img). Each array of strings makes up a command line switch
|
||||
// that overrides matching default switch/value pairs. Any value specified
|
||||
// as an empty string is ignored. All values after the switch are
|
||||
// concatenated with no separator.
|
||||
//
|
||||
// ~> **Warning:** The qemu command line allows extreme flexibility, so
|
||||
// beware of conflicting arguments causing failures of your run. For
|
||||
// instance, using --no-acpi could break the ability to send power signal
|
||||
// type commands (e.g., shutdown -P now) to the virtual machine, thus
|
||||
// preventing proper shutdown. To see the defaults, look in the packer.log
|
||||
// file and search for the qemu-system-x86 command. The arguments are all
|
||||
// printed for review.
|
||||
//
|
||||
// The following shows a sample usage:
|
||||
//
|
||||
// In JSON:
|
||||
// ```json
|
||||
// "qemuargs": [
|
||||
// [ "-m", "1024M" ],
|
||||
// [ "--no-acpi", "" ],
|
||||
// [
|
||||
// "-netdev",
|
||||
// "user,id=mynet0,",
|
||||
// "hostfwd=hostip:hostport-guestip:guestport",
|
||||
// ""
|
||||
// ],
|
||||
// [ "-device", "virtio-net,netdev=mynet0" ]
|
||||
// ]
|
||||
// ```
|
||||
//
|
||||
// In HCL2:
|
||||
// ```hcl
|
||||
// qemuargs = [
|
||||
// [ "-m", "1024M" ],
|
||||
// [ "--no-acpi", "" ],
|
||||
// [
|
||||
// "-netdev",
|
||||
// "user,id=mynet0,",
|
||||
// "hostfwd=hostip:hostport-guestip:guestport",
|
||||
// ""
|
||||
// ],
|
||||
// [ "-device", "virtio-net,netdev=mynet0" ]
|
||||
// ]
|
||||
// ```
|
||||
//
|
||||
// would produce the following (not including other defaults supplied by
|
||||
// the builder and not otherwise conflicting with the qemuargs):
|
||||
//
|
||||
// ```text
|
||||
// qemu-system-x86 -m 1024m --no-acpi -netdev
|
||||
// user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device
|
||||
// virtio-net,netdev=mynet0"
|
||||
// ```
|
||||
//
|
||||
// ~> **Windows Users:** [QEMU for Windows](https://qemu.weilnetz.de/)
|
||||
// builds are available though an environmental variable does need to be
|
||||
// set for QEMU for Windows to redirect stdout to the console instead of
|
||||
// stdout.txt.
|
||||
//
|
||||
// The following shows the environment variable that needs to be set for
|
||||
// Windows QEMU support:
|
||||
//
|
||||
// ```text
|
||||
// setx SDL_STDIO_REDIRECT=0
|
||||
// ```
|
||||
//
|
||||
// You can also use the `SSHHostPort` template variable to produce a packer
|
||||
// template that can be invoked by `make` in parallel:
|
||||
//
|
||||
// In JSON:
|
||||
// ```json
|
||||
// "qemuargs": [
|
||||
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
|
||||
// [ "-device", "virtio-net,netdev=forward,id=net0"]
|
||||
// ]
|
||||
// ```
|
||||
//
|
||||
// In HCL2:
|
||||
// ```hcl
|
||||
// qemuargs = [
|
||||
// [ "-netdev", "user,hostfwd=tcp::{{ .SSHHostPort }}-:22,id=forward"],
|
||||
// [ "-device", "virtio-net,netdev=forward,id=net0"]
|
||||
// ]
|
||||
//
|
||||
// `make -j 3 my-awesome-packer-templates` spawns 3 packer processes, each
|
||||
// of which will bind to their own SSH port as determined by each process.
|
||||
// This will also work with WinRM, just change the port forward in
|
||||
// `qemuargs` to map to WinRM's default port of `5985` or whatever value
|
||||
// you have the service set to listen on.
|
||||
//
|
||||
// This is a template engine and allows access to the following variables:
|
||||
// `{{ .HTTPIP }}`, `{{ .HTTPPort }}`, `{{ .HTTPDir }}`,
|
||||
// `{{ .OutputDir }}`, `{{ .Name }}`, and `{{ .SSHHostPort }}`
|
||||
QemuArgs [][]string `mapstructure:"qemuargs" required:"false"`
|
||||
// The name of the Qemu binary to look for. This
|
||||
// defaults to qemu-system-x86_64, but may need to be changed for
|
||||
// some platforms. For example qemu-kvm, or qemu-system-i386 may be a
|
||||
// better choice for some systems.
|
||||
QemuBinary string `mapstructure:"qemu_binary" required:"false"`
|
||||
// Enable QMP socket. Location is specified by `qmp_socket_path`. Defaults
|
||||
// to false.
|
||||
QMPEnable bool `mapstructure:"qmp_enable" required:"false"`
|
||||
// QMP Socket Path when `qmp_enable` is true. Defaults to
|
||||
// `output_directory`/`vm_name`.monitor.
|
||||
QMPSocketPath string `mapstructure:"qmp_socket_path" required:"false"`
|
||||
// If true, do not pass a -display option
|
||||
// to qemu, allowing it to choose the default. This may be needed when running
|
||||
// under macOS, and getting errors about sdl not being available.
|
||||
UseDefaultDisplay bool `mapstructure:"use_default_display" required:"false"`
|
||||
// What QEMU -display option to use. Defaults to gtk, use none to not pass the
|
||||
// -display option allowing QEMU to choose the default. This may be needed when
|
||||
// running under macOS, and getting errors about sdl not being available.
|
||||
Display string `mapstructure:"display" required:"false"`
|
||||
// The IP address that should be
|
||||
// binded to for VNC. By default packer will use 127.0.0.1 for this. If you
|
||||
// wish to bind to all interfaces use 0.0.0.0.
|
||||
VNCBindAddress string `mapstructure:"vnc_bind_address" required:"false"`
|
||||
// Whether or not to set a password on the VNC server. This option
|
||||
// automatically enables the QMP socket. See `qmp_socket_path`. Defaults to
|
||||
// `false`.
|
||||
VNCUsePassword bool `mapstructure:"vnc_use_password" required:"false"`
|
||||
// The minimum and maximum port
|
||||
// to use for VNC access to the virtual machine. The builder uses VNC to type
|
||||
// the initial boot_command. Because Packer generally runs in parallel,
|
||||
// Packer uses a randomly chosen port in this range that appears available. By
|
||||
// default this is 5900 to 6000. The minimum and maximum ports are inclusive.
|
||||
VNCPortMin int `mapstructure:"vnc_port_min" required:"false"`
|
||||
VNCPortMax int `mapstructure:"vnc_port_max"`
|
||||
// This is the name of the image (QCOW2 or IMG) file for
|
||||
// the new virtual machine. By default this is packer-BUILDNAME, where
|
||||
// "BUILDNAME" is the name of the build. Currently, no file extension will be
|
||||
// used unless it is specified in this option.
|
||||
VMName string `mapstructure:"vm_name" required:"false"`
|
||||
// The interface to use for the CDROM device which contains the ISO image.
|
||||
// Allowed values include any of `ide`, `scsi`, `virtio` or
|
||||
// `virtio-scsi`. The Qemu builder uses `virtio` by default.
|
||||
// Some ARM64 images require `virtio-scsi`.
|
||||
CDROMInterface string `mapstructure:"cdrom_interface" required:"false"`
|
||||
|
||||
// TODO(mitchellh): deprecate
|
||||
RunOnce bool `mapstructure:"run_once"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"boot_command",
|
||||
"qemuargs",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors and warnings
|
||||
var errs *packer.MultiError
|
||||
warnings := make([]string, 0)
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...)
|
||||
|
||||
if c.DiskSize == "" || c.DiskSize == "0" {
|
||||
c.DiskSize = "40960M"
|
||||
} else {
|
||||
// Make sure supplied disk size is valid
|
||||
// (digits, plus an optional valid unit character). e.g. 5000, 40G, 1t
|
||||
re := regexp.MustCompile(`^[\d]+(b|k|m|g|t){0,1}$`)
|
||||
matched := re.MatchString(strings.ToLower(c.DiskSize))
|
||||
if !matched {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid disk size."))
|
||||
} else {
|
||||
// Okay, it's valid -- if it doesn't alreay have a suffix, then
|
||||
// append "M" as the default unit.
|
||||
re = regexp.MustCompile(`^[\d]+$`)
|
||||
matched = re.MatchString(strings.ToLower(c.DiskSize))
|
||||
if matched {
|
||||
// Needs M added.
|
||||
c.DiskSize = fmt.Sprintf("%sM", c.DiskSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.DiskCache == "" {
|
||||
c.DiskCache = "writeback"
|
||||
}
|
||||
|
||||
if c.DiskDiscard == "" {
|
||||
c.DiskDiscard = "ignore"
|
||||
}
|
||||
|
||||
if c.DetectZeroes == "" {
|
||||
c.DetectZeroes = "off"
|
||||
}
|
||||
|
||||
if c.Accelerator == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
c.Accelerator = "tcg"
|
||||
} else {
|
||||
// /dev/kvm is a kernel module that may be loaded if kvm is
|
||||
// installed and the host supports VT-x extensions. To make sure
|
||||
// this will actually work we need to os.Open() it. If os.Open fails
|
||||
// the kernel module was not installed or loaded correctly.
|
||||
if fp, err := os.Open("/dev/kvm"); err != nil {
|
||||
c.Accelerator = "tcg"
|
||||
} else {
|
||||
fp.Close()
|
||||
c.Accelerator = "kvm"
|
||||
}
|
||||
}
|
||||
log.Printf("use detected accelerator: %s", c.Accelerator)
|
||||
} else {
|
||||
log.Printf("use specified accelerator: %s", c.Accelerator)
|
||||
}
|
||||
|
||||
if c.MachineType == "" {
|
||||
c.MachineType = "pc"
|
||||
}
|
||||
|
||||
if c.OutputDir == "" {
|
||||
c.OutputDir = fmt.Sprintf("output-%s", c.PackerBuildName)
|
||||
}
|
||||
|
||||
if c.QemuBinary == "" {
|
||||
c.QemuBinary = "qemu-system-x86_64"
|
||||
}
|
||||
|
||||
if c.MemorySize < 10 {
|
||||
log.Printf("MemorySize %d is too small, using default: 512", c.MemorySize)
|
||||
c.MemorySize = 512
|
||||
}
|
||||
|
||||
if c.CpuCount < 1 {
|
||||
log.Printf("CpuCount %d too small, using default: 1", c.CpuCount)
|
||||
c.CpuCount = 1
|
||||
}
|
||||
|
||||
if c.VNCBindAddress == "" {
|
||||
c.VNCBindAddress = "127.0.0.1"
|
||||
}
|
||||
|
||||
if c.VNCPortMin == 0 {
|
||||
c.VNCPortMin = 5900
|
||||
}
|
||||
|
||||
if c.VNCPortMax == 0 {
|
||||
c.VNCPortMax = 6000
|
||||
}
|
||||
|
||||
if c.VMName == "" {
|
||||
c.VMName = fmt.Sprintf("packer-%s", c.PackerBuildName)
|
||||
}
|
||||
|
||||
if c.Format == "" {
|
||||
c.Format = "qcow2"
|
||||
}
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
|
||||
|
||||
if c.NetDevice == "" {
|
||||
c.NetDevice = "virtio-net"
|
||||
}
|
||||
|
||||
if c.DiskInterface == "" {
|
||||
c.DiskInterface = "virtio"
|
||||
}
|
||||
|
||||
if c.ISOSkipCache {
|
||||
c.ISOChecksum = "none"
|
||||
}
|
||||
isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx)
|
||||
warnings = append(warnings, isoWarnings...)
|
||||
errs = packer.MultiErrorAppend(errs, isoErrs...)
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
|
||||
commConfigWarnings, es := c.CommConfig.Prepare(&c.ctx)
|
||||
if len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
warnings = append(warnings, commConfigWarnings...)
|
||||
|
||||
if !(c.Format == "qcow2" || c.Format == "raw") {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
|
||||
}
|
||||
|
||||
if c.Format != "qcow2" {
|
||||
c.SkipCompaction = true
|
||||
c.DiskCompression = false
|
||||
}
|
||||
|
||||
if c.UseBackingFile {
|
||||
c.SkipCompaction = true
|
||||
if !(c.DiskImage && c.Format == "qcow2") {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.DiskImage && len(c.AdditionalDiskSize) > 0 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("disk_additional_size can only be used when disk_image is false"))
|
||||
}
|
||||
|
||||
if c.SkipResizeDisk && !(c.DiskImage) {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("skip_resize_disk can only be used when disk_image is true"))
|
||||
}
|
||||
|
||||
if _, ok := accels[c.Accelerator]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
|
||||
}
|
||||
|
||||
if _, ok := diskInterface[c.DiskInterface]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk interface type"))
|
||||
}
|
||||
|
||||
if _, ok := diskCache[c.DiskCache]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk cache type"))
|
||||
}
|
||||
|
||||
if _, ok := diskDiscard[c.DiskDiscard]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk discard type"))
|
||||
}
|
||||
|
||||
if _, ok := diskDZeroes[c.DetectZeroes]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk detect zeroes setting"))
|
||||
}
|
||||
|
||||
if !c.PackerForce {
|
||||
if _, err := os.Stat(c.OutputDir); err == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
fmt.Errorf("Output directory '%s' already exists. It must not exist.", c.OutputDir))
|
||||
}
|
||||
}
|
||||
|
||||
if c.VNCPortMin > c.VNCPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
}
|
||||
|
||||
if c.NetBridge != "" && runtime.GOOS != "linux" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("net_bridge is only supported in Linux based OSes"))
|
||||
}
|
||||
|
||||
if c.NetBridge != "" || c.VNCUsePassword {
|
||||
c.QMPEnable = true
|
||||
}
|
||||
|
||||
if c.QMPEnable && c.QMPSocketPath == "" {
|
||||
socketName := fmt.Sprintf("%s.monitor", c.VMName)
|
||||
c.QMPSocketPath = filepath.Join(c.OutputDir, socketName)
|
||||
}
|
||||
|
||||
if c.QemuArgs == nil {
|
||||
c.QemuArgs = make([][]string, 0)
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,670 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
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": "md5:0B0F137F17AC10944716020B018F8126",
|
||||
"iso_url": "http://www.google.com/",
|
||||
"ssh_username": "foo",
|
||||
packer.BuildNameConfigKey: "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if c.OutputDir != "output-foo" {
|
||||
t.Errorf("bad output dir: %s", c.OutputDir)
|
||||
}
|
||||
|
||||
if c.CommConfig.HostPortMin != 2222 {
|
||||
t.Errorf("bad min ssh host port: %d", c.CommConfig.HostPortMin)
|
||||
}
|
||||
|
||||
if c.CommConfig.HostPortMax != 4444 {
|
||||
t.Errorf("bad max ssh host port: %d", c.CommConfig.HostPortMax)
|
||||
}
|
||||
|
||||
if c.CommConfig.Comm.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.CommConfig.Comm.SSHPort)
|
||||
}
|
||||
|
||||
if c.VMName != "packer-foo" {
|
||||
t.Errorf("bad vm name: %s", c.VMName)
|
||||
}
|
||||
|
||||
if c.Format != "qcow2" {
|
||||
t.Errorf("bad format: %s", c.Format)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCBindAddress(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Test a default boot_wait
|
||||
delete(config, "vnc_bind_address")
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.VNCBindAddress != "127.0.0.1" {
|
||||
t.Fatalf("bad value: %s", c.VNCBindAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_DiskCompaction(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Bad
|
||||
config["skip_compaction"] = false
|
||||
config["disk_compression"] = true
|
||||
config["format"] = "img"
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if c.SkipCompaction != true {
|
||||
t.Fatalf("SkipCompaction should be true")
|
||||
}
|
||||
if c.DiskCompression != false {
|
||||
t.Fatalf("DiskCompression should be false")
|
||||
}
|
||||
|
||||
// Good
|
||||
config["skip_compaction"] = false
|
||||
config["disk_compression"] = true
|
||||
config["format"] = "qcow2"
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if c.SkipCompaction != false {
|
||||
t.Fatalf("SkipCompaction should be false")
|
||||
}
|
||||
if c.DiskCompression != true {
|
||||
t.Fatalf("DiskCompression should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||
type testcase struct {
|
||||
InputSize string
|
||||
OutputSize string
|
||||
ErrExpected bool
|
||||
}
|
||||
|
||||
testCases := []testcase{
|
||||
{"", "40960M", false}, // not provided
|
||||
{"12345", "12345M", false}, // no unit given, defaults to M
|
||||
{"12345x", "12345x", true}, // invalid unit
|
||||
{"12345T", "12345T", false}, // terabytes
|
||||
{"12345b", "12345b", false}, // bytes get preserved when set.
|
||||
{"60000M", "60000M", false}, // Original test case
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
// Set input disk size
|
||||
var c Config
|
||||
config := testConfig()
|
||||
delete(config, "disk_size")
|
||||
config["disk_size"] = tc.InputSize
|
||||
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if (err == nil) == tc.ErrExpected {
|
||||
t.Fatalf("bad: error when providing disk size %s; Err expected: %t; err recieved: %v", tc.InputSize, tc.ErrExpected, err)
|
||||
}
|
||||
|
||||
if c.DiskSize != tc.OutputSize {
|
||||
t.Fatalf("bad size: received: %s but expected %s", c.DiskSize, tc.OutputSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AdditionalDiskSize(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
config["disk_additional_size"] = []string{"1M"}
|
||||
config["disk_image"] = true
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
delete(config, "disk_image")
|
||||
config["disk_additional_size"] = []string{"1M"}
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if c.AdditionalDiskSize[0] != "1M" {
|
||||
t.Fatalf("bad size: %s", c.AdditionalDiskSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Format(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Bad
|
||||
config["format"] = "illegal value"
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Good
|
||||
config["format"] = "qcow2"
|
||||
c = Config{}
|
||||
warns, err = c.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"] = "raw"
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_UseBackingFile(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
config["use_backing_file"] = true
|
||||
|
||||
// Bad: iso_url is not a disk_image
|
||||
config["disk_image"] = false
|
||||
config["format"] = "qcow2"
|
||||
c = Config{}
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Bad: format is not 'qcow2'
|
||||
config["disk_image"] = true
|
||||
config["format"] = "raw"
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Good: iso_url is a disk image and format is 'qcow2'
|
||||
config["disk_image"] = true
|
||||
config["format"] = "qcow2"
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SkipResizeDisk(t *testing.T) {
|
||||
config := testConfig()
|
||||
config["skip_resize_disk"] = true
|
||||
config["disk_image"] = false
|
||||
|
||||
var c Config
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Errorf("unexpected warns when calling prepare with skip_resize_disk set to true: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("setting skip_resize_disk to true when disk_image is false should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "floppy_files")
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
}
|
||||
|
||||
if len(c.FloppyFiles) != 0 {
|
||||
t.Fatalf("bad: %#v", c.FloppyFiles)
|
||||
}
|
||||
|
||||
floppies_path := "../../common/test-fixtures/floppies"
|
||||
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
|
||||
if !reflect.DeepEqual(c.FloppyFiles, expected) {
|
||||
t.Fatalf("bad: %#v", c.FloppyFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
|
||||
c = Config{}
|
||||
_, errs := c.Prepare(config)
|
||||
if errs == nil {
|
||||
t.Fatalf("Nonexistent floppies should trigger multierror")
|
||||
}
|
||||
|
||||
if len(errs.(*packer.MultiError).Errors) != 2 {
|
||||
t.Fatalf("Multierror should work and report 2 errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_OutputDir(t *testing.T) {
|
||||
var c Config
|
||||
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
|
||||
c = Config{}
|
||||
warns, err := c.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"
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Test with a bad value
|
||||
config["shutdown_timeout"] = "this is not good"
|
||||
warns, err := c.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"
|
||||
c = Config{}
|
||||
warns, err = c.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 c Config
|
||||
config := testConfig()
|
||||
|
||||
// Bad
|
||||
config["host_port_min"] = 1000
|
||||
config["host_port_max"] = 500
|
||||
c = Config{}
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Bad
|
||||
config["host_port_min"] = -500
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Good
|
||||
config["host_port_min"] = 500
|
||||
config["host_port_max"] = 1000
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHPrivateKey(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
config["ssh_private_key_file"] = ""
|
||||
c = Config{}
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
config["ssh_private_key_file"] = "/i/dont/exist"
|
||||
c = Config{}
|
||||
warns, err = c.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_private_key_file"] = tf.Name()
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good contents
|
||||
if _, err := tf.Seek(0, 0); err != nil {
|
||||
t.Fatalf("errorf getting key")
|
||||
}
|
||||
if err := tf.Truncate(0); err != nil {
|
||||
t.Fatalf("errorf getting key")
|
||||
}
|
||||
if _, err := tf.Write([]byte(testPem)); err != nil {
|
||||
t.Fatalf("errorf getting key")
|
||||
}
|
||||
config["ssh_private_key_file"] = tf.Name()
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Test a default boot_wait
|
||||
delete(config, "ssh_timeout")
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a bad value
|
||||
config["ssh_timeout"] = "this is not good"
|
||||
c = Config{}
|
||||
warns, err = c.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_timeout"] = "5s"
|
||||
c = Config{}
|
||||
warns, err = c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_QemuArgs(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
|
||||
// Test with empty
|
||||
delete(config, "qemuargs")
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c.QemuArgs, [][]string{}) {
|
||||
t.Fatalf("bad: %#v", c.QemuArgs)
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
config["qemuargs"] = [][]interface{}{
|
||||
{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
c = Config{}
|
||||
warns, err = c.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", "baz"},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c.QemuArgs, expected) {
|
||||
t.Fatalf("bad: %#v", c.QemuArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCPassword(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
config["vnc_use_password"] = true
|
||||
config["output_directory"] = "not-a-real-directory"
|
||||
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
expected := filepath.Join("not-a-real-directory", "packer-foo.monitor")
|
||||
if !reflect.DeepEqual(c.QMPSocketPath, expected) {
|
||||
t.Fatalf("Bad QMP socket Path: %s", c.QMPSocketPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommConfigPrepare_BackwardsCompatibility(t *testing.T) {
|
||||
var c Config
|
||||
config := testConfig()
|
||||
hostPortMin := 1234
|
||||
hostPortMax := 4321
|
||||
sshTimeout := 2 * time.Minute
|
||||
|
||||
config["ssh_wait_timeout"] = sshTimeout
|
||||
config["ssh_host_port_min"] = hostPortMin
|
||||
config["ssh_host_port_max"] = hostPortMax
|
||||
|
||||
warns, err := c.Prepare(config)
|
||||
if len(warns) == 0 {
|
||||
t.Fatalf("should have deprecation warn")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if c.CommConfig.Comm.SSHTimeout != sshTimeout {
|
||||
t.Fatalf("SSHTimeout should be %s for backwards compatibility, but it was %s", sshTimeout.String(), c.CommConfig.Comm.SSHTimeout.String())
|
||||
}
|
||||
|
||||
if c.CommConfig.HostPortMin != hostPortMin {
|
||||
t.Fatalf("HostPortMin should be %d for backwards compatibility, but it was %d", hostPortMin, c.CommConfig.HostPortMin)
|
||||
}
|
||||
|
||||
if c.CommConfig.HostPortMax != hostPortMax {
|
||||
t.Fatalf("HostPortMax should be %d for backwards compatibility, but it was %d", hostPortMax, c.CommConfig.HostPortMax)
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ import (
|
|||
// This step adds a NAT port forwarding definition so that SSH or WinRM is available
|
||||
// on the guest machine.
|
||||
type stepPortForward struct {
|
||||
CommunicatorType string
|
||||
NetBridge string
|
||||
|
||||
l *net.Listener
|
||||
}
|
||||
|
||||
|
@ -20,6 +23,15 @@ func (s *stepPortForward) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.CommunicatorType == "none" {
|
||||
ui.Message("No communicator is set; skipping port forwarding setup.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
if s.NetBridge != "" {
|
||||
ui.Message("net_bridge is set; skipping port forwarding setup.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
commHostPort := config.CommConfig.Comm.Port()
|
||||
|
||||
if config.CommConfig.SkipNatMapping {
|
||||
|
|
|
@ -15,8 +15,7 @@ import (
|
|||
|
||||
// stepRun runs the virtual machine
|
||||
type stepRun struct {
|
||||
BootDrive string
|
||||
Message string
|
||||
DiskImage bool
|
||||
}
|
||||
|
||||
type qemuArgsTemplateData struct {
|
||||
|
@ -32,9 +31,17 @@ func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.S
|
|||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say(s.Message)
|
||||
// Run command is different depending whether we're booting from an
|
||||
// installation CD or a pre-baked image
|
||||
bootDrive := "once=d"
|
||||
message := "Starting VM, booting from CD-ROM"
|
||||
if s.DiskImage {
|
||||
bootDrive = "c"
|
||||
message = "Starting VM, booting disk image"
|
||||
}
|
||||
ui.Say(message)
|
||||
|
||||
command, err := getCommandArgs(s.BootDrive, state)
|
||||
command, err := getCommandArgs(bootDrive, state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error processing QemuArgs: %s", err)
|
||||
ui.Error(err.Error())
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func runTestState(t *testing.T, config *Config) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
|
||||
state.Put("ui", packer.TestUi(t))
|
||||
state.Put("config", config)
|
||||
|
||||
d := new(DriverMock)
|
||||
d.VersionResult = "3.0.0"
|
||||
state.Put("driver", d)
|
||||
|
||||
state.Put("commHostPort", 5000)
|
||||
state.Put("floppy_path", "fake_floppy_path")
|
||||
state.Put("http_ip", "127.0.0.1")
|
||||
state.Put("http_port", 1234)
|
||||
state.Put("iso_path", "/path/to/test.iso")
|
||||
state.Put("qemu_disk_paths", []string{})
|
||||
state.Put("vnc_port", 5905)
|
||||
state.Put("vnc_password", "fake_vnc_password")
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func Test_getCommandArgs(t *testing.T) {
|
||||
state := runTestState(t, &Config{})
|
||||
|
||||
args, err := getCommandArgs("", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, "unexpected generated args")
|
||||
}
|
||||
|
||||
func Test_CDFilesPath(t *testing.T) {
|
||||
// cd_path is set and DiskImage is false
|
||||
state := runTestState(t, &Config{})
|
||||
state.Put("cd_path", "fake_cd_path.iso")
|
||||
|
||||
args, err := getCommandArgs("", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
|
||||
|
||||
// cd_path is set and DiskImage is true
|
||||
config := &Config{
|
||||
DiskImage: true,
|
||||
DiskInterface: "virtio-scsi",
|
||||
}
|
||||
state = runTestState(t, config)
|
||||
state.Put("cd_path", "fake_cd_path.iso")
|
||||
|
||||
args, err = getCommandArgs("c", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
expected = []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "c",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905",
|
||||
"-machine", "type=,accel=",
|
||||
"-device", ",netdev=user.0",
|
||||
"-device", "virtio-scsi-pci,id=scsi0",
|
||||
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
|
||||
"-drive", "if=none,file=,id=drive0,cache=,discard=,format=,detect-zeroes=",
|
||||
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, fmt.Sprintf("unexpected generated args: %#v", args))
|
||||
}
|
||||
|
||||
func Test_OptionalConfigOptionsGetSet(t *testing.T) {
|
||||
c := &Config{
|
||||
VNCUsePassword: true,
|
||||
QMPEnable: true,
|
||||
QMPSocketPath: "qmp_path",
|
||||
VMName: "MyFancyName",
|
||||
MachineType: "pc",
|
||||
Accelerator: "hvf",
|
||||
}
|
||||
|
||||
state := runTestState(t, c)
|
||||
|
||||
args, err := getCommandArgs("once=d", state)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have an error getting args. Error: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"-display", "gtk",
|
||||
"-m", "0M",
|
||||
"-boot", "once=d",
|
||||
"-fda", "fake_floppy_path",
|
||||
"-name", "MyFancyName",
|
||||
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
|
||||
"-vnc", ":5905,password",
|
||||
"-machine", "type=pc,accel=hvf",
|
||||
"-device", ",netdev=user.0",
|
||||
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
|
||||
"-qmp", "unix:qmp_path,server,nowait",
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set.")
|
||||
}
|
|
@ -19,19 +19,31 @@ import (
|
|||
// This step waits for the guest address to become available in the network
|
||||
// bridge, then it sets the guestAddress state property.
|
||||
type stepWaitGuestAddress struct {
|
||||
CommunicatorType string
|
||||
NetBridge string
|
||||
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (s *stepWaitGuestAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.CommunicatorType == "none" {
|
||||
ui.Message("No communicator is configured -- skipping StepWaitGuestAddress")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
if s.NetBridge == "" {
|
||||
ui.Message("Not using a NetBridge -- skipping StepWaitGuestAddress")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
qmpMonitor := state.Get("qmp_monitor").(*qmp.SocketMonitor)
|
||||
ctx, cancel := context.WithTimeout(ctx, s.timeout)
|
||||
defer cancel()
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", config.NetBridge))
|
||||
ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", s.NetBridge))
|
||||
for {
|
||||
guestAddress := getGuestAddress(qmpMonitor, config.NetBridge, "user.0")
|
||||
guestAddress := getGuestAddress(qmpMonitor, s.NetBridge, "user.0")
|
||||
if guestAddress != "" {
|
||||
log.Printf("Found guest address %s", guestAddress)
|
||||
state.Put("guestAddress", guestAddress)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Code generated from the comments of the Config struct in builder/qemu/builder.go; DO NOT EDIT MANUALLY -->
|
||||
<!-- Code generated from the comments of the Config struct in builder/qemu/config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `iso_skip_cache` (bool) - Use iso from provided url. Qemu must support
|
||||
curl block device. This defaults to `false`.
|
||||
|
|
Loading…
Reference in New Issue