diff --git a/builder/vmware/common/output_dir.go b/builder/vmware/common/output_dir.go new file mode 100644 index 000000000..8af513a19 --- /dev/null +++ b/builder/vmware/common/output_dir.go @@ -0,0 +1,15 @@ +package common + +// OutputDir is an interface type that abstracts the creation and handling +// of the output directory for VMware-based products. The abstraction is made +// so that the output directory can be properly made on remote (ESXi) based +// VMware products as well as local. +type OutputDir interface { + DirExists() (bool, error) + ListFiles() ([]string, error) + MkdirAll() error + Remove(string) error + RemoveAll() error + SetOutputDir(string) + String() string +} diff --git a/builder/vmware/common/output_dir_local.go b/builder/vmware/common/output_dir_local.go new file mode 100644 index 000000000..e864ca316 --- /dev/null +++ b/builder/vmware/common/output_dir_local.go @@ -0,0 +1,53 @@ +package common + +import ( + "os" + "path/filepath" +) + +// LocalOutputDir is an OutputDir implementation where the directory +// is on the local machine. +type LocalOutputDir struct { + Dir string +} + +func (d *LocalOutputDir) DirExists() (bool, error) { + _, err := os.Stat(d.Dir) + return err == nil, nil +} + +func (d *LocalOutputDir) ListFiles() ([]string, error) { + files := make([]string, 0, 10) + + visit := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + files = append(files, path) + } + return nil + } + + return files, filepath.Walk(d.Dir, visit) +} + +func (d *LocalOutputDir) MkdirAll() error { + return os.MkdirAll(d.Dir, 0755) +} + +func (d *LocalOutputDir) Remove(path string) error { + return os.Remove(path) +} + +func (d *LocalOutputDir) RemoveAll() error { + return os.RemoveAll(d.Dir) +} + +func (d *LocalOutputDir) SetOutputDir(path string) { + d.Dir = path +} + +func (d *LocalOutputDir) String() string { + return d.Dir +} diff --git a/builder/vmware/common/output_dir_local_test.go b/builder/vmware/common/output_dir_local_test.go new file mode 100644 index 000000000..c3197117e --- /dev/null +++ b/builder/vmware/common/output_dir_local_test.go @@ -0,0 +1,9 @@ +package common + +import ( + "testing" +) + +func TestLocalOuputDir_impl(t *testing.T) { + var _ OutputDir = new(LocalOutputDir) +} diff --git a/builder/vmware/common/step_output_dir.go b/builder/vmware/common/step_output_dir.go new file mode 100644 index 000000000..17f13d5d3 --- /dev/null +++ b/builder/vmware/common/step_output_dir.go @@ -0,0 +1,74 @@ +package common + +import ( + "fmt" + "log" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepOutputDir sets up the output directory by creating it if it does +// not exist, deleting it if it does exist and we're forcing, and cleaning +// it up when we're done with it. +type StepOutputDir struct { + Force bool + + success bool +} + +func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction { + dir := state.Get("dir").(OutputDir) + ui := state.Get("ui").(packer.Ui) + + exists, err := dir.DirExists() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + if exists { + if s.Force { + ui.Say("Deleting previous output directory...") + dir.RemoveAll() + } else { + state.Put("error", fmt.Errorf( + "Output directory '%s' already exists.", dir.String())) + return multistep.ActionHalt + } + } + + if err := dir.MkdirAll(); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + s.success = true + return multistep.ActionContinue +} + +func (s *StepOutputDir) Cleanup(state multistep.StateBag) { + if !s.success { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + dir := state.Get("dir").(OutputDir) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := dir.RemoveAll() + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/vmware/common/step_output_dir_test.go b/builder/vmware/common/step_output_dir_test.go new file mode 100644 index 000000000..bacc1d85c --- /dev/null +++ b/builder/vmware/common/step_output_dir_test.go @@ -0,0 +1,150 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "io/ioutil" + "os" + "testing" +) + +func testOutputDir(t *testing.T) *LocalOutputDir { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + os.RemoveAll(td) + return &LocalOutputDir{Dir: td} +} + +func TestStepOutputDir_impl(t *testing.T) { + var _ multistep.Step = new(StepOutputDir) +} + +func TestStepOutputDir(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // 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") + } + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestStepOutputDir_existsNoForce(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Make sure the dir exists + if err := os.MkdirAll(dir.Dir, 0755); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the run + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("should have error") + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatal("should not delete dir") + } +} + +func TestStepOutputDir_existsForce(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + step.Force = true + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Make sure the dir exists + if err := os.MkdirAll(dir.Dir, 0755); err != nil { + t.Fatalf("err: %s", err) + } + + // 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") + } + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestStepOutputDir_cancel(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // 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") + } + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test cancel/halt + state.Put(multistep.StateCancelled, true) + step.Cleanup(state) + if _, err := os.Stat(dir.Dir); err == nil { + t.Fatal("directory should not exist") + } +} + +func TestStepOutputDir_halt(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // 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") + } + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test cancel/halt + state.Put(multistep.StateHalted, true) + step.Cleanup(state) + if _, err := os.Stat(dir.Dir); err == nil { + t.Fatal("directory should not exist") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 2dd7f3776..173a8fc7c 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -346,6 +346,25 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } + // Determine the output dir implementation + var dir OutputDir + switch d := driver.(type) { + case OutputDir: + dir = d + default: + dir = new(vmwcommon.LocalOutputDir) + } + dir.SetOutputDir(b.config.OutputDir) + + // Setup the state bag + state := new(multistep.BasicStateBag) + state.Put("cache", cache) + state.Put("config", &b.config) + state.Put("dir", dir) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + // Seed the random number generator rand.Seed(time.Now().UTC().UnixNano()) @@ -358,7 +377,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ResultKey: "iso_path", Url: b.config.ISOUrls, }, - &stepPrepareOutputDir{}, + &vmwcommon.StepOutputDir{ + Force: b.config.PackerForce, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -390,14 +411,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepCompactDisk{}, } - // Setup the state bag - state := new(multistep.BasicStateBag) - state.Put("cache", cache) - state.Put("config", &b.config) - state.Put("driver", driver) - state.Put("hook", hook) - state.Put("ui", ui) - // Run! if b.config.PackerDebug { b.runner = &multistep.DebugRunner{ diff --git a/builder/vmware/iso/step_prepare_output_dir.go b/builder/vmware/iso/step_prepare_output_dir.go deleted file mode 100644 index abd2bbe29..000000000 --- a/builder/vmware/iso/step_prepare_output_dir.go +++ /dev/null @@ -1,84 +0,0 @@ -package iso - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" - "time" -) - -type stepPrepareOutputDir struct { - dir OutputDir -} - -func (s *stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) - ui := state.Get("ui").(packer.Ui) - - dir := s.outputDir(state) - dir.SetOutputDir(config.OutputDir) - - exists, err := dir.DirExists() - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - if exists { - if config.PackerForce { - ui.Say("Deleting previous output directory...") - dir.RemoveAll() - } else { - state.Put("error", fmt.Errorf( - "Output directory '%s' already exists.", config.OutputDir)) - return multistep.ActionHalt - } - } - - if err := dir.MkdirAll(); err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - s.dir = dir - state.Put("dir", dir) - return multistep.ActionContinue -} - -func (s *stepPrepareOutputDir) Cleanup(state multistep.StateBag) { - _, cancelled := state.GetOk(multistep.StateCancelled) - _, halted := state.GetOk(multistep.StateHalted) - - if cancelled || halted { - ui := state.Get("ui").(packer.Ui) - - if s.dir != nil { - ui.Say("Deleting output directory...") - for i := 0; i < 5; i++ { - err := s.dir.RemoveAll() - if err == nil { - break - } - - log.Printf("Error removing output dir: %s", err) - time.Sleep(2 * time.Second) - } - } - } -} - -func (s *stepPrepareOutputDir) outputDir(state multistep.StateBag) (dir OutputDir) { - driver := state.Get("driver").(Driver) - - switch d := driver.(type) { - case OutputDir: - log.Printf("Using driver as the OutputDir implementation") - dir = d - default: - log.Printf("Using localOutputDir implementation") - dir = new(localOutputDir) - } - - return -}