Added proper support for downloading via a Windows UNC path or a relative uri.

Added proper support for validating a downloadableURL containing a UNC or relative uri.
Removed the workaround for an earlier Go issue that had remained dormant in common/download.go (issue #5927).
When building a .vmx file via the vmware-iso builder, transform the path to the correct os-formatted one (using filepath.FromSlash).
This commit is contained in:
Ali Rizvi-Santiago 2015-11-01 21:46:14 -06:00
parent e222d60b5a
commit 281dd1258a
4 changed files with 115 additions and 35 deletions

View File

@ -43,6 +43,11 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
isoPath := state.Get("iso_path").(string) isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui) 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") ui.Say("Building and writing VMX file")
vmxTemplate := DefaultVMXTemplate vmxTemplate := DefaultVMXTemplate

View File

@ -56,77 +56,109 @@ func DownloadableURL(original string) (string, error) {
// for more info about valid windows URIs // for more info about valid windows URIs
idx := strings.Index(original, ":") idx := strings.Index(original, ":")
if idx == 1 { if idx == 1 {
original = "file:///" + original original = "file://" + filepath.ToSlash(original)
} }
} }
u, err := url.Parse(original)
// XXX: The validation here is later re-parsed in common/download.go and
// thus any modifications here must remain consistent over there too.
uri, err := url.Parse(original)
if err != nil { if err != nil {
return "", err return "", err
} }
if u.Scheme == "" { if uri.Scheme == "" {
u.Scheme = "file" uri.Scheme = "file"
} }
if u.Scheme == "file" { const UNCPrefix = string(os.PathSeparator)+string(os.PathSeparator)
// Windows file handling is all sorts of tricky... if uri.Scheme == "file" {
var ospath string // os-formatted pathname
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// If the path is using Windows-style slashes, URL parses // Move any extra path components that were mis-parsed into the Host
// it into the host field. // field back into the uri.Path field
if u.Path == "" && strings.Contains(u.Host, `\`) { if len(uri.Host) >= len(UNCPrefix) && uri.Host[:len(UNCPrefix)] == UNCPrefix {
u.Path = u.Host idx := strings.Index(uri.Host[len(UNCPrefix):], string(os.PathSeparator))
u.Host = "" if idx > -1 {
uri.Path = filepath.ToSlash(uri.Host[idx+len(UNCPrefix):]) + uri.Path
uri.Host = uri.Host[:idx+len(UNCPrefix)]
} }
} }
// Now all we need to do to convert the uri to a platform-specific path
// is to trade it's slashes for some os.PathSeparator ones.
ospath = uri.Host + filepath.FromSlash(uri.Path)
} else {
// Since we're already using sane paths on a sane platform, anything in
// uri.Host can be assumed that the user is describing a relative uri.
// This means that if we concatenate it with uri.Path, the filepath
// transform will still open the file correctly.
// i.e. file://localdirectory/filename -> localdirectory/filename
ospath = uri.Host + uri.Path
}
// Only do the filepath transformations if the file appears // Only do the filepath transformations if the file appears
// to actually exist. // to actually exist. We don't do it on windows, because EvalSymlinks
if _, err := os.Stat(u.Path); err == nil { // won't understand how to handle UNC paths and other Windows-specific minutae.
u.Path, err = filepath.Abs(u.Path) if _, err := os.Stat(ospath); err == nil && runtime.GOOS != "windows" {
ospath, err = filepath.Abs(ospath)
if err != nil { if err != nil {
return "", err return "", err
} }
u.Path, err = filepath.EvalSymlinks(u.Path) ospath, err = filepath.EvalSymlinks(ospath)
if err != nil { if err != nil {
return "", err return "", err
} }
u.Path = filepath.Clean(u.Path) ospath = filepath.Clean(ospath)
} }
// now that ospath was normalized and such..
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Also replace all backslashes with forwardslashes since Windows uri.Host = ""
// users are likely to do this but the URL should actually only // Check to see if our ospath is unc-prefixed, and if it is then split
// contain forward slashes. // the UNC host into uri.Host, leaving the rest in ospath.
u.Path = strings.Replace(u.Path, `\`, `/`, -1) // This way, our UNC-uri is protected from injury in the call to uri.String()
// prepend absolute windows paths with "/" so that when we if len(ospath) >= len(UNCPrefix) && ospath[:len(UNCPrefix)] == UNCPrefix {
// compose u.String() below the outcome will be correct idx := strings.Index(ospath[len(UNCPrefix):], string(os.PathSeparator))
// file:///c/blah syntax; otherwise u.String() will only add if idx > -1 {
// file:// which is not technically a correct windows URI uri.Host = ospath[:len(UNCPrefix)+idx]
if filepath.IsAbs(u.Path) && !strings.HasPrefix(u.Path, "/") { ospath = ospath[len(UNCPrefix)+idx:]
u.Path = "/" + u.Path
} }
}
// Restore the uri by re-transforming our os-formatted path
uri.Path = filepath.ToSlash(ospath)
} else {
uri.Host = ""
uri.Path = filepath.ToSlash(ospath)
} }
} }
// Make sure it is lowercased // Make sure it is lowercased
u.Scheme = strings.ToLower(u.Scheme) uri.Scheme = strings.ToLower(uri.Scheme)
// Verify that the scheme is something we support in our common downloader. // Verify that the scheme is something we support in our common downloader.
supported := []string{"file", "http", "https"} supported := []string{"file", "http", "https"}
found := false found := false
for _, s := range supported { for _, s := range supported {
if u.Scheme == s { if uri.Scheme == s {
found = true found = true
break break
} }
} }
if !found { if !found {
return "", fmt.Errorf("Unsupported URL scheme: %s", u.Scheme) return "", fmt.Errorf("Unsupported URL scheme: %s", uri.Scheme)
} }
return u.String(), nil
// explicit check to see if we need to manually replace the uri host with a UNC one
if runtime.GOOS == "windows" && uri.Scheme == "file" {
if len(uri.Host) >= len(UNCPrefix) && uri.Host[:len(UNCPrefix)] == UNCPrefix {
escapedHost := url.QueryEscape(uri.Host)
return strings.Replace(uri.String(), escapedHost, uri.Host, 1), nil
}
}
return uri.String(), nil
} }
// FileExistsLocally takes the URL output from DownloadableURL, and determines // FileExistsLocally takes the URL output from DownloadableURL, and determines

View File

@ -16,6 +16,9 @@ import (
"net/url" "net/url"
"os" "os"
"runtime" "runtime"
"path"
"path/filepath"
"strings"
) )
// DownloadConfig is the configuration given to instantiate a new // DownloadConfig is the configuration given to instantiate a new
@ -130,9 +133,49 @@ func (d *DownloadClient) Get() (string, error) {
// locally and we don't make a copy. Normally we would copy or download. // locally and we don't make a copy. Normally we would copy or download.
log.Printf("[DEBUG] Using local file: %s", finalPath) log.Printf("[DEBUG] Using local file: %s", finalPath)
// Remove forward slash on absolute Windows file URLs before processing // FIXME:
if runtime.GOOS == "windows" && len(finalPath) > 0 && finalPath[0] == '/' { // cwd should point to the path relative to client.json, but
finalPath = finalPath[1:] // since this isn't exposed to us anywhere, we use os.Getwd()
// to figure it out.
cwd,err := os.Getwd()
if err != nil {
return "", fmt.Errorf("Unable to get working directory")
}
// convert the actual file uri to a windowsy path
// (this logic must correspond to the same logic in common/config.go)
if runtime.GOOS == "windows" {
const UNCPrefix = string(os.PathSeparator)+string(os.PathSeparator)
// move any extra path components that were parsed into Host due
// to UNC into the url.Path field so that it's PathSeparators get
// normalized
if len(url.Host) >= len(UNCPrefix) && url.Host[:len(UNCPrefix)] == UNCPrefix {
idx := strings.Index(url.Host[len(UNCPrefix):], string(os.PathSeparator))
if idx > -1 {
url.Path = filepath.ToSlash(url.Host[idx+len(UNCPrefix):]) + url.Path
url.Host = url.Host[:idx+len(UNCPrefix)]
}
}
// clean up backward-slashes since they only matter when part of a unc path
urlPath := filepath.ToSlash(url.Path)
// semi-absolute path (current drive letter) -- file:///absolute/path
if url.Host == "" && len(urlPath) > 0 && urlPath[0] == '/' {
finalPath = path.Join(filepath.VolumeName(cwd), urlPath)
// relative path -- file://./relative/path
// file://relative/path
} else if url.Host == "" || (len(url.Host) > 0 && url.Host[0] == '.') {
finalPath = path.Join(filepath.ToSlash(cwd), urlPath)
// absolute path
} else {
// UNC -- file://\\host/share/whatever
// drive -- file://c:/absolute/path
finalPath = path.Join(url.Host, urlPath)
}
} }
// Keep track of the source so we can make sure not to delete this later // Keep track of the source so we can make sure not to delete this later

View File

@ -67,7 +67,7 @@ func NewCore(c *CoreConfig) (*Core, error) {
return nil, err 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. // to do this at this point with the variables.
result.builds = make(map[string]*template.Builder) result.builds = make(map[string]*template.Builder)
for _, b := range c.Template.Builders { for _, b := range c.Template.Builders {