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  }