2013-05-14 22:37:35 +00:00
package utils
import (
"bytes"
2013-10-18 05:40:41 +00:00
"crypto/sha1"
2013-05-14 22:37:35 +00:00
"crypto/sha256"
"encoding/hex"
2013-05-25 15:51:26 +00:00
"encoding/json"
2013-05-14 22:37:35 +00:00
"errors"
"fmt"
"index/suffixarray"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
2013-08-18 03:49:33 +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"
"time"
)
2013-10-18 05:40:41 +00:00
var (
IAMSTATIC bool // whether or not Docker itself was compiled statically via ./hack/make.sh binary
INITSHA1 string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
)
2013-10-27 07:13:45 +00:00
// A common interface to access the Fatal method of
// both testing.B and testing.T.
type Fataler interface {
Fatal ( args ... interface { } )
}
2013-10-10 08:49:18 +00:00
// ListOpts type
type ListOpts [ ] string
func ( opts * ListOpts ) String ( ) string {
return fmt . Sprint ( * opts )
}
func ( opts * ListOpts ) Set ( value string ) error {
* opts = append ( * opts , value )
return nil
}
2013-05-14 22:37:35 +00: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 {
ch := make ( chan error )
go func ( ) {
ch <- f ( )
} ( )
return ch
}
// Request a given URL and return an io.Reader
func Download ( url string , stderr io . Writer ) ( * http . Response , error ) {
var resp * http . Response
2013-06-04 13:51:12 +00:00
var 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 {
return nil , errors . New ( "Got HTTP status code >= 400: " + resp . Status )
}
return resp , nil
}
2013-10-08 07:54:47 +00:00
func logf ( level string , format string , a ... interface { } ) {
// Retrieve the stack infos
_ , file , line , ok := runtime . Caller ( 2 )
if ! ok {
file = "<unknown>"
line = - 1
} else {
file = file [ strings . LastIndex ( file , "/" ) + 1 : ]
}
fmt . Fprintf ( os . Stderr , fmt . Sprintf ( "[%s] %s:%d %s\n" , level , file , line , format ) , a ... )
}
2013-05-14 22:37:35 +00:00
// Debug function, if the debug flag is set, then display. Do nothing otherwise
// If Docker is in damon mode, also send the debug info on the socket
func Debugf ( format string , a ... interface { } ) {
if os . Getenv ( "DEBUG" ) != "" {
2013-10-08 07:54:47 +00:00
logf ( "debug" , format , a ... )
2013-05-14 22:37:35 +00:00
}
}
2013-10-08 07:54:47 +00:00
func Errorf ( format string , a ... interface { } ) {
logf ( "error" , format , a ... )
}
2013-05-14 22:37:35 +00:00
// Reader with progress bar
type progressReader struct {
reader io . ReadCloser // Stream to read from
output io . Writer // Where to send progress bar to
readTotal int // Expected stream length (bytes)
readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update
template string // Template to print. Default "%v/%v (%v)"
2013-06-05 21:20:19 +00:00
sf * StreamFormatter
2013-08-06 14:31:51 +00:00
newLine bool
2013-05-14 22:37:35 +00:00
}
func ( r * progressReader ) Read ( p [ ] byte ) ( n int , err error ) {
read , err := io . ReadCloser ( r . reader ) . Read ( p )
r . readProgress += read
2013-06-28 19:32:41 +00:00
updateEvery := 1024 * 512 //512kB
2013-05-14 22:37:35 +00:00
if r . readTotal > 0 {
2013-06-25 14:03:15 +00:00
// Update progress for every 1% read if 1% < 512kB
if increment := int ( 0.01 * float64 ( r . readTotal ) ) ; increment < updateEvery {
2013-05-14 22:37:35 +00:00
updateEvery = increment
}
}
if r . readProgress - r . lastUpdate > updateEvery || err != nil {
if r . readTotal > 0 {
2013-07-12 18:08:45 +00:00
fmt . Fprintf ( r . output , r . template , HumanSize ( int64 ( r . readProgress ) ) , HumanSize ( int64 ( r . readTotal ) ) , fmt . Sprintf ( "%.0f%%" , float64 ( r . readProgress ) / float64 ( r . readTotal ) * 100 ) )
2013-05-14 22:37:35 +00:00
} else {
2013-05-24 14:43:52 +00:00
fmt . Fprintf ( r . output , r . template , r . readProgress , "?" , "n/a" )
2013-05-14 22:37:35 +00:00
}
r . lastUpdate = r . readProgress
}
2013-08-06 14:31:51 +00:00
// Send newline when complete
if r . newLine && err != nil {
r . output . Write ( r . sf . FormatStatus ( "" , "" ) )
}
2013-05-14 22:37:35 +00:00
return read , err
}
func ( r * progressReader ) Close ( ) error {
return io . ReadCloser ( r . reader ) . Close ( )
}
2013-08-06 14:31:51 +00:00
func ProgressReader ( r io . ReadCloser , size int , output io . Writer , tpl [ ] byte , sf * StreamFormatter , newline bool ) * progressReader {
return & progressReader {
reader : r ,
output : NewWriteFlusher ( output ) ,
readTotal : size ,
template : string ( tpl ) ,
sf : sf ,
newLine : newline ,
2013-05-14 22:37:35 +00:00
}
}
// HumanDuration returns a human-readable approximation of a duration
// (eg. "About a minute", "4 hours ago", etc.)
func HumanDuration ( d time . Duration ) string {
if seconds := int ( d . Seconds ( ) ) ; seconds < 1 {
return "Less than a second"
} else if seconds < 60 {
return fmt . Sprintf ( "%d seconds" , seconds )
} else if minutes := int ( d . Minutes ( ) ) ; minutes == 1 {
return "About a minute"
} else if minutes < 60 {
return fmt . Sprintf ( "%d minutes" , minutes )
} else if hours := int ( d . Hours ( ) ) ; hours == 1 {
return "About an hour"
} else if hours < 48 {
return fmt . Sprintf ( "%d hours" , hours )
} else if hours < 24 * 7 * 2 {
return fmt . Sprintf ( "%d days" , hours / 24 )
} else if hours < 24 * 30 * 3 {
return fmt . Sprintf ( "%d weeks" , hours / 24 / 7 )
} else if hours < 24 * 365 * 2 {
return fmt . Sprintf ( "%d months" , hours / 24 / 30 )
}
2013-06-27 04:40:13 +00:00
return fmt . Sprintf ( "%f years" , d . Hours ( ) / 24 / 365 )
2013-05-14 22:37:35 +00:00
}
2013-05-23 09:35:20 +00:00
// HumanSize returns a human-readable approximation of a size
2013-05-23 10:29:09 +00:00
// using SI standard (eg. "44kB", "17MB")
2013-05-22 13:41:29 +00:00
func HumanSize ( size int64 ) string {
i := 0
var sizef float64
sizef = float64 ( size )
units := [ ] string { "B" , "kB" , "MB" , "GB" , "TB" , "PB" , "EB" , "ZB" , "YB" }
2013-05-23 10:29:09 +00:00
for sizef >= 1000.0 {
sizef = sizef / 1000.0
2013-05-22 13:41:29 +00:00
i ++
}
2013-07-12 18:08:45 +00:00
return fmt . Sprintf ( "%.4g %s" , sizef , units [ i ] )
2013-05-22 13:41:29 +00:00
}
2013-10-03 18:23:29 +00:00
// Parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes or gibibytes, and returns the
// number of bytes, or -1 if the string is unparseable.
// Units are case-insensitive, and the 'b' suffix is optional.
func RAMInBytes ( size string ) ( bytes int64 , err error ) {
re , error := regexp . Compile ( "^(\\d+)([kKmMgG])?[bB]?$" )
2013-11-11 20:28:20 +00:00
if error != nil {
return - 1 , error
}
2013-10-03 18:23:29 +00:00
matches := re . FindStringSubmatch ( size )
if len ( matches ) != 3 {
return - 1 , fmt . Errorf ( "Invalid size: '%s'" , size )
}
memLimit , error := strconv . ParseInt ( matches [ 1 ] , 10 , 0 )
2013-11-11 20:28:20 +00:00
if error != nil {
return - 1 , error
}
2013-10-03 18:23:29 +00:00
unit := strings . ToLower ( matches [ 2 ] )
if unit == "k" {
memLimit *= 1024
} else if unit == "m" {
2013-11-11 20:28:20 +00:00
memLimit *= 1024 * 1024
2013-10-03 18:23:29 +00:00
} else if unit == "g" {
2013-11-11 20:28:20 +00:00
memLimit *= 1024 * 1024 * 1024
2013-10-03 18:23:29 +00:00
}
return memLimit , nil
}
2013-05-14 22:37:35 +00:00
func Trunc ( s string , maxlen int ) string {
if len ( s ) <= maxlen {
return s
}
return s [ : maxlen ]
}
// Figure out the absolute path of our own binary
func SelfPath ( ) string {
path , err := exec . LookPath ( os . Args [ 0 ] )
if err != nil {
panic ( err )
}
path , err = filepath . Abs ( path )
if err != nil {
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)
if IAMSTATIC {
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 INITSHA1 != "" && dockerInitSha1 ( target ) == INITSHA1
}
// Figure out the path of our dockerinit (which may be SelfPath())
func DockerInitPath ( ) string {
selfPath := SelfPath ( )
if isValidDockerInitPath ( selfPath , selfPath ) {
// if we're valid, don't bother checking anything else
return selfPath
}
var possibleInits = [ ] string {
filepath . Join ( filepath . Dir ( selfPath ) , "dockerinit" ) ,
// "/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."
"/usr/libexec/docker/dockerinit" ,
"/usr/local/libexec/docker/dockerinit" ,
}
for _ , dockerInit := range possibleInits {
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 21:55:20 +00:00
type NopWriter struct { }
2013-05-14 22:37:35 +00:00
2013-07-02 21:55:20 +00:00
func ( * NopWriter ) Write ( buf [ ] byte ) ( int , error ) {
2013-05-14 22:37:35 +00: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 22:46:32 +00:00
sync . Mutex
2013-05-14 22:37:35 +00: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 22:46:32 +00:00
reader . wait . L = & reader . Mutex
2013-05-14 22:37:35 +00:00
go reader . drain ( )
return reader
}
func ( r * bufReader ) drain ( ) {
buf := make ( [ ] byte , 1024 )
for {
n , err := r . reader . Read ( buf )
2013-07-02 22:46:32 +00:00
r . Lock ( )
2013-05-14 22:37:35 +00:00
if err != nil {
r . err = err
} else {
r . buf . Write ( buf [ 0 : n ] )
}
r . wait . Signal ( )
2013-07-02 22:46:32 +00:00
r . Unlock ( )
2013-05-14 22:37:35 +00:00
if err != nil {
break
}
}
}
func ( r * bufReader ) Read ( p [ ] byte ) ( n int , err error ) {
2013-07-02 22:46:32 +00:00
r . Lock ( )
defer r . Unlock ( )
2013-05-14 22:37:35 +00: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 ( )
}
type WriteBroadcaster struct {
2013-07-02 22:46:32 +00:00
sync . Mutex
2013-07-15 16:17:58 +00:00
buf * bytes . Buffer
writers map [ StreamWriter ] bool
2013-05-14 22:37:35 +00:00
}
2013-07-11 17:18:28 +00:00
type StreamWriter struct {
2013-07-15 16:17:58 +00:00
wc io . WriteCloser
2013-07-11 17:18:28 +00:00
stream string
2013-05-14 22:37:35 +00:00
}
2013-07-11 17:18:28 +00:00
func ( w * WriteBroadcaster ) AddWriter ( writer io . WriteCloser , stream string ) {
2013-07-02 22:46:32 +00:00
w . Lock ( )
2013-07-11 17:18:28 +00:00
sw := StreamWriter { wc : writer , stream : stream }
2013-07-15 16:17:58 +00:00
w . writers [ sw ] = true
2013-07-02 22:46:32 +00:00
w . Unlock ( )
2013-05-14 22:37:35 +00:00
}
2013-07-11 17:18:28 +00:00
type JSONLog struct {
2013-07-15 16:17:58 +00:00
Log string ` json:"log,omitempty" `
Stream string ` json:"stream,omitempty" `
2013-07-11 17:18:28 +00:00
Created time . Time ` json:"time" `
}
2013-05-14 22:37:35 +00:00
func ( w * WriteBroadcaster ) Write ( p [ ] byte ) ( n int , err error ) {
2013-07-02 22:46:32 +00:00
w . Lock ( )
defer w . Unlock ( )
2013-07-15 16:17:58 +00:00
w . buf . Write ( p )
2013-07-11 17:18:28 +00:00
for sw := range w . writers {
lp := p
if sw . stream != "" {
2013-07-15 16:17:58 +00:00
lp = nil
for {
line , err := w . buf . ReadString ( '\n' )
if err != nil {
w . buf . Write ( [ ] byte ( line ) )
break
}
2013-11-22 00:41:41 +00:00
b , err := json . Marshal ( & JSONLog { Log : line , Stream : sw . stream , Created : time . Now ( ) . UTC ( ) } )
2013-07-11 17:18:28 +00:00
if err != nil {
// On error, evict the writer
delete ( w . writers , sw )
continue
}
2013-07-15 16:17:58 +00:00
lp = append ( lp , b ... )
2013-08-27 19:09:26 +00:00
lp = append ( lp , '\n' )
2013-07-11 17:18:28 +00:00
}
}
if n , err := sw . wc . Write ( lp ) ; err != nil || n != len ( lp ) {
2013-05-14 22:37:35 +00:00
// On error, evict the writer
2013-07-11 17:18:28 +00:00
delete ( w . writers , sw )
2013-05-14 22:37:35 +00:00
}
}
return len ( p ) , nil
}
func ( w * WriteBroadcaster ) CloseWriters ( ) error {
2013-07-02 22:46:32 +00:00
w . Lock ( )
defer w . Unlock ( )
2013-07-11 17:18:28 +00:00
for sw := range w . writers {
sw . wc . Close ( )
2013-05-14 22:37:35 +00:00
}
2013-07-15 16:17:58 +00:00
w . writers = make ( map [ StreamWriter ] bool )
2013-05-14 22:37:35 +00:00
return nil
}
func NewWriteBroadcaster ( ) * WriteBroadcaster {
2013-07-15 16:17:58 +00:00
return & WriteBroadcaster { writers : make ( map [ StreamWriter ] bool ) , buf : bytes . NewBuffer ( nil ) }
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 {
2013-10-08 07:54:47 +00:00
Errorf ( "Error opening /proc/%d/fd: %s" , os . Getpid ( ) , err )
2013-05-14 22:37:35 +00:00
} else {
return len ( fds )
}
return - 1
}
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
type TruncIndex struct {
index * suffixarray . Index
ids map [ string ] bool
bytes [ ] byte
}
func NewTruncIndex ( ) * TruncIndex {
return & TruncIndex {
index : suffixarray . New ( [ ] byte { ' ' } ) ,
ids : make ( map [ string ] bool ) ,
bytes : [ ] byte { ' ' } ,
}
}
func ( idx * TruncIndex ) Add ( id string ) error {
if strings . Contains ( id , " " ) {
return fmt . Errorf ( "Illegal character: ' '" )
}
if _ , exists := idx . ids [ id ] ; exists {
return fmt . Errorf ( "Id already exists: %s" , id )
}
idx . ids [ id ] = true
idx . bytes = append ( idx . bytes , [ ] byte ( id + " " ) ... )
idx . index = suffixarray . New ( idx . bytes )
return nil
}
func ( idx * TruncIndex ) Delete ( id string ) error {
if _ , exists := idx . ids [ id ] ; ! exists {
return fmt . Errorf ( "No such id: %s" , id )
}
before , after , err := idx . lookup ( id )
if err != nil {
return err
}
delete ( idx . ids , id )
idx . bytes = append ( idx . bytes [ : before ] , idx . bytes [ after : ] ... )
idx . index = suffixarray . New ( idx . bytes )
return nil
}
func ( idx * TruncIndex ) lookup ( s string ) ( int , int , error ) {
offsets := idx . index . Lookup ( [ ] byte ( " " + s ) , - 1 )
//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
if offsets == nil || len ( offsets ) == 0 || len ( offsets ) > 1 {
return - 1 , - 1 , fmt . Errorf ( "No such id: %s" , s )
}
offsetBefore := offsets [ 0 ] + 1
offsetAfter := offsetBefore + strings . Index ( string ( idx . bytes [ offsetBefore : ] ) , " " )
return offsetBefore , offsetAfter , nil
}
func ( idx * TruncIndex ) Get ( s string ) ( string , error ) {
before , after , err := idx . lookup ( s )
//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
if err != nil {
return "" , err
}
return string ( idx . bytes [ before : after ] ) , err
}
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 ]
}
// 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
}
return 0 , io . EOF
}
}
// ---- 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
}
type KernelVersionInfo struct {
Kernel int
Major int
Minor int
Flavor string
}
func ( k * KernelVersionInfo ) String ( ) string {
flavor := ""
if len ( k . Flavor ) > 0 {
flavor = fmt . Sprintf ( "-%s" , k . Flavor )
}
return fmt . Sprintf ( "%d.%d.%d%s" , k . Kernel , k . Major , k . Minor , flavor )
}
// Compare two KernelVersionInfo struct.
// Returns -1 if a < b, = if a == b, 1 it a > b
func CompareKernelVersion ( a , b * KernelVersionInfo ) int {
if a . Kernel < b . Kernel {
return - 1
} else if a . Kernel > b . Kernel {
return 1
}
if a . Major < b . Major {
return - 1
} else if a . Major > b . Major {
return 1
}
if a . Minor < b . Minor {
return - 1
} else if a . Minor > b . Minor {
return 1
}
return 0
}
func FindCgroupMountpoint ( cgroupType string ) ( string , error ) {
output , err := ioutil . ReadFile ( "/proc/mounts" )
if err != nil {
return "" , err
}
// /proc/mounts has 6 fields per line, one mount per line, e.g.
// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
for _ , line := range strings . Split ( string ( output ) , "\n" ) {
parts := strings . Split ( line , " " )
if len ( parts ) == 6 && parts [ 2 ] == "cgroup" {
for _ , opt := range strings . Split ( parts [ 3 ] , "," ) {
if opt == cgroupType {
return parts [ 1 ] , nil
}
}
}
}
return "" , fmt . Errorf ( "cgroup mountpoint not found for %s" , cgroupType )
}
2013-05-16 00:40:47 +00:00
func GetKernelVersion ( ) ( * KernelVersionInfo , error ) {
var (
2013-08-24 07:24:40 +00:00
err error
2013-05-16 00:40:47 +00:00
)
uts , err := uname ( )
if err != nil {
return nil , err
}
release := make ( [ ] byte , len ( uts . Release ) )
i := 0
for _ , c := range uts . Release {
release [ i ] = byte ( c )
i ++
}
// Remove the \x00 from the release for Atoi to parse correctly
release = release [ : bytes . IndexByte ( release , 0 ) ]
2013-08-24 07:24:40 +00:00
return ParseRelease ( string ( release ) )
}
func ParseRelease ( release string ) ( * KernelVersionInfo , error ) {
var (
flavor string
kernel , major , minor int
err error
)
tmp := strings . SplitN ( release , "-" , 2 )
2013-08-23 21:37:37 +00:00
tmp2 := strings . Split ( tmp [ 0 ] , "." )
2013-05-16 00:40:47 +00:00
if len ( tmp2 ) > 0 {
kernel , err = strconv . Atoi ( tmp2 [ 0 ] )
if err != nil {
return nil , err
}
}
if len ( tmp2 ) > 1 {
major , err = strconv . Atoi ( tmp2 [ 1 ] )
if err != nil {
return nil , err
}
}
if len ( tmp2 ) > 2 {
2013-06-27 14:51:17 +00:00
// Removes "+" because git kernels might set it
minorUnparsed := strings . Trim ( tmp2 [ 2 ] , "+" )
minor , err = strconv . Atoi ( minorUnparsed )
2013-05-16 00:40:47 +00:00
if err != nil {
return nil , err
}
}
if len ( tmp ) == 2 {
flavor = tmp [ 1 ]
} else {
flavor = ""
}
return & KernelVersionInfo {
Kernel : kernel ,
Major : major ,
Minor : minor ,
Flavor : flavor ,
} , nil
}
2013-05-18 14:03:53 +00:00
2013-06-14 23:43:39 +00:00
// FIXME: this is deprecated by CopyWithTar in archive.go
2013-05-28 20:38:26 +00:00
func CopyDirectory ( source , dest string ) error {
2013-05-28 22:22:01 +00: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 20:38:26 +00:00
}
return nil
}
2013-05-20 17:58:35 +00:00
type NopFlusher struct { }
func ( f * NopFlusher ) Flush ( ) { }
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 {
flusher = & NopFlusher { }
}
return & WriteFlusher { w : w , flusher : flusher }
}
2013-05-24 14:43:52 +00:00
2013-07-24 03:01:24 +00:00
type JSONError struct {
Code int ` json:"code,omitempty" `
Message string ` json:"message,omitempty" `
}
2013-06-04 18:00:22 +00:00
type JSONMessage struct {
2013-07-24 03:01:24 +00:00
Status string ` json:"status,omitempty" `
Progress string ` json:"progress,omitempty" `
ErrorMessage string ` json:"error,omitempty" ` //deprecated
ID string ` json:"id,omitempty" `
2013-08-12 11:50:03 +00:00
From string ` json:"from,omitempty" `
2013-07-24 03:01:24 +00:00
Time int64 ` json:"time,omitempty" `
Error * JSONError ` json:"errorDetail,omitempty" `
}
func ( e * JSONError ) Error ( ) string {
return e . Message
2013-05-25 15:51:26 +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-11-13 05:31:45 +00:00
func ( jm * JSONMessage ) Display ( out io . Writer , isTerminal bool ) error {
2013-08-05 16:25:42 +00:00
if jm . Error != nil {
if jm . Error . Code == 401 {
return fmt . Errorf ( "Authentication is required." )
}
return jm . Error
2013-07-24 17:10:59 +00:00
}
2013-11-13 05:31:45 +00:00
endl := ""
if isTerminal {
// <ESC>[2K = erase entire current line
fmt . Fprintf ( out , "%c[2K\r" , 27 )
endl = "\r"
}
2013-07-12 15:09:20 +00:00
if jm . Time != 0 {
fmt . Fprintf ( out , "[%s] " , time . Unix ( jm . Time , 0 ) )
}
2013-07-24 17:10:59 +00:00
if jm . ID != "" {
fmt . Fprintf ( out , "%s: " , jm . ID )
}
2013-08-12 11:50:03 +00:00
if jm . From != "" {
fmt . Fprintf ( out , "(from %s) " , jm . From )
}
2013-07-24 17:10:59 +00:00
if jm . Progress != "" {
2013-11-13 05:31:45 +00:00
fmt . Fprintf ( out , "%s %s%s" , jm . Status , jm . Progress , endl )
2013-07-10 12:55:05 +00:00
} else {
2013-11-13 05:31:45 +00:00
fmt . Fprintf ( out , "%s%s\n" , jm . Status , endl )
2013-07-24 20:16:02 +00:00
}
return nil
}
2013-11-13 05:31:45 +00:00
func DisplayJSONMessagesStream ( in io . Reader , out io . Writer , isTerminal bool ) error {
2013-07-24 20:16:02 +00:00
dec := json . NewDecoder ( in )
ids := make ( map [ string ] int )
diff := 0
for {
2013-08-08 19:15:02 +00:00
jm := JSONMessage { }
2013-07-24 20:16:02 +00:00
if err := dec . Decode ( & jm ) ; err == io . EOF {
break
} else if err != nil {
return err
}
2013-08-08 19:15:02 +00:00
if jm . Progress != "" && jm . ID != "" {
2013-07-24 20:16:02 +00:00
line , ok := ids [ jm . ID ]
if ! ok {
line = len ( ids )
ids [ jm . ID ] = line
fmt . Fprintf ( out , "\n" )
diff = 0
} else {
diff = len ( ids ) - line
}
2013-11-13 05:31:45 +00:00
if isTerminal {
// <ESC>[{diff}A = move cursor up diff rows
fmt . Fprintf ( out , "%c[%dA" , 27 , diff )
}
2013-07-24 20:16:02 +00:00
}
2013-11-13 05:31:45 +00:00
err := jm . Display ( out , isTerminal )
2013-07-24 20:16:02 +00:00
if jm . ID != "" {
2013-11-13 05:31:45 +00:00
if isTerminal {
// <ESC>[{diff}B = move cursor down diff rows
fmt . Fprintf ( out , "%c[%dB" , 27 , diff )
}
2013-07-24 20:16:02 +00:00
}
2013-07-25 14:32:46 +00:00
if err != nil {
return err
}
2013-07-24 20:16:02 +00:00
}
2013-07-10 12:55:05 +00:00
return nil
}
2013-05-25 15:09:46 +00:00
type StreamFormatter struct {
json bool
used bool
}
func NewStreamFormatter ( json bool ) * StreamFormatter {
return & StreamFormatter { json , false }
}
2013-07-24 17:10:59 +00:00
func ( sf * StreamFormatter ) FormatStatus ( id , format string , a ... interface { } ) [ ] byte {
2013-05-25 15:09:46 +00:00
sf . used = true
2013-05-26 23:45:45 +00:00
str := fmt . Sprintf ( format , a ... )
2013-05-25 15:09:46 +00:00
if sf . json {
2013-07-24 17:10:59 +00:00
b , err := json . Marshal ( & JSONMessage { ID : id , Status : str } )
2013-05-25 15:51:26 +00:00
if err != nil {
return sf . FormatError ( err )
}
2013-05-26 23:45:45 +00:00
return b
2013-05-24 14:43:52 +00:00
}
2013-05-26 23:45:45 +00:00
return [ ] byte ( str + "\r\n" )
2013-05-24 14:43:52 +00:00
}
2013-05-26 23:45:45 +00:00
func ( sf * StreamFormatter ) FormatError ( err error ) [ ] byte {
2013-05-25 15:09:46 +00:00
sf . used = true
if sf . json {
2013-07-30 22:48:20 +00:00
jsonError , ok := err . ( * JSONError )
if ! ok {
jsonError = & JSONError { Message : err . Error ( ) }
}
if b , err := json . Marshal ( & JSONMessage { Error : jsonError , ErrorMessage : err . Error ( ) } ) ; err == nil {
2013-05-26 23:45:45 +00:00
return b
2013-05-25 15:51:26 +00:00
}
2013-05-26 23:45:45 +00:00
return [ ] byte ( "{\"error\":\"format error\"}" )
2013-05-24 14:43:52 +00:00
}
2013-05-26 23:45:45 +00:00
return [ ] byte ( "Error: " + err . Error ( ) + "\r\n" )
2013-05-25 14:12:02 +00:00
}
2013-07-24 17:10:59 +00:00
func ( sf * StreamFormatter ) FormatProgress ( id , action , progress string ) [ ] byte {
2013-05-25 15:09:46 +00:00
sf . used = true
if sf . json {
2013-07-30 12:09:07 +00:00
b , err := json . Marshal ( & JSONMessage { Status : action , Progress : progress , ID : id } )
2013-05-25 15:51:26 +00:00
if err != nil {
2013-06-05 21:20:19 +00:00
return nil
}
2013-05-26 23:45:45 +00:00
return b
2013-05-24 14:43:52 +00:00
}
2013-07-24 17:10:59 +00:00
return [ ] byte ( action + " " + progress + "\r" )
2013-05-25 15:09:46 +00:00
}
func ( sf * StreamFormatter ) Used ( ) bool {
return sf . used
2013-05-24 14:43:52 +00:00
}
2013-06-06 18:01:09 +00:00
2013-06-06 22:50:09 +00:00
func IsURL ( str string ) bool {
return strings . HasPrefix ( str , "http://" ) || strings . HasPrefix ( str , "https://" )
}
2013-06-06 23:09:46 +00:00
func IsGIT ( str string ) bool {
return strings . HasPrefix ( str , "git://" ) || strings . HasPrefix ( str , "github.com/" )
}
2013-06-15 00:08:39 +00:00
2013-08-02 22:23:36 +00:00
// GetResolvConf opens and read the content of /etc/resolv.conf.
// It returns it as byte slice.
func GetResolvConf ( ) ( [ ] byte , error ) {
2013-06-06 18:01:09 +00:00
resolv , err := ioutil . ReadFile ( "/etc/resolv.conf" )
if err != nil {
2013-10-08 07:54:47 +00:00
Errorf ( "Error openning resolv.conf: %s" , err )
2013-08-02 22:23:36 +00:00
return nil , err
2013-06-06 18:01:09 +00:00
}
2013-08-02 22:23:36 +00:00
return resolv , nil
}
// 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 {
2013-08-21 13:48:39 +00:00
var parsedResolvConf = StripComments ( resolvConf , [ ] byte ( "#" ) )
2013-08-21 13:23:12 +00:00
if ! bytes . Contains ( parsedResolvConf , [ ] byte ( "nameserver" ) ) {
2013-08-02 22:23:36 +00:00
return true
}
for _ , ip := range [ ] [ ] byte {
[ ] byte ( "127.0.0.1" ) ,
[ ] byte ( "127.0.1.1" ) ,
2013-06-06 18:01:09 +00:00
} {
2013-08-21 13:23:12 +00:00
if bytes . Contains ( parsedResolvConf , ip ) {
2013-06-06 18:01:09 +00:00
return true
}
}
return false
}
2013-06-18 18:59:56 +00:00
2013-08-21 13:48:39 +00:00
// StripComments parses input into lines and strips away comments.
func StripComments ( input [ ] byte , commentMarker [ ] byte ) [ ] byte {
lines := bytes . Split ( input , [ ] byte ( "\n" ) )
var output [ ] byte
2013-08-21 13:23:12 +00:00
for _ , currentLine := range lines {
2013-08-21 13:48:39 +00:00
var commentIndex = bytes . Index ( currentLine , commentMarker )
2013-09-06 23:00:21 +00:00
if commentIndex == - 1 {
2013-08-21 13:48:39 +00:00
output = append ( output , currentLine ... )
2013-08-21 13:23:12 +00:00
} else {
2013-08-21 13:48:39 +00:00
output = append ( output , currentLine [ : commentIndex ] ... )
2013-08-21 13:23:12 +00:00
}
2013-08-21 13:48:39 +00:00
output = append ( output , [ ] byte ( "\n" ) ... )
2013-08-21 13:23:12 +00:00
}
2013-08-21 13:48:39 +00:00
return output
2013-08-21 13:23:12 +00:00
}
2013-11-07 20:19:24 +00:00
// GetNameserversAsCIDR returns nameservers (if any) listed in
2013-08-18 03:49:33 +00:00
// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
// This function's output is intended for net.ParseCIDR
func GetNameserversAsCIDR ( resolvConf [ ] byte ) [ ] string {
var parsedResolvConf = StripComments ( resolvConf , [ ] byte ( "#" ) )
nameservers := [ ] string { }
2013-11-12 02:53:34 +00:00
re := regexp . MustCompile ( ` ^\s*nameserver\s*(([0-9]+\.) { 3}([0-9]+))\s*$ ` )
2013-08-18 03:49:33 +00:00
for _ , line := range bytes . Split ( parsedResolvConf , [ ] byte ( "\n" ) ) {
var ns = re . FindSubmatch ( line )
if len ( ns ) > 0 {
nameservers = append ( nameservers , string ( ns [ 1 ] ) + "/32" )
}
}
return nameservers
}
2013-10-24 05:23:02 +00:00
func ParseHost ( host string , port int , addr string ) ( string , error ) {
2013-10-23 10:29:35 +00:00
var proto string
switch {
case strings . HasPrefix ( addr , "unix://" ) :
2013-10-24 05:23:02 +00:00
return addr , nil
2013-10-23 10:29:35 +00:00
case strings . HasPrefix ( addr , "tcp://" ) :
proto = "tcp"
2013-06-19 12:31:54 +00:00
addr = strings . TrimPrefix ( addr , "tcp://" )
2013-10-23 10:29:35 +00:00
default :
if strings . Contains ( addr , "://" ) {
2013-10-24 05:23:02 +00:00
return "" , fmt . Errorf ( "Invalid bind address protocol: %s" , addr )
2013-10-23 10:29:35 +00:00
}
proto = "tcp"
2013-06-19 12:31:54 +00:00
}
2013-10-23 10:29:35 +00:00
2013-06-19 12:31:54 +00:00
if strings . Contains ( addr , ":" ) {
hostParts := strings . Split ( addr , ":" )
if len ( hostParts ) != 2 {
2013-10-24 05:23:02 +00:00
return "" , fmt . Errorf ( "Invalid bind address format: %s" , addr )
2013-06-19 12:31:54 +00:00
}
if hostParts [ 0 ] != "" {
host = hostParts [ 0 ]
}
if p , err := strconv . Atoi ( hostParts [ 1 ] ) ; err == nil {
port = p
}
} else {
host = addr
}
2013-10-24 05:23:02 +00:00
return fmt . Sprintf ( "%s://%s:%d" , proto , host , port ) , nil
2013-06-19 12:31:54 +00:00
}
2013-07-09 15:06:10 +00:00
2013-07-03 17:11:00 +00:00
func GetReleaseVersion ( ) string {
2013-07-12 11:45:40 +00:00
resp , err := http . Get ( "http://get.docker.io/latest" )
2013-07-03 17:11:00 +00:00
if err != nil {
return ""
}
defer resp . Body . Close ( )
2013-07-30 16:39:35 +00:00
if resp . ContentLength > 24 || resp . StatusCode != 200 {
return ""
}
2013-07-03 17:11:00 +00:00
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return ""
}
2013-07-12 11:45:40 +00:00
return strings . TrimSpace ( string ( body ) )
}
2013-07-09 15:06:10 +00:00
// Get a repos name and returns the right reposName + tag
// The tag can be confusing because of a port in a repository name.
// Ex: localhost.localdomain:5000/samalba/hipache:latest
func ParseRepositoryTag ( repos string ) ( string , string ) {
n := strings . LastIndex ( repos , ":" )
if n < 0 {
return repos , ""
}
if tag := repos [ n + 1 : ] ; ! strings . Contains ( tag , "/" ) {
return repos [ : n ] , tag
}
return repos , ""
}
2013-07-01 23:48:59 +00:00
2013-09-06 23:00:21 +00:00
type User struct {
Uid string // user id
Gid string // primary group id
Username string
Name string
HomeDir string
}
2013-07-02 00:08:42 +00:00
// UserLookup check if the given username or uid is present in /etc/passwd
// and returns the user struct.
// If the username is not found, an error is returned.
2013-09-06 23:00:21 +00:00
func UserLookup ( uid string ) ( * User , error ) {
2013-07-01 23:48:59 +00:00
file , err := ioutil . ReadFile ( "/etc/passwd" )
if err != nil {
return nil , err
}
for _ , line := range strings . Split ( string ( file ) , "\n" ) {
data := strings . Split ( line , ":" )
if len ( data ) > 5 && ( data [ 0 ] == uid || data [ 2 ] == uid ) {
2013-09-06 23:00:21 +00:00
return & User {
2013-07-01 23:48:59 +00:00
Uid : data [ 2 ] ,
Gid : data [ 3 ] ,
Username : data [ 0 ] ,
Name : data [ 4 ] ,
HomeDir : data [ 5 ] ,
} , nil
}
}
return nil , fmt . Errorf ( "User not found in /etc/passwd" )
}
2013-09-04 00:20:50 +00:00
2013-09-06 23:00:21 +00:00
type DependencyGraph struct {
2013-09-04 00:20:50 +00:00
nodes map [ string ] * DependencyNode
}
2013-09-06 23:00:21 +00:00
type DependencyNode struct {
id string
deps map [ * DependencyNode ] bool
2013-09-04 00:20:50 +00:00
}
func NewDependencyGraph ( ) DependencyGraph {
return DependencyGraph {
nodes : map [ string ] * DependencyNode { } ,
}
}
func ( graph * DependencyGraph ) addNode ( node * DependencyNode ) string {
if graph . nodes [ node . id ] == nil {
graph . nodes [ node . id ] = node
}
return node . id
}
func ( graph * DependencyGraph ) NewNode ( id string ) string {
if graph . nodes [ id ] != nil {
return id
}
nd := & DependencyNode {
2013-09-06 23:00:21 +00:00
id : id ,
2013-09-04 00:20:50 +00:00
deps : map [ * DependencyNode ] bool { } ,
}
graph . addNode ( nd )
return id
}
func ( graph * DependencyGraph ) AddDependency ( node , to string ) error {
if graph . nodes [ node ] == nil {
return fmt . Errorf ( "Node %s does not belong to this graph" , node )
}
if graph . nodes [ to ] == nil {
return fmt . Errorf ( "Node %s does not belong to this graph" , to )
}
if node == to {
return fmt . Errorf ( "Dependency loops are forbidden!" )
}
graph . nodes [ node ] . addDependency ( graph . nodes [ to ] )
return nil
}
func ( node * DependencyNode ) addDependency ( to * DependencyNode ) bool {
node . deps [ to ] = true
return node . deps [ to ]
}
func ( node * DependencyNode ) Degree ( ) int {
return len ( node . deps )
}
// The magic happens here ::
func ( graph * DependencyGraph ) GenerateTraversalMap ( ) ( [ ] [ ] string , error ) {
Debugf ( "Generating traversal map. Nodes: %d" , len ( graph . nodes ) )
result := [ ] [ ] string { }
processed := map [ * DependencyNode ] bool { }
// As long as we haven't processed all nodes...
for len ( processed ) < len ( graph . nodes ) {
// Use a temporary buffer for processed nodes, otherwise
// nodes that depend on each other could end up in the same round.
2013-11-18 23:35:56 +00:00
tmpProcessed := [ ] * DependencyNode { }
2013-09-04 00:20:50 +00:00
for _ , node := range graph . nodes {
// If the node has more dependencies than what we have cleared,
// it won't be valid for this round.
if node . Degree ( ) > len ( processed ) {
continue
}
// If it's already processed, get to the next one
if processed [ node ] {
continue
}
// It's not been processed yet and has 0 deps. Add it!
// (this is a shortcut for what we're doing below)
if node . Degree ( ) == 0 {
2013-11-18 23:35:56 +00:00
tmpProcessed = append ( tmpProcessed , node )
2013-09-04 00:20:50 +00:00
continue
}
// If at least one dep hasn't been processed yet, we can't
// add it.
ok := true
2013-09-06 23:00:21 +00:00
for dep := range node . deps {
2013-09-04 00:20:50 +00:00
if ! processed [ dep ] {
ok = false
break
}
}
// All deps have already been processed. Add it!
if ok {
2013-11-18 23:35:56 +00:00
tmpProcessed = append ( tmpProcessed , node )
2013-09-04 00:20:50 +00:00
}
}
2013-11-18 23:35:56 +00:00
Debugf ( "Round %d: found %d available nodes" , len ( result ) , len ( tmpProcessed ) )
2013-09-06 23:00:21 +00:00
// If no progress has been made this round,
2013-09-04 00:20:50 +00:00
// that means we have circular dependencies.
2013-11-18 23:35:56 +00:00
if len ( tmpProcessed ) == 0 {
2013-09-04 00:20:50 +00:00
return nil , fmt . Errorf ( "Could not find a solution to this dependency graph" )
}
round := [ ] string { }
2013-11-18 23:35:56 +00:00
for _ , nd := range tmpProcessed {
2013-09-04 00:20:50 +00:00
round = append ( round , nd . id )
processed [ nd ] = true
}
result = append ( result , round )
}
return result , nil
2013-09-06 23:00:21 +00:00
}
2013-09-09 21:26:35 +00:00
// An StatusError reports an unsuccessful exit by a command.
type StatusError struct {
Status int
}
func ( e * StatusError ) Error ( ) string {
return fmt . Sprintf ( "Status: %d" , e . Status )
}
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-10-16 20:40:52 +00:00
func IsClosedError ( err error ) bool {
/ * This comparison is ugly , but unfortunately , net . go doesn ' t export errClosing .
* See :
* http : //golang.org/src/pkg/net/net.go
* https : //code.google.com/p/go/issues/detail?id=4337
* https : //groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
* /
return strings . HasSuffix ( err . Error ( ) , "use of closed network connection" )
}
2013-10-05 02:25:15 +00:00
func PartParser ( template , data string ) ( map [ string ] string , error ) {
// ip:public:private
2013-11-22 18:12:27 +00:00
var (
templateParts = strings . Split ( template , ":" )
parts = strings . Split ( data , ":" )
out = make ( map [ string ] string , len ( templateParts ) )
)
2013-10-05 02:25:15 +00:00
if len ( parts ) != len ( templateParts ) {
return nil , fmt . Errorf ( "Invalid format to parse. %s should match template %s" , data , template )
}
for i , t := range templateParts {
value := ""
if len ( parts ) > i {
value = parts [ i ]
}
out [ t ] = value
}
return out , nil
}
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 != "" {
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
}