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 }