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