github.com/eatbyte/docker@v1.6.0/engine/streams.go (about) 1 package engine 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "strings" 9 "sync" 10 "unicode" 11 ) 12 13 type Output struct { 14 sync.Mutex 15 dests []io.Writer 16 tasks sync.WaitGroup 17 used bool 18 } 19 20 // Tail returns the n last lines of a buffer 21 // stripped out of trailing white spaces, if any. 22 // 23 // if n <= 0, returns an empty string 24 func Tail(buffer *bytes.Buffer, n int) string { 25 if n <= 0 { 26 return "" 27 } 28 s := strings.TrimRightFunc(buffer.String(), unicode.IsSpace) 29 i := len(s) - 1 30 for ; i >= 0 && n > 0; i-- { 31 if s[i] == '\n' { 32 n-- 33 if n == 0 { 34 break 35 } 36 } 37 } 38 // when i == -1, return the whole string which is s[0:] 39 return s[i+1:] 40 } 41 42 // NewOutput returns a new Output object with no destinations attached. 43 // Writing to an empty Output will cause the written data to be discarded. 44 func NewOutput() *Output { 45 return &Output{} 46 } 47 48 // Return true if something was written on this output 49 func (o *Output) Used() bool { 50 o.Lock() 51 defer o.Unlock() 52 return o.used 53 } 54 55 // Add attaches a new destination to the Output. Any data subsequently written 56 // to the output will be written to the new destination in addition to all the others. 57 // This method is thread-safe. 58 func (o *Output) Add(dst io.Writer) { 59 o.Lock() 60 defer o.Unlock() 61 o.dests = append(o.dests, dst) 62 } 63 64 // Set closes and remove existing destination and then attaches a new destination to 65 // the Output. Any data subsequently written to the output will be written to the new 66 // destination in addition to all the others. This method is thread-safe. 67 func (o *Output) Set(dst io.Writer) { 68 o.Close() 69 o.Lock() 70 defer o.Unlock() 71 o.dests = []io.Writer{dst} 72 } 73 74 // AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination, 75 // and returns its reading end for consumption by the caller. 76 // This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package. 77 // This method is thread-safe. 78 func (o *Output) AddPipe() (io.Reader, error) { 79 r, w := io.Pipe() 80 o.Add(w) 81 return r, nil 82 } 83 84 // Write writes the same data to all registered destinations. 85 // This method is thread-safe. 86 func (o *Output) Write(p []byte) (n int, err error) { 87 o.Lock() 88 defer o.Unlock() 89 o.used = true 90 var firstErr error 91 for _, dst := range o.dests { 92 _, err := dst.Write(p) 93 if err != nil && firstErr == nil { 94 firstErr = err 95 } 96 } 97 return len(p), firstErr 98 } 99 100 // Close unregisters all destinations and waits for all background 101 // AddTail and AddString tasks to complete. 102 // The Close method of each destination is called if it exists. 103 func (o *Output) Close() error { 104 o.Lock() 105 defer o.Unlock() 106 var firstErr error 107 for _, dst := range o.dests { 108 if closer, ok := dst.(io.Closer); ok { 109 err := closer.Close() 110 if err != nil && firstErr == nil { 111 firstErr = err 112 } 113 } 114 } 115 o.tasks.Wait() 116 o.dests = nil 117 return firstErr 118 } 119 120 type Input struct { 121 src io.Reader 122 sync.Mutex 123 } 124 125 // NewInput returns a new Input object with no source attached. 126 // Reading to an empty Input will return io.EOF. 127 func NewInput() *Input { 128 return &Input{} 129 } 130 131 // Read reads from the input in a thread-safe way. 132 func (i *Input) Read(p []byte) (n int, err error) { 133 i.Mutex.Lock() 134 defer i.Mutex.Unlock() 135 if i.src == nil { 136 return 0, io.EOF 137 } 138 return i.src.Read(p) 139 } 140 141 // Closes the src 142 // Not thread safe on purpose 143 func (i *Input) Close() error { 144 if i.src != nil { 145 if closer, ok := i.src.(io.Closer); ok { 146 return closer.Close() 147 } 148 } 149 return nil 150 } 151 152 // Add attaches a new source to the input. 153 // Add can only be called once per input. Subsequent calls will 154 // return an error. 155 func (i *Input) Add(src io.Reader) error { 156 i.Mutex.Lock() 157 defer i.Mutex.Unlock() 158 if i.src != nil { 159 return fmt.Errorf("Maximum number of sources reached: 1") 160 } 161 i.src = src 162 return nil 163 } 164 165 // AddEnv starts a new goroutine which will decode all subsequent data 166 // as a stream of json-encoded objects, and point `dst` to the last 167 // decoded object. 168 // The result `env` can be queried using the type-neutral Env interface. 169 // It is not safe to query `env` until the Output is closed. 170 func (o *Output) AddEnv() (dst *Env, err error) { 171 src, err := o.AddPipe() 172 if err != nil { 173 return nil, err 174 } 175 dst = &Env{} 176 o.tasks.Add(1) 177 go func() { 178 defer o.tasks.Done() 179 decoder := NewDecoder(src) 180 for { 181 env, err := decoder.Decode() 182 if err != nil { 183 return 184 } 185 *dst = *env 186 } 187 }() 188 return dst, nil 189 } 190 191 func (o *Output) AddListTable() (dst *Table, err error) { 192 src, err := o.AddPipe() 193 if err != nil { 194 return nil, err 195 } 196 dst = NewTable("", 0) 197 o.tasks.Add(1) 198 go func() { 199 defer o.tasks.Done() 200 content, err := ioutil.ReadAll(src) 201 if err != nil { 202 return 203 } 204 if _, err := dst.ReadListFrom(content); err != nil { 205 return 206 } 207 }() 208 return dst, nil 209 } 210 211 func (o *Output) AddTable() (dst *Table, err error) { 212 src, err := o.AddPipe() 213 if err != nil { 214 return nil, err 215 } 216 dst = NewTable("", 0) 217 o.tasks.Add(1) 218 go func() { 219 defer o.tasks.Done() 220 if _, err := dst.ReadFrom(src); err != nil { 221 return 222 } 223 }() 224 return dst, nil 225 }