Merge pull request #9956 from hashicorp/do_6734
builder/qemu: Add qemu_img_args option to set special cli flags for our calls to qemu-img
This commit is contained in:
commit
77817f80a2
|
@ -68,7 +68,16 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Files: b.config.CDConfig.CDFiles,
|
||||
Label: b.config.CDConfig.CDLabel,
|
||||
},
|
||||
new(stepCreateDisk),
|
||||
&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,
|
||||
|
@ -76,7 +85,16 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
UseBackingFile: b.config.UseBackingFile,
|
||||
VMName: b.config.VMName,
|
||||
},
|
||||
new(stepResizeDisk),
|
||||
&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),
|
||||
&common.StepHTTPServer{
|
||||
HTTPDir: b.config.HTTPDir,
|
||||
|
@ -113,7 +131,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
Comm: &b.config.CommConfig.Comm,
|
||||
},
|
||||
new(stepShutdown),
|
||||
new(stepConvertDisk),
|
||||
&stepConvertDisk{
|
||||
DiskCompression: b.config.DiskCompression,
|
||||
Format: b.config.Format,
|
||||
OutputDir: b.config.OutputDir,
|
||||
SkipCompaction: b.config.SkipCompaction,
|
||||
VMName: b.config.VMName,
|
||||
},
|
||||
)
|
||||
|
||||
// Setup the state bag
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
//go:generate mapstructure-to-hcl2 -type Config,QemuImgArgs
|
||||
|
||||
package qemu
|
||||
|
||||
|
@ -57,6 +57,12 @@ var diskDZeroes = map[string]bool{
|
|||
"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"`
|
||||
common.HTTPConfig `mapstructure:",squash"`
|
||||
|
@ -297,6 +303,36 @@ type Config struct {
|
|||
// `{{ .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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
|
||||
// Code generated by "mapstructure-to-hcl2 -type Config,QemuImgArgs"; DO NOT EDIT.
|
||||
package qemu
|
||||
|
||||
import (
|
||||
|
@ -112,6 +112,7 @@ type FlatConfig struct {
|
|||
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"`
|
||||
|
@ -241,6 +242,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"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},
|
||||
|
@ -256,3 +258,30 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testPem = `
|
||||
|
@ -668,3 +669,26 @@ func TestCommConfigPrepare_BackwardsCompatibility(t *testing.T) {
|
|||
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")
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ type DriverMock struct {
|
|||
WaitForShutdownState bool
|
||||
|
||||
QemuImgCalled bool
|
||||
QemuImgCalls [][]string
|
||||
QemuImgCalls []string
|
||||
QemuImgErrs []error
|
||||
|
||||
VerifyCalled bool
|
||||
|
@ -55,7 +55,7 @@ func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool {
|
|||
|
||||
func (d *DriverMock) QemuImg(args ...string) error {
|
||||
d.QemuImgCalled = true
|
||||
d.QemuImgCalls = append(d.QemuImgCalls, args)
|
||||
d.QemuImgCalls = append(d.QemuImgCalls, args...)
|
||||
|
||||
if len(d.QemuImgErrs) >= len(d.QemuImgCalls) {
|
||||
return d.QemuImgErrs[len(d.QemuImgCalls)-1]
|
||||
|
|
|
@ -17,37 +17,32 @@ import (
|
|||
|
||||
// This step converts the virtual disk that was used as the
|
||||
// hard drive for the virtual machine.
|
||||
type stepConvertDisk struct{}
|
||||
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 {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
diskName := config.VMName
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if config.SkipCompaction && !config.DiskCompression {
|
||||
diskName := s.VMName
|
||||
|
||||
if s.SkipCompaction && !s.DiskCompression {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
name := diskName + ".convert"
|
||||
|
||||
sourcePath := filepath.Join(config.OutputDir, diskName)
|
||||
targetPath := filepath.Join(config.OutputDir, name)
|
||||
sourcePath := filepath.Join(s.OutputDir, diskName)
|
||||
targetPath := filepath.Join(s.OutputDir, name)
|
||||
|
||||
command := []string{
|
||||
"convert",
|
||||
}
|
||||
|
||||
if config.DiskCompression {
|
||||
command = append(command, "-c")
|
||||
}
|
||||
|
||||
command = append(command, []string{
|
||||
"-O", config.Format,
|
||||
sourcePath,
|
||||
targetPath,
|
||||
}...,
|
||||
)
|
||||
command := s.buildConvertCommand(sourcePath, targetPath)
|
||||
|
||||
ui.Say("Converting hard drive...")
|
||||
// Retry the conversion a few times in case it takes the qemu process a
|
||||
|
@ -90,4 +85,20 @@ func (s *stepConvertDisk) Run(ctx context.Context, state multistep.StateBag) mul
|
|||
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) {}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
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))
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ type stepCopyDisk struct {
|
|||
OutputDir string
|
||||
UseBackingFile bool
|
||||
VMName string
|
||||
|
||||
QemuImgArgs QemuImgArgs
|
||||
}
|
||||
|
||||
func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -43,12 +45,7 @@ func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multis
|
|||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
command := []string{
|
||||
"convert",
|
||||
"-O", s.Format,
|
||||
isoPath,
|
||||
path,
|
||||
}
|
||||
command := s.buildConvertCommand(isoPath, path)
|
||||
|
||||
ui.Say("Copying hard drive...")
|
||||
if err := driver.QemuImg(command...); err != nil {
|
||||
|
@ -61,4 +58,16 @@ func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multis
|
|||
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) {}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func copyTestState(t *testing.T, d *DriverMock) multistep.StateBag {
|
||||
|
@ -89,3 +90,33 @@ func Test_StepQemuImgCalled(t *testing.T) {
|
|||
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")
|
||||
}
|
||||
|
|
|
@ -12,15 +12,23 @@ import (
|
|||
|
||||
// This step creates the virtual disk that will be used as the
|
||||
// hard drive for the virtual machine.
|
||||
type stepCreateDisk struct{}
|
||||
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 {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
name := config.VMName
|
||||
name := s.VMName
|
||||
|
||||
if config.DiskImage && !config.UseBackingFile {
|
||||
if s.DiskImage && !s.UseBackingFile {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
@ -28,12 +36,12 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
|
||||
ui.Say("Creating required virtual machine disks")
|
||||
// The 'main' or 'default' disk
|
||||
diskFullPaths = append(diskFullPaths, filepath.Join(config.OutputDir, name))
|
||||
diskSizes = append(diskSizes, config.DiskSize)
|
||||
diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, name))
|
||||
diskSizes = append(diskSizes, s.DiskSize)
|
||||
// Additional disks
|
||||
if len(config.AdditionalDiskSize) > 0 {
|
||||
for i, diskSize := range config.AdditionalDiskSize {
|
||||
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d", name, i+1))
|
||||
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)
|
||||
size := diskSize
|
||||
diskSizes = append(diskSizes, size)
|
||||
|
@ -43,19 +51,8 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
// Create all required disks
|
||||
for i, diskFullPath := range diskFullPaths {
|
||||
log.Printf("[INFO] Creating disk with Path: %s and Size: %s", diskFullPath, diskSizes[i])
|
||||
command := []string{
|
||||
"create",
|
||||
"-f", config.Format,
|
||||
}
|
||||
|
||||
if config.UseBackingFile && i == 0 {
|
||||
isoPath := state.Get("iso_path").(string)
|
||||
command = append(command, "-b", isoPath)
|
||||
}
|
||||
|
||||
command = append(command,
|
||||
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)
|
||||
|
@ -71,4 +68,21 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
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.UseBackingFile && i == 0 {
|
||||
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) {}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/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",
|
||||
UseBackingFile: true,
|
||||
},
|
||||
0,
|
||||
[]string{"create", "-f", "qcow2", "-b", "source.qcow2", "target.qcow2", "1234M"},
|
||||
"Basic, happy path, backing store, no extra args",
|
||||
},
|
||||
{
|
||||
&stepCreateDisk{
|
||||
Format: "qcow2",
|
||||
UseBackingFile: 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,
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -11,22 +11,26 @@ import (
|
|||
|
||||
// This step resizes the virtual disk that will be used as the
|
||||
// hard drive for the virtual machine.
|
||||
type stepResizeDisk struct{}
|
||||
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 {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
path := filepath.Join(config.OutputDir, config.VMName)
|
||||
path := filepath.Join(s.OutputDir, s.VMName)
|
||||
|
||||
command := []string{
|
||||
"resize",
|
||||
"-f", config.Format,
|
||||
path,
|
||||
config.DiskSize,
|
||||
}
|
||||
command := s.buildResizeCommand(path)
|
||||
|
||||
if config.DiskImage == false || config.SkipResizeDisk == true {
|
||||
if s.DiskImage == false || s.SkipResizeDisk == true {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
@ -41,4 +45,16 @@ func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) mult
|
|||
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) {}
|
||||
|
|
|
@ -2,81 +2,76 @@ package qemu
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStepResizeDisk_Run(t *testing.T) {
|
||||
state := testState(t)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
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)
|
||||
|
||||
config := &Config{
|
||||
DiskImage: true,
|
||||
SkipResizeDisk: false,
|
||||
DiskSize: "4096M",
|
||||
Format: "qcow2",
|
||||
OutputDir: "/test/",
|
||||
VMName: "test",
|
||||
}
|
||||
state.Put("config", config)
|
||||
step := new(stepResizeDisk)
|
||||
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 qemu-img called")
|
||||
}
|
||||
if len(driver.QemuImgCalls[0]) != 5 {
|
||||
t.Fatal("should 5 qemu-img parameters")
|
||||
// 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 TestStepResizeDisk_SkipIso(t *testing.T) {
|
||||
state := testState(t)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
config := &Config{
|
||||
DiskImage: false,
|
||||
SkipResizeDisk: false,
|
||||
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",
|
||||
},
|
||||
}
|
||||
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 qemu-img called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepResizeDisk_SkipOption(t *testing.T) {
|
||||
state := testState(t)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
config := &Config{
|
||||
DiskImage: false,
|
||||
SkipResizeDisk: true,
|
||||
}
|
||||
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 qemu-img called")
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
|
||||
inArgs := make(map[string][]string)
|
||||
if len(config.QemuArgs) > 0 {
|
||||
ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
|
||||
ui.Say("Overriding default Qemu arguments with QemuArgs...")
|
||||
|
||||
httpIp := state.Get("http_ip").(string)
|
||||
httpPort := state.Get("http_port").(int)
|
||||
|
|
|
@ -231,6 +231,36 @@
|
|||
`{{ .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
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<!-- 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
|
Loading…
Reference in New Issue