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  }