Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parallels: Add "CompactDisk" build step #2731

Merged
merged 4 commits into from
Sep 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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,
Expand All @@ -121,6 +148,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 @@ -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,
},
&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