diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 1f4ee812f..62546d212 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -43,6 +43,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 7bef253ee..7b01191a8 100644 --- a/common/config.go +++ b/common/config.go @@ -56,77 +56,109 @@ func DownloadableURL(original string) (string, error) { // for more info about valid windows URIs idx := strings.Index(original, ":") 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 { return "", err } - if u.Scheme == "" { - u.Scheme = "file" + if uri.Scheme == "" { + uri.Scheme = "file" } - if u.Scheme == "file" { - // Windows file handling is all sorts of tricky... + const UNCPrefix = string(os.PathSeparator)+string(os.PathSeparator) + if uri.Scheme == "file" { + var ospath string // os-formatted pathname if runtime.GOOS == "windows" { - // If the path is using Windows-style slashes, URL parses - // it into the host field. - if u.Path == "" && strings.Contains(u.Host, `\`) { - u.Path = u.Host - u.Host = "" + // Move any extra path components that were mis-parsed into the Host + // field back into the uri.Path field + if len(uri.Host) >= len(UNCPrefix) && uri.Host[:len(UNCPrefix)] == UNCPrefix { + idx := strings.Index(uri.Host[len(UNCPrefix):], string(os.PathSeparator)) + 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 - // to actually exist. - if _, err := os.Stat(u.Path); err == nil { - u.Path, err = filepath.Abs(u.Path) + // to actually exist. We don't do it on windows, because EvalSymlinks + // won't understand how to handle UNC paths and other Windows-specific minutae. + if _, err := os.Stat(ospath); err == nil && runtime.GOOS != "windows" { + ospath, err = filepath.Abs(ospath) if err != nil { return "", err } - u.Path, err = filepath.EvalSymlinks(u.Path) + ospath, err = filepath.EvalSymlinks(ospath) if err != nil { return "", err } - u.Path = filepath.Clean(u.Path) + ospath = filepath.Clean(ospath) } + // now that ospath was normalized and such.. 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. - u.Path = strings.Replace(u.Path, `\`, `/`, -1) - // prepend absolute windows paths with "/" so that when we - // compose u.String() below the outcome will be correct - // file:///c/blah syntax; otherwise u.String() will only add - // file:// which is not technically a correct windows URI - if filepath.IsAbs(u.Path) && !strings.HasPrefix(u.Path, "/") { - u.Path = "/" + u.Path + uri.Host = "" + // Check to see if our ospath is unc-prefixed, and if it is then split + // the UNC host into uri.Host, leaving the rest in ospath. + // This way, our UNC-uri is protected from injury in the call to uri.String() + if len(ospath) >= len(UNCPrefix) && ospath[:len(UNCPrefix)] == UNCPrefix { + idx := strings.Index(ospath[len(UNCPrefix):], string(os.PathSeparator)) + if idx > -1 { + uri.Host = ospath[:len(UNCPrefix)+idx] + ospath = ospath[len(UNCPrefix)+idx:] + } } - + // 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 - u.Scheme = strings.ToLower(u.Scheme) + uri.Scheme = strings.ToLower(uri.Scheme) // Verify that the scheme is something we support in our common downloader. supported := []string{"file", "http", "https"} found := false for _, s := range supported { - if u.Scheme == s { + if uri.Scheme == s { found = true break } } 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 diff --git a/common/download.go b/common/download.go index ada4c9756..5f9940056 100644 --- a/common/download.go +++ b/common/download.go @@ -16,6 +16,9 @@ import ( "net/url" "os" "runtime" + "path" + "path/filepath" + "strings" ) // 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. 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:] + // FIXME: + // cwd should point to the path relative to client.json, but + // 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 diff --git a/packer/core.go b/packer/core.go index 7da3e4510..c8ac2cfb7 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 {