mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
c30a55f14d
Signed-off-by: Antonio Murdaca <me@runcom.ninja>
333 lines
8.7 KiB
Go
333 lines
8.7 KiB
Go
package utils
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/docker/autogen/dockerversion"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/fileutils"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
)
|
|
|
|
// Figure out the absolute path of our own binary (if it's still around).
|
|
func SelfPath() string {
|
|
path, err := exec.LookPath(os.Args[0])
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return ""
|
|
}
|
|
if execErr, ok := err.(*exec.Error); ok && os.IsNotExist(execErr.Err) {
|
|
return ""
|
|
}
|
|
panic(err)
|
|
}
|
|
path, err = filepath.Abs(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return ""
|
|
}
|
|
panic(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func dockerInitSha1(target string) string {
|
|
f, err := os.Open(target)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
defer f.Close()
|
|
h := sha1.New()
|
|
_, err = io.Copy(h, f)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
func isValidDockerInitPath(target string, selfPath string) bool { // target and selfPath should be absolute (InitPath and SelfPath already do this)
|
|
if target == "" {
|
|
return false
|
|
}
|
|
if dockerversion.IAMSTATIC == "true" {
|
|
if selfPath == "" {
|
|
return false
|
|
}
|
|
if target == selfPath {
|
|
return true
|
|
}
|
|
targetFileInfo, err := os.Lstat(target)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
selfPathFileInfo, err := os.Lstat(selfPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return os.SameFile(targetFileInfo, selfPathFileInfo)
|
|
}
|
|
return dockerversion.INITSHA1 != "" && dockerInitSha1(target) == dockerversion.INITSHA1
|
|
}
|
|
|
|
// Figure out the path of our dockerinit (which may be SelfPath())
|
|
func DockerInitPath(localCopy string) string {
|
|
selfPath := SelfPath()
|
|
if isValidDockerInitPath(selfPath, selfPath) {
|
|
// if we're valid, don't bother checking anything else
|
|
return selfPath
|
|
}
|
|
var possibleInits = []string{
|
|
localCopy,
|
|
dockerversion.INITPATH,
|
|
filepath.Join(filepath.Dir(selfPath), "dockerinit"),
|
|
|
|
// 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."
|
|
// https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec
|
|
"/usr/libexec/docker/dockerinit",
|
|
"/usr/local/libexec/docker/dockerinit",
|
|
|
|
// 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."
|
|
// https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA
|
|
"/usr/lib/docker/dockerinit",
|
|
"/usr/local/lib/docker/dockerinit",
|
|
}
|
|
for _, dockerInit := range possibleInits {
|
|
if dockerInit == "" {
|
|
continue
|
|
}
|
|
path, err := exec.LookPath(dockerInit)
|
|
if err == nil {
|
|
path, err = filepath.Abs(path)
|
|
if err != nil {
|
|
// LookPath already validated that this file exists and is executable (following symlinks), so how could Abs fail?
|
|
panic(err)
|
|
}
|
|
if isValidDockerInitPath(path, selfPath) {
|
|
return path
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// FIXME: move to httputils? ioutils?
|
|
type WriteFlusher struct {
|
|
sync.Mutex
|
|
w io.Writer
|
|
flusher http.Flusher
|
|
}
|
|
|
|
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
|
wf.Lock()
|
|
defer wf.Unlock()
|
|
n, err = wf.w.Write(b)
|
|
wf.flusher.Flush()
|
|
return n, err
|
|
}
|
|
|
|
// Flush the stream immediately.
|
|
func (wf *WriteFlusher) Flush() {
|
|
wf.Lock()
|
|
defer wf.Unlock()
|
|
wf.flusher.Flush()
|
|
}
|
|
|
|
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
|
var flusher http.Flusher
|
|
if f, ok := w.(http.Flusher); ok {
|
|
flusher = f
|
|
} else {
|
|
flusher = &ioutils.NopFlusher{}
|
|
}
|
|
return &WriteFlusher{w: w, flusher: flusher}
|
|
}
|
|
|
|
var globalTestID string
|
|
|
|
// TestDirectory creates a new temporary directory and returns its path.
|
|
// The contents of directory at path `templateDir` is copied into the
|
|
// new directory.
|
|
func TestDirectory(templateDir string) (dir string, err error) {
|
|
if globalTestID == "" {
|
|
globalTestID = stringid.GenerateRandomID()[:4]
|
|
}
|
|
prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2))
|
|
if prefix == "" {
|
|
prefix = "docker-test-"
|
|
}
|
|
dir, err = ioutil.TempDir("", prefix)
|
|
if err = os.Remove(dir); err != nil {
|
|
return
|
|
}
|
|
if templateDir != "" {
|
|
if err = archive.CopyWithTar(templateDir, dir); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetCallerName introspects the call stack and returns the name of the
|
|
// function `depth` levels down in the stack.
|
|
func GetCallerName(depth int) string {
|
|
// Use the caller function name as a prefix.
|
|
// This helps trace temp directories back to their test.
|
|
pc, _, _, _ := runtime.Caller(depth + 1)
|
|
callerLongName := runtime.FuncForPC(pc).Name()
|
|
parts := strings.Split(callerLongName, ".")
|
|
callerShortName := parts[len(parts)-1]
|
|
return callerShortName
|
|
}
|
|
|
|
// ReplaceOrAppendValues returns the defaults with the overrides either
|
|
// replaced by env key or appended to the list
|
|
func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
|
|
cache := make(map[string]int, len(defaults))
|
|
for i, e := range defaults {
|
|
parts := strings.SplitN(e, "=", 2)
|
|
cache[parts[0]] = i
|
|
}
|
|
|
|
for _, value := range overrides {
|
|
// Values w/o = means they want this env to be removed/unset.
|
|
if !strings.Contains(value, "=") {
|
|
if i, exists := cache[value]; exists {
|
|
defaults[i] = "" // Used to indicate it should be removed
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Just do a normal set/update
|
|
parts := strings.SplitN(value, "=", 2)
|
|
if i, exists := cache[parts[0]]; exists {
|
|
defaults[i] = value
|
|
} else {
|
|
defaults = append(defaults, value)
|
|
}
|
|
}
|
|
|
|
// Now remove all entries that we want to "unset"
|
|
for i := 0; i < len(defaults); i++ {
|
|
if defaults[i] == "" {
|
|
defaults = append(defaults[:i], defaults[i+1:]...)
|
|
i--
|
|
}
|
|
}
|
|
|
|
return defaults
|
|
}
|
|
|
|
func DoesEnvExist(name string) bool {
|
|
for _, entry := range os.Environ() {
|
|
parts := strings.SplitN(entry, "=", 2)
|
|
if parts[0] == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidateContextDirectory checks if all the contents of the directory
|
|
// can be read and returns an error if some files can't be read
|
|
// symlinks which point to non-existing files don't trigger an error
|
|
func ValidateContextDirectory(srcPath string, excludes []string) error {
|
|
return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
|
|
// skip this directory/file if it's not in the path, it won't get added to the context
|
|
if relFilePath, err := filepath.Rel(srcPath, filePath); err != nil {
|
|
return err
|
|
} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
|
|
return err
|
|
} else if skip {
|
|
if f.IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
if os.IsPermission(err) {
|
|
return fmt.Errorf("can't stat '%s'", filePath)
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// skip checking if symlinks point to non-existing files, such symlinks can be useful
|
|
// also skip named pipes, because they hanging on open
|
|
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
|
|
return nil
|
|
}
|
|
|
|
if !f.IsDir() {
|
|
currentFile, err := os.Open(filePath)
|
|
if err != nil && os.IsPermission(err) {
|
|
return fmt.Errorf("no permission to read from '%s'", filePath)
|
|
}
|
|
currentFile.Close()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Reads a .dockerignore file and returns the list of file patterns
|
|
// to ignore. Note this will trim whitespace from each line as well
|
|
// as use GO's "clean" func to get the shortest/cleanest path for each.
|
|
func ReadDockerIgnore(path string) ([]string, error) {
|
|
// Note that a missing .dockerignore file isn't treated as an error
|
|
reader, err := os.Open(path)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("Error reading '%s': %v", path, err)
|
|
}
|
|
return nil, nil
|
|
}
|
|
defer reader.Close()
|
|
|
|
scanner := bufio.NewScanner(reader)
|
|
var excludes []string
|
|
|
|
for scanner.Scan() {
|
|
pattern := strings.TrimSpace(scanner.Text())
|
|
if pattern == "" {
|
|
continue
|
|
}
|
|
pattern = filepath.Clean(pattern)
|
|
excludes = append(excludes, pattern)
|
|
}
|
|
if err = scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("Error reading '%s': %v", path, err)
|
|
}
|
|
return excludes, nil
|
|
}
|
|
|
|
// ImageReference combines `repo` and `ref` and returns a string representing
|
|
// the combination. If `ref` is a digest (meaning it's of the form
|
|
// <algorithm>:<digest>, the returned string is <repo>@<ref>. Otherwise,
|
|
// ref is assumed to be a tag, and the returned string is <repo>:<tag>.
|
|
func ImageReference(repo, ref string) string {
|
|
if DigestReference(ref) {
|
|
return repo + "@" + ref
|
|
}
|
|
return repo + ":" + ref
|
|
}
|
|
|
|
// DigestReference returns true if ref is a digest reference; i.e. if it
|
|
// is of the form <algorithm>:<digest>.
|
|
func DigestReference(ref string) bool {
|
|
return strings.Contains(ref, ":")
|
|
}
|