diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 033b93cb2a9..ff672837c96 100755 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -182,6 +182,11 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { isoPath := state.Get("iso_path").(string) ui := state.Get("ui").(packer.Ui) + // Convert the iso_path into a path relative to the .vmx file if possible + if relativeIsoPath,err := filepath.Rel(config.VMXTemplatePath, filepath.FromSlash(isoPath)); err == nil { + isoPath = relativeIsoPath + } + ui.Say("Building and writing VMX file") vmxTemplate := DefaultVMXTemplate diff --git a/common/config.go b/common/config.go index e6c6477b5ef..0d8228cc6c2 100644 --- a/common/config.go +++ b/common/config.go @@ -2,11 +2,10 @@ package common import ( "fmt" - "net/url" "os" "path/filepath" - "runtime" "strings" + "net/url" ) // ScrubConfig is a helper that returns a string representation of @@ -37,89 +36,46 @@ func ChooseString(vals ...string) string { // a completely valid URL. For example, the original URL might be "local/file.iso" // which isn't a valid URL. DownloadableURL will return "file:///local/file.iso" func DownloadableURL(original string) (string, error) { - if runtime.GOOS == "windows" { - // If the distance to the first ":" is just one character, assume - // we're dealing with a drive letter and thus a file path. - idx := strings.Index(original, ":") - if idx == 1 { - original = "file:///" + original - } - } - url, err := url.Parse(original) - if err != nil { - return "", err - } - - if url.Scheme == "" { - url.Scheme = "file" - } - - if url.Scheme == "file" { - // Windows file handling is all sorts of tricky... - if runtime.GOOS == "windows" { - // If the path is using Windows-style slashes, URL parses - // it into the host field. - if url.Path == "" && strings.Contains(url.Host, `\`) { - url.Path = url.Host - url.Host = "" - } - - // For Windows absolute file paths, remove leading / prior to processing - // since net/url turns "C:/" into "/C:/" - if len(url.Path) > 0 && url.Path[0] == '/' { - url.Path = url.Path[1:len(url.Path)] - } + // Verify that the scheme is something we support in our common downloader. + supported := []string{"file", "http", "https", "ftp", "smb"} + found := false + for _, s := range supported { + if strings.HasPrefix(strings.ToLower(original), s + "://") { + found = true + break } + } - // Only do the filepath transformations if the file appears - // to actually exist. - if _, err := os.Stat(url.Path); err == nil { - url.Path, err = filepath.Abs(url.Path) - if err != nil { - return "", err - } + // If it's properly prefixed with something we support, then we don't need + // to make it a uri. + if found { + original = filepath.ToSlash(original) - url.Path, err = filepath.EvalSymlinks(url.Path) - if err != nil { - return "", err - } + // make sure that it can be parsed though.. + uri,err := url.Parse(original) + if err != nil { return "", err } - url.Path = filepath.Clean(url.Path) - } + uri.Scheme = strings.ToLower(uri.Scheme) - if runtime.GOOS == "windows" { - // Also replace all backslashes with forwardslashes since Windows - // users are likely to do this but the URL should actually only - // contain forward slashes. - url.Path = strings.Replace(url.Path, `\`, `/`, -1) - } + return uri.String(), nil } - // Make sure it is lowercased - url.Scheme = strings.ToLower(url.Scheme) + // If the file exists, then make it an absolute path + _,err := os.Stat(original) + if err == nil { + original, err = filepath.Abs(filepath.FromSlash(original)) + if err != nil { return "", err } - // This is to work around issue #5927. This can safely be removed once - // we distribute with a version of Go that fixes that bug. - // - // See: https://code.google.com/p/go/issues/detail?id=5927 - if url.Path != "" && url.Path[0] != '/' { - url.Path = "/" + url.Path - } + original, err = filepath.EvalSymlinks(original) + if err != nil { return "", err } - // Verify that the scheme is something we support in our common downloader. - supported := []string{"file", "http", "https"} - found := false - for _, s := range supported { - if url.Scheme == s { - found = true - break - } + original = filepath.Clean(original) + original = filepath.ToSlash(original) } - if !found { - return "", fmt.Errorf("Unsupported URL scheme: %s", url.Scheme) - } + // Since it wasn't properly prefixed, let's make it into a well-formed + // file:// uri. - return url.String(), nil + return "file://" + original, nil } diff --git a/common/config_test.go b/common/config_test.go index 92a7316a314..4cdeb9b7315 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "os" "path/filepath" - "runtime" "strings" "testing" ) @@ -41,13 +40,7 @@ func TestDownloadableURL(t *testing.T) { // Invalid URL: has hex code in host _, err := DownloadableURL("http://what%20.com") if err == nil { - t.Fatal("expected err") - } - - // Invalid: unsupported scheme - _, err = DownloadableURL("ftp://host.com/path") - if err == nil { - t.Fatal("expected err") + t.Fatalf("expected err : %s", err) } // Valid: http @@ -85,11 +78,7 @@ func TestDownloadableURL_FilePaths(t *testing.T) { } tfPath = filepath.Clean(tfPath) - filePrefix := "file://" - if runtime.GOOS == "windows" { - filePrefix += "/" - } // Relative filepath. We run this test in a func so that // the defers run right away. @@ -111,9 +100,7 @@ func TestDownloadableURL_FilePaths(t *testing.T) { t.Fatalf("err: %s", err) } - expected := fmt.Sprintf("%s%s", - filePrefix, - strings.Replace(tfPath, `\`, `/`, -1)) + expected := "file://" + strings.Replace(tfPath, `\`, `/`, -1) if u != expected { t.Fatalf("unexpected: %#v != %#v", u, expected) } diff --git a/common/download.go b/common/download.go index e4b4dc2e0f8..eeb53fb3a40 100644 --- a/common/download.go +++ b/common/download.go @@ -10,14 +10,23 @@ import ( "errors" "fmt" "hash" - "io" "log" - "net/http" "net/url" "os" "runtime" + "path" + "strings" +) + +// imports related to each Downloader implementation +import ( + "io" + "path/filepath" + "net/http" + "github.com/jlaffaye/ftp" ) + // DownloadConfig is the configuration given to instantiate a new // download instance. Once a configuration is used to instantiate // a download client, it must not be modified. @@ -75,23 +84,40 @@ func HashForType(t string) hash.Hash { // NewDownloadClient returns a new DownloadClient for the given // configuration. func NewDownloadClient(c *DownloadConfig) *DownloadClient { + const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */ + if c.DownloaderMap == nil { c.DownloaderMap = map[string]Downloader{ + "file": &FileDownloader{bufferSize: nil}, + "ftp": &FTPDownloader{userInfo: url.UserPassword("anonymous", "anonymous@"), mtu: mtu}, "http": &HTTPDownloader{userAgent: c.UserAgent}, "https": &HTTPDownloader{userAgent: c.UserAgent}, + "smb": &SMBDownloader{bufferSize: nil}, } } return &DownloadClient{config: c} } -// A downloader is responsible for actually taking a remote URL and -// downloading it. +// A downloader implements the ability to transfer a file, and cancel or resume +// it. type Downloader interface { + Resume() Cancel() + Progress() uint64 + Total() uint64 +} + +// A LocalDownloader is responsible for converting a uri to a local path +// that the platform can open directly. +type LocalDownloader interface { + toPath(string, url.URL) (string,error) +} + +// A RemoteDownloader is responsible for actually taking a remote URL and +// downloading it. +type RemoteDownloader interface { Download(*os.File, *url.URL) error - Progress() uint - Total() uint } func (d *DownloadClient) Cancel() { @@ -105,50 +131,54 @@ func (d *DownloadClient) Get() (string, error) { return d.config.TargetPath, nil } + /* parse the configuration url into a net/url object */ url, err := url.Parse(d.config.Url) - if err != nil { - return "", err - } - + if err != nil { return "", err } log.Printf("Parsed URL: %#v", url) - // Files when we don't copy the file are special cased. - var f *os.File + /* use the current working directory as the base for relative uri's */ + cwd,err := os.Getwd() + if err != nil { return "", err } + + // Determine which is the correct downloader to use var finalPath string - sourcePath := "" - if url.Scheme == "file" && !d.config.CopyFile { - // This is a special case where we use a source file that already exists - // locally and we don't make a copy. Normally we would copy or download. - finalPath = url.Path - log.Printf("[DEBUG] Using local file: %s", finalPath) - // Remove forward slash on absolute Windows file URLs before processing - if runtime.GOOS == "windows" && len(finalPath) > 0 && finalPath[0] == '/' { - finalPath = finalPath[1:len(finalPath)] - } - // Keep track of the source so we can make sure not to delete this later - sourcePath = finalPath - } else { - finalPath = d.config.TargetPath + var ok bool + d.downloader, ok = d.config.DownloaderMap[url.Scheme] + if !ok { + return "", fmt.Errorf("No downloader for scheme: %s", url.Scheme) + } - var ok bool - d.downloader, ok = d.config.DownloaderMap[url.Scheme] - if !ok { - return "", fmt.Errorf("No downloader for scheme: %s", url.Scheme) - } + remote,ok := d.downloader.(RemoteDownloader) + if !ok { + return "", fmt.Errorf("Unable to treat uri scheme %s as a Downloader : %T", url.Scheme, d.downloader) + } + + local,ok := d.downloader.(LocalDownloader) + if !ok && !d.config.CopyFile{ + return "", fmt.Errorf("Not allowed to use uri scheme %s in no copy file mode : %T", url.Scheme, d.downloader) + } + + // If we're copying the file, then just use the actual downloader + if d.config.CopyFile { + var f *os.File + finalPath = d.config.TargetPath - // Otherwise, download using the downloader. f, err = os.OpenFile(finalPath, os.O_RDWR|os.O_CREATE, os.FileMode(0666)) - if err != nil { - return "", err - } + if err != nil { return "", err } log.Printf("[DEBUG] Downloading: %s", url.String()) - err = d.downloader.Download(f, url) + err = remote.Download(f, url) f.Close() - if err != nil { - return "", err - } + if err != nil { return "", err } + + // Otherwise if our Downloader is a LocalDownloader we can just use the + // path after transforming it. + } else { + finalPath,err = local.toPath(cwd, *url) + if err != nil { return "", err } + + log.Printf("[DEBUG] Using local file: %s", finalPath) } if d.config.Hash != nil { @@ -156,9 +186,7 @@ func (d *DownloadClient) Get() (string, error) { verify, err = d.VerifyChecksum(finalPath) if err == nil && !verify { // Only delete the file if we made a copy or downloaded it - if sourcePath != finalPath { - os.Remove(finalPath) - } + if d.config.CopyFile { os.Remove(finalPath) } err = fmt.Errorf( "checksums didn't match expected: %s", @@ -171,10 +199,7 @@ func (d *DownloadClient) Get() (string, error) { // PercentProgress returns the download progress as a percentage. func (d *DownloadClient) PercentProgress() int { - if d.downloader == nil { - return -1 - } - + if d.downloader == nil { return -1 } return int((float64(d.downloader.Progress()) / float64(d.downloader.Total())) * 100) } @@ -200,17 +225,21 @@ func (d *DownloadClient) VerifyChecksum(path string) (bool, error) { // HTTPDownloader is an implementation of Downloader that downloads // files over HTTP. type HTTPDownloader struct { - progress uint - total uint + progress uint64 + total uint64 userAgent string } -func (*HTTPDownloader) Cancel() { +func (d *HTTPDownloader) Cancel() { + // TODO(mitchellh): Implement +} + +func (d *HTTPDownloader) Resume() { // TODO(mitchellh): Implement } func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { - log.Printf("Starting download: %s", src.String()) + log.Printf("Starting download over HTTP: %s", src.String()) // Seek to the beginning by default if _, err := dst.Seek(0, 0); err != nil { @@ -246,7 +275,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { if fi, err := dst.Stat(); err == nil { if _, err = dst.Seek(0, os.SEEK_END); err == nil { req.Header.Set("Range", fmt.Sprintf("bytes=%d-", fi.Size())) - d.progress = uint(fi.Size()) + d.progress = uint64(fi.Size()) } } } @@ -260,7 +289,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { return err } - d.total = d.progress + uint(resp.ContentLength) + d.total = d.progress + uint64(resp.ContentLength) var buffer [4096]byte for { n, err := resp.Body.Read(buffer[:]) @@ -268,7 +297,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { return err } - d.progress += uint(n) + d.progress += uint64(n) if _, werr := dst.Write(buffer[:n]); werr != nil { return werr @@ -282,10 +311,330 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { return nil } -func (d *HTTPDownloader) Progress() uint { +func (d *HTTPDownloader) Progress() uint64 { + return d.progress +} + +func (d *HTTPDownloader) Total() uint64 { + return d.total +} + +// FTPDownloader is an implementation of Downloader that downloads +// files over FTP. +type FTPDownloader struct { + userInfo *url.Userinfo + mtu uint + + active bool + progress uint64 + total uint64 +} + +func (d *FTPDownloader) Progress() uint64 { + return d.progress +} + +func (d *FTPDownloader) Total() uint64 { + return d.total +} + +func (d *FTPDownloader) Cancel() { + d.active = false +} + +func (d *FTPDownloader) Resume() { + // TODO: Implement +} + +func (d *FTPDownloader) Download(dst *os.File, src *url.URL) error { + var userinfo *url.Userinfo + + userinfo = d.userInfo + d.active = false + + // check the uri is correct + if src == nil || src.Scheme != "ftp" { + return fmt.Errorf("Unexpected uri scheme: %s", src.Scheme) + } + uri := src + + // connect to ftp server + var cli *ftp.ServerConn + + log.Printf("Starting download over FTP: %s : %s\n", uri.Host, uri.Path) + cli,err := ftp.Dial(uri.Host) + if err != nil { return nil } + defer cli.Quit() + + // handle authentication + if uri.User != nil { userinfo = uri.User } + + pass,ok := userinfo.Password() + if !ok { pass = "ftp@" } + + log.Printf("Authenticating to FTP server: %s : %s\n", userinfo.Username(), pass) + err = cli.Login(userinfo.Username(), pass) + if err != nil { return err } + + // locate specified path + p := path.Dir(uri.Path) + + log.Printf("Changing to FTP directory : %s\n", p) + err = cli.ChangeDir(p) + if err != nil { return nil } + + curpath,err := cli.CurrentDir() + if err != nil { return err } + log.Printf("Current FTP directory : %s\n", curpath) + + // collect stats about the specified file + var name string + var entry *ftp.Entry + + _,name = path.Split(uri.Path) + entry = nil + + entries,err := cli.List(curpath) + for _,e := range entries { + if e.Type == ftp.EntryTypeFile && e.Name == name { + entry = e + break + } + } + + if entry == nil { + return fmt.Errorf("Unable to find file: %s", uri.Path) + } + log.Printf("Found file : %s : %v bytes\n", entry.Name, entry.Size) + + d.progress = 0 + d.total = entry.Size + + // download specified file + d.active = true + reader,err := cli.RetrFrom(uri.Path, d.progress) + if err != nil { return nil } + + // do it in a goro so that if someone wants to cancel it, they can + errch := make(chan error) + go func(d *FTPDownloader, r io.Reader, w io.Writer, e chan error) { + for ; d.active; { + n,err := io.CopyN(w, r, int64(d.mtu)) + if err != nil { break } + d.progress += uint64(n) + } + d.active = false + e <- err + }(d, reader, dst, errch) + + // spin until it's done + err = <-errch + reader.Close() + + if err == nil && d.progress != d.total { + err = fmt.Errorf("FTP total transfer size was %d when %d was expected", d.progress, d.total) + } + + // log out + cli.Logout() + return err +} + +// FileDownloader is an implementation of Downloader that downloads +// files using the regular filesystem. +type FileDownloader struct { + bufferSize *uint + + active bool + progress uint64 + total uint64 +} + +func (d *FileDownloader) Progress() uint64 { return d.progress } -func (d *HTTPDownloader) Total() uint { +func (d *FileDownloader) Total() uint64 { return d.total } + +func (d *FileDownloader) Cancel() { + d.active = false +} + +func (d *FileDownloader) Resume() { + // TODO: Implement +} + +func (d *FileDownloader) toPath(base string, uri url.URL) (string,error) { + var result string + + // absolute path -- file://c:/absolute/path -> c:/absolute/path + if strings.HasSuffix(uri.Host, ":") { + result = path.Join(uri.Host, uri.Path) + + // semi-absolute path (current drive letter) + // -- file:///absolute/path -> /absolute/path + } else if uri.Host == "" && strings.HasPrefix(uri.Path, "/") { + result = path.Join(filepath.VolumeName(base), uri.Path) + + // relative path -- file://./relative/path -> ./relative/path + } else if uri.Host == "." { + result = path.Join(base, uri.Path) + + // relative path -- file://relative/path -> ./relative/path + } else { + result = path.Join(base, uri.Host, uri.Path) + } + return filepath.ToSlash(result),nil +} + +func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { + d.active = false + + /* check the uri's scheme to make sure it matches */ + if src == nil || src.Scheme != "file" { + return fmt.Errorf("Unexpected uri scheme: %s", src.Scheme) + } + uri := src + + /* use the current working directory as the base for relative uri's */ + cwd,err := os.Getwd() + if err != nil { return err } + + /* determine which uri format is being used and convert to a real path */ + realpath,err := d.toPath(cwd, *uri) + if err != nil { return err } + + /* download the file using the operating system's facilities */ + d.progress = 0 + d.active = true + + f, err := os.Open(realpath) + if err != nil { return err } + defer f.Close() + + // get the file size + fi, err := f.Stat() + if err != nil { return err } + d.total = uint64(fi.Size()) + + // no bufferSize specified, so copy synchronously. + if d.bufferSize == nil { + var n int64 + n,err = io.Copy(dst, f) + d.active = false + d.progress += uint64(n) + + // use a goro in case someone else wants to enable cancel/resume + } else { + errch := make(chan error) + go func(d* FileDownloader, r io.Reader, w io.Writer, e chan error) { + for ; d.active; { + n,err := io.CopyN(w, r, int64(*d.bufferSize)) + if err != nil { break } + d.progress += uint64(n) + } + d.active = false + e <- err + }(d, f, dst, errch) + + // ...and we spin until it's done + err = <-errch + } + f.Close() + return err +} + +// SMBDownloader is an implementation of Downloader that downloads +// files using the "\\" path format on Windows +type SMBDownloader struct { + bufferSize *uint + + active bool + progress uint64 + total uint64 +} + +func (d *SMBDownloader) Progress() uint64 { + return d.progress +} + +func (d *SMBDownloader) Total() uint64 { + return d.total +} + +func (d *SMBDownloader) Cancel() { + d.active = false +} + +func (d *SMBDownloader) Resume() { + // TODO: Implement +} + +func (d *SMBDownloader) toPath(base string, uri url.URL) (string,error) { + const UNCPrefix = string(os.PathSeparator)+string(os.PathSeparator) + + if runtime.GOOS != "windows" { + return "",fmt.Errorf("Support for SMB based uri's are not supported on %s", runtime.GOOS) + } + + return UNCPrefix + filepath.ToSlash(path.Join(uri.Host, uri.Path)), nil +} + +func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { + d.active = false + + /* convert the uri using the net/url module to a UNC path */ + if src == nil || src.Scheme != "smb" { + return fmt.Errorf("Unexpected uri scheme: %s", src.Scheme) + } + uri := src + + /* use the current working directory as the base for relative uri's */ + cwd,err := os.Getwd() + if err != nil { return err } + + /* convert uri to an smb-path */ + realpath,err := d.toPath(cwd, *uri) + if err != nil { return err } + + /* Open up the "\\"-prefixed path using the Windows filesystem */ + d.progress = 0 + d.active = true + + f, err := os.Open(realpath) + if err != nil { return err } + defer f.Close() + + // get the file size (at the risk of performance) + fi, err := f.Stat() + if err != nil { return err } + d.total = uint64(fi.Size()) + + // no bufferSize specified, so copy synchronously. + if d.bufferSize == nil { + var n int64 + n,err = io.Copy(dst, f) + d.active = false + d.progress += uint64(n) + + // use a goro in case someone else wants to enable cancel/resume + } else { + errch := make(chan error) + go func(d* SMBDownloader, r io.Reader, w io.Writer, e chan error) { + for ; d.active; { + n,err := io.CopyN(w, r, int64(*d.bufferSize)) + if err != nil { break } + d.progress += uint64(n) + } + d.active = false + e <- err + }(d, f, dst, errch) + + // ...and as usual we spin until it's done + err = <-errch + } + f.Close() + return err +} diff --git a/common/download_test.go b/common/download_test.go index 51f6f270c82..d47638b09c7 100644 --- a/common/download_test.go +++ b/common/download_test.go @@ -8,6 +8,8 @@ import ( "net/http" "net/http/httptest" "os" + "strings" + "path/filepath" "testing" ) @@ -55,6 +57,7 @@ func TestDownloadClient_basic(t *testing.T) { client := NewDownloadClient(&DownloadConfig{ Url: ts.URL + "/basic.txt", TargetPath: tf.Name(), + CopyFile: true, }) path, err := client.Get() if err != nil { @@ -89,6 +92,7 @@ func TestDownloadClient_checksumBad(t *testing.T) { TargetPath: tf.Name(), Hash: HashForType("md5"), Checksum: checksum, + CopyFile: true, }) if _, err := client.Get(); err == nil { t.Fatal("should error") @@ -113,6 +117,7 @@ func TestDownloadClient_checksumGood(t *testing.T) { TargetPath: tf.Name(), Hash: HashForType("md5"), Checksum: checksum, + CopyFile: true, }) path, err := client.Get() if err != nil { @@ -143,6 +148,7 @@ func TestDownloadClient_checksumNoDownload(t *testing.T) { TargetPath: "./test-fixtures/root/another.txt", Hash: HashForType("md5"), Checksum: checksum, + CopyFile: true, }) path, err := client.Get() if err != nil { @@ -181,6 +187,7 @@ func TestDownloadClient_resume(t *testing.T) { client := NewDownloadClient(&DownloadConfig{ Url: ts.URL, TargetPath: tf.Name(), + CopyFile: true, }) path, err := client.Get() if err != nil { @@ -238,6 +245,7 @@ func TestDownloadClient_usesDefaultUserAgent(t *testing.T) { config := &DownloadConfig{ Url: server.URL, TargetPath: tf.Name(), + CopyFile: true, } client := NewDownloadClient(config) @@ -269,6 +277,7 @@ func TestDownloadClient_setsUserAgent(t *testing.T) { Url: server.URL, TargetPath: tf.Name(), UserAgent: "fancy user agent", + CopyFile: true, } client := NewDownloadClient(config) @@ -349,6 +358,7 @@ func TestDownloadFileUrl(t *testing.T) { if err != nil { t.Fatalf("Unable to detect working directory: %s", err) } + cwd = filepath.ToSlash(cwd) // source_path is a file path and source is a network path sourcePath := fmt.Sprintf("%s/test-fixtures/fileurl/%s", cwd, "cake") @@ -374,5 +384,103 @@ func TestDownloadFileUrl(t *testing.T) { if _, err = os.Stat(sourcePath); err != nil { t.Errorf("Could not stat source file: %s", sourcePath) } +} +// SimulateFileUriDownload is a simple utility function that converts a uri +// into a testable file path whilst ignoring a correct checksum match, stripping +// UNC path info, and then calling stat to ensure the correct file exists. +// (used by TestFileUriTransforms) +func SimulateFileUriDownload(t *testing.T, uri string) (string,error) { + // source_path is a file path and source is a network path + source := fmt.Sprintf(uri) + t.Logf("Trying to download %s", source) + + config := &DownloadConfig{ + Url: source, + // This should be wrong. We want to make sure we don't delete + Checksum: []byte("nope"), + Hash: HashForType("sha256"), + CopyFile: false, + } + + // go go go + client := NewDownloadClient(config) + path, err := client.Get() + + // ignore any non-important checksum errors if it's not a unc path + if !strings.HasPrefix(path, "\\\\") && err.Error() != "checksums didn't match expected: 6e6f7065" { + t.Fatalf("Unexpected failure; expected checksum not to match") + } + + // if it's a unc path, then remove the host and share name so we don't have + // to force the user to enable ADMIN$ and Windows File Sharing + if strings.HasPrefix(path, "\\\\") { + res := strings.SplitN(path, "/", 3) + path = "/" + res[2] + } + + if _, err = os.Stat(path); err != nil { + t.Errorf("Could not stat source file: %s", path) + } + return path,err +} + +// TestFileUriTransforms tests the case where we use a local file uri +// for iso_url. There's a few different formats that a file uri can exist as +// and so we try to test the most useful and common ones. +func TestFileUriTransforms(t *testing.T) { + const testpath = /* have your */ "test-fixtures/fileurl/cake" /* and eat it too */ + const host = "localhost" + + var cwd string + var volume string + var share string + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("Unable to detect working directory: %s", err) + return + } + cwd = filepath.ToSlash(cwd) + volume = filepath.VolumeName(cwd) + share = volume + if share[len(share)-1] == ':' { + share = share[:len(share)-1] + "$" + } + cwd = cwd[len(volume):] + + t.Logf("TestFileUriTransforms : Running with cwd : '%s'", cwd) + t.Logf("TestFileUriTransforms : Running with volume : '%s'", volume) + + // ./relative/path -> ./relative/path + // /absolute/path -> /absolute/path + // c:/windows/absolute -> c:/windows/absolute + testcases := []string{ + "./%s", + cwd + "/%s", + volume + cwd + "/%s", + } + + // all regular slashed testcases + for _,testcase := range testcases { + uri := "file://" + fmt.Sprintf(testcase, testpath) + t.Logf("TestFileUriTransforms : Trying Uri '%s'", uri) + res,err := SimulateFileUriDownload(t, uri) + if err != nil { + t.Errorf("Unable to transform uri '%s' into a path : %v", uri, err) + } + t.Logf("TestFileUriTransforms : Result Path '%s'", res) + } + + // ...and finally the oddball windows native path + // smb://host/sharename/file -> \\host\sharename\file + testcase := host + "/" + share + "/" + cwd[1:] + "/%s" + uri := "smb://" + fmt.Sprintf(testcase, testpath) + t.Logf("TestFileUriTransforms : Trying Uri '%s'", uri) + res,err := SimulateFileUriDownload(t, uri) + if err != nil { + t.Errorf("Unable to transform uri '%s' into a path", uri) + return + } + t.Logf("TestFileUriTransforms : Result Path '%s'", res) } diff --git a/packer/core.go b/packer/core.go index 496fce6bd3c..08916494c95 100644 --- a/packer/core.go +++ b/packer/core.go @@ -67,7 +67,7 @@ func NewCore(c *CoreConfig) (*Core, error) { return nil, err } - // Go through and interpolate all the build names. We shuld be able + // Go through and interpolate all the build names. We should be able // to do this at this point with the variables. result.builds = make(map[string]*template.Builder) for _, b := range c.Template.Builders {