diff --git a/common/step_create_floppy.go b/common/step_create_floppy.go index 0f4e2939c..7b9b8c537 100644 --- a/common/step_create_floppy.go +++ b/common/step_create_floppy.go @@ -11,6 +11,7 @@ import ( "log" "os" "path/filepath" + "strings" ) // StepCreateFloppy will create a floppy disk with the given files. @@ -20,6 +21,8 @@ type StepCreateFloppy struct { Files []string floppyPath string + + FilesAdded map[string]bool } func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { @@ -28,6 +31,8 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + s.FilesAdded = make(map[string]bool) + ui := state.Get("ui").(packer.Ui) ui.Say("Creating floppy disk...") @@ -43,7 +48,7 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { // Set the path so we can remove it later s.floppyPath = floppyF.Name() - log.Printf("Floppy path: %s", floppyF.Name()) + log.Printf("Floppy path: %s", s.floppyPath) // Set the size of the file to be a floppy sized if err := floppyF.Truncate(1440 * 1024); err != nil { @@ -89,8 +94,8 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction { // Go over each file and copy it. for _, filename := range s.Files { - ui.Message(fmt.Sprintf("Copying: %s", filepath.Base(filename))) - if err := s.addSingleFile(rootDir, filename); err != nil { + ui.Message(fmt.Sprintf("Copying: %s", filename)) + if err := s.addFilespec(rootDir, filename); err != nil { state.Put("error", fmt.Errorf("Error adding file to floppy: %s", err)) return multistep.ActionHalt } @@ -109,6 +114,61 @@ func (s *StepCreateFloppy) Cleanup(multistep.StateBag) { } } +func (s *StepCreateFloppy) addFilespec(dir fs.Directory, src string) error { + // same as http://golang.org/src/pkg/path/filepath/match.go#L308 + if strings.IndexAny(src, "*?[") >= 0 { + matches, err := filepath.Glob(src) + if err != nil { + return err + } + return s.addFiles(dir, matches) + } + + finfo, err := os.Stat(src) + if err != nil { + return err + } + + if finfo.IsDir() { + return s.addDirectory(dir, src) + } + + return s.addSingleFile(dir, src) +} + +func (s *StepCreateFloppy) addFiles(dir fs.Directory, files []string) error { + for _, file := range files { + err := s.addFilespec(dir, file) + if err != nil { + return err + } + } + + return nil +} + +func (s *StepCreateFloppy) addDirectory(dir fs.Directory, src string) error { + log.Printf("Adding directory to floppy: %s", src) + + walkFn := func(path string, finfo os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == src { + return nil + } + + if finfo.IsDir() { + return s.addDirectory(dir, path) + } + + return s.addSingleFile(dir, path) + } + + return filepath.Walk(src, walkFn) +} + func (s *StepCreateFloppy) addSingleFile(dir fs.Directory, src string) error { log.Printf("Adding file to floppy: %s", src) @@ -132,5 +192,7 @@ func (s *StepCreateFloppy) addSingleFile(dir fs.Directory, src string) error { return err } + s.FilesAdded[src] = true + return nil } diff --git a/common/step_create_floppy_test.go b/common/step_create_floppy_test.go new file mode 100644 index 000000000..b6e591a41 --- /dev/null +++ b/common/step_create_floppy_test.go @@ -0,0 +1,193 @@ +package common + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io/ioutil" + "os" + "path" + "strconv" + "testing" +) + +func TestStepCreateFloppy_Impl(t *testing.T) { + var raw interface{} + raw = new(StepCreateFloppy) + if _, ok := raw.(multistep.Step); !ok { + t.Fatalf("StepCreateFloppy should be a step") + } +} + +func testStepCreateFloppyState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} + +func TestStepCreateFloppy(t *testing.T) { + state := testStepCreateFloppyState(t) + step := new(StepCreateFloppy) + + dir, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + count := 10 + expected := count + files := make([]string, count) + + prefix := "exists" + ext := ".tmp" + + for i := 0; i < expected; i++ { + files[i] = path.Join(dir, prefix+strconv.Itoa(i)+ext) + + _, err := os.Create(files[i]) + if err != nil { + t.Fatalf("err: %s", err) + } + } + + lists := [][]string{ + files, + []string{dir + string(os.PathSeparator) + prefix + "*" + ext}, + []string{dir + string(os.PathSeparator) + prefix + "?" + ext}, + []string{dir + string(os.PathSeparator) + prefix + "[0123456789]" + ext}, + []string{dir + string(os.PathSeparator) + prefix + "[0-9]" + ext}, + []string{dir + string(os.PathSeparator)}, + []string{dir}, + } + + for _, step.Files = range lists { + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v for %v", action, step.Files) + } + + if _, ok := state.GetOk("error"); ok { + t.Fatalf("state should be ok for %v", step.Files) + } + + floppy_path := state.Get("floppy_path").(string) + + if _, err := os.Stat(floppy_path); err != nil { + t.Fatal("file not found: %s for %v", floppy_path, step.Files) + } + + if len(step.FilesAdded) != expected { + t.Fatalf("expected %d, found %d for %v", expected, len(step.FilesAdded), step.Files) + } + + step.Cleanup(state) + + if _, err := os.Stat(floppy_path); err == nil { + t.Fatal("file found: %s for %v", floppy_path, step.Files) + } + } +} + +func xxxTestStepCreateFloppy_missing(t *testing.T) { + state := testStepCreateFloppyState(t) + step := new(StepCreateFloppy) + + dir, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + count := 2 + expected := 0 + files := make([]string, count) + + prefix := "missing" + + for i := 0; i < count; i++ { + files[i] = path.Join(dir, prefix+strconv.Itoa(i)) + } + + lists := [][]string{ + files, + } + + for _, step.Files = range lists { + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v for %v", action, step.Files) + } + + if _, ok := state.GetOk("error"); !ok { + t.Fatalf("state should not be ok for %v", step.Files) + } + + floppy_path := state.Get("floppy_path") + + if floppy_path != nil { + t.Fatalf("floppy_path is not nil for %v", step.Files) + } + + if len(step.FilesAdded) != expected { + t.Fatalf("expected %d, found %d for %v", expected, len(step.FilesAdded), step.Files) + } + } +} + +func xxxTestStepCreateFloppy_notfound(t *testing.T) { + state := testStepCreateFloppyState(t) + step := new(StepCreateFloppy) + + dir, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(dir) + + count := 2 + expected := 0 + files := make([]string, count) + + prefix := "notfound" + + for i := 0; i < count; i++ { + files[i] = path.Join(dir, prefix+strconv.Itoa(i)) + } + + lists := [][]string{ + []string{dir + string(os.PathSeparator) + prefix + "*"}, + []string{dir + string(os.PathSeparator) + prefix + "?"}, + []string{dir + string(os.PathSeparator) + prefix + "[0123456789]"}, + []string{dir + string(os.PathSeparator) + prefix + "[0-9]"}, + []string{dir + string(os.PathSeparator)}, + []string{dir}, + } + + for _, step.Files = range lists { + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v for %v", action, step.Files) + } + + if _, ok := state.GetOk("error"); ok { + t.Fatalf("state should be ok for %v", step.Files) + } + + floppy_path := state.Get("floppy_path").(string) + + if _, err := os.Stat(floppy_path); err != nil { + t.Fatal("file not found: %s for %v", floppy_path, step.Files) + } + + if len(step.FilesAdded) != expected { + t.Fatalf("expected %d, found %d for %v", expected, len(step.FilesAdded), step.Files) + } + + step.Cleanup(state) + + if _, err := os.Stat(floppy_path); err == nil { + t.Fatal("file found: %s for %v", floppy_path, step.Files) + } + } +} diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index d72437219..9cb78d2e6 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -119,12 +119,14 @@ Optional: format of the virtual machine image. This defaults to "qcow2". * `floppy_files` (array of strings) - A list of files to place onto a floppy - disk that gets attached when Packer powers up the VM. This is most useful + disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file - on removable media. By default no floppy will be attached. All files + on removable media. By default, no floppy will be attached. All files listed in this setting get placed into the root directory of the floppy - and teh floppy is attached as the first floppy device. Currently, no - support exists for sub-directories. + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `headless` (bool) - Packer defaults to building virtual machines by launching a GUI that shows the console of the machine being built. diff --git a/website/source/docs/builders/virtualbox-iso.html.markdown b/website/source/docs/builders/virtualbox-iso.html.markdown index c59b4ae5e..8e5eaa373 100644 --- a/website/source/docs/builders/virtualbox-iso.html.markdown +++ b/website/source/docs/builders/virtualbox-iso.html.markdown @@ -85,12 +85,15 @@ Optional: * `disk_size` (int) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `format` (string) - Either "ovf" or "ova", this specifies the output format of the exported virtual machine. This defaults to "ovf". diff --git a/website/source/docs/builders/virtualbox-ovf.html.markdown b/website/source/docs/builders/virtualbox-ovf.html.markdown index 602ced197..e1a170d4c 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.markdown +++ b/website/source/docs/builders/virtualbox-ovf.html.markdown @@ -52,12 +52,15 @@ Required: Optional: -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `format` (string) - Either "ovf" or "ova", this specifies the output format of the exported virtual machine. This defaults to "ovf". diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index dd545f56a..1276d2ead 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -95,12 +95,15 @@ Optional: [Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is "/Applications/VMware Fusion.app" but this setting allows you to @@ -186,7 +189,7 @@ Optional: VM being prepared by some other process (kickstart, etc.). * `ssh_host` (string) - Hostname or IP address of the host. By default, DHCP - is used to connect to the host and this field is not used. + is used to connect to the host and this field is not used. * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. diff --git a/website/source/docs/builders/vmware-vmx.html.markdown b/website/source/docs/builders/vmware-vmx.html.markdown index 1fbbb3103..eacaa82c0 100644 --- a/website/source/docs/builders/vmware-vmx.html.markdown +++ b/website/source/docs/builders/vmware-vmx.html.markdown @@ -57,12 +57,15 @@ Optional: five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. * `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is "/Applications/VMware Fusion.app" but this setting allows you to