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