207 lines
6.0 KiB
Go
207 lines
6.0 KiB
Go
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// PackerKeyEnv is used to specify the key interval (delay) between keystrokes
|
|
// sent to the VM, typically in boot commands. This is to prevent host CPU
|
|
// utilization from causing key presses to be skipped or repeated incorrectly.
|
|
const PackerKeyEnv = "PACKER_KEY_INTERVAL"
|
|
|
|
// PackerKeyDefault 100ms is appropriate for shared build infrastructure while a
|
|
// shorter delay (e.g. 10ms) can be used on a workstation. See PackerKeyEnv.
|
|
const PackerKeyDefault = 100 * time.Millisecond
|
|
|
|
// ChooseString returns the first non-empty value.
|
|
func ChooseString(vals ...string) string {
|
|
for _, el := range vals {
|
|
if el != "" {
|
|
return el
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// SupportedProtocol verifies that the url passed is actually supported or not
|
|
// This will also validate that the protocol is one that's actually implemented.
|
|
func SupportedProtocol(u *url.URL) bool {
|
|
// url.Parse shouldn't return nil except on error....but it can.
|
|
if u == nil {
|
|
return false
|
|
}
|
|
|
|
// build a dummy NewDownloadClient since this is the only place that valid
|
|
// protocols are actually exposed.
|
|
cli := NewDownloadClient(&DownloadConfig{})
|
|
|
|
// Iterate through each downloader to see if a protocol was found.
|
|
ok := false
|
|
for scheme := range cli.config.DownloaderMap {
|
|
if strings.ToLower(u.Scheme) == strings.ToLower(scheme) {
|
|
ok = true
|
|
}
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// DownloadableURL processes a URL that may also be a file path and returns
|
|
// a completely valid URL representing the requested file. For example,
|
|
// the original URL might be "local/file.iso" which isn't a valid URL,
|
|
// and so DownloadableURL will return "file://local/file.iso"
|
|
// No other transformations are done to the path.
|
|
func DownloadableURL(original string) (string, error) {
|
|
var absPrefix, result string
|
|
|
|
absPrefix = ""
|
|
if runtime.GOOS == "windows" {
|
|
absPrefix = "/"
|
|
}
|
|
|
|
// Check that the user specified a UNC path, and promote it to an smb:// uri.
|
|
if strings.HasPrefix(original, "\\\\") && len(original) > 2 && original[2] != '?' {
|
|
result = filepath.ToSlash(original[2:])
|
|
return fmt.Sprintf("smb://%s", result), nil
|
|
}
|
|
|
|
// Fix the url if it's using bad characters commonly mistaken with a path.
|
|
original = filepath.ToSlash(original)
|
|
|
|
// Check to see that this is a parseable URL with a scheme and a host.
|
|
// If so, then just pass it through.
|
|
if u, err := url.Parse(original); err == nil && u.Scheme != "" && u.Host != "" {
|
|
return original, nil
|
|
}
|
|
|
|
// If it's a file scheme, then convert it back to a regular path so the next
|
|
// case which forces it to an absolute path, will correct it.
|
|
if u, err := url.Parse(original); err == nil && strings.ToLower(u.Scheme) == "file" {
|
|
original = u.Path
|
|
}
|
|
|
|
// If we're on Windows and we start with a slash, then this absolute path
|
|
// is wrong. Fix it up, so the next case can figure out the absolute path.
|
|
if rpath := strings.SplitN(original, "/", 2); rpath[0] == "" && runtime.GOOS == "windows" {
|
|
result = rpath[1]
|
|
} else {
|
|
result = original
|
|
}
|
|
|
|
// Since we should be some kind of path (relative or absolute), check
|
|
// that the file exists, then make it an absolute path so we can return an
|
|
// absolute uri.
|
|
if _, err := os.Stat(result); err == nil {
|
|
result, err = filepath.Abs(filepath.FromSlash(result))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
result, err = filepath.EvalSymlinks(result)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
result = filepath.Clean(result)
|
|
return fmt.Sprintf("file://%s%s", absPrefix, filepath.ToSlash(result)), nil
|
|
}
|
|
|
|
// Otherwise, check if it was originally an absolute path, and fix it if so.
|
|
if strings.HasPrefix(original, "/") {
|
|
return fmt.Sprintf("file://%s%s", absPrefix, result), nil
|
|
}
|
|
|
|
// Anything left should be a non-existent relative path. So fix it up here.
|
|
result = filepath.ToSlash(filepath.Clean(result))
|
|
return fmt.Sprintf("file://./%s", result), nil
|
|
}
|
|
|
|
// Force the parameter into a url. This will transform the parameter into
|
|
// a proper url, removing slashes, adding the proper prefix, etc.
|
|
func ValidatedURL(original string) (string, error) {
|
|
|
|
// See if the user failed to give a url
|
|
if ok, _ := regexp.MatchString("(?m)^[^[:punct:]]+://", original); !ok {
|
|
|
|
// So since no magic was found, this must be a path.
|
|
result, err := DownloadableURL(original)
|
|
if err == nil {
|
|
return ValidatedURL(result)
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
// Verify that the url is parseable...just in case.
|
|
u, err := url.Parse(original)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// We should now have a url, so verify that it's a protocol we support.
|
|
if !SupportedProtocol(u) {
|
|
return "", fmt.Errorf("Unsupported protocol scheme! (%#v)", u)
|
|
}
|
|
|
|
// We should now have a properly formatted and supported url
|
|
return u.String(), nil
|
|
}
|
|
|
|
// FileExistsLocally takes the URL output from DownloadableURL, and determines
|
|
// whether it is present on the file system.
|
|
// example usage:
|
|
//
|
|
// myFile, err = common.DownloadableURL(c.SourcePath)
|
|
// ...
|
|
// fileExists := common.StatURL(myFile)
|
|
// possible output:
|
|
// true -- should occur if the file is present, or if the file is not present,
|
|
// but is not supposed to be (e.g. the schema is http://, not file://)
|
|
// false -- should occur if there was an error stating the file, so the
|
|
// file is not present when it should be.
|
|
|
|
func FileExistsLocally(original string) bool {
|
|
// original should be something like file://C:/my/path.iso
|
|
u, _ := url.Parse(original)
|
|
|
|
// First create a dummy downloader so we can figure out which
|
|
// protocol to use.
|
|
cli := NewDownloadClient(&DownloadConfig{})
|
|
d, ok := cli.config.DownloaderMap[u.Scheme]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
// Check to see that it's got a Local way of doing things.
|
|
local, ok := d.(LocalDownloader)
|
|
if !ok {
|
|
return true // XXX: Remote URLs short-circuit this logic.
|
|
}
|
|
|
|
// Figure out where we're at.
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Now figure out the real path to the file.
|
|
realpath, err := local.toPath(wd, *u)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Finally we can seek the truth via os.Stat.
|
|
_, err = os.Stat(realpath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|