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  }