remote qemu plugin
This commit is contained in:
parent
88f8feecfe
commit
642ed07476
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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")
|
||||
}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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.
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -845,10 +845,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "QEMU",
|
||||
"path": "builders/qemu"
|
||||
},
|
||||
{
|
||||
"title": "Scaleway",
|
||||
"path": "builders/scaleway"
|
||||
|
|
Loading…
Reference in New Issue