From 4ebee7bf3f4fecf7b49018225e798de845b3dbe2 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Sun, 21 Jun 2015 12:17:06 +0300 Subject: [PATCH 1/4] builder/parallels: Add "DiskPath" driver function This function determines path to the first virtual disk image of the specified virtual machine. --- builder/parallels/common/driver.go | 3 +++ builder/parallels/common/driver_9.go | 17 +++++++++++++++++ builder/parallels/common/driver_mock.go | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index 03a4e0f09..81b7cd974 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -18,6 +18,9 @@ type Driver interface { // Adds new CD/DVD drive to the VM and returns name of this device DeviceAddCdRom(string, string) (string, error) + // Get path to the first virtual disk image + DiskPath(string) (string, error) + // Import a VM Import(string, string, string, bool) error diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index c577151dc..1093351c8 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -121,6 +121,23 @@ func (d *Parallels9Driver) DeviceAddCdRom(name string, image string) (string, er return device_name, nil } +func (d *Parallels9Driver) DiskPath(name string) (string, error) { + out, err := exec.Command(d.PrlctlPath, "list", "-i", name).Output() + if err != nil { + return "", err + } + + hddRe := regexp.MustCompile("hdd0.* image='(.*)' type=*") + matches := hddRe.FindStringSubmatch(string(out)) + if matches == nil { + return "", fmt.Errorf( + "Could not determine hdd image path in the output:\n%s", string(out)) + } + + hdd_path := matches[1] + return hdd_path, nil +} + func (d *Parallels9Driver) IsRunning(name string) (bool, error) { var stdout bytes.Buffer diff --git a/builder/parallels/common/driver_mock.go b/builder/parallels/common/driver_mock.go index 5629a6db9..fc43247f5 100644 --- a/builder/parallels/common/driver_mock.go +++ b/builder/parallels/common/driver_mock.go @@ -11,6 +11,11 @@ type DriverMock struct { DeviceAddCdRomResult string DeviceAddCdRomErr error + DiskPathCalled bool + DiskPathName string + DiskPathResult string + DiskPathErr error + ImportCalled bool ImportName string ImportSrcPath string @@ -61,6 +66,12 @@ func (d *DriverMock) DeviceAddCdRom(name string, image string) (string, error) { return d.DeviceAddCdRomResult, d.DeviceAddCdRomErr } +func (d *DriverMock) DiskPath(name string) (string, error) { + d.DiskPathCalled = true + d.DiskPathName = name + return d.DiskPathResult, d.DiskPathErr +} + func (d *DriverMock) Import(name, srcPath, dstPath string, reassignMac bool) error { d.ImportCalled = true d.ImportName = name From f7b26e44fe747879e942e497efef78c20c45ce71 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Sun, 21 Jun 2015 13:34:35 +0300 Subject: [PATCH 2/4] builder/parallels: Add "CompactDisk" driver function This function compacts the specified virtual disk image. --- builder/parallels/common/driver.go | 3 +++ builder/parallels/common/driver_9.go | 27 +++++++++++++++++++++++++ builder/parallels/common/driver_mock.go | 10 +++++++++ 3 files changed, 40 insertions(+) diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index 81b7cd974..e6a5b7bb1 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -15,6 +15,9 @@ import ( // versions out of the builder steps, so sometimes the methods are // extremely specific. type Driver interface { + // Compact a virtual disk image. + CompactDisk(string) error + // Adds new CD/DVD drive to the VM and returns name of this device DeviceAddCdRom(string, string) (string, error) diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index 1093351c8..2c7f2fc2f 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -98,6 +98,33 @@ func getAppPath(bundleId string) (string, error) { return pathOutput, nil } +func (d *Parallels9Driver) CompactDisk(diskPath string) error { + prlDiskToolPath, err := exec.LookPath("prl_disk_tool") + if err != nil { + return err + } + + // Analyze the disk content and remove unused blocks + command := []string{ + "compact", + "--hdd", diskPath, + } + if err := exec.Command(prlDiskToolPath, command...).Run(); err != nil { + return err + } + + // Remove null blocks + command = []string{ + "compact", "--buildmap", + "--hdd", diskPath, + } + if err := exec.Command(prlDiskToolPath, command...).Run(); err != nil { + return err + } + + return nil +} + func (d *Parallels9Driver) DeviceAddCdRom(name string, image string) (string, error) { command := []string{ "set", name, diff --git a/builder/parallels/common/driver_mock.go b/builder/parallels/common/driver_mock.go index fc43247f5..fcd6b4b88 100644 --- a/builder/parallels/common/driver_mock.go +++ b/builder/parallels/common/driver_mock.go @@ -5,6 +5,10 @@ import "sync" type DriverMock struct { sync.Mutex + CompactDiskCalled bool + CompactDiskPath string + CompactDiskErr error + DeviceAddCdRomCalled bool DeviceAddCdRomName string DeviceAddCdRomImage string @@ -59,6 +63,12 @@ type DriverMock struct { IpAddressError error } +func (d *DriverMock) CompactDisk(path string) error { + d.CompactDiskCalled = true + d.CompactDiskPath = path + return d.CompactDiskErr +} + func (d *DriverMock) DeviceAddCdRom(name string, image string) (string, error) { d.DeviceAddCdRomCalled = true d.DeviceAddCdRomName = name From abfe706fc693f80c5bfe450bf4e9d860d69bb1d3 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Sun, 21 Jun 2015 13:35:52 +0300 Subject: [PATCH 3/4] builder/parallels: Add "StepCompactDisk" --- builder/parallels/common/step_compact_disk.go | 51 +++++++++++++ .../common/step_compact_disk_test.go | 73 +++++++++++++++++++ builder/parallels/iso/builder.go | 4 + 3 files changed, 128 insertions(+) create mode 100644 builder/parallels/common/step_compact_disk.go create mode 100644 builder/parallels/common/step_compact_disk_test.go diff --git a/builder/parallels/common/step_compact_disk.go b/builder/parallels/common/step_compact_disk.go new file mode 100644 index 000000000..0ebc7a134 --- /dev/null +++ b/builder/parallels/common/step_compact_disk.go @@ -0,0 +1,51 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// This step removes all empty blocks from expanding Parallels virtual disks +// and reduces the result disk size +// +// Uses: +// driver Driver +// vmName string +// ui packer.Ui +// +// Produces: +// +type StepCompactDisk struct { + Skip bool +} + +func (s *StepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + if s.Skip { + ui.Say("Skipping disk compaction step...") + return multistep.ActionContinue + } + + ui.Say("Compacting the disk image") + diskPath, err := driver.DiskPath(vmName) + if err != nil { + err := fmt.Errorf("Error detecting virtual disk path: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if err := driver.CompactDisk(diskPath); err != nil { + state.Put("error", fmt.Errorf("Error compacting disk: %s", err)) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (*StepCompactDisk) Cleanup(multistep.StateBag) {} diff --git a/builder/parallels/common/step_compact_disk_test.go b/builder/parallels/common/step_compact_disk_test.go new file mode 100644 index 000000000..ace932a2d --- /dev/null +++ b/builder/parallels/common/step_compact_disk_test.go @@ -0,0 +1,73 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCompactDisk_impl(t *testing.T) { + var _ multistep.Step = new(StepCompactDisk) +} + +func TestStepCompactDisk(t *testing.T) { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + state := testState(t) + step := new(StepCompactDisk) + + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Mock results + driver.DiskPathResult = tf.Name() + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.CompactDiskCalled { + t.Fatal("should've called") + } + + path, _ := driver.DiskPath("foo") + if path != tf.Name() { + t.Fatal("should call with right path") + } +} + +func TestStepCompactDisk_skip(t *testing.T) { + state := testState(t) + step := new(StepCompactDisk) + step.Skip = true + + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if driver.CompactDiskCalled { + t.Fatal("should not have called") + } +} diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index 6b731544d..bfc7c23c4 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -45,6 +45,7 @@ type Config struct { ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls"` + SkipCompaction bool `mapstructure:"skip_compaction"` VMName string `mapstructure:"vm_name"` RawSingleISOUrl string `mapstructure:"iso_url"` @@ -272,6 +273,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Commands: b.config.PrlctlPost, Ctx: b.config.ctx, }, + ¶llelscommon.StepCompactDisk{ + Skip: b.config.SkipCompaction, + }, } // Setup the state bag From 36a6fc2cc450d35e0ab41132fe50a1e3f0dfe54c Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 11 Sep 2015 16:13:39 +0300 Subject: [PATCH 4/4] website/docs: Add description of "skip_compaction" option for Parallels builders --- website/source/docs/builders/parallels-iso.html.markdown | 5 +++++ website/source/docs/builders/parallels-pvm.html.markdown | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/website/source/docs/builders/parallels-iso.html.markdown b/website/source/docs/builders/parallels-iso.html.markdown index 76278ec2b..4200adb73 100644 --- a/website/source/docs/builders/parallels-iso.html.markdown +++ b/website/source/docs/builders/parallels-iso.html.markdown @@ -196,6 +196,11 @@ builder. doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. +- `skip_compaction` (boolean) - Virtual disk image is compacted at the end of + the build process using `prl_disk_tool` utility. In certain rare cases, this + might corrupt the resulting disk image. If you find this to be the case, + you can disable compaction using this configuration value. + - `vm_name` (string) - This is the name of the PVM directory for the new virtual machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. diff --git a/website/source/docs/builders/parallels-pvm.html.markdown b/website/source/docs/builders/parallels-pvm.html.markdown index ce13f2c19..2c81ecd44 100644 --- a/website/source/docs/builders/parallels-pvm.html.markdown +++ b/website/source/docs/builders/parallels-pvm.html.markdown @@ -142,6 +142,11 @@ builder. doesn't shut down in this time, it is an error. By default, the timeout is "5m", or five minutes. +- `skip_compaction` (boolean) - Virtual disk image is compacted at the end of + the build process using `prl_disk_tool` utility. In certain rare cases, this + might corrupt the resulting disk image. If you find this to be the case, + you can disable compaction using this configuration value. + - `vm_name` (string) - This is the name of the virtual machine when it is imported as well as the name of the PVM directory when the virtual machine is exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is the