2013-05-14 18:37:35 -04:00
package utils
import (
2014-10-23 17:30:11 -04:00
"bufio"
2013-10-18 01:40:41 -04:00
"crypto/sha1"
2013-05-14 18:37:35 -04:00
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
2013-11-07 15:19:24 -05:00
"runtime"
2013-05-14 18:37:35 -04:00
"strings"
2014-05-05 18:51:32 -04:00
2015-07-21 16:30:32 -04:00
"github.com/docker/distribution/registry/api/errcode"
2015-02-04 16:22:38 -05:00
"github.com/docker/docker/autogen/dockerversion"
2014-11-01 12:23:08 -04:00
"github.com/docker/docker/pkg/archive"
2014-09-30 02:21:41 -04:00
"github.com/docker/docker/pkg/fileutils"
2015-04-01 10:21:07 -04:00
"github.com/docker/docker/pkg/stringid"
2013-05-14 18:37:35 -04:00
)
2015-07-21 17:20:12 -04:00
// SelfPath figures out the absolute path of our own binary (if it's still around).
2013-05-14 18:37:35 -04:00
func SelfPath ( ) string {
path , err := exec . LookPath ( os . Args [ 0 ] )
if err != nil {
2013-12-05 04:10:41 -05:00
if os . IsNotExist ( err ) {
return ""
}
if execErr , ok := err . ( * exec . Error ) ; ok && os . IsNotExist ( execErr . Err ) {
return ""
}
2013-05-14 18:37:35 -04:00
panic ( err )
}
path , err = filepath . Abs ( path )
if err != nil {
2013-12-05 04:10:41 -05:00
if os . IsNotExist ( err ) {
return ""
}
2013-05-14 18:37:35 -04:00
panic ( err )
}
return path
}
2013-10-18 01:40:41 -04:00
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)
2013-12-05 04:10:41 -05:00
if target == "" {
return false
}
2015-01-16 23:46:01 -05:00
if dockerversion . IAMSTATIC == "true" {
2013-12-05 04:10:41 -05:00
if selfPath == "" {
return false
}
2013-10-18 01:40:41 -04:00
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 )
}
2014-02-11 19:26:54 -05:00
return dockerversion . INITSHA1 != "" && dockerInitSha1 ( target ) == dockerversion . INITSHA1
2013-10-18 01:40:41 -04:00
}
2015-07-21 17:20:12 -04:00
// DockerInitPath figures out the path of our dockerinit (which may be SelfPath())
2013-11-25 17:42:22 -05:00
func DockerInitPath ( localCopy string ) string {
2013-10-18 01:40:41 -04:00
selfPath := SelfPath ( )
if isValidDockerInitPath ( selfPath , selfPath ) {
// if we're valid, don't bother checking anything else
return selfPath
}
var possibleInits = [ ] string {
2013-11-25 17:42:22 -05:00
localCopy ,
2014-02-11 19:26:54 -05:00
dockerversion . INITPATH ,
2013-10-18 01:40:41 -04:00
filepath . Join ( filepath . Dir ( selfPath ) , "dockerinit" ) ,
2013-11-27 18:49:40 -05:00
// 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."
2015-04-11 13:31:34 -04:00
// https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec
2013-10-18 01:40:41 -04:00
"/usr/libexec/docker/dockerinit" ,
"/usr/local/libexec/docker/dockerinit" ,
2013-11-27 18:49:40 -05:00
// 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."
2015-04-11 13:31:34 -04:00
// https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA
2013-11-27 18:49:40 -05:00
"/usr/lib/docker/dockerinit" ,
"/usr/local/lib/docker/dockerinit" ,
2013-10-18 01:40:41 -04:00
}
for _ , dockerInit := range possibleInits {
2013-12-05 04:10:41 -05:00
if dockerInit == "" {
continue
}
2013-10-18 01:40:41 -04:00
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 ""
}
2013-11-14 01:08:08 -05:00
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 == "" {
2015-07-28 20:19:17 -04:00
globalTestID = stringid . GenerateNonCryptoID ( ) [ : 4 ]
2013-11-14 01:08:08 -05:00
}
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 != "" {
2014-11-01 12:23:08 -04:00
if err = archive . CopyWithTar ( templateDir , dir ) ; err != nil {
2013-11-14 01:08:08 -05:00
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
}
2013-11-25 17:42:22 -05:00
2015-07-21 17:20:12 -04:00
// ReplaceOrAppendEnvValues returns the defaults with the overrides either
2014-03-01 02:29:00 -05:00
// 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
}
2015-01-16 15:57:08 -05:00
2014-03-01 02:29:00 -05:00
for _ , value := range overrides {
2015-01-16 15:57:08 -05:00
// 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
2014-03-01 02:29:00 -05:00
parts := strings . SplitN ( value , "=" , 2 )
if i , exists := cache [ parts [ 0 ] ] ; exists {
defaults [ i ] = value
} else {
defaults = append ( defaults , value )
}
}
2015-01-16 15:57:08 -05:00
// 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 --
}
}
2014-03-01 02:29:00 -05:00
return defaults
}
2014-02-24 16:10:06 -05:00
2014-05-10 03:58:45 -04:00
// 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
2014-07-07 08:23:07 -04:00
func ValidateContextDirectory ( srcPath string , excludes [ ] string ) error {
2015-08-24 17:07:22 -04:00
contextRoot , err := getContextRoot ( srcPath )
if err != nil {
return err
}
return filepath . Walk ( contextRoot , func ( filePath string , f os . FileInfo , err error ) error {
2014-05-10 03:58:45 -04:00
// skip this directory/file if it's not in the path, it won't get added to the context
2015-08-24 17:07:22 -04:00
if relFilePath , err := filepath . Rel ( contextRoot , filePath ) ; err != nil {
2014-09-14 11:13:40 -04:00
return err
2014-09-30 02:21:41 -04:00
} else if skip , err := fileutils . Matches ( relFilePath , excludes ) ; err != nil {
2014-09-14 11:13:40 -04:00
return err
} else if skip {
2014-07-07 08:23:07 -04:00
if f . IsDir ( ) {
return filepath . SkipDir
}
return nil
}
2014-09-14 11:13:40 -04:00
if err != nil {
if os . IsPermission ( err ) {
return fmt . Errorf ( "can't stat '%s'" , filePath )
}
if os . IsNotExist ( err ) {
return nil
}
2014-05-10 03:58:45 -04:00
return err
}
2014-09-14 11:13:40 -04:00
2014-05-10 03:58:45 -04:00
// skip checking if symlinks point to non-existing files, such symlinks can be useful
2014-08-22 10:37:37 -04:00
// also skip named pipes, because they hanging on open
2014-09-14 11:13:40 -04:00
if f . Mode ( ) & ( os . ModeSymlink | os . ModeNamedPipe ) != 0 {
2014-08-22 10:37:37 -04:00
return nil
2014-05-10 03:58:45 -04:00
}
if ! f . IsDir ( ) {
currentFile , err := os . Open ( filePath )
if err != nil && os . IsPermission ( err ) {
2014-09-14 11:13:40 -04:00
return fmt . Errorf ( "no permission to read from '%s'" , filePath )
2014-05-10 03:58:45 -04:00
}
2014-08-29 07:21:28 -04:00
currentFile . Close ( )
2014-05-10 03:58:45 -04:00
}
return nil
} )
}
2014-07-10 14:41:11 -04:00
2015-07-21 17:20:12 -04:00
// ReadDockerIgnore reads a .dockerignore file and returns the list of file patterns
2014-10-23 17:30:11 -04:00
// 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.
2015-09-06 13:26:40 -04:00
func ReadDockerIgnore ( reader io . ReadCloser ) ( [ ] string , error ) {
if reader == nil {
2014-10-23 17:30:11 -04:00
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 )
}
2015-09-06 13:26:40 -04:00
if err := scanner . Err ( ) ; err != nil {
return nil , fmt . Errorf ( "Error reading .dockerignore: %v" , err )
2014-10-23 17:30:11 -04:00
}
return excludes , nil
}
2015-01-18 19:27:14 -05:00
2015-02-26 21:23:50 -05:00
// 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 , ":" )
}
2015-07-21 16:30:32 -04:00
// GetErrorMessage returns the human readable message associated with
// the passed-in error. In some cases the default Error() func returns
// something that is less than useful so based on its types this func
// will go and get a better piece of text.
func GetErrorMessage ( err error ) string {
switch err . ( type ) {
case errcode . Error :
e , _ := err . ( errcode . Error )
return e . Message
case errcode . ErrorCode :
ec , _ := err . ( errcode . ErrorCode )
return ec . Message ( )
default :
return err . Error ( )
}
}