2013-05-14 22:37:35 +00:00
package utils
import (
2014-10-23 21:30:11 +00:00
"bufio"
2013-05-14 22:37:35 +00:00
"bytes"
2014-03-08 01:36:47 +00:00
"crypto/rand"
2013-10-18 05:40:41 +00:00
"crypto/sha1"
2013-05-14 22:37:35 +00:00
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
2014-09-15 19:43:21 +00:00
"regexp"
2013-11-07 20:19:24 +00:00
"runtime"
2013-05-16 00:40:47 +00:00
"strconv"
2013-05-14 22:37:35 +00:00
"strings"
"sync"
2014-05-05 22:51:32 +00:00
2014-10-24 22:11:48 +00:00
log "github.com/Sirupsen/logrus"
2014-07-24 22:19:50 +00:00
"github.com/docker/docker/dockerversion"
2014-11-01 16:23:08 +00:00
"github.com/docker/docker/pkg/archive"
2014-09-30 06:21:41 +00:00
"github.com/docker/docker/pkg/fileutils"
2014-08-12 16:10:43 +00:00
"github.com/docker/docker/pkg/ioutils"
2013-05-14 22:37:35 +00:00
)
2014-03-13 16:03:09 +00:00
type KeyValuePair struct {
Key string
Value string
}
2014-11-27 21:55:03 +00:00
var (
validHex = regexp . MustCompile ( ` ^([a-f0-9] { 64})$ ` )
)
2013-05-14 22:37:35 +00:00
// Request a given URL and return an io.Reader
2014-01-08 22:20:30 +00:00
func Download ( url string ) ( resp * http . Response , err error ) {
2013-05-14 22:37:35 +00:00
if resp , err = http . Get ( url ) ; err != nil {
return nil , err
}
if resp . StatusCode >= 400 {
2014-01-08 22:20:30 +00:00
return nil , fmt . Errorf ( "Got HTTP status code >= 400: %s" , resp . Status )
2013-05-14 22:37:35 +00:00
}
return resp , nil
}
func Trunc ( s string , maxlen int ) string {
if len ( s ) <= maxlen {
return s
}
return s [ : maxlen ]
}
2013-12-05 09:10:41 +00:00
// Figure out the absolute path of our own binary (if it's still around).
2013-05-14 22:37:35 +00:00
func SelfPath ( ) string {
path , err := exec . LookPath ( os . Args [ 0 ] )
if err != nil {
2013-12-05 09:10:41 +00:00
if os . IsNotExist ( err ) {
return ""
}
if execErr , ok := err . ( * exec . Error ) ; ok && os . IsNotExist ( execErr . Err ) {
return ""
}
2013-05-14 22:37:35 +00:00
panic ( err )
}
path , err = filepath . Abs ( path )
if err != nil {
2013-12-05 09:10:41 +00:00
if os . IsNotExist ( err ) {
return ""
}
2013-05-14 22:37:35 +00:00
panic ( err )
}
return path
}
2013-10-18 05:40:41 +00: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 09:10:41 +00:00
if target == "" {
return false
}
2015-01-17 04:46:01 +00:00
if dockerversion . IAMSTATIC == "true" {
2013-12-05 09:10:41 +00:00
if selfPath == "" {
return false
}
2013-10-18 05:40:41 +00: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-12 00:26:54 +00:00
return dockerversion . INITSHA1 != "" && dockerInitSha1 ( target ) == dockerversion . INITSHA1
2013-10-18 05:40:41 +00:00
}
// Figure out the path of our dockerinit (which may be SelfPath())
2013-11-25 22:42:22 +00:00
func DockerInitPath ( localCopy string ) string {
2013-10-18 05:40:41 +00: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 22:42:22 +00:00
localCopy ,
2014-02-12 00:26:54 +00:00
dockerversion . INITPATH ,
2013-10-18 05:40:41 +00:00
filepath . Join ( filepath . Dir ( selfPath ) , "dockerinit" ) ,
2013-11-27 23:49:40 +00: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."
// http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec
2013-10-18 05:40:41 +00:00
"/usr/libexec/docker/dockerinit" ,
"/usr/local/libexec/docker/dockerinit" ,
2013-11-27 23:49:40 +00: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."
// http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA
"/usr/lib/docker/dockerinit" ,
"/usr/local/lib/docker/dockerinit" ,
2013-10-18 05:40:41 +00:00
}
for _ , dockerInit := range possibleInits {
2013-12-05 09:10:41 +00:00
if dockerInit == "" {
continue
}
2013-10-18 05:40:41 +00: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-05-14 22:37:35 +00:00
func GetTotalUsedFds ( ) int {
if fds , err := ioutil . ReadDir ( fmt . Sprintf ( "/proc/%d/fd" , os . Getpid ( ) ) ) ; err != nil {
2014-07-24 20:37:44 +00:00
log . Errorf ( "Error opening /proc/%d/fd: %s" , os . Getpid ( ) , err )
2013-05-14 22:37:35 +00:00
} else {
return len ( fds )
}
return - 1
}
2013-06-04 18:00:22 +00:00
// TruncateID returns a shorthand version of a string identifier for convenience.
2013-05-14 22:37:35 +00:00
// A collision with other shorthands is very unlikely, but possible.
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length Id.
2013-06-04 18:00:22 +00:00
func TruncateID ( id string ) string {
2013-05-14 22:37:35 +00:00
shortLen := 12
if len ( id ) < shortLen {
shortLen = len ( id )
}
return id [ : shortLen ]
}
2014-03-08 01:36:47 +00:00
// GenerateRandomID returns an unique id
func GenerateRandomID ( ) string {
for {
id := make ( [ ] byte , 32 )
if _ , err := io . ReadFull ( rand . Reader , id ) ; err != nil {
panic ( err ) // This shouldn't happen
}
value := hex . EncodeToString ( id )
// if we try to parse the truncated for as an int and we don't have
// an error then the value is all numberic and causes issues when
// used as a hostname. ref #3869
2014-05-27 21:03:12 +00:00
if _ , err := strconv . ParseInt ( TruncateID ( value ) , 10 , 64 ) ; err == nil {
2014-03-08 01:36:47 +00:00
continue
}
return value
}
}
func ValidateID ( id string ) error {
2014-11-27 21:55:03 +00:00
if ok := validHex . MatchString ( id ) ; ! ok {
err := fmt . Errorf ( "image ID '%s' is invalid" , id )
return err
2014-03-08 01:36:47 +00:00
}
return nil
}
2013-05-14 22:37:35 +00:00
// Code c/c from io.Copy() modified to handle escape sequence
func CopyEscapable ( dst io . Writer , src io . ReadCloser ) ( written int64 , err error ) {
buf := make ( [ ] byte , 32 * 1024 )
for {
nr , er := src . Read ( buf )
if nr > 0 {
// ---- Docker addition
// char 16 is C-p
if nr == 1 && buf [ 0 ] == 16 {
nr , er = src . Read ( buf )
// char 17 is C-q
if nr == 1 && buf [ 0 ] == 17 {
if err := src . Close ( ) ; err != nil {
return 0 , err
}
2013-11-29 00:57:51 +00:00
return 0 , nil
2013-05-14 22:37:35 +00:00
}
}
// ---- End of docker
nw , ew := dst . Write ( buf [ 0 : nr ] )
if nw > 0 {
written += int64 ( nw )
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io . ErrShortWrite
break
}
}
if er == io . EOF {
break
}
if er != nil {
err = er
break
}
}
return written , err
}
func HashData ( src io . Reader ) ( string , error ) {
h := sha256 . New ( )
if _ , err := io . Copy ( h , src ) ; err != nil {
return "" , err
}
return "sha256:" + hex . EncodeToString ( h . Sum ( nil ) ) , nil
}
2013-05-20 17:22:50 +00:00
type WriteFlusher struct {
2013-07-24 15:41:34 +00:00
sync . Mutex
2013-05-20 17:58:35 +00:00
w io . Writer
flusher http . Flusher
2013-05-20 17:22:50 +00:00
}
2013-05-18 14:03:53 +00:00
2013-05-20 17:22:50 +00:00
func ( wf * WriteFlusher ) Write ( b [ ] byte ) ( n int , err error ) {
2013-07-24 15:41:34 +00:00
wf . Lock ( )
defer wf . Unlock ( )
2013-05-20 17:58:35 +00:00
n , err = wf . w . Write ( b )
wf . flusher . Flush ( )
2013-05-18 14:03:53 +00:00
return n , err
2013-05-20 17:22:50 +00:00
}
2013-05-20 17:58:35 +00:00
2013-11-01 17:11:21 +00:00
// Flush the stream immediately.
func ( wf * WriteFlusher ) Flush ( ) {
wf . Lock ( )
defer wf . Unlock ( )
wf . flusher . Flush ( )
}
2013-05-20 17:58:35 +00:00
func NewWriteFlusher ( w io . Writer ) * WriteFlusher {
var flusher http . Flusher
if f , ok := w . ( http . Flusher ) ; ok {
flusher = f
} else {
2014-08-12 16:10:43 +00:00
flusher = & ioutils . NopFlusher { }
2013-05-20 17:58:35 +00:00
}
return & WriteFlusher { w : w , flusher : flusher }
}
2013-05-24 14:43:52 +00:00
2013-07-30 22:48:20 +00:00
func NewHTTPRequestError ( msg string , res * http . Response ) error {
return & JSONError {
Message : msg ,
Code : res . StatusCode ,
}
2013-05-25 15:51:26 +00:00
}
2013-09-09 21:26:35 +00:00
// An StatusError reports an unsuccessful exit by a command.
type StatusError struct {
2013-10-12 12:14:52 +00:00
Status string
StatusCode int
2013-09-09 21:26:35 +00:00
}
func ( e * StatusError ) Error ( ) string {
2013-10-12 12:14:52 +00:00
return fmt . Sprintf ( "Status: %s, Code: %d" , e . Status , e . StatusCode )
2013-09-09 21:26:35 +00:00
}
2013-10-16 20:40:52 +00:00
2013-09-12 13:17:39 +00:00
func quote ( word string , buf * bytes . Buffer ) {
// Bail out early for "simple" strings
if word != "" && ! strings . ContainsAny ( word , "\\'\"`${[|&;<>()~*?! \t\n" ) {
buf . WriteString ( word )
return
}
buf . WriteString ( "'" )
for i := 0 ; i < len ( word ) ; i ++ {
b := word [ i ]
if b == '\'' {
// Replace literal ' with a close ', a \', and a open '
buf . WriteString ( "'\\''" )
} else {
buf . WriteByte ( b )
}
}
buf . WriteString ( "'" )
}
// Take a list of strings and escape them so they will be handled right
// when passed as arguments to an program via a shell
func ShellQuoteArguments ( args [ ] string ) string {
var buf bytes . Buffer
for i , arg := range args {
if i != 0 {
buf . WriteByte ( ' ' )
}
quote ( arg , & buf )
}
return buf . String ( )
}
2013-11-14 06:08:08 +00: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 == "" {
globalTestID = RandomString ( ) [ : 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 != "" {
2014-11-01 16:23:08 +00:00
if err = archive . CopyWithTar ( templateDir , dir ) ; err != nil {
2013-11-14 06:08:08 +00: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 22:42:22 +00:00
func CopyFile ( src , dst string ) ( int64 , error ) {
if src == dst {
return 0 , nil
}
sf , err := os . Open ( src )
if err != nil {
return 0 , err
}
defer sf . Close ( )
if err := os . Remove ( dst ) ; err != nil && ! os . IsNotExist ( err ) {
return 0 , err
}
df , err := os . Create ( dst )
if err != nil {
return 0 , err
}
defer df . Close ( )
return io . Copy ( df , sf )
}
2014-01-16 19:02:51 +00:00
2014-03-01 07:29:00 +00:00
// 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
}
2015-01-16 20:57:08 +00:00
2014-03-01 07:29:00 +00:00
for _ , value := range overrides {
2015-01-16 20:57:08 +00: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 07:29:00 +00:00
parts := strings . SplitN ( value , "=" , 2 )
if i , exists := cache [ parts [ 0 ] ] ; exists {
defaults [ i ] = value
} else {
defaults = append ( defaults , value )
}
}
2015-01-16 20:57:08 +00: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 07:29:00 +00:00
return defaults
}
2014-02-24 21:10:06 +00:00
2015-01-16 20:57:08 +00:00
func DoesEnvExist ( name string ) bool {
for _ , entry := range os . Environ ( ) {
parts := strings . SplitN ( entry , "=" , 2 )
if parts [ 0 ] == name {
return true
}
}
return false
}
2014-02-24 21:10:06 +00:00
// ReadSymlinkedDirectory returns the target directory of a symlink.
// The target of the symbolic link may not be a file.
func ReadSymlinkedDirectory ( path string ) ( string , error ) {
var realPath string
var err error
if realPath , err = filepath . Abs ( path ) ; err != nil {
return "" , fmt . Errorf ( "unable to get absolute path for %s: %s" , path , err )
}
if realPath , err = filepath . EvalSymlinks ( realPath ) ; err != nil {
return "" , fmt . Errorf ( "failed to canonicalise path for %s: %s" , path , err )
}
realPathInfo , err := os . Stat ( realPath )
if err != nil {
return "" , fmt . Errorf ( "failed to stat target '%s' of '%s': %s" , realPath , path , err )
}
if ! realPathInfo . Mode ( ) . IsDir ( ) {
return "" , fmt . Errorf ( "canonical path points to a file '%s'" , realPath )
}
return realPath , nil
}
2014-03-13 16:03:09 +00:00
2014-05-10 07:58:45 +00: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 12:23:07 +00:00
func ValidateContextDirectory ( srcPath string , excludes [ ] string ) error {
2014-09-14 15:13:40 +00:00
return filepath . Walk ( filepath . Join ( srcPath , "." ) , func ( filePath string , f os . FileInfo , err error ) error {
2014-05-10 07:58:45 +00:00
// skip this directory/file if it's not in the path, it won't get added to the context
2014-09-14 15:13:40 +00:00
if relFilePath , err := filepath . Rel ( srcPath , filePath ) ; err != nil {
return err
2014-09-30 06:21:41 +00:00
} else if skip , err := fileutils . Matches ( relFilePath , excludes ) ; err != nil {
2014-09-14 15:13:40 +00:00
return err
} else if skip {
2014-07-07 12:23:07 +00:00
if f . IsDir ( ) {
return filepath . SkipDir
}
return nil
}
2014-09-14 15:13:40 +00: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 07:58:45 +00:00
return err
}
2014-09-14 15:13:40 +00:00
2014-05-10 07:58:45 +00:00
// skip checking if symlinks point to non-existing files, such symlinks can be useful
2014-08-22 14:37:37 +00:00
// also skip named pipes, because they hanging on open
2014-09-14 15:13:40 +00:00
if f . Mode ( ) & ( os . ModeSymlink | os . ModeNamedPipe ) != 0 {
2014-08-22 14:37:37 +00:00
return nil
2014-05-10 07:58:45 +00:00
}
if ! f . IsDir ( ) {
currentFile , err := os . Open ( filePath )
if err != nil && os . IsPermission ( err ) {
2014-09-14 15:13:40 +00:00
return fmt . Errorf ( "no permission to read from '%s'" , filePath )
2014-05-10 07:58:45 +00:00
}
2014-08-29 11:21:28 +00:00
currentFile . Close ( )
2014-05-10 07:58:45 +00:00
}
return nil
} )
}
2014-07-10 18:41:11 +00:00
2014-07-10 22:31:01 +00:00
func StringsContainsNoCase ( slice [ ] string , s string ) bool {
2014-07-10 18:41:11 +00:00
for _ , ss := range slice {
2014-07-10 22:31:01 +00:00
if strings . ToLower ( s ) == strings . ToLower ( ss ) {
2014-07-10 18:41:11 +00:00
return true
}
}
return false
}
2014-10-23 21:30:11 +00:00
// 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
}