remote qemu plugin

This commit is contained in:
sylviamoss 2021-04-19 16:28:12 +02:00
parent 88f8feecfe
commit 642ed07476
41 changed files with 0 additions and 6016 deletions

View File

@ -1,38 +0,0 @@
package qemu
import (
"fmt"
"os"
)
// Artifact is the result of running the Qemu builder, namely a set
// of files associated with the resulting machine.
type Artifact struct {
dir string
f []string
state map[string]interface{}
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return a.f
}
func (*Artifact) Id() string {
return "VM"
}
func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *Artifact) State(name string) interface{} {
return a.state[name]
}
func (a *Artifact) Destroy() error {
return os.RemoveAll(a.dir)
}

View File

@ -1,229 +0,0 @@
package qemu
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
const BuilderId = "transcend.qemu"
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
warnings, errs := b.config.Prepare(raws...)
if errs != nil {
return nil, warnings, errs
}
return nil, warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
// Create the driver that we'll use to communicate with Qemu
driver, err := b.newDriver(b.config.QemuBinary)
if err != nil {
return nil, fmt.Errorf("Failed creating Qemu driver: %s", err)
}
steps := []multistep.Step{}
if !b.config.ISOSkipCache {
steps = append(steps, &commonsteps.StepDownload{
Checksum: b.config.ISOChecksum,
Description: "ISO",
Extension: b.config.TargetExtension,
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),
&commonsteps.StepCreateFloppy{
Files: b.config.FloppyConfig.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel,
},
&commonsteps.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&stepCreateDisk{
AdditionalDiskSize: b.config.AdditionalDiskSize,
DiskImage: b.config.DiskImage,
DiskSize: b.config.DiskSize,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
UseBackingFile: b.config.UseBackingFile,
VMName: b.config.VMName,
QemuImgArgs: b.config.QemuImgArgs,
},
&stepCopyDisk{
DiskImage: b.config.DiskImage,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
UseBackingFile: b.config.UseBackingFile,
VMName: b.config.VMName,
},
&stepResizeDisk{
DiskCompression: b.config.DiskCompression,
DiskImage: b.config.DiskImage,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
SkipResizeDisk: b.config.SkipResizeDisk,
VMName: b.config.VMName,
DiskSize: b.config.DiskSize,
QemuImgArgs: b.config.QemuImgArgs,
},
new(stepHTTPIPDiscover),
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&stepPortForward{
CommunicatorType: b.config.CommConfig.Comm.Type,
NetBridge: b.config.NetBridge,
},
new(stepConfigureVNC),
&stepRun{
DiskImage: b.config.DiskImage,
},
&stepConfigureQMP{
QMPSocketPath: b.config.QMPSocketPath,
},
&stepTypeBootCommand{},
&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(commonsteps.StepProvision),
&commonsteps.StepCleanupTempKeys{
Comm: &b.config.CommConfig.Comm,
},
&stepShutdown{
ShutdownTimeout: b.config.ShutdownTimeout,
ShutdownCommand: b.config.ShutdownCommand,
Comm: &b.config.CommConfig.Comm,
},
&stepConvertDisk{
DiskCompression: b.config.DiskCompression,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
SkipCompaction: b.config.SkipCompaction,
VMName: b.config.VMName,
QemuImgArgs: b.config.QemuImgArgs,
},
)
// Setup the state bag
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("debug", b.config.PackerDebug)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Run
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
// Compile the artifact list
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
return nil, err
}
artifact := &Artifact{
dir: b.config.OutputDir,
f: files,
state: make(map[string]interface{}),
}
artifact.state["generated_data"] = state.Get("generated_data")
artifact.state["diskName"] = b.config.VMName
// placed in state in step_create_disk.go
diskpaths, ok := state.Get("qemu_disk_paths").([]string)
if ok {
artifact.state["diskPaths"] = diskpaths
}
artifact.state["diskType"] = b.config.Format
artifact.state["diskSize"] = b.config.DiskSize
artifact.state["domainType"] = b.config.Accelerator
return artifact, nil
}
func (b *Builder) newDriver(qemuBinary string) (Driver, error) {
qemuPath, err := exec.LookPath(qemuBinary)
if err != nil {
return nil, err
}
qemuImgPath, err := exec.LookPath("qemu-img")
if err != nil {
return nil, err
}
log.Printf("Qemu path: %s, Qemu Image page: %s", qemuPath, qemuImgPath)
driver := &QemuDriver{
QemuPath: qemuPath,
QemuImgPath: qemuImgPath,
}
if err := driver.Verify(); err != nil {
return nil, err
}
return driver, nil
}

View File

@ -1,15 +0,0 @@
package qemu
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Error("Builder must implement builder.")
}
}

View File

@ -1,73 +0,0 @@
//go:generate packer-sdc struct-markdown
package qemu
import (
"errors"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
type CommConfig struct {
Comm communicator.Config `mapstructure:",squash"`
// The minimum port to use for the Communicator port on the host machine which is forwarded
// to the SSH or WinRM port on the guest machine. By default this is 2222.
HostPortMin int `mapstructure:"host_port_min" required:"false"`
// The maximum port to use for the Communicator port on the host machine which is forwarded
// to the SSH or WinRM port on the guest machine. Because Packer often runs in parallel,
// Packer will choose a randomly available port in this range to use as the
// host port. By default this is 4444.
HostPortMax int `mapstructure:"host_port_max" required:"false"`
// Defaults to false. When enabled, Packer
// does not setup forwarded port mapping for communicator (SSH or WinRM) requests and uses ssh_port or winrm_port
// on the host to communicate to the virtual machine.
SkipNatMapping bool `mapstructure:"skip_nat_mapping" required:"false"`
// These are deprecated, but we keep them around for backwards compatibility
// TODO: remove later
SSHHostPortMin int `mapstructure:"ssh_host_port_min" required:"false"`
// TODO: remove later
SSHHostPortMax int `mapstructure:"ssh_host_port_max"`
}
func (c *CommConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs []error) {
// Backwards compatibility
if c.SSHHostPortMin != 0 {
warnings = append(warnings, "ssh_host_port_min is deprecated and is being replaced by host_port_min. "+
"Please, update your template to use host_port_min. In future versions of Packer, inclusion of ssh_host_port_min will error your builds.")
c.HostPortMin = c.SSHHostPortMin
}
// Backwards compatibility
if c.SSHHostPortMax != 0 {
warnings = append(warnings, "ssh_host_port_max is deprecated and is being replaced by host_port_max. "+
"Please, update your template to use host_port_max. In future versions of Packer, inclusion of ssh_host_port_max will error your builds.")
c.HostPortMax = c.SSHHostPortMax
}
if c.Comm.SSHHost == "" && c.SkipNatMapping {
c.Comm.SSHHost = "127.0.0.1"
c.Comm.WinRMHost = "127.0.0.1"
}
if c.HostPortMin == 0 {
c.HostPortMin = 2222
}
if c.HostPortMax == 0 {
c.HostPortMax = 4444
}
errs = c.Comm.Prepare(ctx)
if c.HostPortMin > c.HostPortMax {
errs = append(errs,
errors.New("host_port_min must be less than host_port_max"))
}
if c.HostPortMin < 0 {
errs = append(errs, errors.New("host_port_min must be positive"))
}
return
}

View File

@ -1,145 +0,0 @@
package qemu
import (
"io/ioutil"
"os"
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
func testCommConfig() *CommConfig {
return &CommConfig{
Comm: communicator.Config{
SSH: communicator.SSH{
SSHUsername: "foo",
},
},
}
}
func TestCommConfigPrepare(t *testing.T) {
c := testCommConfig()
warns, errs := c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
if c.HostPortMin != 2222 {
t.Errorf("bad min communicator host port: %d", c.HostPortMin)
}
if c.HostPortMax != 4444 {
t.Errorf("bad max communicator host port: %d", c.HostPortMax)
}
if c.Comm.SSHPort != 22 {
t.Errorf("bad communicator port: %d", c.Comm.SSHPort)
}
}
func TestCommConfigPrepare_SSHHostPort(t *testing.T) {
var c *CommConfig
var errs []error
var warns []string
// Bad
c = testCommConfig()
c.HostPortMin = 1000
c.HostPortMax = 500
warns, errs = c.Prepare(interpolate.NewContext())
if len(errs) == 0 {
t.Fatalf("bad: %#v", errs)
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
// Good
c = testCommConfig()
c.HostPortMin = 50
c.HostPortMax = 500
warns, errs = c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("should not have error: %s", errs)
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
}
func TestCommConfigPrepare_SSHPrivateKey(t *testing.T) {
var c *CommConfig
var errs []error
var warns []string
c = testCommConfig()
c.Comm.SSHPrivateKeyFile = ""
warns, errs = c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
c = testCommConfig()
c.Comm.SSHPrivateKeyFile = "/i/dont/exist"
warns, errs = c.Prepare(interpolate.NewContext())
if len(errs) == 0 {
t.Fatal("should have error")
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
c = testCommConfig()
c.Comm.SSHPrivateKeyFile = tf.Name()
warns, errs = c.Prepare(interpolate.NewContext())
if len(errs) == 0 {
t.Fatal("should have error")
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
// Test good contents
_, err = tf.Seek(0, 0)
if err != nil {
t.Fatalf("err: %s", err)
}
err = tf.Truncate(0)
if err != nil {
t.Fatalf("err: %s", err)
}
_, err = tf.Write([]byte(testPem))
if err != nil {
t.Fatalf("err: %s", err)
}
c = testCommConfig()
c.Comm.SSHPrivateKeyFile = tf.Name()
warns, errs = c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if len(warns) != 0 {
t.Fatal("should not have any warnings")
}
}

View File

@ -1,642 +0,0 @@
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,QemuImgArgs
package qemu
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/shutdowncommand"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/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 QemuImgArgs struct {
Convert []string `mapstructure:"convert" required:"false"`
Create []string `mapstructure:"create" required:"false"`
Resize []string `mapstructure:"resize" required:"false"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
commonsteps.HTTPConfig `mapstructure:",squash"`
commonsteps.ISOConfig `mapstructure:",squash"`
bootcommand.VNCConfig `mapstructure:",squash"`
shutdowncommand.ShutdownConfig `mapstructure:",squash"`
CommConfig CommConfig `mapstructure:",squash"`
commonsteps.FloppyConfig `mapstructure:",squash"`
commonsteps.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 firmware file to be used by QEMU, which is to be set by the -bios
// option of QEMU. Particularly, this option can be set to use EFI instead
// of BIOS, by using "OVMF.fd" from OpenFirmware.
// If unset, no -bios option is passed to QEMU, using the default of QEMU.
// Also see the QEMU documentation.
Firmware string `mapstructure:"firmware" 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`. Due to a long-standing bug with
// `qemu-img convert` on OSX, sometimes the qemu-img convert call will
// create a corrupted image. If this is an issue for you, make sure that the
// the output format matches the input file's format, and Packer will
// perform a simple copy operation instead. See
// https://bugs.launchpad.net/qemu/+bug/1776920 for more details.
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 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 adding a "--drive" or "--device" override will mean that
// none of the default configuration Packer sets will be used. To see the
// defaults that Packer sets, look in your packer.log
// file (set PACKER_LOG=1 to get verbose logging) and search for the
// qemu-system-x86 command. The arguments are all printed for review, and
// you can use those arguments along with the template engines allowed
// by qemu-args to set up a working configuration that includes both the
// Packer defaults and your extra arguments.
//
// Another pitfall could be setting arguments like --no-acpi, which could
// break the ability to send power signal type commands
// (e.g., shutdown -P now) to the virtual machine, thus preventing proper
// shutdown.
//
// 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"`
// A map of custom arguments to pass to qemu-img commands, where the key
// is the subcommand, and the values are lists of strings for each flag.
// Example:
//
// In JSON:
// ```json
// {
// "qemu_img_args": {
// "convert": ["-o", "preallocation=full"],
// "resize": ["-foo", "bar"]
// }
// ```
// Please note
// that unlike qemuargs, these commands are not split into switch-value
// sub-arrays, because the basic elements in qemu-img calls are unlikely
// to need an actual override.
// The arguments will be constructed as follows:
// - Convert:
// Default is `qemu-img convert -O $format $sourcepath $targetpath`. Adding
// arguments ["-foo", "bar"] to qemu_img_args.convert will change this to
// `qemu-img convert -foo bar -O $format $sourcepath $targetpath`
// - Create:
// Default is `create -f $format $targetpath $size`. Adding arguments
// ["-foo", "bar"] to qemu_img_args.create will change this to
// "create -f qcow2 -foo bar target.qcow2 1234M"
// - Resize:
// Default is `qemu-img resize -f $format $sourcepath $size`. Adding
// arguments ["-foo", "bar"] to qemu_img_args.resize will change this to
// `qemu-img resize -f $format -foo bar $sourcepath $size`
QemuImgArgs QemuImgArgs `mapstructure:"qemu_img_args" 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.
// The minimum port cannot be set below 5900 due to a quirk in how QEMU parses
// vnc display address.
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{
PluginType: BuilderId,
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 *packersdk.MultiError
warnings := make([]string, 0)
errs = packersdk.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 = packersdk.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 = packersdk.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packersdk.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
errs = packersdk.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 = packersdk.MultiErrorAppend(errs, isoErrs...)
errs = packersdk.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
commConfigWarnings, es := c.CommConfig.Prepare(&c.ctx)
if len(es) > 0 {
errs = packersdk.MultiErrorAppend(errs, es...)
}
warnings = append(warnings, commConfigWarnings...)
if !(c.Format == "qcow2" || c.Format == "raw") {
errs = packersdk.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 = packersdk.MultiErrorAppend(
errs, errors.New("use_backing_file can only be enabled for QCOW2 images and when disk_image is true"))
}
}
if c.SkipResizeDisk && !(c.DiskImage) {
errs = packersdk.MultiErrorAppend(
errs, errors.New("skip_resize_disk can only be used when disk_image is true"))
}
if _, ok := accels[c.Accelerator]; !ok {
errs = packersdk.MultiErrorAppend(
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
}
if _, ok := diskInterface[c.DiskInterface]; !ok {
errs = packersdk.MultiErrorAppend(
errs, errors.New("unrecognized disk interface type"))
}
if _, ok := diskCache[c.DiskCache]; !ok {
errs = packersdk.MultiErrorAppend(
errs, errors.New("unrecognized disk cache type"))
}
if _, ok := diskDiscard[c.DiskDiscard]; !ok {
errs = packersdk.MultiErrorAppend(
errs, errors.New("unrecognized disk discard type"))
}
if _, ok := diskDZeroes[c.DetectZeroes]; !ok {
errs = packersdk.MultiErrorAppend(
errs, errors.New("unrecognized disk detect zeroes setting"))
}
if !c.PackerForce {
if _, err := os.Stat(c.OutputDir); err == nil {
errs = packersdk.MultiErrorAppend(
errs,
fmt.Errorf("Output directory '%s' already exists. It must not exist.", c.OutputDir))
}
}
if c.VNCPortMin < 5900 {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min cannot be below 5900"))
}
if c.VNCPortMin > 65535 || c.VNCPortMax > 65535 {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("vmc_port_min and vnc_port_max must both be below 65535 to be valid TCP ports"))
}
if c.VNCPortMin > c.VNCPortMax {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
if c.NetBridge != "" && runtime.GOOS != "linux" {
errs = packersdk.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
}

View File

@ -1,298 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package qemu
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc" hcl:"disable_vnc"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command" hcl:"shutdown_command"`
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout" hcl:"shutdown_timeout"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
HostPortMin *int `mapstructure:"host_port_min" required:"false" cty:"host_port_min" hcl:"host_port_min"`
HostPortMax *int `mapstructure:"host_port_max" required:"false" cty:"host_port_max" hcl:"host_port_max"`
SkipNatMapping *bool `mapstructure:"skip_nat_mapping" required:"false" cty:"skip_nat_mapping" hcl:"skip_nat_mapping"`
SSHHostPortMin *int `mapstructure:"ssh_host_port_min" required:"false" cty:"ssh_host_port_min" hcl:"ssh_host_port_min"`
SSHHostPortMax *int `mapstructure:"ssh_host_port_max" cty:"ssh_host_port_max" hcl:"ssh_host_port_max"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
ISOSkipCache *bool `mapstructure:"iso_skip_cache" required:"false" cty:"iso_skip_cache" hcl:"iso_skip_cache"`
Accelerator *string `mapstructure:"accelerator" required:"false" cty:"accelerator" hcl:"accelerator"`
AdditionalDiskSize []string `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
CpuCount *int `mapstructure:"cpus" required:"false" cty:"cpus" hcl:"cpus"`
Firmware *string `mapstructure:"firmware" required:"false" cty:"firmware" hcl:"firmware"`
DiskInterface *string `mapstructure:"disk_interface" required:"false" cty:"disk_interface" hcl:"disk_interface"`
DiskSize *string `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
SkipResizeDisk *bool `mapstructure:"skip_resize_disk" required:"false" cty:"skip_resize_disk" hcl:"skip_resize_disk"`
DiskCache *string `mapstructure:"disk_cache" required:"false" cty:"disk_cache" hcl:"disk_cache"`
DiskDiscard *string `mapstructure:"disk_discard" required:"false" cty:"disk_discard" hcl:"disk_discard"`
DetectZeroes *string `mapstructure:"disk_detect_zeroes" required:"false" cty:"disk_detect_zeroes" hcl:"disk_detect_zeroes"`
SkipCompaction *bool `mapstructure:"skip_compaction" required:"false" cty:"skip_compaction" hcl:"skip_compaction"`
DiskCompression *bool `mapstructure:"disk_compression" required:"false" cty:"disk_compression" hcl:"disk_compression"`
Format *string `mapstructure:"format" required:"false" cty:"format" hcl:"format"`
Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"`
DiskImage *bool `mapstructure:"disk_image" required:"false" cty:"disk_image" hcl:"disk_image"`
UseBackingFile *bool `mapstructure:"use_backing_file" required:"false" cty:"use_backing_file" hcl:"use_backing_file"`
MachineType *string `mapstructure:"machine_type" required:"false" cty:"machine_type" hcl:"machine_type"`
MemorySize *int `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
NetDevice *string `mapstructure:"net_device" required:"false" cty:"net_device" hcl:"net_device"`
NetBridge *string `mapstructure:"net_bridge" required:"false" cty:"net_bridge" hcl:"net_bridge"`
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
QemuArgs [][]string `mapstructure:"qemuargs" required:"false" cty:"qemuargs" hcl:"qemuargs"`
QemuImgArgs *FlatQemuImgArgs `mapstructure:"qemu_img_args" required:"false" cty:"qemu_img_args" hcl:"qemu_img_args"`
QemuBinary *string `mapstructure:"qemu_binary" required:"false" cty:"qemu_binary" hcl:"qemu_binary"`
QMPEnable *bool `mapstructure:"qmp_enable" required:"false" cty:"qmp_enable" hcl:"qmp_enable"`
QMPSocketPath *string `mapstructure:"qmp_socket_path" required:"false" cty:"qmp_socket_path" hcl:"qmp_socket_path"`
UseDefaultDisplay *bool `mapstructure:"use_default_display" required:"false" cty:"use_default_display" hcl:"use_default_display"`
Display *string `mapstructure:"display" required:"false" cty:"display" hcl:"display"`
VNCBindAddress *string `mapstructure:"vnc_bind_address" required:"false" cty:"vnc_bind_address" hcl:"vnc_bind_address"`
VNCUsePassword *bool `mapstructure:"vnc_use_password" required:"false" cty:"vnc_use_password" hcl:"vnc_use_password"`
VNCPortMin *int `mapstructure:"vnc_port_min" required:"false" cty:"vnc_port_min" hcl:"vnc_port_min"`
VNCPortMax *int `mapstructure:"vnc_port_max" cty:"vnc_port_max" hcl:"vnc_port_max"`
VMName *string `mapstructure:"vm_name" required:"false" cty:"vm_name" hcl:"vm_name"`
CDROMInterface *string `mapstructure:"cdrom_interface" required:"false" cty:"cdrom_interface" hcl:"cdrom_interface"`
RunOnce *bool `mapstructure:"run_once" cty:"run_once" hcl:"run_once"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
"shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"host_port_min": &hcldec.AttrSpec{Name: "host_port_min", Type: cty.Number, Required: false},
"host_port_max": &hcldec.AttrSpec{Name: "host_port_max", Type: cty.Number, Required: false},
"skip_nat_mapping": &hcldec.AttrSpec{Name: "skip_nat_mapping", Type: cty.Bool, Required: false},
"ssh_host_port_min": &hcldec.AttrSpec{Name: "ssh_host_port_min", Type: cty.Number, Required: false},
"ssh_host_port_max": &hcldec.AttrSpec{Name: "ssh_host_port_max", Type: cty.Number, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"iso_skip_cache": &hcldec.AttrSpec{Name: "iso_skip_cache", Type: cty.Bool, Required: false},
"accelerator": &hcldec.AttrSpec{Name: "accelerator", Type: cty.String, Required: false},
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.String), Required: false},
"cpus": &hcldec.AttrSpec{Name: "cpus", Type: cty.Number, Required: false},
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
"disk_interface": &hcldec.AttrSpec{Name: "disk_interface", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false},
"skip_resize_disk": &hcldec.AttrSpec{Name: "skip_resize_disk", Type: cty.Bool, Required: false},
"disk_cache": &hcldec.AttrSpec{Name: "disk_cache", Type: cty.String, Required: false},
"disk_discard": &hcldec.AttrSpec{Name: "disk_discard", Type: cty.String, Required: false},
"disk_detect_zeroes": &hcldec.AttrSpec{Name: "disk_detect_zeroes", Type: cty.String, Required: false},
"skip_compaction": &hcldec.AttrSpec{Name: "skip_compaction", Type: cty.Bool, Required: false},
"disk_compression": &hcldec.AttrSpec{Name: "disk_compression", Type: cty.Bool, Required: false},
"format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false},
"headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false},
"disk_image": &hcldec.AttrSpec{Name: "disk_image", Type: cty.Bool, Required: false},
"use_backing_file": &hcldec.AttrSpec{Name: "use_backing_file", Type: cty.Bool, Required: false},
"machine_type": &hcldec.AttrSpec{Name: "machine_type", Type: cty.String, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"net_device": &hcldec.AttrSpec{Name: "net_device", Type: cty.String, Required: false},
"net_bridge": &hcldec.AttrSpec{Name: "net_bridge", Type: cty.String, Required: false},
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
"qemuargs": &hcldec.AttrSpec{Name: "qemuargs", Type: cty.List(cty.List(cty.String)), Required: false},
"qemu_img_args": &hcldec.BlockSpec{TypeName: "qemu_img_args", Nested: hcldec.ObjectSpec((*FlatQemuImgArgs)(nil).HCL2Spec())},
"qemu_binary": &hcldec.AttrSpec{Name: "qemu_binary", Type: cty.String, Required: false},
"qmp_enable": &hcldec.AttrSpec{Name: "qmp_enable", Type: cty.Bool, Required: false},
"qmp_socket_path": &hcldec.AttrSpec{Name: "qmp_socket_path", Type: cty.String, Required: false},
"use_default_display": &hcldec.AttrSpec{Name: "use_default_display", Type: cty.Bool, Required: false},
"display": &hcldec.AttrSpec{Name: "display", Type: cty.String, Required: false},
"vnc_bind_address": &hcldec.AttrSpec{Name: "vnc_bind_address", Type: cty.String, Required: false},
"vnc_use_password": &hcldec.AttrSpec{Name: "vnc_use_password", Type: cty.Bool, Required: false},
"vnc_port_min": &hcldec.AttrSpec{Name: "vnc_port_min", Type: cty.Number, Required: false},
"vnc_port_max": &hcldec.AttrSpec{Name: "vnc_port_max", Type: cty.Number, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
"cdrom_interface": &hcldec.AttrSpec{Name: "cdrom_interface", Type: cty.String, Required: false},
"run_once": &hcldec.AttrSpec{Name: "run_once", Type: cty.Bool, Required: false},
}
return s
}
// FlatQemuImgArgs is an auto-generated flat version of QemuImgArgs.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatQemuImgArgs struct {
Convert []string `mapstructure:"convert" required:"false" cty:"convert" hcl:"convert"`
Create []string `mapstructure:"create" required:"false" cty:"create" hcl:"create"`
Resize []string `mapstructure:"resize" required:"false" cty:"resize" hcl:"resize"`
}
// FlatMapstructure returns a new FlatQemuImgArgs.
// FlatQemuImgArgs is an auto-generated flat version of QemuImgArgs.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*QemuImgArgs) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatQemuImgArgs)
}
// HCL2Spec returns the hcl spec of a QemuImgArgs.
// This spec is used by HCL to read the fields of QemuImgArgs.
// The decoded values from this spec will then be applied to a FlatQemuImgArgs.
func (*FlatQemuImgArgs) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"convert": &hcldec.AttrSpec{Name: "convert", Type: cty.List(cty.String), Required: false},
"create": &hcldec.AttrSpec{Name: "create", Type: cty.List(cty.String), Required: false},
"resize": &hcldec.AttrSpec{Name: "resize", Type: cty.List(cty.String), Required: false},
}
return s
}

View File

@ -1,695 +0,0 @@
package qemu
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/stretchr/testify/assert"
)
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",
common.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 not 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 := "../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.(*packersdk.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)
}
}
func TestBuilderPrepare_LoadQemuImgArgs(t *testing.T) {
var c Config
config := testConfig()
config["qemu_img_args"] = map[string][]string{
"convert": []string{"-o", "preallocation=full"},
"resize": []string{"-foo", "bar"},
"create": []string{"-baz", "bang"},
}
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)
}
assert.Equal(t, []string{"-o", "preallocation=full"},
c.QemuImgArgs.Convert, "Convert args not loaded properly")
assert.Equal(t, []string{"-foo", "bar"},
c.QemuImgArgs.Resize, "Resize args not loaded properly")
assert.Equal(t, []string{"-baz", "bang"},
c.QemuImgArgs.Create, "Create args not loaded properly")
}

View File

@ -1,247 +0,0 @@
package qemu
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"time"
"unicode"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
type DriverCancelCallback func(state multistep.StateBag) bool
// A driver is able to talk to qemu-system-x86_64 and perform certain
// operations with it.
type Driver interface {
// Copy bypasses qemu-img convert and directly copies an image
// that doesn't need converting.
Copy(string, string) error
// Stop stops a running machine, forcefully.
Stop() error
// Qemu executes the given command via qemu-system-x86_64
Qemu(qemuArgs ...string) error
// wait on shutdown of the VM with option to cancel
WaitForShutdown(<-chan struct{}) bool
// Qemu executes the given command via qemu-img
QemuImg(...string) error
// Verify checks to make sure that this driver should function
// properly. If there is any indication the driver can't function,
// this will return an error.
Verify() error
// Version reads the version of Qemu that is installed.
Version() (string, error)
}
type QemuDriver struct {
QemuPath string
QemuImgPath string
vmCmd *exec.Cmd
vmEndCh <-chan int
lock sync.Mutex
}
func (d *QemuDriver) Stop() error {
d.lock.Lock()
defer d.lock.Unlock()
if d.vmCmd != nil {
if err := d.vmCmd.Process.Kill(); err != nil {
return err
}
}
return nil
}
func (d *QemuDriver) Copy(sourceName, targetName string) error {
source, err := os.Open(sourceName)
if err != nil {
err = fmt.Errorf("Error opening iso for copy: %s", err)
return err
}
defer source.Close()
// Create will truncate an existing file
target, err := os.Create(targetName)
if err != nil {
err = fmt.Errorf("Error creating hard drive in output dir: %s", err)
return err
}
defer target.Close()
log.Printf("Copying %s to %s", source.Name(), target.Name())
bytes, err := io.Copy(target, source)
if err != nil {
err = fmt.Errorf("Error copying iso to output dir: %s", err)
return err
}
log.Printf(fmt.Sprintf("Copied %d bytes", bytes))
return nil
}
func (d *QemuDriver) Qemu(qemuArgs ...string) error {
d.lock.Lock()
defer d.lock.Unlock()
if d.vmCmd != nil {
panic("Existing VM state found")
}
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
log.Printf("Executing %s: %#v", d.QemuPath, qemuArgs)
cmd := exec.Command(d.QemuPath, qemuArgs...)
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
err := cmd.Start()
if err != nil {
err = fmt.Errorf("Error starting VM: %s", err)
return err
}
go logReader("Qemu stdout", stdout_r)
go logReader("Qemu stderr", stderr_r)
log.Printf("Started Qemu. Pid: %d", cmd.Process.Pid)
// Wait for Qemu to complete in the background, and mark when its done
endCh := make(chan int, 1)
go func() {
defer stderr_w.Close()
defer stdout_w.Close()
var exitCode int = 0
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitCode = status.ExitStatus()
} else {
exitCode = 254
}
}
}
endCh <- exitCode
d.lock.Lock()
defer d.lock.Unlock()
d.vmCmd = nil
d.vmEndCh = nil
}()
// Wait at least a couple seconds for an early fail from Qemu so
// we can report that.
select {
case exit := <-endCh:
if exit != 0 {
return fmt.Errorf("Qemu failed to start. Please run with PACKER_LOG=1 to get more info.")
}
case <-time.After(2 * time.Second):
}
// Setup our state so we know we are running
d.vmCmd = cmd
d.vmEndCh = endCh
return nil
}
func (d *QemuDriver) WaitForShutdown(cancelCh <-chan struct{}) bool {
d.lock.Lock()
endCh := d.vmEndCh
d.lock.Unlock()
if endCh == nil {
return true
}
select {
case <-endCh:
return true
case <-cancelCh:
return false
}
}
func (d *QemuDriver) QemuImg(args ...string) error {
var stdout, stderr bytes.Buffer
log.Printf("Executing qemu-img: %#v", args)
cmd := exec.Command(d.QemuImgPath, args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
stdoutString := strings.TrimSpace(stdout.String())
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("QemuImg error: %s", stderrString)
}
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
return err
}
func (d *QemuDriver) Verify() error {
return nil
}
func (d *QemuDriver) Version() (string, error) {
var stdout bytes.Buffer
cmd := exec.Command(d.QemuPath, "-version")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", err
}
versionOutput := strings.TrimSpace(stdout.String())
log.Printf("Qemu --version output: %s", versionOutput)
versionRe := regexp.MustCompile(`[\.[0-9]+]*`)
matches := versionRe.FindStringSubmatch(versionOutput)
if len(matches) == 0 {
return "", fmt.Errorf("No version found: %s", versionOutput)
}
log.Printf("Qemu version: %s", matches[0])
return matches[0], nil
}
func logReader(name string, r io.Reader) {
bufR := bufio.NewReader(r)
for {
line, err := bufR.ReadString('\n')
if line != "" {
line = strings.TrimRightFunc(line, unicode.IsSpace)
log.Printf("%s: %s", name, line)
}
if err == io.EOF {
break
}
}
}

View File

@ -1,74 +0,0 @@
package qemu
import "sync"
type DriverMock struct {
sync.Mutex
CopyCalled bool
CopyErr error
StopCalled bool
StopErr error
QemuCalls [][]string
QemuErrs []error
WaitForShutdownCalled bool
WaitForShutdownState bool
QemuImgCalled bool
QemuImgCalls []string
QemuImgErrs []error
VerifyCalled bool
VerifyErr error
VersionCalled bool
VersionResult string
VersionErr error
}
func (d *DriverMock) Copy(source, dst string) error {
d.CopyCalled = true
return d.CopyErr
}
func (d *DriverMock) Stop() error {
d.StopCalled = true
return d.StopErr
}
func (d *DriverMock) Qemu(args ...string) error {
d.QemuCalls = append(d.QemuCalls, args)
if len(d.QemuErrs) >= len(d.QemuCalls) {
return d.QemuErrs[len(d.QemuCalls)-1]
}
return nil
}
func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool {
d.WaitForShutdownCalled = true
return d.WaitForShutdownState
}
func (d *DriverMock) QemuImg(args ...string) error {
d.QemuImgCalled = true
d.QemuImgCalls = append(d.QemuImgCalls, args...)
if len(d.QemuImgErrs) >= len(d.QemuImgCalls) {
return d.QemuImgErrs[len(d.QemuImgCalls)-1]
}
return nil
}
func (d *DriverMock) Verify() error {
d.VerifyCalled = true
return d.VerifyErr
}
func (d *DriverMock) Version() (string, error) {
d.VersionCalled = true
return d.VersionResult, d.VersionErr
}

View File

@ -1,135 +0,0 @@
package qemu
import (
"encoding/json"
"fmt"
"strings"
"github.com/digitalocean/go-qemu/qmp"
)
type qomListRequest struct {
Execute string `json:"execute"`
Arguments qomListRequestArguments `json:"arguments"`
}
type qomListRequestArguments struct {
Path string `json:"path"`
}
type qomListResponse struct {
Return []qomListReturn `json:"return"`
}
type qomListReturn struct {
Name string `json:"name"`
Type string `json:"type"`
}
func qmpQomList(qmpMonitor *qmp.SocketMonitor, path string) ([]qomListReturn, error) {
request, _ := json.Marshal(qomListRequest{
Execute: "qom-list",
Arguments: qomListRequestArguments{
Path: path,
},
})
result, err := qmpMonitor.Run(request)
if err != nil {
return nil, err
}
var response qomListResponse
if err := json.Unmarshal(result, &response); err != nil {
return nil, err
}
return response.Return, nil
}
type qomGetRequest struct {
Execute string `json:"execute"`
Arguments qomGetRequestArguments `json:"arguments"`
}
type qomGetRequestArguments struct {
Path string `json:"path"`
Property string `json:"property"`
}
type qomGetResponse struct {
Return string `json:"return"`
}
func qmpQomGet(qmpMonitor *qmp.SocketMonitor, path string, property string) (string, error) {
request, _ := json.Marshal(qomGetRequest{
Execute: "qom-get",
Arguments: qomGetRequestArguments{
Path: path,
Property: property,
},
})
result, err := qmpMonitor.Run(request)
if err != nil {
return "", err
}
var response qomGetResponse
if err := json.Unmarshal(result, &response); err != nil {
return "", err
}
return response.Return, nil
}
type netDevice struct {
Path string
Name string
Type string
MacAddress string
}
func getNetDevices(qmpMonitor *qmp.SocketMonitor) ([]netDevice, error) {
devices := []netDevice{}
for _, parentPath := range []string{"/machine/peripheral", "/machine/peripheral-anon"} {
listResponse, err := qmpQomList(qmpMonitor, parentPath)
if err != nil {
return nil, fmt.Errorf("failed to get qmp qom list %v: %w", parentPath, err)
}
for _, p := range listResponse {
if strings.HasPrefix(p.Type, "child<") {
path := fmt.Sprintf("%s/%s", parentPath, p.Name)
r, err := qmpQomList(qmpMonitor, path)
if err != nil {
return nil, fmt.Errorf("failed to get qmp qom list %v: %w", path, err)
}
isNetdev := false
for _, d := range r {
if d.Name == "netdev" {
isNetdev = true
break
}
}
if isNetdev {
device := netDevice{
Path: path,
}
for _, d := range r {
if d.Name != "type" && d.Name != "netdev" && d.Name != "mac" {
continue
}
value, err := qmpQomGet(qmpMonitor, path, d.Name)
if err != nil {
return nil, fmt.Errorf("failed to get qmp qom property %v %v: %w", path, d.Name, err)
}
switch d.Name {
case "type":
device.Type = value
case "netdev":
device.Name = value
case "mac":
device.MacAddress = value
}
}
devices = append(devices, device)
}
}
}
}
return devices, nil
}

View File

@ -1,30 +0,0 @@
package qemu
import (
"log"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func commHost(host string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
if host != "" {
log.Printf("Using host value: %s", host)
return host, nil
}
if guestAddress, ok := state.Get("guestAddress").(string); ok {
return guestAddress, nil
}
return "127.0.0.1", nil
}
}
func commPort(state multistep.StateBag) (int, error) {
commHostPort, ok := state.Get("commHostPort").(int)
if !ok {
commHostPort = 22
}
return commHostPort, nil
}

View File

@ -1,92 +0,0 @@
package qemu
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/digitalocean/go-qemu/qmp"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step configures the VM to enable the QMP listener.
//
// Uses:
// config *config
// ui packersdk.Ui
//
// Produces:
type stepConfigureQMP struct {
monitor *qmp.SocketMonitor
QMPSocketPath string
}
func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
if !config.QMPEnable {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("QMP socket at: %s", s.QMPSocketPath))
// Only initialize and open QMP when we have a use for it.
// Open QMP socket
var err error
var cmd []byte
var result []byte
s.monitor, err = qmp.NewSocketMonitor("unix", s.QMPSocketPath, 2*time.Second)
if err != nil {
err := fmt.Errorf("Error opening QMP socket: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Connect to QMP
// function automatically calls capabilities so is immediately ready for commands
err = s.monitor.Connect()
if err != nil {
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("QMP socket open SUCCESS")
vncPassword := state.Get("vnc_password")
if vncPassword != "" {
cmd = []byte(fmt.Sprintf("{ \"execute\": \"change-vnc-password\", \"arguments\": { \"password\": \"%s\" } }",
vncPassword))
result, err = s.monitor.Run(cmd)
if err != nil {
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("QMP Command: %s\nResult: %s", cmd, result)
}
// make the qmp_monitor available to other steps.
state.Put("qmp_monitor", s.monitor)
return multistep.ActionContinue
}
func (s *stepConfigureQMP) Cleanup(multistep.StateBag) {
if s.monitor != nil {
err := s.monitor.Disconnect()
if err != nil {
log.Printf("failed to disconnect QMP: %v", err)
}
// Delete file associated with qmp socket.
if err := os.Remove(s.QMPSocketPath); err != nil {
log.Printf("Failed to delete the qmp socket file: %s", err)
}
}
}

View File

@ -1,89 +0,0 @@
package qemu
import (
"context"
"fmt"
"log"
"math/rand"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/net"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step configures the VM to enable the VNC server.
//
// Uses:
// config *config
// ui packersdk.Ui
//
// Produces:
// vnc_port int - The port that VNC is configured to listen on.
type stepConfigureVNC struct {
l *net.Listener
}
func VNCPassword() string {
length := int(8)
charSet := []byte("012345689abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
charSetLength := len(charSet)
password := make([]byte, length)
for i := 0; i < length; i++ {
password[i] = charSet[rand.Intn(charSetLength)]
}
return string(password)
}
func (s *stepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
// Find an open VNC port. Note that this can still fail later on
// because we have to release the port at some point. But this does its
// best.
msg := fmt.Sprintf("Looking for available port between %d and %d on %s", config.VNCPortMin, config.VNCPortMax, config.VNCBindAddress)
ui.Say(msg)
log.Print(msg)
var vncPassword string
var err error
s.l, err = net.ListenRangeConfig{
Addr: config.VNCBindAddress,
Min: config.VNCPortMin,
Max: config.VNCPortMax,
Network: "tcp",
}.Listen(ctx)
if err != nil {
err := fmt.Errorf("Error finding VNC port: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.l.Listener.Close() // free port, but don't unlock lock file
vncPort := s.l.Port
if config.VNCUsePassword {
vncPassword = VNCPassword()
} else {
vncPassword = ""
}
log.Printf("Found available VNC port: %d on IP: %s", vncPort, config.VNCBindAddress)
state.Put("vnc_port", vncPort)
state.Put("vnc_password", vncPassword)
return multistep.ActionContinue
}
func (s *stepConfigureVNC) Cleanup(multistep.StateBag) {
if s.l != nil {
err := s.l.Close()
if err != nil {
log.Printf("failed to unlock port lockfile: %v", err)
}
}
}

View File

@ -1,104 +0,0 @@
package qemu
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
"os"
)
// This step converts the virtual disk that was used as the
// hard drive for the virtual machine.
type stepConvertDisk struct {
DiskCompression bool
Format string
OutputDir string
SkipCompaction bool
VMName string
QemuImgArgs QemuImgArgs
}
func (s *stepConvertDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packersdk.Ui)
diskName := s.VMName
if s.SkipCompaction && !s.DiskCompression {
return multistep.ActionContinue
}
name := diskName + ".convert"
sourcePath := filepath.Join(s.OutputDir, diskName)
targetPath := filepath.Join(s.OutputDir, name)
command := s.buildConvertCommand(sourcePath, targetPath)
ui.Say("Converting hard drive...")
// Retry the conversion a few times in case it takes the qemu process a
// moment to release the lock
err := retry.Config{
Tries: 10,
ShouldRetry: func(err error) bool {
if strings.Contains(err.Error(), `Failed to get shared "write" lock`) {
ui.Say("Error getting file lock for conversion; retrying...")
return true
}
return false
},
RetryDelay: (&retry.Backoff{InitialBackoff: 1 * time.Second, MaxBackoff: 10 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
return driver.QemuImg(command...)
})
if err != nil {
switch err.(type) {
case *retry.RetryExhaustedError:
err = fmt.Errorf("Exhausted retries for getting file lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
default:
err := fmt.Errorf("Error converting hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if err := os.Rename(targetPath, sourcePath); err != nil {
err := fmt.Errorf("Error moving converted hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepConvertDisk) buildConvertCommand(sourcePath, targetPath string) []string {
command := []string{"convert"}
if s.DiskCompression {
command = append(command, "-c")
}
// Add user-provided convert args
command = append(command, s.QemuImgArgs.Convert...)
// Add format, and paths.
command = append(command, "-O", s.Format, sourcePath, targetPath)
return command
}
func (s *stepConvertDisk) Cleanup(state multistep.StateBag) {}

View File

@ -1,52 +0,0 @@
package qemu
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_buildConvertCommand(t *testing.T) {
type testCase struct {
Step *stepConvertDisk
Expected []string
Reason string
}
testcases := []testCase{
{
&stepConvertDisk{
Format: "qcow2",
DiskCompression: false,
},
[]string{"convert", "-O", "qcow2", "source.qcow", "target.qcow2"},
"Basic, happy path, no compression, no extra args",
},
{
&stepConvertDisk{
Format: "qcow2",
DiskCompression: true,
},
[]string{"convert", "-c", "-O", "qcow2", "source.qcow", "target.qcow2"},
"Basic, happy path, with compression, no extra args",
},
{
&stepConvertDisk{
Format: "qcow2",
DiskCompression: true,
QemuImgArgs: QemuImgArgs{
Convert: []string{"-o", "preallocation=full"},
},
},
[]string{"convert", "-c", "-o", "preallocation=full", "-O", "qcow2", "source.qcow", "target.qcow2"},
"Basic, happy path, with compression, one set of extra args",
},
}
for _, tc := range testcases {
command := tc.Step.buildConvertCommand("source.qcow", "target.qcow2")
assert.Equal(t, command, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}

View File

@ -1,78 +0,0 @@
package qemu
import (
"context"
"fmt"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step copies the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepCopyDisk struct {
DiskImage bool
Format string
OutputDir string
UseBackingFile bool
VMName string
QemuImgArgs QemuImgArgs
}
func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packersdk.Ui)
path := filepath.Join(s.OutputDir, s.VMName)
// Only make a copy of the ISO as the 'main' or 'default' disk if is a disk
// image and not using a backing file. The create disk step (step_create_disk.go)
// would already have made the disk otherwise
if !s.DiskImage || s.UseBackingFile {
return multistep.ActionContinue
}
// In some cases, the file formats provided are equivalent by comparing the
// file extensions. Skip the conversion step
// This also serves as a workaround for a QEMU bug: https://bugs.launchpad.net/qemu/+bug/1776920
ext := filepath.Ext(isoPath)
if len(ext) >= 1 && ext[1:] == s.Format && len(s.QemuImgArgs.Convert) == 0 {
ui.Message("File extension already matches desired output format. " +
"Skipping qemu-img convert step")
err := driver.Copy(isoPath, path)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
command := s.buildConvertCommand(isoPath, path)
ui.Say("Copying hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepCopyDisk) buildConvertCommand(sourcePath, targetPath string) []string {
command := []string{"convert"}
// Add user-provided convert args
command = append(command, s.QemuImgArgs.Convert...)
// Add format, and paths.
command = append(command, "-O", s.Format, sourcePath, targetPath)
return command
}
func (s *stepCopyDisk) Cleanup(state multistep.StateBag) {}

View File

@ -1,122 +0,0 @@
package qemu
import (
"context"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/stretchr/testify/assert"
)
func copyTestState(t *testing.T, d *DriverMock) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", packersdk.TestUi(t))
state.Put("driver", d)
state.Put("iso_path", "example_source.qcow2")
return state
}
func Test_StepCopySkip(t *testing.T) {
testcases := []stepCopyDisk{
stepCopyDisk{
DiskImage: false,
UseBackingFile: false,
},
stepCopyDisk{
DiskImage: true,
UseBackingFile: true,
},
stepCopyDisk{
DiskImage: false,
UseBackingFile: true,
},
}
for _, tc := range testcases {
d := new(DriverMock)
state := copyTestState(t, d)
action := tc.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled || d.QemuImgCalled {
t.Fatalf("Should have skipped step since DiskImage and UseBackingFile are not set")
}
}
}
func Test_StepCopyCalled(t *testing.T) {
step := stepCopyDisk{
DiskImage: true,
Format: "qcow2",
VMName: "output.qcow2",
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if !d.CopyCalled {
t.Fatalf("Should have copied since all extensions are qcow2")
}
if d.QemuImgCalled {
t.Fatalf("Should not have called qemu-img when formats match")
}
}
func Test_StepQemuImgCalled(t *testing.T) {
step := stepCopyDisk{
DiskImage: true,
Format: "raw",
VMName: "output.qcow2",
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled {
t.Fatalf("Should not have copied since extensions don't match")
}
if !d.QemuImgCalled {
t.Fatalf("Should have called qemu-img since extensions don't match")
}
}
func Test_StepQemuImgCalledWithExtraArgs(t *testing.T) {
step := &stepCopyDisk{
DiskImage: true,
Format: "raw",
VMName: "output.qcow2",
QemuImgArgs: QemuImgArgs{
Convert: []string{"-o", "preallocation=full"},
},
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled {
t.Fatalf("Should not have copied since extensions don't match")
}
if !d.QemuImgCalled {
t.Fatalf("Should have called qemu-img since extensions don't match")
}
assert.Equal(
t,
d.QemuImgCalls,
[]string{"convert", "-o", "preallocation=full", "-O", "raw",
"example_source.qcow2", "output.qcow2"},
"should have added user extra args")
}

View File

@ -1,91 +0,0 @@
package qemu
import (
"context"
"fmt"
"log"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step creates the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepCreateDisk struct {
AdditionalDiskSize []string
DiskImage bool
DiskSize string
Format string
OutputDir string
UseBackingFile bool
VMName string
QemuImgArgs QemuImgArgs
}
func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packersdk.Ui)
name := s.VMName
if len(s.AdditionalDiskSize) > 0 || s.UseBackingFile {
ui.Say("Creating required virtual machine disks")
}
// The 'main' or 'default' disk
diskFullPaths := []string{filepath.Join(s.OutputDir, name)}
diskSizes := []string{s.DiskSize}
// Additional disks
if len(s.AdditionalDiskSize) > 0 {
for i, diskSize := range s.AdditionalDiskSize {
path := filepath.Join(s.OutputDir, fmt.Sprintf("%s-%d", name, i+1))
diskFullPaths = append(diskFullPaths, path)
diskSizes = append(diskSizes, diskSize)
}
}
// Create all required disks
for i, diskFullPath := range diskFullPaths {
if s.DiskImage && !s.UseBackingFile && i == 0 {
// Let the copy disk step (step_copy_disk.go) create the 'main' or
// 'default' disk.
continue
}
log.Printf("[INFO] Creating disk with Path: %s and Size: %s", diskFullPath, diskSizes[i])
command := s.buildCreateCommand(diskFullPath, diskSizes[i], i, state)
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Stash the disk paths so we can retrieve later
state.Put("qemu_disk_paths", diskFullPaths)
return multistep.ActionContinue
}
func (s *stepCreateDisk) buildCreateCommand(path string, size string, i int, state multistep.StateBag) []string {
command := []string{"create", "-f", s.Format}
if s.DiskImage && s.UseBackingFile && i == 0 {
// Use a backing file for the 'main' or 'default' disk
isoPath := state.Get("iso_path").(string)
command = append(command, "-b", isoPath)
}
// add user-provided convert args
command = append(command, s.QemuImgArgs.Create...)
// add target path and size.
command = append(command, path, size)
return command
}
func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {}

View File

@ -1,176 +0,0 @@
package qemu
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/stretchr/testify/assert"
)
func Test_buildCreateCommand(t *testing.T) {
type testCase struct {
Step *stepCreateDisk
I int
Expected []string
Reason string
}
testcases := []testCase{
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: false,
},
0,
[]string{"create", "-f", "qcow2", "target.qcow2", "1234M"},
"Basic, happy path, no backing store, no extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
DiskImage: true,
UseBackingFile: true,
AdditionalDiskSize: []string{"1M", "2M"},
},
0,
[]string{"create", "-f", "qcow2", "-b", "source.qcow2", "target.qcow2", "1234M"},
"Basic, happy path, backing store, additional disks",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
DiskImage: true,
},
1,
[]string{"create", "-f", "qcow2", "target.qcow2", "1234M"},
"Basic, happy path, backing store set but not at first index, no extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
DiskImage: true,
QemuImgArgs: QemuImgArgs{
Create: []string{"-foo", "bar"},
},
},
0,
[]string{"create", "-f", "qcow2", "-b", "source.qcow2", "-foo", "bar", "target.qcow2", "1234M"},
"Basic, happy path, backing store set, extra args",
},
{
&stepCreateDisk{
Format: "qcow2",
UseBackingFile: true,
QemuImgArgs: QemuImgArgs{
Create: []string{"-foo", "bar"},
},
},
1,
[]string{"create", "-f", "qcow2", "-foo", "bar", "target.qcow2", "1234M"},
"Basic, happy path, backing store set but not at first index, extra args",
},
}
for _, tc := range testcases {
state := new(multistep.BasicStateBag)
state.Put("iso_path", "source.qcow2")
command := tc.Step.buildCreateCommand("target.qcow2", "1234M", tc.I, state)
assert.Equal(t, command, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}
func Test_StepCreateCalled(t *testing.T) {
type testCase struct {
Step *stepCreateDisk
Expected []string
Reason string
}
testcases := []testCase{
{
&stepCreateDisk{
Format: "qcow2",
DiskImage: true,
DiskSize: "1M",
VMName: "target",
UseBackingFile: true,
},
[]string{
"create", "-f", "qcow2", "-b", "source.qcow2", "target", "1M",
},
"Basic, happy path, backing store, no additional disks",
},
{
&stepCreateDisk{
Format: "raw",
DiskImage: false,
DiskSize: "4M",
VMName: "target",
UseBackingFile: false,
},
[]string{
"create", "-f", "raw", "target", "4M",
},
"Basic, happy path, raw, no additional disks",
},
{
&stepCreateDisk{
Format: "qcow2",
DiskImage: true,
DiskSize: "4M",
VMName: "target",
UseBackingFile: false,
AdditionalDiskSize: []string{"3M", "8M"},
},
[]string{
"create", "-f", "qcow2", "target-1", "3M",
"create", "-f", "qcow2", "target-2", "8M",
},
"Skips disk creation when disk can be copied",
},
{
&stepCreateDisk{
Format: "qcow2",
DiskImage: true,
DiskSize: "4M",
VMName: "target",
UseBackingFile: false,
},
nil,
"Skips disk creation when disk can be copied",
},
{
&stepCreateDisk{
Format: "qcow2",
DiskImage: true,
DiskSize: "1M",
VMName: "target",
UseBackingFile: true,
AdditionalDiskSize: []string{"3M", "8M"},
},
[]string{
"create", "-f", "qcow2", "-b", "source.qcow2", "target", "1M",
"create", "-f", "qcow2", "target-1", "3M",
"create", "-f", "qcow2", "target-2", "8M",
},
"Basic, happy path, backing store, additional disks",
},
}
for _, tc := range testcases {
d := new(DriverMock)
state := copyTestState(t, d)
state.Put("iso_path", "source.qcow2")
action := tc.Step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
assert.Equal(t, d.QemuImgCalls, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}

View File

@ -1,68 +0,0 @@
package qemu
import (
"context"
"fmt"
"net"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// Step to discover the http ip
// which guests use to reach the vm host
// To make sure the IP is set before boot command and http server steps
type stepHTTPIPDiscover struct{}
func (s *stepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
hostIP := ""
if config.NetBridge == "" {
hostIP = "10.0.2.2"
} else {
bridgeInterface, err := net.InterfaceByName(config.NetBridge)
if err != nil {
err := fmt.Errorf("Error getting the bridge %s interface: %s", config.NetBridge, err)
ui.Error(err.Error())
return multistep.ActionHalt
}
addrs, err := bridgeInterface.Addrs()
if err != nil {
err := fmt.Errorf("Error getting the bridge %s interface addresses: %s", config.NetBridge, err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil {
continue
}
ip = ip.To4()
if ip == nil {
continue
}
hostIP = ip.String()
break
}
if hostIP == "" {
err := fmt.Errorf("Error getting an IPv4 address from the bridge %s: cannot find any IPv4 address", config.NetBridge)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
state.Put("http_ip", hostIP)
return multistep.ActionContinue
}
func (s *stepHTTPIPDiscover) Cleanup(state multistep.StateBag) {}

View File

@ -1,34 +0,0 @@
package qemu
import (
"bytes"
"context"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestStepHTTPIPDiscover_Run(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
config := &Config{}
state.Put("config", config)
step := new(stepHTTPIPDiscover)
hostIp := "10.0.2.2"
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
httpIp := state.Get("http_ip").(string)
if httpIp != hostIp {
t.Fatalf("bad: Http ip is %s but was supposed to be %s", httpIp, hostIp)
}
}

View File

@ -1,74 +0,0 @@
package qemu
import (
"context"
"fmt"
"log"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/net"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// 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
}
func (s *stepPortForward) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.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 {
log.Printf("Skipping NAT port forwarding. Using communicator (SSH, WinRM, etc) port %d", commHostPort)
state.Put("commHostPort", commHostPort)
return multistep.ActionContinue
}
log.Printf("Looking for available communicator (SSH, WinRM, etc) port between %d and %d", config.CommConfig.HostPortMin, config.CommConfig.HostPortMax)
var err error
s.l, err = net.ListenRangeConfig{
Addr: config.VNCBindAddress,
Min: config.CommConfig.HostPortMin,
Max: config.CommConfig.HostPortMax,
Network: "tcp",
}.Listen(ctx)
if err != nil {
err := fmt.Errorf("Error finding port: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.l.Listener.Close() // free port, but don't unlock lock file
commHostPort = s.l.Port
ui.Say(fmt.Sprintf("Found port for communicator (SSH, WinRM, etc): %d.", commHostPort))
// Save the port we're using so that future steps can use it
state.Put("commHostPort", commHostPort)
return multistep.ActionContinue
}
func (s *stepPortForward) Cleanup(state multistep.StateBag) {
if s.l != nil {
err := s.l.Close()
if err != nil {
log.Printf("failed to unlock port lockfile: %v", err)
}
}
}

View File

@ -1,51 +0,0 @@
package qemu
import (
"context"
"log"
"os"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepPrepareOutputDir struct{}
func (stepPrepareOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir)
if err == nil {
break
}
log.Printf("Error removing output dir: %s", err)
time.Sleep(2 * time.Second)
}
}
}

View File

@ -1,60 +0,0 @@
package qemu
import (
"context"
"fmt"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step resizes the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepResizeDisk struct {
DiskCompression bool
DiskImage bool
Format string
OutputDir string
SkipResizeDisk bool
VMName string
DiskSize string
QemuImgArgs QemuImgArgs
}
func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packersdk.Ui)
path := filepath.Join(s.OutputDir, s.VMName)
command := s.buildResizeCommand(path)
if s.DiskImage == false || s.SkipResizeDisk == true {
return multistep.ActionContinue
}
ui.Say("Resizing hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepResizeDisk) buildResizeCommand(path string) []string {
command := []string{"resize", "-f", s.Format}
// add user-provided convert args
command = append(command, s.QemuImgArgs.Resize...)
// Add file and size
command = append(command, path, s.DiskSize)
return command
}
func (s *stepResizeDisk) Cleanup(state multistep.StateBag) {}

View File

@ -1,77 +0,0 @@
package qemu
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/stretchr/testify/assert"
)
func TestStepResizeDisk_Skips(t *testing.T) {
testConfigs := []*Config{
&Config{
DiskImage: false,
SkipResizeDisk: false,
},
&Config{
DiskImage: false,
SkipResizeDisk: true,
},
}
for _, config := range testConfigs {
state := testState(t)
driver := state.Get("driver").(*DriverMock)
state.Put("config", config)
step := new(stepResizeDisk)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if len(driver.QemuImgCalls) > 0 {
t.Fatal("should NOT have called qemu-img")
}
}
}
func Test_buildResizeCommand(t *testing.T) {
type testCase struct {
Step *stepResizeDisk
Expected []string
Reason string
}
testcases := []testCase{
{
&stepResizeDisk{
Format: "qcow2",
DiskSize: "1234M",
},
[]string{"resize", "-f", "qcow2", "source.qcow", "1234M"},
"no extra args",
},
{
&stepResizeDisk{
Format: "qcow2",
DiskSize: "1234M",
QemuImgArgs: QemuImgArgs{
Resize: []string{"-foo", "bar"},
},
},
[]string{"resize", "-f", "qcow2", "-foo", "bar", "source.qcow", "1234M"},
"one set of extra args",
},
}
for _, tc := range testcases {
command := tc.Step.buildResizeCommand("source.qcow")
assert.Equal(t, command, tc.Expected,
fmt.Sprintf("%s. Expected %#v", tc.Reason, tc.Expected))
}
}

View File

@ -1,400 +0,0 @@
package qemu
import (
"context"
"fmt"
"log"
"path/filepath"
"strings"
"github.com/hashicorp/go-version"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// stepRun runs the virtual machine
type stepRun struct {
DiskImage bool
atLeastVersion2 bool
ui packersdk.Ui
}
func (s *stepRun) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
s.ui = state.Get("ui").(packersdk.Ui)
// Figure out version of qemu; store on step for later use
rawVersion, err := driver.Version()
if err != nil {
err := fmt.Errorf("Error determining qemu version: %s", err)
s.ui.Error(err.Error())
return multistep.ActionHalt
}
qemuVersion, err := version.NewVersion(rawVersion)
if err != nil {
err := fmt.Errorf("Error parsing qemu version: %s", err)
s.ui.Error(err.Error())
return multistep.ActionHalt
}
v2 := version.Must(version.NewVersion("2.0"))
s.atLeastVersion2 = qemuVersion.GreaterThanOrEqual(v2)
// Generate the qemu command
command, err := s.getCommandArgs(config, state)
if err != nil {
err := fmt.Errorf("Error processing QemuArgs: %s", err)
s.ui.Error(err.Error())
return multistep.ActionHalt
}
// run the qemu command
if err := driver.Qemu(command...); err != nil {
err := fmt.Errorf("Error launching VM: %s", err)
s.ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepRun) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packersdk.Ui)
if err := driver.Stop(); err != nil {
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
}
}
func (s *stepRun) getDefaultArgs(config *Config, state multistep.StateBag) map[string]interface{} {
defaultArgs := make(map[string]interface{})
// Configure "boot" arguement
// 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"
}
s.ui.Say(message)
defaultArgs["-boot"] = bootDrive
// configure "-qmp" arguments
if config.QMPEnable {
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
}
// configure "-name" arguments
defaultArgs["-name"] = config.VMName
// Configure "-machine" arguments
if config.Accelerator == "none" {
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
s.ui.Message("WARNING: The VM will be started with no hardware acceleration.\n" +
"The installation may take considerably longer to finish.\n")
} else {
defaultArgs["-machine"] = fmt.Sprintf("type=%s,accel=%s",
config.MachineType, config.Accelerator)
}
// Firmware
if config.Firmware != "" {
defaultArgs["-bios"] = config.Firmware
}
// Configure "-netdev" arguments
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
if config.NetBridge == "" {
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
if config.CommConfig.Comm.Type != "none" {
commHostPort := state.Get("commHostPort").(int)
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", commHostPort, config.CommConfig.Comm.Port())
}
}
// Configure "-vnc" arguments
// vncPort is always set in stepConfigureVNC, so we don't need to
// defensively assert
vncPort := state.Get("vnc_port").(int)
vncIP := config.VNCBindAddress
vncRealAddress := fmt.Sprintf("%s:%d", vncIP, vncPort)
vncPort = vncPort - 5900
vncArgs := fmt.Sprintf("%s:%d", vncIP, vncPort)
if config.VNCUsePassword {
vncArgs = fmt.Sprintf("%s:%d,password", vncIP, vncPort)
}
defaultArgs["-vnc"] = vncArgs
// Track the connection for the user
vncPass, _ := state.Get("vnc_password").(string)
message = getVncConnectionMessage(config.Headless, vncRealAddress, vncPass)
if message != "" {
s.ui.Message(message)
}
// Configure "-m" memory argument
defaultArgs["-m"] = fmt.Sprintf("%dM", config.MemorySize)
// Configure "-smp" processor hardware arguments
if config.CpuCount > 1 {
defaultArgs["-smp"] = fmt.Sprintf("cpus=%d,sockets=%d", config.CpuCount, config.CpuCount)
}
// Configure "-fda" floppy disk attachment
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
defaultArgs["-fda"] = floppyPathRaw.(string)
} else {
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
}
// Configure GUI display
if !config.Headless {
if s.atLeastVersion2 {
// FIXME: "none" is a valid display option in qemu but we have
// departed from the qemu usage here to instaed mean "let qemu
// set a reasonable default". We need to deprecate this behavior
// and let users just set "UseDefaultDisplay" if they want to let
// qemu do its thing.
if len(config.Display) > 0 && config.Display != "none" {
defaultArgs["-display"] = config.Display
} else if !config.UseDefaultDisplay {
defaultArgs["-display"] = "gtk"
}
} else {
s.ui.Message("WARNING: The version of qemu on your host doesn't support display mode.\n" +
"The display parameter will be ignored.")
}
}
deviceArgs, driveArgs := s.getDeviceAndDriveArgs(config, state)
defaultArgs["-device"] = deviceArgs
defaultArgs["-drive"] = driveArgs
return defaultArgs
}
func getVncConnectionMessage(headless bool, vnc string, vncPass string) string {
// Configure GUI display
if headless {
if vnc == "" {
return "The VM will be run headless, without a GUI, as configured.\n" +
"If the run isn't succeeding as you expect, please enable the GUI\n" +
"to inspect the progress of the build."
}
if vncPass != "" {
return fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC to vnc://%s\n"+
"with the password: %s", vnc, vncPass)
}
return fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC without a password to\n"+
"vnc://%s", vnc)
}
return ""
}
func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag) ([]string, []string) {
var deviceArgs []string
var driveArgs []string
vmName := config.VMName
imgPath := filepath.Join(config.OutputDir, vmName)
// Configure virtual hard drives
if s.atLeastVersion2 {
drivesToAttach := []string{}
if v, ok := state.GetOk("qemu_disk_paths"); ok {
diskFullPaths := v.([]string)
drivesToAttach = append(drivesToAttach, diskFullPaths...)
}
for i, drivePath := range drivesToAttach {
driveArgumentString := fmt.Sprintf("file=%s,if=%s,cache=%s,discard=%s,format=%s", drivePath, config.DiskInterface, config.DiskCache, config.DiskDiscard, config.Format)
if config.DiskInterface == "virtio-scsi" {
// TODO: Megan: Remove this conditional. This, and the code
// under the TODO below, reproduce the old behavior. While it
// may be broken, the goal of this commit is to refactor in a way
// that creates a result that is testably the same as the old
// code. A pr will follow fixing this broken behavior.
if i == 0 {
deviceArgs = append(deviceArgs, fmt.Sprintf("virtio-scsi-pci,id=scsi%d", i))
}
// TODO: Megan: When you remove above conditional,
// set deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi%d.0,drive=drive%d", i, i))
deviceArgs = append(deviceArgs, fmt.Sprintf("scsi-hd,bus=scsi0.0,drive=drive%d", i))
driveArgumentString = fmt.Sprintf("if=none,file=%s,id=drive%d,cache=%s,discard=%s,format=%s", drivePath, i, config.DiskCache, config.DiskDiscard, config.Format)
}
if config.DetectZeroes != "off" {
driveArgumentString = fmt.Sprintf("%s,detect-zeroes=%s", driveArgumentString, config.DetectZeroes)
}
driveArgs = append(driveArgs, driveArgumentString)
}
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,cache=%s,format=%s", imgPath, config.DiskInterface, config.DiskCache, config.Format))
}
deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
// Configure virtual CDs
cdPaths := []string{}
// Add the installation CD to the run command
if !config.DiskImage {
isoPath := state.Get("iso_path").(string)
cdPaths = append(cdPaths, isoPath)
}
// Add our custom CD created from cd_files, if it exists
cdFilesPath, ok := state.Get("cd_path").(string)
if ok {
if cdFilesPath != "" {
cdPaths = append(cdPaths, cdFilesPath)
}
}
for i, cdPath := range cdPaths {
if config.CDROMInterface == "" {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,media=cdrom", cdPath))
} else if config.CDROMInterface == "virtio-scsi" {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=none,index=%d,id=cdrom%d,media=cdrom", cdPath, i, i))
deviceArgs = append(deviceArgs, "virtio-scsi-device", fmt.Sprintf("scsi-cd,drive=cdrom%d", i))
} else {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=%s,index=%d,id=cdrom%d,media=cdrom", cdPath, config.CDROMInterface, i, i))
}
}
return deviceArgs, driveArgs
}
func (s *stepRun) applyUserOverrides(defaultArgs map[string]interface{}, config *Config, state multistep.StateBag) ([]string, error) {
// Done setting up defaults; time to process user args and defaults together
// and generate output args
inArgs := make(map[string][]string)
if len(config.QemuArgs) > 0 {
s.ui.Say("Overriding default Qemu arguments with qemuargs template option...")
commHostPort := 0
if config.CommConfig.Comm.Type != "none" {
if v, ok := state.GetOk("commHostPort"); ok {
commHostPort = v.(int)
}
}
httpIp := state.Get("http_ip").(string)
httpPort := state.Get("http_port").(int)
type qemuArgsTemplateData struct {
HTTPIP string
HTTPPort int
HTTPDir string
HTTPContent map[string]string
OutputDir string
Name string
SSHHostPort int
}
ictx := config.ctx
ictx.Data = qemuArgsTemplateData{
HTTPIP: httpIp,
HTTPPort: httpPort,
HTTPDir: config.HTTPDir,
HTTPContent: config.HTTPContent,
OutputDir: config.OutputDir,
Name: config.VMName,
SSHHostPort: commHostPort,
}
// Interpolate each string in qemuargs
newQemuArgs, err := processArgs(config.QemuArgs, &ictx)
if err != nil {
return nil, err
}
// Qemu supports multiple appearances of the same switch. This means
// each key in the args hash will have an array of string values
for _, qemuArgs := range newQemuArgs {
key := qemuArgs[0]
val := strings.Join(qemuArgs[1:], "")
if _, ok := inArgs[key]; !ok {
inArgs[key] = make([]string, 0)
}
if len(val) > 0 {
inArgs[key] = append(inArgs[key], val)
}
}
}
// get any remaining missing default args from the default settings
for key := range defaultArgs {
if _, ok := inArgs[key]; !ok {
arg := make([]string, 1)
switch defaultArgs[key].(type) {
case string:
arg[0] = defaultArgs[key].(string)
case []string:
arg = defaultArgs[key].([]string)
}
inArgs[key] = arg
}
}
// Check if we are missing the netDevice #6804
if x, ok := inArgs["-device"]; ok {
if !strings.Contains(strings.Join(x, ""), config.NetDevice) {
inArgs["-device"] = append(inArgs["-device"], fmt.Sprintf("%s,netdev=user.0", config.NetDevice))
}
}
// Flatten to array of strings
outArgs := make([]string, 0)
for key, values := range inArgs {
if len(values) > 0 {
for idx := range values {
outArgs = append(outArgs, key, values[idx])
}
} else {
outArgs = append(outArgs, key)
}
}
return outArgs, nil
}
func (s *stepRun) getCommandArgs(config *Config, state multistep.StateBag) ([]string, error) {
defaultArgs := s.getDefaultArgs(config, state)
return s.applyUserOverrides(defaultArgs, config, state)
}
func processArgs(args [][]string, ctx *interpolate.Context) ([][]string, error) {
var err error
if args == nil {
return make([][]string, 0), err
}
newArgs := make([][]string, len(args))
for argsIdx, rowArgs := range args {
parms := make([]string, len(rowArgs))
newArgs[argsIdx] = parms
for i, parm := range rowArgs {
parms[i], err = interpolate.Render(parm, ctx)
if err != nil {
return nil, err
}
}
}
return newArgs, err
}

View File

@ -1,710 +0,0 @@
package qemu
import (
"fmt"
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/stretchr/testify/assert"
)
func runTestState(t *testing.T, config *Config) multistep.StateBag {
state := new(multistep.BasicStateBag)
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_UserOverrides(t *testing.T) {
type testCase struct {
Config *Config
Expected []string
Reason string
}
testcases := []testCase{
{
&Config{
HTTPConfig: commonsteps.HTTPConfig{
HTTPDir: "http/directory",
},
OutputDir: "output/directory",
VMName: "myvm",
QemuArgs: [][]string{
{"-randomflag1", "{{.HTTPIP}}-{{.HTTPPort}}-{{.HTTPDir}}"},
{"-randomflag2", "{{.OutputDir}}-{{.Name}}"},
},
},
[]string{
"-display", "gtk",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-randomflag1", "127.0.0.1-1234-http/directory",
"-randomflag2", "output/directory-myvm",
"-device", ",netdev=user.0",
},
"Test that interpolation overrides work.",
},
{
&Config{
VMName: "myvm",
QemuArgs: [][]string{{"-display", "partydisplay"}},
},
[]string{
"-display", "partydisplay",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-device", ",netdev=user.0",
},
"User input overrides default, rest is populated as normal",
},
{
&Config{
VMName: "myvm",
NetDevice: "mynetdevice",
QemuArgs: [][]string{{"-device", "somerandomdevice"}},
},
[]string{
"-display", "gtk",
"-device", "somerandomdevice",
"-device", "mynetdevice,netdev=user.0",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"Net device gets added",
},
}
for _, tc := range testcases {
state := runTestState(t, tc.Config)
step := &stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
}
args, err := step.getCommandArgs(tc.Config, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
expected := append([]string{
"-m", "0M",
"-boot", "once=d",
"-fda", "fake_floppy_path",
"-name", "myvm",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5",
"-machine", "type=,accel="},
tc.Expected...)
assert.ElementsMatch(t, args, expected,
fmt.Sprintf("%s, \nRecieved: %#v", tc.Reason, args))
}
}
func Test_DriveAndDeviceArgs(t *testing.T) {
type testCase struct {
Config *Config
ExtraState map[string]interface{}
Step *stepRun
Expected []string
Reason string
}
testcases := []testCase{
{
&Config{},
map[string]interface{}{},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"Boot value should default to once=d when diskImage isn't set",
},
{
&Config{
DiskImage: true,
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "off",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"path_to_output"},
},
&stepRun{
DiskImage: true,
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "c",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes off",
},
{
&Config{
DiskImage: true,
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "on",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"path_to_output"},
},
&stepRun{
DiskImage: true,
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "c",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes on",
},
{
&Config{
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "off",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2",
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, bootable iso, cdrom",
},
{
&Config{
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
DetectZeroes: "on",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage false, extra cdrom, detect zeroes on",
},
{
&Config{
DiskInterface: "virtio-scsi",
OutputDir: "path_to_output",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=output/dir/path/mydisk.qcow2,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage false, no extra disks or cds",
},
{
&Config{},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"cd_path is set and DiskImage is false",
},
{
&Config{},
map[string]interface{}{
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"empty config",
},
{
&Config{
OutputDir: "path_to_output",
DiskInterface: "virtio",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
// when disk image is false, we will always have at least one
// disk path: the one we create to be the main disk.
"qemu_disk_paths": []string{"output/dir/path/mydisk.qcow2"},
},
&stepRun{
atLeastVersion2: false,
ui: packersdk.TestUi(t),
},
[]string{
"-boot", "once=d",
"-drive", "file=path_to_output,if=virtio,cache=writeback,format=qcow2",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"version less than 2",
},
{
&Config{
OutputDir: "path_to_output",
DiskInterface: "virtio",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"qemupath1", "qemupath2"},
},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=qemupath1,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=qemupath2,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"virtio interface with extra disks",
},
{
&Config{
DiskImage: true,
OutputDir: "path_to_output",
DiskInterface: "virtio",
DiskCache: "writeback",
Format: "qcow2",
},
map[string]interface{}{
"cd_path": "fake_cd_path.iso",
"qemu_disk_paths": []string{"path_to_output"},
},
&stepRun{
DiskImage: true,
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{
"-display", "gtk",
"-boot", "c",
"-drive", "file=path_to_output,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio interface with disk image",
},
}
for _, tc := range testcases {
state := runTestState(t, &Config{})
for k, v := range tc.ExtraState {
state.Put(k, v)
}
args, err := tc.Step.getCommandArgs(tc.Config, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
expected := append([]string{
"-m", "0M",
"-fda", "fake_floppy_path",
"-name", "",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5",
"-machine", "type=,accel=",
"-device", ",netdev=user.0"},
tc.Expected...)
assert.ElementsMatch(t, args, expected,
fmt.Sprintf("%s, \nRecieved: %#v", tc.Reason, 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)
step := &stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
}
args, err := step.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", "once=d",
"-fda", "fake_floppy_path",
"-name", "MyFancyName",
"-netdev", "user,id=user.0,hostfwd=tcp::5000-:0",
"-vnc", ":5,password",
"-machine", "type=pc,accel=hvf",
"-device", ",netdev=user.0",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-qmp", "unix:qmp_path,server,nowait",
}
assert.ElementsMatch(t, args, expected, "password flag should be set, and d drive should be set: %s", args)
}
// Tests for presence of Packer-generated arguments. Doesn't test that
// arguments which shouldn't be there are absent.
func Test_Defaults(t *testing.T) {
type testCase struct {
Config *Config
ExtraState map[string]interface{}
Step *stepRun
Expected []string
Reason string
}
testcases := []testCase{
{
&Config{},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-boot", "once=d"},
"Boot value should default to once=d",
},
{
&Config{},
map[string]interface{}{},
&stepRun{
DiskImage: true,
ui: packersdk.TestUi(t),
},
[]string{"-boot", "c"},
"Boot value should be set to c when DiskImage is set on step",
},
{
&Config{
QMPEnable: true,
QMPSocketPath: "/path/to/socket",
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-qmp", "unix:/path/to/socket,server,nowait"},
"Args should contain -qmp when qmp_enable is set",
},
{
&Config{
QMPEnable: true,
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-qmp", "unix:,server,nowait"},
"Args contain -qmp even when socket path isn't set, if qmp enabled",
},
{
&Config{
VMName: "partyname",
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-name", "partyname"},
"Name is set from config",
},
{
&Config{},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-name", ""},
"Name is set from config, even when name is blank (which won't " +
"happen for real thanks to defaulting in build prepare)",
},
{
&Config{
Accelerator: "none",
MachineType: "fancymachine",
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-machine", "type=fancymachine"},
"Don't add accelerator tag when no accelerator is set.",
},
{
&Config{
Accelerator: "kvm",
MachineType: "fancymachine",
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-machine", "type=fancymachine,accel=kvm"},
"Add accelerator tag when accelerator is set.",
},
{
&Config{
NetBridge: "fakebridge",
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-netdev", "bridge,id=user.0,br=fakebridge"},
"Add netbridge tag when netbridge is set.",
},
{
&Config{
CommConfig: CommConfig{
Comm: communicator.Config{
Type: "none",
},
},
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-netdev", "user,id=user.0"},
"No host forwarding when no net bridge and no communicator",
},
{
&Config{
CommConfig: CommConfig{
Comm: communicator.Config{
Type: "ssh",
SSH: communicator.SSH{
SSHPort: 4567,
},
},
},
},
map[string]interface{}{
"commHostPort": 1111,
},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-netdev", "user,id=user.0,hostfwd=tcp::1111-:4567"},
"Host forwarding when a communicator is configured",
},
{
&Config{
VNCBindAddress: "1.1.1.1",
},
map[string]interface{}{
"vnc_port": 5959,
},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-vnc", "1.1.1.1:59"},
"no VNC password should be set",
},
{
&Config{
VNCBindAddress: "1.1.1.1",
VNCUsePassword: true,
},
map[string]interface{}{
"vnc_port": 5959,
},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-vnc", "1.1.1.1:59,password"},
"VNC password should be set",
},
{
&Config{
MemorySize: 2345,
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-m", "2345M"},
"Memory is set, with unit M",
},
{
&Config{
CpuCount: 2,
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-smp", "cpus=2,sockets=2"},
"both cpus and sockets are set to config's CpuCount",
},
{
&Config{
CpuCount: 2,
},
map[string]interface{}{},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-smp", "cpus=2,sockets=2"},
"both cpus and sockets are set to config's CpuCount",
},
{
&Config{
CpuCount: 2,
},
map[string]interface{}{
"floppy_path": "/path/to/floppy",
},
&stepRun{ui: packersdk.TestUi(t)},
[]string{"-fda", "/path/to/floppy"},
"floppy path should be set under fda flag, when it exists",
},
{
&Config{
Headless: false,
Display: "fakedisplay",
UseDefaultDisplay: false,
},
map[string]interface{}{},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{"-display", "fakedisplay"},
"Display option should value config display",
},
{
&Config{
Headless: false,
},
map[string]interface{}{},
&stepRun{
atLeastVersion2: true,
ui: packersdk.TestUi(t),
},
[]string{"-display", "gtk"},
"Display option should default to gtk",
},
}
for _, tc := range testcases {
state := runTestState(t, &Config{})
for k, v := range tc.ExtraState {
state.Put(k, v)
}
args, err := tc.Step.getCommandArgs(tc.Config, state)
if err != nil {
t.Fatalf("should not have an error getting args. Error: %s", err)
}
if !matchArgument(args, tc.Expected) {
t.Fatalf("Couldn't find %#v in result. Got: %#v, Reason: %s",
tc.Expected, args, tc.Reason)
}
}
}
// This test makes sure that arguments don't end up in the final boot command
// if they aren't configured in the config.
// func TestDefaultsAbsentValues(t *testing.T) {}
func matchArgument(actual []string, expected []string) bool {
key := expected[0]
for i, k := range actual {
if key == k {
if expected[1] == actual[i+1] {
return true
}
}
}
return false
}

View File

@ -1,54 +0,0 @@
package qemu
import (
"context"
"fmt"
"net/http"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/net"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step set iso_patch to available url
type stepSetISO struct {
ResultKey string
Url []string
}
func (s *stepSetISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
iso_path := ""
for _, url := range s.Url {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
continue
}
req.Header.Set("User-Agent", "Packer")
httpClient := net.HttpClientWithEnvironmentProxy()
res, err := httpClient.Do(req)
if err == nil && (res.StatusCode >= 200 && res.StatusCode < 300) {
if res.Header.Get("Accept-Ranges") == "bytes" {
iso_path = url
}
}
}
if iso_path == "" {
err := fmt.Errorf("No byte serving support. The HTTP server must support Accept-Ranges=bytes")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put(s.ResultKey, iso_path)
return multistep.ActionContinue
}
func (s *stepSetISO) Cleanup(state multistep.StateBag) {}

View File

@ -1,94 +0,0 @@
package qemu
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
// communicator packersdk.Communicator
// config *config
// driver Driver
// ui packersdk.Ui
//
// Produces:
// <nothing>
type stepShutdown struct {
ShutdownCommand string
ShutdownTimeout time.Duration
Comm *communicator.Config
}
func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packersdk.Ui)
if s.Comm.Type == "none" {
cancelCh := make(chan struct{}, 1)
go func() {
defer close(cancelCh)
<-time.After(s.ShutdownTimeout)
}()
ui.Say("Waiting for shutdown...")
if ok := driver.WaitForShutdown(cancelCh); ok {
log.Println("VM shut down.")
return multistep.ActionContinue
} else {
err := fmt.Errorf("Failed to shutdown")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if s.ShutdownCommand != "" {
comm := state.Get("communicator").(packersdk.Communicator)
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", s.ShutdownCommand)
cmd := &packersdk.RemoteCmd{Command: s.ShutdownCommand}
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Start the goroutine that will time out our graceful attempt
cancelCh := make(chan struct{}, 1)
go func() {
defer close(cancelCh)
<-time.After(s.ShutdownTimeout)
}()
log.Printf("Waiting max %s for shutdown to complete", s.ShutdownTimeout)
if ok := driver.WaitForShutdown(cancelCh); !ok {
err := errors.New("Timeout while waiting for machine to shut down.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
ui.Say("Halting the virtual machine...")
if err := driver.Stop(); err != nil {
err := fmt.Errorf("Error stopping VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
log.Println("VM shut down.")
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}

View File

@ -1,88 +0,0 @@
package qemu
import (
"context"
"testing"
"time"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func Test_Shutdown_Null_success(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", packersdk.TestUi(t))
driverMock := new(DriverMock)
driverMock.WaitForShutdownState = true
state.Put("driver", driverMock)
step := &stepShutdown{
ShutdownCommand: "",
ShutdownTimeout: 5 * time.Minute,
Comm: &communicator.Config{
Type: "none",
},
}
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have successfully shut down.")
}
err := state.Get("error")
if err != nil {
err = err.(error)
t.Fatalf("Shutdown shouldn't have errored; err: %v", err)
}
}
func Test_Shutdown_Null_failure(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", packersdk.TestUi(t))
driverMock := new(DriverMock)
driverMock.WaitForShutdownState = false
state.Put("driver", driverMock)
step := &stepShutdown{
ShutdownCommand: "",
ShutdownTimeout: 5 * time.Minute,
Comm: &communicator.Config{
Type: "none",
},
}
action := step.Run(context.TODO(), state)
if action != multistep.ActionHalt {
t.Fatalf("Shouldn't have successfully shut down.")
}
err := state.Get("error")
if err == nil {
t.Fatalf("Shutdown should have errored")
}
}
func Test_Shutdown_NoShutdownCommand(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", packersdk.TestUi(t))
driverMock := new(DriverMock)
state.Put("driver", driverMock)
step := &stepShutdown{
ShutdownCommand: "",
ShutdownTimeout: 5 * time.Minute,
Comm: &communicator.Config{
Type: "ssh",
},
}
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have successfully shut down.")
}
if !driverMock.StopCalled {
t.Fatalf("should have called Stop through the driver.")
}
err := state.Get("error")
if err != nil {
err = err.(error)
t.Fatalf("Shutdown shouldn't have errored; err: %v", err)
}
}

View File

@ -1,19 +0,0 @@
package qemu
import (
"bytes"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("driver", new(DriverMock))
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}

View File

@ -1,139 +0,0 @@
package qemu
import (
"context"
"fmt"
"log"
"net"
"time"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/mitchellh/go-vnc"
)
const KeyLeftShift uint32 = 0xFFE1
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort int
Name string
}
// This step "types" the boot command into the VM over VNC.
//
// Uses:
// config *config
// http_port int
// ui packersdk.Ui
// vnc_port int
//
// Produces:
// <nothing>
type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
debug := state.Get("debug").(bool)
httpPort := state.Get("http_port").(int)
ui := state.Get("ui").(packersdk.Ui)
vncPort := state.Get("vnc_port").(int)
vncIP := config.VNCBindAddress
vncPassword := state.Get("vnc_password")
if config.VNCConfig.DisableVNC {
log.Println("Skipping boot command step...")
return multistep.ActionContinue
}
// Wait the for the vm to boot.
if int64(config.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait))
select {
case <-time.After(config.BootWait):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
var pauseFn multistep.DebugPauseFn
if debug {
pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn)
}
// Connect to VNC
ui.Say(fmt.Sprintf("Connecting to VM via VNC (%s:%d)", vncIP, vncPort))
nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIP, vncPort))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
defer nc.Close()
var auth []vnc.ClientAuth
if vncPassword != nil && len(vncPassword.(string)) > 0 {
auth = []vnc.ClientAuth{&vnc.PasswordAuth{Password: vncPassword.(string)}}
} else {
auth = []vnc.ClientAuth{new(vnc.ClientAuthNone)}
}
c, err := vnc.Client(nc, &vnc.ClientConfig{Auth: auth, Exclusive: false})
if err != nil {
err := fmt.Errorf("Error handshaking with VNC: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
defer c.Close()
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
hostIP := state.Get("http_ip").(string)
configCtx := config.ctx
configCtx.Data = &bootCommandTemplateData{
hostIP,
httpPort,
config.VMName,
}
d := bootcommand.NewVNCDriver(c, config.VNCConfig.BootKeyInterval)
ui.Say("Typing the boot command over VNC...")
command, err := interpolate.Render(config.VNCConfig.FlatBootCommand(), &configCtx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
seq, err := bootcommand.GenerateExpressionSequence(command)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if pauseFn != nil {
pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state)
}
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}

View File

@ -1,136 +0,0 @@
package qemu
import (
"bufio"
"context"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/digitalocean/go-qemu/qmp"
)
// 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 {
ui := state.Get("ui").(packersdk.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...", s.NetBridge))
for {
guestAddress := getGuestAddress(qmpMonitor, s.NetBridge, "user.0")
if guestAddress != "" {
log.Printf("Found guest address %s", guestAddress)
state.Put("guestAddress", guestAddress)
return multistep.ActionContinue
}
select {
case <-time.After(10 * time.Second):
continue
case <-ctx.Done():
return multistep.ActionHalt
}
}
}
func (s *stepWaitGuestAddress) Cleanup(state multistep.StateBag) {
}
func getGuestAddress(qmpMonitor *qmp.SocketMonitor, bridgeName string, deviceName string) string {
devices, err := getNetDevices(qmpMonitor)
if err != nil {
log.Printf("Could not retrieve QEMU QMP network device list: %v", err)
return ""
}
for _, device := range devices {
if device.Name == deviceName {
ipAddress, _ := getDeviceIPAddress(bridgeName, device.MacAddress)
return ipAddress
}
}
log.Printf("QEMU QMP network device %s was not found", deviceName)
return ""
}
func getDeviceIPAddress(device string, macAddress string) (string, error) {
// this parses /proc/net/arp to retrieve the given device IP address.
//
// /proc/net/arp is normally someting alike:
//
// IP address HW type Flags HW address Mask Device
// 192.168.121.111 0x1 0x2 52:54:00:12:34:56 * virbr0
//
const (
IPAddressIndex int = iota
HWTypeIndex
FlagsIndex
HWAddressIndex
MaskIndex
DeviceIndex
)
// see ARP flags at https://github.com/torvalds/linux/blob/v5.4/include/uapi/linux/if_arp.h#L132
const (
AtfCom int = 0x02 // ATF_COM (complete)
)
f, err := os.Open("/proc/net/arp")
if err != nil {
return "", fmt.Errorf("failed to open /proc/net/arp: %w", err)
}
defer f.Close()
s := bufio.NewScanner(f)
s.Scan()
for s.Scan() {
fields := strings.Fields(s.Text())
if device != "" && fields[DeviceIndex] != device {
continue
}
if fields[HWAddressIndex] != macAddress {
continue
}
flags, err := strconv.ParseInt(fields[FlagsIndex], 0, 32)
if err != nil {
return "", fmt.Errorf("failed to parse /proc/net/arp flags field %s: %w", fields[FlagsIndex], err)
}
if int(flags)&AtfCom == AtfCom {
return fields[IPAddressIndex], nil
}
}
return "", fmt.Errorf("could not find %s", macAddress)
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var QemuPluginVersion *version.PluginVersion
func init() {
QemuPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -44,7 +44,6 @@ import (
proxmoxbuilder "github.com/hashicorp/packer/builder/proxmox"
proxmoxclonebuilder "github.com/hashicorp/packer/builder/proxmox/clone"
proxmoxisobuilder "github.com/hashicorp/packer/builder/proxmox/iso"
qemubuilder "github.com/hashicorp/packer/builder/qemu"
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
tritonbuilder "github.com/hashicorp/packer/builder/triton"
@ -117,7 +116,6 @@ var Builders = map[string]packersdk.Builder{
"proxmox": new(proxmoxbuilder.Builder),
"proxmox-clone": new(proxmoxclonebuilder.Builder),
"proxmox-iso": new(proxmoxisobuilder.Builder),
"qemu": new(qemubuilder.Builder),
"scaleway": new(scalewaybuilder.Builder),
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
"triton": new(tritonbuilder.Builder),

View File

@ -1,219 +0,0 @@
---
modeline: |
vim: set ft=pandoc:
description: |
The Qemu Packer builder is able to create KVM virtual machine images.
page_title: QEMU - Builders
---
# QEMU Builder
Type: `qemu`
Artifact BuilderId: `transcend.qemu`
The Qemu Packer builder is able to create [KVM](http://www.linux-kvm.org) virtual
machine images.
The builder builds a virtual machine by creating a new virtual machine from
scratch, booting it, installing an OS, rebooting the machine with the boot media
as the virtual hard drive, provisioning software within the OS, then shutting it
down. The result of the Qemu builder is a directory containing the image file
necessary to run the virtual machine on KVM.
## Basic Example
Here is a basic example. This example is functional so long as you fixup paths
to files, URLS for ISOs and checksums.
<Tabs>
<Tab heading="JSON">
```json
{
"builders": [
{
"type": "qemu",
"iso_url": "http://mirror.raystedman.net/centos/6/isos/x86_64/CentOS-6.9-x86_64-minimal.iso",
"iso_checksum": "md5:af4a1640c0c6f348c6c41f1ea9e192a2",
"output_directory": "output_centos_tdhtest",
"shutdown_command": "echo 'packer' | sudo -S shutdown -P now",
"disk_size": "5000M",
"format": "qcow2",
"accelerator": "kvm",
"http_directory": "path/to/httpdir",
"ssh_username": "root",
"ssh_password": "s0m3password",
"ssh_timeout": "20m",
"vm_name": "tdhtest",
"net_device": "virtio-net",
"disk_interface": "virtio",
"boot_wait": "10s",
"boot_command": [
"<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg<enter><wait>"
]
}
]
}
```
</Tab>
<Tab heading="HCL2">
```hcl
source "qemu" "example" {
iso_url = "http://mirror.raystedman.net/centos/6/isos/x86_64/CentOS-6.9-x86_64-minimal.iso"
iso_checksum = "md5:af4a1640c0c6f348c6c41f1ea9e192a2"
output_directory = "output_centos_tdhtest"
shutdown_command = "echo 'packer' | sudo -S shutdown -P now"
disk_size = "5000M"
format = "qcow2"
accelerator = "kvm"
http_directory = "path/to/httpdir"
ssh_username = "root"
ssh_password = "s0m3password"
ssh_timeout = "20m"
vm_name = "tdhtest"
net_device = "virtio-net"
disk_interface = "virtio"
boot_wait = "10s"
boot_command = ["<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg<enter><wait>"]
}
build {
sources = ["source.qemu.example"]
}
```
</Tab>
</Tabs>
This is an example only, and will time out waiting for SSH because we have not
provided a kickstart file. You must add a valid kickstart file to the
"http_directory" and then provide the file in the "boot_command" in order for
this build to run. We recommend you check out the
[Community Templates](/community-tools#templates)
for a practical usage example.
Note that you will need to set `"headless": true` if you are running Packer
on a Linux server without X11; or if you are connected via SSH to a remote
Linux server and have not enabled X11 forwarding (`ssh -X`).
## Qemu Specific Configuration Reference
There are many configuration options available for the builder. In addition to
the items listed here, you will want to look at the general configuration
references for [ISO](#iso-configuration),
[HTTP](#http-directory-configuration),
[Floppy](#floppy-configuration),
[Boot](#boot-configuration),
[Shutdown](#shutdown-configuration),
[Communicator](#communicator-configuration)
configuration references, which are
necessary for this build to succeed and can be found further down the page.
### Optional:
@include 'builder/qemu/Config-not-required.mdx'
## ISO Configuration
@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig.mdx'
### Required:
@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-required.mdx'
### Optional:
@include 'packer-plugin-sdk/multistep/commonsteps/ISOConfig-not-required.mdx'
## Http directory configuration
@include 'packer-plugin-sdk/multistep/commonsteps/HTTPConfig.mdx'
### Optional:
@include 'packer-plugin-sdk/multistep/commonsteps/HTTPConfig-not-required.mdx'
## Floppy configuration
@include 'packer-plugin-sdk/multistep/commonsteps/FloppyConfig.mdx'
### Optional:
@include 'packer-plugin-sdk/multistep/commonsteps/FloppyConfig-not-required.mdx'
### CD configuration
@include 'packer-plugin-sdk/multistep/commonsteps/CDConfig.mdx'
#### Optional:
@include 'packer-plugin-sdk/multistep/commonsteps/CDConfig-not-required.mdx'
## Shutdown configuration
### Optional:
@include 'packer-plugin-sdk/shutdowncommand/ShutdownConfig-not-required.mdx'
## Communicator configuration
### Optional common fields:
@include 'packer-plugin-sdk/communicator/Config-not-required.mdx'
@include 'builder/qemu/CommConfig-not-required.mdx'
### Optional SSH fields:
@include 'packer-plugin-sdk/communicator/SSH-not-required.mdx'
@include 'packer-plugin-sdk/communicator/SSH-Private-Key-File-not-required.mdx'
### Optional WinRM fields:
@include 'packer-plugin-sdk/communicator/WinRM-not-required.mdx'
## Boot Configuration
@include 'packer-plugin-sdk/bootcommand/VNCConfig.mdx'
@include 'packer-plugin-sdk/bootcommand/BootConfig.mdx'
### Optional:
@include 'packer-plugin-sdk/bootcommand/VNCConfig-not-required.mdx'
@include 'packer-plugin-sdk/bootcommand/BootConfig-not-required.mdx'
### Communicator Configuration
#### Optional:
@include 'packer-plugin-sdk/communicator/Config-not-required.mdx'
### Troubleshooting
#### Invalid Keymaps
Some users have experienced errors complaining about invalid keymaps. This
seems to be related to having a `common` directory or file in the directory
they've run Packer in, like the Packer source directory. This appears to be an
upstream bug with qemu, and the best solution for now is to remove the
file/directory or run in another directory.
Some users have reported issues with incorrect keymaps using qemu version 2.11.
This is a bug with qemu, and the solution is to upgrade, or downgrade to 2.10.1
or earlier.
#### Corrupted image after Packer calls qemu-img convert on OSX
Due to an upstream bug with `qemu-img convert` on OSX, sometimes the qemu-img
convert call will create a corrupted image. If this is an issue for you, make
sure that the the output format (provided using the option `format`) matches
the input file's format and file extension, and Packer will
perform a simple copy operation instead. You will also want to set
`"skip_compaction": true,` and `"disk_compression": false` to skip a final
image conversion at the end of the build. See
https://bugs.launchpad.net/qemu/+bug/1776920 for more details.

View File

@ -1,15 +0,0 @@
<!-- Code generated from the comments of the CommConfig struct in builder/qemu/comm_config.go; DO NOT EDIT MANUALLY -->
- `host_port_min` (int) - The minimum port to use for the Communicator port on the host machine which is forwarded
to the SSH or WinRM port on the guest machine. By default this is 2222.
- `host_port_max` (int) - The maximum port to use for the Communicator port on the host machine which is forwarded
to the SSH or WinRM port on the guest machine. Because Packer often runs in parallel,
Packer will choose a randomly available port in this range to use as the
host port. By default this is 4444.
- `skip_nat_mapping` (bool) - Defaults to false. When enabled, Packer
does not setup forwarded port mapping for communicator (SSH or WinRM) requests and uses ssh_port or winrm_port
on the host to communicate to the virtual machine.
<!-- End of code generated from the comments of the CommConfig struct in builder/qemu/comm_config.go; -->

View File

@ -1,325 +0,0 @@
<!-- 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`.
- `accelerator` (string) - 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.
- `disk_additional_size` ([]string) - 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.
- `cpus` (int) - The number of cpus to use when building the VM.
The default is `1` CPU.
- `firmware` (string) - The firmware file to be used by QEMU, which is to be set by the -bios
option of QEMU. Particularly, this option can be set to use EFI instead
of BIOS, by using "OVMF.fd" from OpenFirmware.
If unset, no -bios option is passed to QEMU, using the default of QEMU.
Also see the QEMU documentation.
- `disk_interface` (string) - 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.
- `disk_size` (string) - 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.
- `skip_resize_disk` (bool) - Packer resizes the QCOW2 image using
qemu-img resize. Set this option to true to disable resizing.
Defaults to false.
- `disk_cache` (string) - 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`.
- `disk_discard` (string) - The discard mode to use for disk. Allowed values
include any of unmap or ignore. By default, this is set to ignore.
- `disk_detect_zeroes` (string) - 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.
- `skip_compaction` (bool) - Packer compacts the QCOW2 image using
qemu-img convert. Set this option to true to disable compacting.
Defaults to false.
- `disk_compression` (bool) - Apply compression to the QCOW2 disk file
using qemu-img convert. Defaults to false.
- `format` (string) - Either `qcow2` or `raw`, this specifies the output format of the virtual
machine image. This defaults to `qcow2`. Due to a long-standing bug with
`qemu-img convert` on OSX, sometimes the qemu-img convert call will
create a corrupted image. If this is an issue for you, make sure that the
the output format matches the input file's format, and Packer will
perform a simple copy operation instead. See
https://bugs.launchpad.net/qemu/+bug/1776920 for more details.
- `headless` (bool) - 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>`
- `disk_image` (bool) - 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.
- `use_backing_file` (bool) - 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.
- `machine_type` (string) - 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`.
- `memory` (int) - The amount of memory to use when building the VM
in megabytes. This defaults to 512 megabytes.
- `net_device` (string) - 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.
- `net_bridge` (string) - 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.
- `output_directory` (string) - This is the path to the directory where the
resulting virtual machine will be created. This may be relative or absolute.
If relative, the path is relative to the working directory when packer
is executed. This directory must not exist or be empty prior to running
the builder. By default this is output-BUILDNAME where "BUILDNAME" is the
name of the build.
- `qemuargs` ([][]string) - Allows complete control over the qemu command line (though not 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 adding a "--drive" or "--device" override will mean that
none of the default configuration Packer sets will be used. To see the
defaults that Packer sets, look in your packer.log
file (set PACKER_LOG=1 to get verbose logging) and search for the
qemu-system-x86 command. The arguments are all printed for review, and
you can use those arguments along with the template engines allowed
by qemu-args to set up a working configuration that includes both the
Packer defaults and your extra arguments.
Another pitfall could be setting arguments like --no-acpi, which could
break the ability to send power signal type commands
(e.g., shutdown -P now) to the virtual machine, thus preventing proper
shutdown.
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 }}`
- `qemu_img_args` (QemuImgArgs) - A map of custom arguments to pass to qemu-img commands, where the key
is the subcommand, and the values are lists of strings for each flag.
Example:
In JSON:
```json
{
"qemu_img_args": {
"convert": ["-o", "preallocation=full"],
"resize": ["-foo", "bar"]
}
```
Please note
that unlike qemuargs, these commands are not split into switch-value
sub-arrays, because the basic elements in qemu-img calls are unlikely
to need an actual override.
The arguments will be constructed as follows:
- Convert:
Default is `qemu-img convert -O $format $sourcepath $targetpath`. Adding
arguments ["-foo", "bar"] to qemu_img_args.convert will change this to
`qemu-img convert -foo bar -O $format $sourcepath $targetpath`
- Create:
Default is `create -f $format $targetpath $size`. Adding arguments
["-foo", "bar"] to qemu_img_args.create will change this to
"create -f qcow2 -foo bar target.qcow2 1234M"
- Resize:
Default is `qemu-img resize -f $format $sourcepath $size`. Adding
arguments ["-foo", "bar"] to qemu_img_args.resize will change this to
`qemu-img resize -f $format -foo bar $sourcepath $size`
- `qemu_binary` (string) - 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.
- `qmp_enable` (bool) - Enable QMP socket. Location is specified by `qmp_socket_path`. Defaults
to false.
- `qmp_socket_path` (string) - QMP Socket Path when `qmp_enable` is true. Defaults to
`output_directory`/`vm_name`.monitor.
- `use_default_display` (bool) - 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.
- `display` (string) - 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.
- `vnc_bind_address` (string) - 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.
- `vnc_use_password` (bool) - 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`.
- `vnc_port_min` (int) - 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.
The minimum port cannot be set below 5900 due to a quirk in how QEMU parses
vnc display address.
- `vnc_port_max` (int) - VNC Port Max
- `vm_name` (string) - 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.
- `cdrom_interface` (string) - 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`.
<!-- End of code generated from the comments of the Config struct in builder/qemu/config.go; -->

View File

@ -1,9 +0,0 @@
<!-- Code generated from the comments of the QemuImgArgs struct in builder/qemu/config.go; DO NOT EDIT MANUALLY -->
- `convert` ([]string) - Convert
- `create` ([]string) - Create
- `resize` ([]string) - Resize
<!-- End of code generated from the comments of the QemuImgArgs struct in builder/qemu/config.go; -->

View File

@ -845,10 +845,6 @@
}
]
},
{
"title": "QEMU",
"path": "builders/qemu"
},
{
"title": "Scaleway",
"path": "builders/scaleway"