github.com/dlintw/docker@v1.5.0-rc4/utils/utils.go (about)

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