Skip to content

Commit

Permalink
Merge pull request #2731 from legal90/prl-compact-disk
Browse files Browse the repository at this point in the history
Parallels: Add "CompactDisk" build step
  • Loading branch information
rickard-von-essen committed Sep 16, 2015
2 parents b528811 + 36a6fc2 commit 3f14b9d
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 0 deletions.
6 changes: 6 additions & 0 deletions builder/parallels/common/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ 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)

// Get path to the first virtual disk image
DiskPath(string) (string, error)

// Import a VM
Import(string, string, string, bool) error

Expand Down
44 changes: 44 additions & 0 deletions builder/parallels/common/driver_9.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,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,
Expand All @@ -125,6 +152,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

Expand Down
21 changes: 21 additions & 0 deletions builder/parallels/common/driver_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ import "sync"
type DriverMock struct {
sync.Mutex

CompactDiskCalled bool
CompactDiskPath string
CompactDiskErr error

DeviceAddCdRomCalled bool
DeviceAddCdRomName string
DeviceAddCdRomImage string
DeviceAddCdRomResult string
DeviceAddCdRomErr error

DiskPathCalled bool
DiskPathName string
DiskPathResult string
DiskPathErr error

ImportCalled bool
ImportName string
ImportSrcPath string
Expand Down Expand Up @@ -54,13 +63,25 @@ 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
d.DeviceAddCdRomImage = image
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
Expand Down
51 changes: 51 additions & 0 deletions builder/parallels/common/step_compact_disk.go
Original file line number Diff line number Diff line change
@@ -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:
// <nothing>
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) {}
73 changes: 73 additions & 0 deletions builder/parallels/common/step_compact_disk_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
4 changes: 4 additions & 0 deletions builder/parallels/iso/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -271,6 +272,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Commands: b.config.PrlctlPost,
Ctx: b.config.ctx,
},
&parallelscommon.StepCompactDisk{
Skip: b.config.SkipCompaction,
},
}

// Setup the state bag
Expand Down
5 changes: 5 additions & 0 deletions website/source/docs/builders/parallels-iso.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions website/source/docs/builders/parallels-pvm.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3f14b9d

Please sign in to comment.