github.com/timsutton/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 }