github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/plugins/drivers/execstreaming.go (about)

     1  package drivers
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  
     9  	"github.com/hashicorp/nomad/plugins/drivers/proto"
    10  )
    11  
    12  // StreamToExecOptions is a convenience method to convert exec stream into
    13  // ExecOptions object.
    14  func StreamToExecOptions(
    15  	ctx context.Context,
    16  	command []string,
    17  	tty bool,
    18  	stream ExecTaskStream) (*ExecOptions, <-chan error) {
    19  
    20  	inReader, inWriter := io.Pipe()
    21  	outReader, outWriter := io.Pipe()
    22  	errReader, errWriter := io.Pipe()
    23  	resize := make(chan TerminalSize, 2)
    24  
    25  	errCh := make(chan error, 3)
    26  
    27  	// handle input
    28  	go func() {
    29  		for {
    30  			msg, err := stream.Recv()
    31  			if err == io.EOF {
    32  				return
    33  			} else if err != nil {
    34  				errCh <- err
    35  				return
    36  			}
    37  
    38  			if msg.Stdin != nil && !msg.Stdin.Close {
    39  				_, err := inWriter.Write(msg.Stdin.Data)
    40  				if err != nil {
    41  					errCh <- err
    42  					return
    43  				}
    44  			} else if msg.Stdin != nil && msg.Stdin.Close {
    45  				inWriter.Close()
    46  			} else if msg.TtySize != nil {
    47  				select {
    48  				case resize <- TerminalSize{
    49  					Height: int(msg.TtySize.Height),
    50  					Width:  int(msg.TtySize.Width),
    51  				}:
    52  				case <-ctx.Done():
    53  					// process terminated before resize is processed
    54  					return
    55  				}
    56  			} else if isHeartbeat(msg) {
    57  				// do nothing
    58  			} else {
    59  				errCh <- fmt.Errorf("unexpected message type: %#v", msg)
    60  			}
    61  		}
    62  	}()
    63  
    64  	var sendLock sync.Mutex
    65  	send := func(v *ExecTaskStreamingResponseMsg) error {
    66  		sendLock.Lock()
    67  		defer sendLock.Unlock()
    68  
    69  		return stream.Send(v)
    70  	}
    71  
    72  	var outWg sync.WaitGroup
    73  	outWg.Add(2)
    74  	// handle Stdout
    75  	go func() {
    76  		defer outWg.Done()
    77  
    78  		reader := outReader
    79  		bytes := make([]byte, 1024)
    80  		msg := &ExecTaskStreamingResponseMsg{Stdout: &proto.ExecTaskStreamingIOOperation{}}
    81  
    82  		for {
    83  			n, err := reader.Read(bytes)
    84  			// always send data if we read some
    85  			if n != 0 {
    86  				msg.Stdout.Data = bytes[:n]
    87  				if err := send(msg); err != nil {
    88  					errCh <- err
    89  					break
    90  				}
    91  			}
    92  
    93  			// then handle error
    94  			if err == io.EOF || err == io.ErrClosedPipe {
    95  				msg.Stdout.Data = nil
    96  				msg.Stdout.Close = true
    97  
    98  				if err := send(msg); err != nil {
    99  					errCh <- err
   100  				}
   101  				break
   102  			}
   103  
   104  			if err != nil {
   105  				errCh <- err
   106  				break
   107  			}
   108  		}
   109  
   110  	}()
   111  	// handle Stderr
   112  	go func() {
   113  		defer outWg.Done()
   114  
   115  		reader := errReader
   116  		bytes := make([]byte, 1024)
   117  		msg := &ExecTaskStreamingResponseMsg{Stderr: &proto.ExecTaskStreamingIOOperation{}}
   118  
   119  		for {
   120  			n, err := reader.Read(bytes)
   121  			// always send data if we read some
   122  			if n != 0 {
   123  				msg.Stderr.Data = bytes[:n]
   124  				if err := send(msg); err != nil {
   125  					errCh <- err
   126  					break
   127  				}
   128  			}
   129  
   130  			// then handle error
   131  			if err == io.EOF || err == io.ErrClosedPipe {
   132  				msg.Stderr.Data = nil
   133  				msg.Stderr.Close = true
   134  
   135  				if err := send(msg); err != nil {
   136  					errCh <- err
   137  				}
   138  				break
   139  			}
   140  
   141  			if err != nil {
   142  				errCh <- err
   143  				break
   144  			}
   145  		}
   146  
   147  	}()
   148  
   149  	doneCh := make(chan error, 1)
   150  	go func() {
   151  		outWg.Wait()
   152  
   153  		select {
   154  		case err := <-errCh:
   155  			doneCh <- err
   156  		default:
   157  		}
   158  		close(doneCh)
   159  	}()
   160  
   161  	return &ExecOptions{
   162  		Command: command,
   163  		Tty:     tty,
   164  
   165  		Stdin:  inReader,
   166  		Stdout: outWriter,
   167  		Stderr: errWriter,
   168  
   169  		ResizeCh: resize,
   170  	}, doneCh
   171  }
   172  
   173  func NewExecStreamingResponseExit(exitCode int) *ExecTaskStreamingResponseMsg {
   174  	return &ExecTaskStreamingResponseMsg{
   175  		Exited: true,
   176  		Result: &proto.ExitResult{
   177  			ExitCode: int32(exitCode),
   178  		},
   179  	}
   180  
   181  }
   182  
   183  func isHeartbeat(r *ExecTaskStreamingRequestMsg) bool {
   184  	return r.Stdin == nil && r.Setup == nil && r.TtySize == nil
   185  }