github.com/lmars/docker@v1.6.0-rc2/utils/utils.go (about) 1 package utils 2 3 import ( 4 "bufio" 5 "bytes" 6 "crypto/sha1" 7 "crypto/sha256" 8 "encoding/hex" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "strings" 19 "sync" 20 21 log "github.com/Sirupsen/logrus" 22 "github.com/docker/docker/autogen/dockerversion" 23 "github.com/docker/docker/pkg/archive" 24 "github.com/docker/docker/pkg/common" 25 "github.com/docker/docker/pkg/fileutils" 26 "github.com/docker/docker/pkg/ioutils" 27 ) 28 29 type KeyValuePair struct { 30 Key string 31 Value string 32 } 33 34 var ( 35 validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) 36 ) 37 38 // Request a given URL and return an io.Reader 39 func Download(url string) (resp *http.Response, err error) { 40 if resp, err = http.Get(url); err != nil { 41 return nil, err 42 } 43 if resp.StatusCode >= 400 { 44 return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status) 45 } 46 return resp, nil 47 } 48 49 func Trunc(s string, maxlen int) string { 50 if len(s) <= maxlen { 51 return s 52 } 53 return s[:maxlen] 54 } 55 56 // Figure out the absolute path of our own binary (if it's still around). 57 func SelfPath() string { 58 path, err := exec.LookPath(os.Args[0]) 59 if err != nil { 60 if os.IsNotExist(err) { 61 return "" 62 } 63 if execErr, ok := err.(*exec.Error); ok && os.IsNotExist(execErr.Err) { 64 return "" 65 } 66 panic(err) 67 } 68 path, err = filepath.Abs(path) 69 if err != nil { 70 if os.IsNotExist(err) { 71 return "" 72 } 73 panic(err) 74 } 75 return path 76 } 77 78 func dockerInitSha1(target string) string { 79 f, err := os.Open(target) 80 if err != nil { 81 return "" 82 } 83 defer f.Close() 84 h := sha1.New() 85 _, err = io.Copy(h, f) 86 if err != nil { 87 return "" 88 } 89 return hex.EncodeToString(h.Sum(nil)) 90 } 91 92 func isValidDockerInitPath(target string, selfPath string) bool { // target and selfPath should be absolute (InitPath and SelfPath already do this) 93 if target == "" { 94 return false 95 } 96 if dockerversion.IAMSTATIC == "true" { 97 if selfPath == "" { 98 return false 99 } 100 if target == selfPath { 101 return true 102 } 103 targetFileInfo, err := os.Lstat(target) 104 if err != nil { 105 return false 106 } 107 selfPathFileInfo, err := os.Lstat(selfPath) 108 if err != nil { 109 return false 110 } 111 return os.SameFile(targetFileInfo, selfPathFileInfo) 112 } 113 return dockerversion.INITSHA1 != "" && dockerInitSha1(target) == dockerversion.INITSHA1 114 } 115 116 // Figure out the path of our dockerinit (which may be SelfPath()) 117 func DockerInitPath(localCopy string) string { 118 selfPath := SelfPath() 119 if isValidDockerInitPath(selfPath, selfPath) { 120 // if we're valid, don't bother checking anything else 121 return selfPath 122 } 123 var possibleInits = []string{ 124 localCopy, 125 dockerversion.INITPATH, 126 filepath.Join(filepath.Dir(selfPath), "dockerinit"), 127 128 // 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." 129 // http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec 130 "/usr/libexec/docker/dockerinit", 131 "/usr/local/libexec/docker/dockerinit", 132 133 // 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." 134 // http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA 135 "/usr/lib/docker/dockerinit", 136 "/usr/local/lib/docker/dockerinit", 137 } 138 for _, dockerInit := range possibleInits { 139 if dockerInit == "" { 140 continue 141 } 142 path, err := exec.LookPath(dockerInit) 143 if err == nil { 144 path, err = filepath.Abs(path) 145 if err != nil { 146 // LookPath already validated that this file exists and is executable (following symlinks), so how could Abs fail? 147 panic(err) 148 } 149 if isValidDockerInitPath(path, selfPath) { 150 return path 151 } 152 } 153 } 154 return "" 155 } 156 157 func GetTotalUsedFds() int { 158 if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { 159 log.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) 160 } else { 161 return len(fds) 162 } 163 return -1 164 } 165 166 func ValidateID(id string) error { 167 if ok := validHex.MatchString(id); !ok { 168 err := fmt.Errorf("image ID '%s' is invalid", id) 169 return err 170 } 171 return nil 172 } 173 174 // Code c/c from io.Copy() modified to handle escape sequence 175 func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { 176 buf := make([]byte, 32*1024) 177 for { 178 nr, er := src.Read(buf) 179 if nr > 0 { 180 // ---- Docker addition 181 // char 16 is C-p 182 if nr == 1 && buf[0] == 16 { 183 nr, er = src.Read(buf) 184 // char 17 is C-q 185 if nr == 1 && buf[0] == 17 { 186 if err := src.Close(); err != nil { 187 return 0, err 188 } 189 return 0, nil 190 } 191 } 192 // ---- End of docker 193 nw, ew := dst.Write(buf[0:nr]) 194 if nw > 0 { 195 written += int64(nw) 196 } 197 if ew != nil { 198 err = ew 199 break 200 } 201 if nr != nw { 202 err = io.ErrShortWrite 203 break 204 } 205 } 206 if er == io.EOF { 207 break 208 } 209 if er != nil { 210 err = er 211 break 212 } 213 } 214 return written, err 215 } 216 217 func HashData(src io.Reader) (string, error) { 218 h := sha256.New() 219 if _, err := io.Copy(h, src); err != nil { 220 return "", err 221 } 222 return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil 223 } 224 225 type WriteFlusher struct { 226 sync.Mutex 227 w io.Writer 228 flusher http.Flusher 229 } 230 231 func (wf *WriteFlusher) Write(b []byte) (n int, err error) { 232 wf.Lock() 233 defer wf.Unlock() 234 n, err = wf.w.Write(b) 235 wf.flusher.Flush() 236 return n, err 237 } 238 239 // Flush the stream immediately. 240 func (wf *WriteFlusher) Flush() { 241 wf.Lock() 242 defer wf.Unlock() 243 wf.flusher.Flush() 244 } 245 246 func NewWriteFlusher(w io.Writer) *WriteFlusher { 247 var flusher http.Flusher 248 if f, ok := w.(http.Flusher); ok { 249 flusher = f 250 } else { 251 flusher = &ioutils.NopFlusher{} 252 } 253 return &WriteFlusher{w: w, flusher: flusher} 254 } 255 256 func NewHTTPRequestError(msg string, res *http.Response) error { 257 return &JSONError{ 258 Message: msg, 259 Code: res.StatusCode, 260 } 261 } 262 263 // An StatusError reports an unsuccessful exit by a command. 264 type StatusError struct { 265 Status string 266 StatusCode int 267 } 268 269 func (e *StatusError) Error() string { 270 return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) 271 } 272 273 func quote(word string, buf *bytes.Buffer) { 274 // Bail out early for "simple" strings 275 if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { 276 buf.WriteString(word) 277 return 278 } 279 280 buf.WriteString("'") 281 282 for i := 0; i < len(word); i++ { 283 b := word[i] 284 if b == '\'' { 285 // Replace literal ' with a close ', a \', and a open ' 286 buf.WriteString("'\\''") 287 } else { 288 buf.WriteByte(b) 289 } 290 } 291 292 buf.WriteString("'") 293 } 294 295 // Take a list of strings and escape them so they will be handled right 296 // when passed as arguments to an program via a shell 297 func ShellQuoteArguments(args []string) string { 298 var buf bytes.Buffer 299 for i, arg := range args { 300 if i != 0 { 301 buf.WriteByte(' ') 302 } 303 quote(arg, &buf) 304 } 305 return buf.String() 306 } 307 308 var globalTestID string 309 310 // TestDirectory creates a new temporary directory and returns its path. 311 // The contents of directory at path `templateDir` is copied into the 312 // new directory. 313 func TestDirectory(templateDir string) (dir string, err error) { 314 if globalTestID == "" { 315 globalTestID = common.RandomString()[:4] 316 } 317 prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2)) 318 if prefix == "" { 319 prefix = "docker-test-" 320 } 321 dir, err = ioutil.TempDir("", prefix) 322 if err = os.Remove(dir); err != nil { 323 return 324 } 325 if templateDir != "" { 326 if err = archive.CopyWithTar(templateDir, dir); err != nil { 327 return 328 } 329 } 330 return 331 } 332 333 // GetCallerName introspects the call stack and returns the name of the 334 // function `depth` levels down in the stack. 335 func GetCallerName(depth int) string { 336 // Use the caller function name as a prefix. 337 // This helps trace temp directories back to their test. 338 pc, _, _, _ := runtime.Caller(depth + 1) 339 callerLongName := runtime.FuncForPC(pc).Name() 340 parts := strings.Split(callerLongName, ".") 341 callerShortName := parts[len(parts)-1] 342 return callerShortName 343 } 344 345 func CopyFile(src, dst string) (int64, error) { 346 if src == dst { 347 return 0, nil 348 } 349 sf, err := os.Open(src) 350 if err != nil { 351 return 0, err 352 } 353 defer sf.Close() 354 if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { 355 return 0, err 356 } 357 df, err := os.Create(dst) 358 if err != nil { 359 return 0, err 360 } 361 defer df.Close() 362 return io.Copy(df, sf) 363 } 364 365 // ReplaceOrAppendValues returns the defaults with the overrides either 366 // replaced by env key or appended to the list 367 func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { 368 cache := make(map[string]int, len(defaults)) 369 for i, e := range defaults { 370 parts := strings.SplitN(e, "=", 2) 371 cache[parts[0]] = i 372 } 373 374 for _, value := range overrides { 375 // Values w/o = means they want this env to be removed/unset. 376 if !strings.Contains(value, "=") { 377 if i, exists := cache[value]; exists { 378 defaults[i] = "" // Used to indicate it should be removed 379 } 380 continue 381 } 382 383 // Just do a normal set/update 384 parts := strings.SplitN(value, "=", 2) 385 if i, exists := cache[parts[0]]; exists { 386 defaults[i] = value 387 } else { 388 defaults = append(defaults, value) 389 } 390 } 391 392 // Now remove all entries that we want to "unset" 393 for i := 0; i < len(defaults); i++ { 394 if defaults[i] == "" { 395 defaults = append(defaults[:i], defaults[i+1:]...) 396 i-- 397 } 398 } 399 400 return defaults 401 } 402 403 func DoesEnvExist(name string) bool { 404 for _, entry := range os.Environ() { 405 parts := strings.SplitN(entry, "=", 2) 406 if parts[0] == name { 407 return true 408 } 409 } 410 return false 411 } 412 413 // ReadSymlinkedDirectory returns the target directory of a symlink. 414 // The target of the symbolic link may not be a file. 415 func ReadSymlinkedDirectory(path string) (string, error) { 416 var realPath string 417 var err error 418 if realPath, err = filepath.Abs(path); err != nil { 419 return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) 420 } 421 if realPath, err = filepath.EvalSymlinks(realPath); err != nil { 422 return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) 423 } 424 realPathInfo, err := os.Stat(realPath) 425 if err != nil { 426 return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) 427 } 428 if !realPathInfo.Mode().IsDir() { 429 return "", fmt.Errorf("canonical path points to a file '%s'", realPath) 430 } 431 return realPath, nil 432 } 433 434 // ValidateContextDirectory checks if all the contents of the directory 435 // can be read and returns an error if some files can't be read 436 // symlinks which point to non-existing files don't trigger an error 437 func ValidateContextDirectory(srcPath string, excludes []string) error { 438 return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error { 439 // skip this directory/file if it's not in the path, it won't get added to the context 440 if relFilePath, err := filepath.Rel(srcPath, filePath); err != nil { 441 return err 442 } else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil { 443 return err 444 } else if skip { 445 if f.IsDir() { 446 return filepath.SkipDir 447 } 448 return nil 449 } 450 451 if err != nil { 452 if os.IsPermission(err) { 453 return fmt.Errorf("can't stat '%s'", filePath) 454 } 455 if os.IsNotExist(err) { 456 return nil 457 } 458 return err 459 } 460 461 // skip checking if symlinks point to non-existing files, such symlinks can be useful 462 // also skip named pipes, because they hanging on open 463 if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 { 464 return nil 465 } 466 467 if !f.IsDir() { 468 currentFile, err := os.Open(filePath) 469 if err != nil && os.IsPermission(err) { 470 return fmt.Errorf("no permission to read from '%s'", filePath) 471 } 472 currentFile.Close() 473 } 474 return nil 475 }) 476 } 477 478 func StringsContainsNoCase(slice []string, s string) bool { 479 for _, ss := range slice { 480 if strings.ToLower(s) == strings.ToLower(ss) { 481 return true 482 } 483 } 484 return false 485 } 486 487 // Reads a .dockerignore file and returns the list of file patterns 488 // to ignore. Note this will trim whitespace from each line as well 489 // as use GO's "clean" func to get the shortest/cleanest path for each. 490 func ReadDockerIgnore(path string) ([]string, error) { 491 // Note that a missing .dockerignore file isn't treated as an error 492 reader, err := os.Open(path) 493 if err != nil { 494 if !os.IsNotExist(err) { 495 return nil, fmt.Errorf("Error reading '%s': %v", path, err) 496 } 497 return nil, nil 498 } 499 defer reader.Close() 500 501 scanner := bufio.NewScanner(reader) 502 var excludes []string 503 504 for scanner.Scan() { 505 pattern := strings.TrimSpace(scanner.Text()) 506 if pattern == "" { 507 continue 508 } 509 pattern = filepath.Clean(pattern) 510 excludes = append(excludes, pattern) 511 } 512 if err = scanner.Err(); err != nil { 513 return nil, fmt.Errorf("Error reading '%s': %v", path, err) 514 } 515 return excludes, nil 516 } 517 518 // Wrap a concrete io.Writer and hold a count of the number 519 // of bytes written to the writer during a "session". 520 // This can be convenient when write return is masked 521 // (e.g., json.Encoder.Encode()) 522 type WriteCounter struct { 523 Count int64 524 Writer io.Writer 525 } 526 527 func NewWriteCounter(w io.Writer) *WriteCounter { 528 return &WriteCounter{ 529 Writer: w, 530 } 531 } 532 533 func (wc *WriteCounter) Write(p []byte) (count int, err error) { 534 count, err = wc.Writer.Write(p) 535 wc.Count += int64(count) 536 return 537 } 538 539 // ImageReference combines `repo` and `ref` and returns a string representing 540 // the combination. If `ref` is a digest (meaning it's of the form 541 // <algorithm>:<digest>, the returned string is <repo>@<ref>. Otherwise, 542 // ref is assumed to be a tag, and the returned string is <repo>:<tag>. 543 func ImageReference(repo, ref string) string { 544 if DigestReference(ref) { 545 return repo + "@" + ref 546 } 547 return repo + ":" + ref 548 } 549 550 // DigestReference returns true if ref is a digest reference; i.e. if it 551 // is of the form <algorithm>:<digest>. 552 func DigestReference(ref string) bool { 553 return strings.Contains(ref, ":") 554 }