extract output dir configuration and defaulting into the step_output_dir, using pointers to make sure they get set properly back on the main config

This commit is contained in:
Megan Marsh 2020-08-18 11:27:12 -07:00
parent 3b2bedf794
commit a2cfaace59
8 changed files with 212 additions and 99 deletions

View File

@ -33,7 +33,11 @@ type RemoteDriverMock struct {
RemovedCachePath string
CacheRemoved bool
ReturnValDirExists bool
ReloadVMErr error
outputDir string
}
func (d *RemoteDriverMock) UploadISO(path string, checksum string, ui packer.Ui) (string, error) {
@ -81,3 +85,33 @@ func (d *RemoteDriverMock) RemoveCache(localPath string) error {
func (d *RemoteDriverMock) ReloadVM() error {
return d.ReloadVMErr
}
// the following functions satisfy the Outputdir interface
func (d *RemoteDriverMock) DirExists() (bool, error) {
return d.ReturnValDirExists, nil
}
func (d *RemoteDriverMock) ListFiles() ([]string, error) {
return []string{}, nil
}
func (d *RemoteDriverMock) MkdirAll() error {
return nil
}
func (d *RemoteDriverMock) Remove(string) error {
return nil
}
func (d *RemoteDriverMock) RemoveAll() error {
return nil
}
func (d *RemoteDriverMock) SetOutputDir(s string) {
d.outputDir = s
}
func (d *RemoteDriverMock) String() string {
return d.outputDir
}

View File

@ -16,13 +16,62 @@ import (
type StepOutputDir struct {
Force bool
OutputConfig *OutputConfig
VMName string
RemoteType string
success bool
}
func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
dir := state.Get("dir").(OutputDir)
ui := state.Get("ui").(packer.Ui)
func (s *StepOutputDir) SetOutputAndExportDirs(state multistep.StateBag) OutputDir {
driver := state.Get("driver")
// Hold on to your pants. The output configuration is a little more complex
// than you'd expect because of all the moving parts between local and
// remote output, and exports, and legacy behavior.
var dir OutputDir
switch d := driver.(type) {
case OutputDir:
// The driver fulfils the OutputDir interface so that it can create
// output files on the remote instance.
dir = d
default:
// The driver will be running the build and creating the output
// directory locally
dir = new(LocalOutputDir)
}
// If remote type is esx, we need to track both the output dir on the remote
// instance and the output dir locally. exportOutputPath is where we track
// the local output dir.
exportOutputPath := s.OutputConfig.OutputDir
if s.RemoteType != "" {
if s.OutputConfig.RemoteOutputDir != "" {
// User set the remote output dir.
s.OutputConfig.OutputDir = s.OutputConfig.RemoteOutputDir
} else {
// Default output dir to vm name. On remote esx instance, this will
// become something like /vmfs/volumes/mydatastore/vmname/vmname.vmx
s.OutputConfig.OutputDir = s.VMName
}
}
// Remember, this one's either the output from a local build, or the remote
// output from a remote build. Not the local export path for a remote build.
dir.SetOutputDir(s.OutputConfig.OutputDir)
// Set dir in the state for use in file cleanup and artifact
state.Put("dir", dir)
state.Put("export_output_path", exportOutputPath)
return dir
}
func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Configuring output and export directories...")
dir := s.SetOutputAndExportDirs(state)
exists, err := dir.DirExists()
if err != nil {
state.Put("error", err)
@ -31,7 +80,7 @@ func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multi
if exists {
if s.Force {
ui.Say("Deleting previous output directory...")
ui.Message("Deleting previous output directory...")
dir.RemoveAll()
} else {
state.Put("error", fmt.Errorf(

View File

@ -9,16 +9,14 @@ import (
"github.com/hashicorp/packer/helper/multistep"
)
func testOutputDir(t *testing.T) *LocalOutputDir {
func testOutputDir(t *testing.T) string {
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
os.RemoveAll(td)
result := new(LocalOutputDir)
result.SetOutputDir(td)
return result
return td
}
func TestStepOutputDir_impl(t *testing.T) {
@ -27,12 +25,20 @@ func TestStepOutputDir_impl(t *testing.T) {
func TestStepOutputDir(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
driver := new(DriverMock)
state.Put("driver", driver)
dir := testOutputDir(t)
td := testOutputDir(t)
outconfig := &OutputConfig{
OutputDir: td,
}
step := &StepOutputDir{
OutputConfig: outconfig,
VMName: "testVM",
}
// Delete the test output directory when done
defer os.RemoveAll(dir.dir)
state.Put("dir", dir)
defer os.RemoveAll(td)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
@ -41,29 +47,37 @@ func TestStepOutputDir(t *testing.T) {
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if _, err := os.Stat(dir.dir); err != nil {
if _, err := os.Stat(td); err != nil {
t.Fatalf("err: %s", err)
}
// Test the cleanup
step.Cleanup(state)
if _, err := os.Stat(dir.dir); err != nil {
if _, err := os.Stat(td); 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)
td := testOutputDir(t)
outconfig := &OutputConfig{
OutputDir: td,
}
step := &StepOutputDir{
OutputConfig: outconfig,
VMName: "testVM",
}
// Delete the test output directory when done
defer os.RemoveAll(td)
// Make sure the dir exists
if err := os.MkdirAll(dir.dir, 0755); err != nil {
if err := os.MkdirAll(td, 0755); err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir.dir)
defer os.RemoveAll(td)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
@ -75,24 +89,33 @@ func TestStepOutputDir_existsNoForce(t *testing.T) {
// Test the cleanup
step.Cleanup(state)
if _, err := os.Stat(dir.dir); err != nil {
if _, err := os.Stat(td); err != nil {
t.Fatal("should not delete dir")
}
}
func TestStepOutputDir_existsForce(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
td := testOutputDir(t)
outconfig := &OutputConfig{
OutputDir: td,
}
step := &StepOutputDir{
OutputConfig: outconfig,
VMName: "testVM",
}
step.Force = true
dir := testOutputDir(t)
state.Put("dir", dir)
// Delete the test output directory when done
defer os.RemoveAll(td)
// Make sure the dir exists
if err := os.MkdirAll(dir.dir, 0755); err != nil {
if err := os.MkdirAll(td, 0755); err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir.dir)
defer os.RemoveAll(td)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
@ -101,17 +124,22 @@ func TestStepOutputDir_existsForce(t *testing.T) {
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if _, err := os.Stat(dir.dir); err != nil {
if _, err := os.Stat(td); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestStepOutputDir_cancel(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
td := testOutputDir(t)
outconfig := &OutputConfig{
OutputDir: td,
}
dir := testOutputDir(t)
state.Put("dir", dir)
step := &StepOutputDir{
OutputConfig: outconfig,
VMName: "testVM",
}
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
@ -120,24 +148,29 @@ func TestStepOutputDir_cancel(t *testing.T) {
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if _, err := os.Stat(dir.dir); err != nil {
if _, err := os.Stat(td); 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 {
if _, err := os.Stat(td); err == nil {
t.Fatal("directory should not exist")
}
}
func TestStepOutputDir_halt(t *testing.T) {
state := testState(t)
step := new(StepOutputDir)
td := testOutputDir(t)
outconfig := &OutputConfig{
OutputDir: td,
}
dir := testOutputDir(t)
state.Put("dir", dir)
step := &StepOutputDir{
OutputConfig: outconfig,
VMName: "testVM",
}
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
@ -146,14 +179,46 @@ func TestStepOutputDir_halt(t *testing.T) {
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if _, err := os.Stat(dir.dir); err != nil {
if _, err := os.Stat(td); 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 {
if _, err := os.Stat(td); err == nil {
t.Fatal("directory should not exist")
}
}
func TestStepOutputDir_Remote(t *testing.T) {
// Tests remote driver
state := testState(t)
driver := new(RemoteDriverMock)
state.Put("driver", driver)
td := testOutputDir(t)
outconfig := &OutputConfig{
OutputDir: td,
RemoteOutputDir: "remote_path",
}
step := &StepOutputDir{
OutputConfig: outconfig,
VMName: "testVM",
RemoteType: "esx5",
}
// Delete the test output directory when done
defer os.RemoveAll(td)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// We don't pre-create the output path for export but we do set it in state.
exportOutputPath := state.Get("export_output_path").(string)
if exportOutputPath != td {
t.Fatalf("err: should have set export_output_path!")
}
}

View File

@ -12,8 +12,20 @@ import (
"github.com/hashicorp/packer/packer"
)
func testLocalOutputDir(t *testing.T) *LocalOutputDir {
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
os.RemoveAll(td)
result := new(LocalOutputDir)
result.SetOutputDir(td)
return result
}
func testStepShutdownState(t *testing.T) multistep.StateBag {
dir := testOutputDir(t)
dir := testLocalOutputDir(t)
if err := dir.MkdirAll(); err != nil {
t.Fatalf("err: %s", err)
}

View File

@ -36,44 +36,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
// Hold on to your pants. The output configuration is a little complex
// because of all the moving parts between local and remote output, and
// exports, and legacy behavior.
var dir vmwcommon.OutputDir
switch d := driver.(type) {
case vmwcommon.OutputDir:
// Remote type is esx; the driver fulfils the OutputDir interface so
// that it can create output files on the remote instance.
dir = d
default:
// Remote type is ""; the driver will be running the build and creating
// the output directory locally
dir = new(vmwcommon.LocalOutputDir)
}
// If remote type is esx, we need to track both the output dir on the remote
// instance and the output dir locally. This is where we track the local
// output dir.
exportOutputPath := b.config.OutputDir
if b.config.RemoteType != "" {
if b.config.RemoteOutputDir != "" {
b.config.OutputDir = b.config.RemoteOutputDir
} else {
// Default output dir to vm name. On remote esx instance, this will
// become something like /vmfs/volumes/mydatastore/vmname/vmname.vmx
b.config.OutputDir = b.config.VMName
}
}
// Remember, this one's either the output from a local build, or the remote
// output from a remote build. Not the local export path for a remote build.
dir.SetOutputDir(b.config.OutputDir)
// Setup the state bag
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("debug", b.config.PackerDebug)
state.Put("dir", dir)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
@ -95,7 +61,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Url: b.config.ISOUrls,
},
&vmwcommon.StepOutputDir{
Force: b.config.PackerForce,
Force: b.config.PackerForce,
OutputConfig: &b.config.OutputConfig,
RemoteType: b.config.RemoteType,
VMName: b.config.VMName,
},
&common.StepCreateFloppy{
Files: b.config.FloppyConfig.FloppyFiles,
@ -194,7 +163,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SkipExport: b.config.SkipExport,
VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions,
OutputDir: exportOutputPath,
},
}
@ -217,6 +185,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}
// Compile the artifact list
exportOutputPath := state.Get("export_output_path").(string) // set in StepOutputDir
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
}

View File

@ -41,28 +41,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
// Determine the output dir implementation
var dir vmwcommon.OutputDir
switch d := driver.(type) {
case vmwcommon.OutputDir:
dir = d
default:
dir = new(vmwcommon.LocalOutputDir)
}
// The OutputDir will track remote esxi output; exportOutputPath preserves
// the path to the output on the machine running Packer.
exportOutputPath := b.config.OutputDir
if b.config.RemoteType != "" {
b.config.OutputDir = b.config.VMName
}
dir.SetOutputDir(b.config.OutputDir)
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("debug", b.config.PackerDebug)
state.Put("dir", dir)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
@ -77,7 +58,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ToolsUploadFlavor: b.config.ToolsUploadFlavor,
},
&vmwcommon.StepOutputDir{
Force: b.config.PackerForce,
Force: b.config.PackerForce,
OutputConfig: &b.config.OutputConfig,
RemoteType: b.config.RemoteType,
VMName: b.config.VMName,
},
&common.StepCreateFloppy{
Files: b.config.FloppyConfig.FloppyFiles,
@ -91,8 +75,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Checksum: "none",
},
&StepCloneVMX{
OutputDir: b.config.OutputDir,
Path: b.config.SourcePath,
OutputDir: &b.config.OutputDir,
VMName: b.config.VMName,
Linked: b.config.Linked,
},
@ -177,7 +161,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SkipExport: b.config.SkipExport,
VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions,
OutputDir: exportOutputPath,
},
}
@ -201,6 +184,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
// Artifact
log.Printf("Generating artifact...")
exportOutputPath := state.Get("export_output_path").(string) // set in StepOutputDir
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
}

View File

@ -16,7 +16,7 @@ import (
// StepCloneVMX takes a VMX file and clones the VM into the output directory.
type StepCloneVMX struct {
OutputDir string
OutputDir *string
Path string
VMName string
Linked bool
@ -33,7 +33,7 @@ func (s *StepCloneVMX) Run(ctx context.Context, state multistep.StateBag) multis
ui := state.Get("ui").(packer.Ui)
// Set the path we want for the new .vmx file and clone
vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx")
vmxPath := filepath.Join(*s.OutputDir, s.VMName+".vmx")
ui.Say("Cloning source VM...")
log.Printf("Cloning from: %s", s.Path)
log.Printf("Cloning to: %s", vmxPath)
@ -96,7 +96,7 @@ func (s *StepCloneVMX) Run(ctx context.Context, state multistep.StateBag) multis
var diskFullPaths []string
for _, diskFilename := range diskFilenames {
log.Printf("Found attached disk with filename: %s", diskFilename)
diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, diskFilename))
diskFullPaths = append(diskFullPaths, filepath.Join(*s.OutputDir, diskFilename))
}
if len(diskFullPaths) == 0 {

View File

@ -62,7 +62,7 @@ func TestStepCloneVMX(t *testing.T) {
state := testState(t)
step := new(StepCloneVMX)
step.OutputDir = td
step.OutputDir = &td
step.Path = sourcePath
step.VMName = "foo"