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  }