2013-05-14 18:37:35 -04:00
package utils
import (
"bytes"
2014-03-07 20:36:47 -05:00
"crypto/rand"
2013-10-18 01:40:41 -04:00
"crypto/sha1"
2013-05-14 18:37:35 -04:00
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
2013-11-07 15:19:24 -05:00
"runtime"
2013-05-15 20:40:47 -04:00
"strconv"
2013-05-14 18:37:35 -04:00
"strings"
"sync"
2014-05-13 13:34:30 -04:00
"syscall"
2014-05-05 18:51:32 -04:00
2014-07-24 18:19:50 -04:00
"github.com/docker/docker/dockerversion"
2014-07-24 16:37:44 -04:00
"github.com/docker/docker/pkg/log"
2013-05-14 18:37:35 -04:00
)
2014-03-13 12:03:09 -04:00
type KeyValuePair struct {
Key string
Value string
}
2013-05-14 18:37:35 -04:00
// Go is a basic promise implementation: it wraps calls a function in a goroutine,
// and returns a channel which will later return the function's return value.
func Go ( f func ( ) error ) chan error {
2014-02-28 19:35:43 -05:00
ch := make ( chan error , 1 )
2013-05-14 18:37:35 -04:00
go func ( ) {
ch <- f ( )
} ( )
return ch
}
// Request a given URL and return an io.Reader
2014-01-08 17:20:30 -05:00
func Download ( url string ) ( resp * http . Response , err error ) {
2013-05-14 18:37:35 -04:00
if resp , err = http . Get ( url ) ; err != nil {
return nil , err
}
if resp . StatusCode >= 400 {
2014-01-08 17:20:30 -05:00
return nil , fmt . Errorf ( "Got HTTP status code >= 400: %s" , resp . Status )
2013-05-14 18:37:35 -04:00
}
return resp , nil
}
func Trunc ( s string , maxlen int ) string {
if len ( s ) <= maxlen {
return s
}
return s [ : maxlen ]
}
2013-12-05 04:10:41 -05:00
// Figure 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
}
2014-02-11 19:26:54 -05:00
if dockerversion . IAMSTATIC {
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
}
// Figure 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."
// http://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."
// http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA
"/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-07-02 17:55:20 -04:00
type NopWriter struct { }
2013-05-14 18:37:35 -04:00
2013-07-02 17:55:20 -04:00
func ( * NopWriter ) Write ( buf [ ] byte ) ( int , error ) {
2013-05-14 18:37:35 -04:00
return len ( buf ) , nil
}
type nopWriteCloser struct {
io . Writer
}
func ( w * nopWriteCloser ) Close ( ) error { return nil }
func NopWriteCloser ( w io . Writer ) io . WriteCloser {
return & nopWriteCloser { w }
}
type bufReader struct {
2013-07-02 18:46:32 -04:00
sync . Mutex
2013-05-14 18:37:35 -04:00
buf * bytes . Buffer
reader io . Reader
err error
wait sync . Cond
}
func NewBufReader ( r io . Reader ) * bufReader {
reader := & bufReader {
buf : & bytes . Buffer { } ,
reader : r ,
}
2013-07-02 18:46:32 -04:00
reader . wait . L = & reader . Mutex
2013-05-14 18:37:35 -04:00
go reader . drain ( )
return reader
}
func ( r * bufReader ) drain ( ) {
buf := make ( [ ] byte , 1024 )
for {
n , err := r . reader . Read ( buf )
2013-07-02 18:46:32 -04:00
r . Lock ( )
2013-05-14 18:37:35 -04:00
if err != nil {
r . err = err
} else {
r . buf . Write ( buf [ 0 : n ] )
}
r . wait . Signal ( )
2013-07-02 18:46:32 -04:00
r . Unlock ( )
2013-05-14 18:37:35 -04:00
if err != nil {
break
}
}
}
func ( r * bufReader ) Read ( p [ ] byte ) ( n int , err error ) {
2013-07-02 18:46:32 -04:00
r . Lock ( )
defer r . Unlock ( )
2013-05-14 18:37:35 -04:00
for {
n , err = r . buf . Read ( p )
if n > 0 {
return n , err
}
if r . err != nil {
return 0 , r . err
}
r . wait . Wait ( )
}
}
func ( r * bufReader ) Close ( ) error {
closer , ok := r . reader . ( io . ReadCloser )
if ! ok {
return nil
}
return closer . Close ( )
}
func GetTotalUsedFds ( ) int {
if fds , err := ioutil . ReadDir ( fmt . Sprintf ( "/proc/%d/fd" , os . Getpid ( ) ) ) ; err != nil {
2014-07-24 16:37:44 -04:00
log . Errorf ( "Error opening /proc/%d/fd: %s" , os . Getpid ( ) , err )
2013-05-14 18:37:35 -04:00
} else {
return len ( fds )
}
return - 1
}
2013-06-04 14:00:22 -04:00
// TruncateID returns a shorthand version of a string identifier for convenience.
2013-05-14 18:37:35 -04: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 14:00:22 -04:00
func TruncateID ( id string ) string {
2013-05-14 18:37:35 -04:00
shortLen := 12
if len ( id ) < shortLen {
shortLen = len ( id )
}
return id [ : shortLen ]
}
2014-03-07 20:36:47 -05: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 17:03:12 -04:00
if _ , err := strconv . ParseInt ( TruncateID ( value ) , 10 , 64 ) ; err == nil {
2014-03-07 20:36:47 -05:00
continue
}
return value
}
}
func ValidateID ( id string ) error {
if id == "" {
return fmt . Errorf ( "Id can't be empty" )
}
if strings . Contains ( id , ":" ) {
return fmt . Errorf ( "Invalid character in id: ':'" )
}
return nil
}
2013-05-14 18:37:35 -04: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-28 19:57:51 -05:00
return 0 , nil
2013-05-14 18:37:35 -04: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-06-14 19:43:39 -04:00
// FIXME: this is deprecated by CopyWithTar in archive.go
2013-05-28 16:38:26 -04:00
func CopyDirectory ( source , dest string ) error {
2013-05-28 18:22:01 -04:00
if output , err := exec . Command ( "cp" , "-ra" , source , dest ) . CombinedOutput ( ) ; err != nil {
return fmt . Errorf ( "Error copy: %s (%s)" , err , output )
2013-05-28 16:38:26 -04:00
}
return nil
}
2013-05-20 13:58:35 -04:00
type NopFlusher struct { }
func ( f * NopFlusher ) Flush ( ) { }
2013-05-20 13:22:50 -04:00
type WriteFlusher struct {
2013-07-24 11:41:34 -04:00
sync . Mutex
2013-05-20 13:58:35 -04:00
w io . Writer
flusher http . Flusher
2013-05-20 13:22:50 -04:00
}
2013-05-18 10:03:53 -04:00
2013-05-20 13:22:50 -04:00
func ( wf * WriteFlusher ) Write ( b [ ] byte ) ( n int , err error ) {
2013-07-24 11:41:34 -04:00
wf . Lock ( )
defer wf . Unlock ( )
2013-05-20 13:58:35 -04:00
n , err = wf . w . Write ( b )
wf . flusher . Flush ( )
2013-05-18 10:03:53 -04:00
return n , err
2013-05-20 13:22:50 -04:00
}
2013-05-20 13:58:35 -04:00
2013-11-01 13:11:21 -04:00
// Flush the stream immediately.
func ( wf * WriteFlusher ) Flush ( ) {
wf . Lock ( )
defer wf . Unlock ( )
wf . flusher . Flush ( )
}
2013-05-20 13:58:35 -04:00
func NewWriteFlusher ( w io . Writer ) * WriteFlusher {
var flusher http . Flusher
if f , ok := w . ( http . Flusher ) ; ok {
flusher = f
} else {
flusher = & NopFlusher { }
}
return & WriteFlusher { w : w , flusher : flusher }
}
2013-05-24 10:43:52 -04:00
2013-07-30 18:48:20 -04:00
func NewHTTPRequestError ( msg string , res * http . Response ) error {
return & JSONError {
Message : msg ,
Code : res . StatusCode ,
}
2013-05-25 11:51:26 -04:00
}
2013-06-06 18:50:09 -04:00
func IsURL ( str string ) bool {
return strings . HasPrefix ( str , "http://" ) || strings . HasPrefix ( str , "https://" )
}
2013-06-06 19:09:46 -04:00
func IsGIT ( str string ) bool {
2014-02-26 18:20:58 -05:00
return strings . HasPrefix ( str , "git://" ) || strings . HasPrefix ( str , "github.com/" ) || strings . HasPrefix ( str , "git@github.com:" ) || ( strings . HasSuffix ( str , ".git" ) && IsURL ( str ) )
2013-06-06 19:09:46 -04:00
}
2013-06-14 20:08:39 -04:00
2013-08-02 18:23:36 -04:00
// CheckLocalDns looks into the /etc/resolv.conf,
// it returns true if there is a local nameserver or if there is no nameserver.
func CheckLocalDns ( resolvConf [ ] byte ) bool {
2014-02-07 11:48:14 -05:00
for _ , line := range GetLines ( resolvConf , [ ] byte ( "#" ) ) {
if ! bytes . Contains ( line , [ ] byte ( "nameserver" ) ) {
continue
}
for _ , ip := range [ ] [ ] byte {
[ ] byte ( "127.0.0.1" ) ,
[ ] byte ( "127.0.1.1" ) ,
} {
if bytes . Contains ( line , ip ) {
return true
}
2013-06-06 14:01:09 -04:00
}
2014-02-07 11:48:14 -05:00
return false
2013-06-06 14:01:09 -04:00
}
2014-02-07 11:48:14 -05:00
return true
2013-06-06 14:01:09 -04:00
}
2013-06-18 14:59:56 -04:00
2014-02-07 11:48:14 -05:00
// GetLines parses input into lines and strips away comments.
func GetLines ( input [ ] byte , commentMarker [ ] byte ) [ ] [ ] byte {
2013-08-21 09:48:39 -04:00
lines := bytes . Split ( input , [ ] byte ( "\n" ) )
2014-02-07 11:48:14 -05:00
var output [ ] [ ] byte
2013-08-21 09:23:12 -04:00
for _ , currentLine := range lines {
2013-08-21 09:48:39 -04:00
var commentIndex = bytes . Index ( currentLine , commentMarker )
2013-09-06 19:00:21 -04:00
if commentIndex == - 1 {
2014-02-07 11:48:14 -05:00
output = append ( output , currentLine )
2013-08-21 09:23:12 -04:00
} else {
2014-02-07 11:48:14 -05:00
output = append ( output , currentLine [ : commentIndex ] )
2013-08-21 09:23:12 -04:00
}
}
2013-08-21 09:48:39 -04:00
return output
2013-08-21 09:23:12 -04:00
}
2013-09-09 17:26:35 -04:00
// An StatusError reports an unsuccessful exit by a command.
type StatusError struct {
2013-10-12 08:14:52 -04:00
Status string
StatusCode int
2013-09-09 17:26:35 -04:00
}
func ( e * StatusError ) Error ( ) string {
2013-10-12 08:14:52 -04:00
return fmt . Sprintf ( "Status: %s, Code: %d" , e . Status , e . StatusCode )
2013-09-09 17:26:35 -04:00
}
2013-10-16 16:40:52 -04:00
2013-09-12 09:17:39 -04: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 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 == "" {
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 != "" {
if err = CopyDirectory ( 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
}
2013-11-25 17:42:22 -05: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 14:02:51 -05:00
type readCloserWrapper struct {
io . Reader
closer func ( ) error
}
func ( r * readCloserWrapper ) Close ( ) error {
return r . closer ( )
}
func NewReadCloserWrapper ( r io . Reader , closer func ( ) error ) io . ReadCloser {
return & readCloserWrapper {
Reader : r ,
closer : closer ,
}
}
2014-03-01 02:29:00 -05: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
}
for _ , value := range overrides {
parts := strings . SplitN ( value , "=" , 2 )
if i , exists := cache [ parts [ 0 ] ] ; exists {
defaults [ i ] = value
} else {
defaults = append ( defaults , value )
}
}
return defaults
}
2014-02-24 16:10:06 -05: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 12:03:09 -04:00
2014-05-13 13:34:30 -04:00
// TreeSize walks a directory tree and returns its total size in bytes.
func TreeSize ( dir string ) ( size int64 , err error ) {
data := make ( map [ uint64 ] struct { } )
err = filepath . Walk ( dir , func ( d string , fileInfo os . FileInfo , e error ) error {
// Ignore directory sizes
if fileInfo == nil {
return nil
}
s := fileInfo . Size ( )
if fileInfo . IsDir ( ) || s == 0 {
return nil
}
// Check inode to handle hard links correctly
inode := fileInfo . Sys ( ) . ( * syscall . Stat_t ) . Ino
// inode is not a uint64 on all platforms. Cast it to avoid issues.
if _ , exists := data [ uint64 ( inode ) ] ; exists {
return nil
}
// inode is not a uint64 on all platforms. Cast it to avoid issues.
data [ uint64 ( inode ) ] = struct { } { }
size += s
return nil
} )
return
}
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 {
2014-05-10 03:58:45 -04:00
var finalError error
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
2014-07-07 08:23:07 -04:00
relFilePath , err := filepath . Rel ( srcPath , filePath )
2014-05-10 03:58:45 -04:00
if err != nil && os . IsPermission ( err ) {
return nil
}
2014-07-07 08:23:07 -04:00
skip , err := Matches ( relFilePath , excludes )
if err != nil {
finalError = err
}
if skip {
if f . IsDir ( ) {
return filepath . SkipDir
}
return nil
}
2014-05-10 03:58:45 -04:00
if _ , err := os . Stat ( filePath ) ; err != nil && os . IsPermission ( err ) {
finalError = fmt . Errorf ( "can't stat '%s'" , filePath )
return err
}
// 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-05-10 03:58:45 -04:00
lstat , _ := os . Lstat ( filePath )
2014-08-18 19:28:44 -04:00
if lstat != nil && lstat . 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 ) {
finalError = fmt . Errorf ( "no permission to read from '%s'" , filePath )
return err
} else {
currentFile . Close ( )
}
}
return nil
} )
return finalError
}
2014-07-10 14:41:11 -04:00
2014-07-10 18:31:01 -04:00
func StringsContainsNoCase ( slice [ ] string , s string ) bool {
2014-07-10 14:41:11 -04:00
for _ , ss := range slice {
2014-07-10 18:31:01 -04:00
if strings . ToLower ( s ) == strings . ToLower ( ss ) {
2014-07-10 14:41:11 -04:00
return true
}
}
return false
}
2014-07-07 08:23:07 -04:00
// Matches returns true if relFilePath matches any of the patterns
func Matches ( relFilePath string , patterns [ ] string ) ( bool , error ) {
for _ , exclude := range patterns {
matched , err := filepath . Match ( exclude , relFilePath )
if err != nil {
2014-07-24 16:37:44 -04:00
log . Errorf ( "Error matching: %s (pattern: %s)" , relFilePath , exclude )
2014-07-07 08:23:07 -04:00
return false , err
}
if matched {
if filepath . Clean ( relFilePath ) == "." {
2014-07-24 16:37:44 -04:00
log . Errorf ( "Can't exclude whole path, excluding pattern: %s" , exclude )
2014-07-07 08:23:07 -04:00
continue
}
2014-07-24 16:37:44 -04:00
log . Debugf ( "Skipping excluded path: %s" , relFilePath )
2014-07-07 08:23:07 -04:00
return true , nil
}
}
return false , nil
}