github.com/robertojrojas/docker@v1.9.1/utils/utils.go (about) 1 package utils 2 3 import ( 4 "bufio" 5 "crypto/sha1" 6 "encoding/hex" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "runtime" 14 "strings" 15 16 "github.com/docker/distribution/registry/api/errcode" 17 "github.com/docker/docker/autogen/dockerversion" 18 "github.com/docker/docker/pkg/archive" 19 "github.com/docker/docker/pkg/fileutils" 20 "github.com/docker/docker/pkg/stringid" 21 ) 22 23 // SelfPath figures out the absolute path of our own binary (if it's still around). 24 func SelfPath() string { 25 path, err := exec.LookPath(os.Args[0]) 26 if err != nil { 27 if os.IsNotExist(err) { 28 return "" 29 } 30 if execErr, ok := err.(*exec.Error); ok && os.IsNotExist(execErr.Err) { 31 return "" 32 } 33 panic(err) 34 } 35 path, err = filepath.Abs(path) 36 if err != nil { 37 if os.IsNotExist(err) { 38 return "" 39 } 40 panic(err) 41 } 42 return path 43 } 44 45 func dockerInitSha1(target string) string { 46 f, err := os.Open(target) 47 if err != nil { 48 return "" 49 } 50 defer f.Close() 51 h := sha1.New() 52 _, err = io.Copy(h, f) 53 if err != nil { 54 return "" 55 } 56 return hex.EncodeToString(h.Sum(nil)) 57 } 58 59 func isValidDockerInitPath(target string, selfPath string) bool { // target and selfPath should be absolute (InitPath and SelfPath already do this) 60 if target == "" { 61 return false 62 } 63 if dockerversion.IAMSTATIC == "true" { 64 if selfPath == "" { 65 return false 66 } 67 if target == selfPath { 68 return true 69 } 70 targetFileInfo, err := os.Lstat(target) 71 if err != nil { 72 return false 73 } 74 selfPathFileInfo, err := os.Lstat(selfPath) 75 if err != nil { 76 return false 77 } 78 return os.SameFile(targetFileInfo, selfPathFileInfo) 79 } 80 return dockerversion.INITSHA1 != "" && dockerInitSha1(target) == dockerversion.INITSHA1 81 } 82 83 // DockerInitPath figures out the path of our dockerinit (which may be SelfPath()) 84 func DockerInitPath(localCopy string) string { 85 selfPath := SelfPath() 86 if isValidDockerInitPath(selfPath, selfPath) { 87 // if we're valid, don't bother checking anything else 88 return selfPath 89 } 90 var possibleInits = []string{ 91 localCopy, 92 dockerversion.INITPATH, 93 filepath.Join(filepath.Dir(selfPath), "dockerinit"), 94 95 // FHS 3.0 Draft: "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec." 96 // https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec 97 "/usr/libexec/docker/dockerinit", 98 "/usr/local/libexec/docker/dockerinit", 99 100 // FHS 2.3: "/usr/lib includes object files, libraries, and internal binaries that are not intended to be executed directly by users or shell scripts." 101 // https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA 102 "/usr/lib/docker/dockerinit", 103 "/usr/local/lib/docker/dockerinit", 104 } 105 for _, dockerInit := range possibleInits { 106 if dockerInit == "" { 107 continue 108 } 109 path, err := exec.LookPath(dockerInit) 110 if err == nil { 111 path, err = filepath.Abs(path) 112 if err != nil { 113 // LookPath already validated that this file exists and is executable (following symlinks), so how could Abs fail? 114 panic(err) 115 } 116 if isValidDockerInitPath(path, selfPath) { 117 return path 118 } 119 } 120 } 121 return "" 122 } 123 124 var globalTestID string 125 126 // TestDirectory creates a new temporary directory and returns its path. 127 // The contents of directory at path `templateDir` is copied into the 128 // new directory. 129 func TestDirectory(templateDir string) (dir string, err error) { 130 if globalTestID == "" { 131 globalTestID = stringid.GenerateNonCryptoID()[:4] 132 } 133 prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2)) 134 if prefix == "" { 135 prefix = "docker-test-" 136 } 137 dir, err = ioutil.TempDir("", prefix) 138 if err = os.Remove(dir); err != nil { 139 return 140 } 141 if templateDir != "" { 142 if err = archive.CopyWithTar(templateDir, dir); err != nil { 143 return 144 } 145 } 146 return 147 } 148 149 // GetCallerName introspects the call stack and returns the name of the 150 // function `depth` levels down in the stack. 151 func GetCallerName(depth int) string { 152 // Use the caller function name as a prefix. 153 // This helps trace temp directories back to their test. 154 pc, _, _, _ := runtime.Caller(depth + 1) 155 callerLongName := runtime.FuncForPC(pc).Name() 156 parts := strings.Split(callerLongName, ".") 157 callerShortName := parts[len(parts)-1] 158 return callerShortName 159 } 160 161 // ReplaceOrAppendEnvValues returns the defaults with the overrides either 162 // replaced by env key or appended to the list 163 func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { 164 cache := make(map[string]int, len(defaults)) 165 for i, e := range defaults { 166 parts := strings.SplitN(e, "=", 2) 167 cache[parts[0]] = i 168 } 169 170 for _, value := range overrides { 171 // Values w/o = means they want this env to be removed/unset. 172 if !strings.Contains(value, "=") { 173 if i, exists := cache[value]; exists { 174 defaults[i] = "" // Used to indicate it should be removed 175 } 176 continue 177 } 178 179 // Just do a normal set/update 180 parts := strings.SplitN(value, "=", 2) 181 if i, exists := cache[parts[0]]; exists { 182 defaults[i] = value 183 } else { 184 defaults = append(defaults, value) 185 } 186 } 187 188 // Now remove all entries that we want to "unset" 189 for i := 0; i < len(defaults); i++ { 190 if defaults[i] == "" { 191 defaults = append(defaults[:i], defaults[i+1:]...) 192 i-- 193 } 194 } 195 196 return defaults 197 } 198 199 // ValidateContextDirectory checks if all the contents of the directory 200 // can be read and returns an error if some files can't be read 201 // symlinks which point to non-existing files don't trigger an error 202 func ValidateContextDirectory(srcPath string, excludes []string) error { 203 contextRoot, err := getContextRoot(srcPath) 204 if err != nil { 205 return err 206 } 207 return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error { 208 // skip this directory/file if it's not in the path, it won't get added to the context 209 if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil { 210 return err 211 } else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil { 212 return err 213 } else if skip { 214 if f.IsDir() { 215 return filepath.SkipDir 216 } 217 return nil 218 } 219 220 if err != nil { 221 if os.IsPermission(err) { 222 return fmt.Errorf("can't stat '%s'", filePath) 223 } 224 if os.IsNotExist(err) { 225 return nil 226 } 227 return err 228 } 229 230 // skip checking if symlinks point to non-existing files, such symlinks can be useful 231 // also skip named pipes, because they hanging on open 232 if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 { 233 return nil 234 } 235 236 if !f.IsDir() { 237 currentFile, err := os.Open(filePath) 238 if err != nil && os.IsPermission(err) { 239 return fmt.Errorf("no permission to read from '%s'", filePath) 240 } 241 currentFile.Close() 242 } 243 return nil 244 }) 245 } 246 247 // ReadDockerIgnore reads a .dockerignore file and returns the list of file patterns 248 // to ignore. Note this will trim whitespace from each line as well 249 // as use GO's "clean" func to get the shortest/cleanest path for each. 250 func ReadDockerIgnore(reader io.ReadCloser) ([]string, error) { 251 if reader == nil { 252 return nil, nil 253 } 254 defer reader.Close() 255 scanner := bufio.NewScanner(reader) 256 var excludes []string 257 258 for scanner.Scan() { 259 pattern := strings.TrimSpace(scanner.Text()) 260 if pattern == "" { 261 continue 262 } 263 pattern = filepath.Clean(pattern) 264 excludes = append(excludes, pattern) 265 } 266 if err := scanner.Err(); err != nil { 267 return nil, fmt.Errorf("Error reading .dockerignore: %v", err) 268 } 269 return excludes, nil 270 } 271 272 // ImageReference combines `repo` and `ref` and returns a string representing 273 // the combination. If `ref` is a digest (meaning it's of the form 274 // <algorithm>:<digest>, the returned string is <repo>@<ref>. Otherwise, 275 // ref is assumed to be a tag, and the returned string is <repo>:<tag>. 276 func ImageReference(repo, ref string) string { 277 if DigestReference(ref) { 278 return repo + "@" + ref 279 } 280 return repo + ":" + ref 281 } 282 283 // DigestReference returns true if ref is a digest reference; i.e. if it 284 // is of the form <algorithm>:<digest>. 285 func DigestReference(ref string) bool { 286 return strings.Contains(ref, ":") 287 } 288 289 // GetErrorMessage returns the human readable message associated with 290 // the passed-in error. In some cases the default Error() func returns 291 // something that is less than useful so based on its types this func 292 // will go and get a better piece of text. 293 func GetErrorMessage(err error) string { 294 switch err.(type) { 295 case errcode.Error: 296 e, _ := err.(errcode.Error) 297 return e.Message 298 299 case errcode.ErrorCode: 300 ec, _ := err.(errcode.ErrorCode) 301 return ec.Message() 302 303 default: 304 return err.Error() 305 } 306 }