github.com/aclaygray/packer@v1.3.2/common/config.go (about)

     1  package common
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"os"
     7  	"path/filepath"
     8  	"regexp"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hashicorp/packer/packer"
    14  )
    15  
    16  // PackerKeyEnv is used to specify the key interval (delay) between keystrokes
    17  // sent to the VM, typically in boot commands. This is to prevent host CPU
    18  // utilization from causing key presses to be skipped or repeated incorrectly.
    19  const PackerKeyEnv = "PACKER_KEY_INTERVAL"
    20  
    21  // PackerKeyDefault 100ms is appropriate for shared build infrastructure while a
    22  // shorter delay (e.g. 10ms) can be used on a workstation. See PackerKeyEnv.
    23  const PackerKeyDefault = 100 * time.Millisecond
    24  
    25  // ChooseString returns the first non-empty value.
    26  func ChooseString(vals ...string) string {
    27  	for _, el := range vals {
    28  		if el != "" {
    29  			return el
    30  		}
    31  	}
    32  
    33  	return ""
    34  }
    35  
    36  // SupportedProtocol verifies that the url passed is actually supported or not
    37  // This will also validate that the protocol is one that's actually implemented.
    38  func SupportedProtocol(u *url.URL) bool {
    39  	// url.Parse shouldn't return nil except on error....but it can.
    40  	if u == nil {
    41  		return false
    42  	}
    43  
    44  	// build a dummy NewDownloadClient since this is the only place that valid
    45  	// protocols are actually exposed.
    46  	cli := NewDownloadClient(&DownloadConfig{}, new(packer.NoopUi))
    47  
    48  	// Iterate through each downloader to see if a protocol was found.
    49  	ok := false
    50  	for scheme := range cli.config.DownloaderMap {
    51  		if strings.ToLower(u.Scheme) == strings.ToLower(scheme) {
    52  			ok = true
    53  		}
    54  	}
    55  	return ok
    56  }
    57  
    58  // DownloadableURL processes a URL that may also be a file path and returns
    59  // a completely valid URL representing the requested file. For example,
    60  // the original URL might be "local/file.iso" which isn't a valid URL,
    61  // and so DownloadableURL will return "file://local/file.iso"
    62  // No other transformations are done to the path.
    63  func DownloadableURL(original string) (string, error) {
    64  	var absPrefix, result string
    65  
    66  	absPrefix = ""
    67  	if runtime.GOOS == "windows" {
    68  		absPrefix = "/"
    69  	}
    70  
    71  	// Check that the user specified a UNC path, and promote it to an smb:// uri.
    72  	if strings.HasPrefix(original, "\\\\") && len(original) > 2 && original[2] != '?' {
    73  		result = filepath.ToSlash(original[2:])
    74  		return fmt.Sprintf("smb://%s", result), nil
    75  	}
    76  
    77  	// Fix the url if it's using bad characters commonly mistaken with a path.
    78  	original = filepath.ToSlash(original)
    79  
    80  	// Check to see that this is a parseable URL with a scheme and a host.
    81  	// If so, then just pass it through.
    82  	if u, err := url.Parse(original); err == nil && u.Scheme != "" && u.Host != "" {
    83  		return original, nil
    84  	}
    85  
    86  	// If it's a file scheme, then convert it back to a regular path so the next
    87  	// case which forces it to an absolute path, will correct it.
    88  	if u, err := url.Parse(original); err == nil && strings.ToLower(u.Scheme) == "file" {
    89  		original = u.Path
    90  	}
    91  
    92  	// If we're on Windows and we start with a slash, then this absolute path
    93  	// is wrong. Fix it up, so the next case can figure out the absolute path.
    94  	if rpath := strings.SplitN(original, "/", 2); rpath[0] == "" && runtime.GOOS == "windows" {
    95  		result = rpath[1]
    96  	} else {
    97  		result = original
    98  	}
    99  
   100  	// Since we should be some kind of path (relative or absolute), check
   101  	// that the file exists, then make it an absolute path so we can return an
   102  	// absolute uri.
   103  	if _, err := os.Stat(result); err == nil {
   104  		result, err = filepath.Abs(filepath.FromSlash(result))
   105  		if err != nil {
   106  			return "", err
   107  		}
   108  
   109  		result, err = filepath.EvalSymlinks(result)
   110  		if err != nil {
   111  			return "", err
   112  		}
   113  
   114  		result = filepath.Clean(result)
   115  		return fmt.Sprintf("file://%s%s", absPrefix, filepath.ToSlash(result)), nil
   116  	}
   117  
   118  	// Otherwise, check if it was originally an absolute path, and fix it if so.
   119  	if strings.HasPrefix(original, "/") {
   120  		return fmt.Sprintf("file://%s%s", absPrefix, result), nil
   121  	}
   122  
   123  	// Anything left should be a non-existent relative path. So fix it up here.
   124  	result = filepath.ToSlash(filepath.Clean(result))
   125  	return fmt.Sprintf("file://./%s", result), nil
   126  }
   127  
   128  // Force the parameter into a url. This will transform the parameter into
   129  // a proper url, removing slashes, adding the proper prefix, etc.
   130  func ValidatedURL(original string) (string, error) {
   131  
   132  	// See if the user failed to give a url
   133  	if ok, _ := regexp.MatchString("(?m)^[^[:punct:]]+://", original); !ok {
   134  
   135  		// So since no magic was found, this must be a path.
   136  		result, err := DownloadableURL(original)
   137  		if err == nil {
   138  			return ValidatedURL(result)
   139  		}
   140  
   141  		return "", err
   142  	}
   143  
   144  	// Verify that the url is parseable...just in case.
   145  	u, err := url.Parse(original)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  
   150  	// We should now have a url, so verify that it's a protocol we support.
   151  	if !SupportedProtocol(u) {
   152  		return "", fmt.Errorf("Unsupported protocol scheme! (%#v)", u)
   153  	}
   154  
   155  	// We should now have a properly formatted and supported url
   156  	return u.String(), nil
   157  }
   158  
   159  // FileExistsLocally takes the URL output from DownloadableURL, and determines
   160  // whether it is present on the file system.
   161  // example usage:
   162  //
   163  // myFile, err = common.DownloadableURL(c.SourcePath)
   164  // ...
   165  // fileExists := common.StatURL(myFile)
   166  // possible output:
   167  // true -- should occur if the file is present, or if the file is not present,
   168  // but is not supposed to be (e.g. the schema is http://, not file://)
   169  // false -- should occur if there was an error stating the file, so the
   170  // file is not present when it should be.
   171  
   172  func FileExistsLocally(original string) bool {
   173  	// original should be something like file://C:/my/path.iso
   174  	u, _ := url.Parse(original)
   175  
   176  	// First create a dummy downloader so we can figure out which
   177  	// protocol to use.
   178  	cli := NewDownloadClient(&DownloadConfig{}, new(packer.NoopUi))
   179  	d, ok := cli.config.DownloaderMap[u.Scheme]
   180  	if !ok {
   181  		return false
   182  	}
   183  
   184  	// Check to see that it's got a Local way of doing things.
   185  	local, ok := d.(LocalDownloader)
   186  	if !ok {
   187  		return true // XXX: Remote URLs short-circuit this logic.
   188  	}
   189  
   190  	// Figure out where we're at.
   191  	wd, err := os.Getwd()
   192  	if err != nil {
   193  		return false
   194  	}
   195  
   196  	// Now figure out the real path to the file.
   197  	realpath, err := local.toPath(wd, *u)
   198  	if err != nil {
   199  		return false
   200  	}
   201  
   202  	// Finally we can seek the truth via os.Stat.
   203  	_, err = os.Stat(realpath)
   204  	if err != nil {
   205  		return false
   206  	}
   207  	return true
   208  }