github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/cluster/calcium/stream.go (about) 1 package calcium 2 3 import ( 4 "bufio" 5 "context" 6 "encoding/json" 7 "io" 8 "sync" 9 10 "github.com/cockroachdb/errors" 11 "github.com/projecteru2/core/engine" 12 enginetypes "github.com/projecteru2/core/engine/types" 13 "github.com/projecteru2/core/log" 14 "github.com/projecteru2/core/types" 15 "github.com/projecteru2/core/utils" 16 ) 17 18 var winchCommand = []byte{0x80} // 128, non-ASCII 19 var escapeCommand = []byte{0x1d} // 29, ^] 20 21 type window struct { 22 Height uint `json:"Row"` 23 Width uint `json:"Col"` 24 } 25 26 func (c *Calcium) execuateInside(ctx context.Context, client engine.API, ID, cmd, user string, env []string, privileged bool) ([]byte, error) { 27 cmds := utils.MakeCommandLineArgs(cmd) 28 execConfig := &enginetypes.ExecConfig{ 29 User: user, 30 Cmd: cmds, 31 Privileged: privileged, 32 Env: env, 33 AttachStderr: true, 34 AttachStdout: true, 35 } 36 b := []byte{} 37 execID, stdout, stderr, _, err := client.Execute(ctx, ID, execConfig) 38 if err != nil { 39 return nil, err 40 } 41 42 for m := range c.processStdStream(ctx, stdout, stderr, bufio.ScanLines, byte('\n')) { 43 b = append(b, m.Data...) 44 } 45 46 exitCode, err := client.ExecExitCode(ctx, ID, execID) 47 if err != nil { 48 return b, err 49 } 50 if exitCode != 0 { 51 return b, errors.New(string(b)) 52 } 53 return b, nil 54 } 55 56 func (c *Calcium) processVirtualizationInStream( 57 ctx context.Context, 58 inStream io.WriteCloser, 59 inCh <-chan []byte, 60 resizeFunc func(height, width uint) error, 61 ) <-chan struct{} { //nolint 62 logger := log.WithFunc("calcium.processVirtualizationInStream") 63 specialPrefixCallback := map[string]func([]byte){ 64 string(winchCommand): func(body []byte) { 65 w := &window{} 66 if err := json.Unmarshal(body, w); err != nil { 67 logger.Errorf(ctx, err, "invalid winch command: %q", body) 68 return 69 } 70 if err := resizeFunc(w.Height, w.Width); err != nil { 71 logger.Error(ctx, err, "resize window error") 72 return 73 } 74 }, 75 76 string(escapeCommand): func(_ []byte) { 77 inStream.Close() 78 }, 79 } 80 return c.rawProcessVirtualizationInStream(ctx, inStream, inCh, specialPrefixCallback) 81 } 82 83 func (c *Calcium) rawProcessVirtualizationInStream( 84 ctx context.Context, 85 inStream io.WriteCloser, 86 inCh <-chan []byte, 87 specialPrefixCallback map[string]func([]byte), 88 ) <-chan struct{} { 89 done := make(chan struct{}) 90 _ = c.pool.Invoke(func() { 91 defer close(done) 92 defer inStream.Close() 93 94 for cmd := range inCh { 95 if len(cmd) == 0 { 96 continue 97 } 98 if f, ok := specialPrefixCallback[string(cmd[:1])]; ok { 99 f(cmd[1:]) 100 continue 101 } 102 if _, err := inStream.Write(cmd); err != nil { 103 log.WithFunc("calcium.rawProcessVirtualizationInStream").Error(ctx, err, "failed to write virtual input stream") 104 continue 105 } 106 } 107 }) 108 109 return done 110 } 111 112 func (c *Calcium) processVirtualizationOutStream( 113 ctx context.Context, 114 outStream io.ReadCloser, 115 splitFunc bufio.SplitFunc, 116 split byte, 117 118 ) <-chan []byte { 119 outCh := make(chan []byte) 120 _ = c.pool.Invoke(func() { 121 defer close(outCh) 122 if outStream == nil { 123 return 124 } 125 defer outStream.Close() 126 scanner := bufio.NewScanner(outStream) 127 scanner.Split(splitFunc) 128 for scanner.Scan() { 129 bs := scanner.Bytes() 130 if split != 0 { 131 bs = append(bs, split) 132 } 133 outCh <- bs 134 } 135 if err := scanner.Err(); err != nil { 136 log.WithFunc("calcium.processVirtualizationOutStream").Warnf(ctx, "failed to read output from output stream: %+v", err) 137 } 138 }) 139 return outCh 140 } 141 142 func (c *Calcium) processBuildImageStream(ctx context.Context, reader io.ReadCloser) chan *types.BuildImageMessage { 143 ch := make(chan *types.BuildImageMessage) 144 _ = c.pool.Invoke(func() { 145 defer close(ch) 146 defer utils.EnsureReaderClosed(ctx, reader) 147 decoder := json.NewDecoder(reader) 148 for { 149 message := &types.BuildImageMessage{} 150 err := decoder.Decode(message) 151 if err != nil { 152 if err != io.EOF { 153 malformed, _ := io.ReadAll(decoder.Buffered()) // TODO err check 154 log.WithFunc("calcium.processBuildImageStream").Errorf(ctx, err, "decode image message failed, buffered: %s", string(malformed)) 155 message.Error = err.Error() 156 ch <- message 157 } 158 break 159 } 160 ch <- message 161 } 162 }) 163 return ch 164 } 165 166 func (c *Calcium) processStdStream(ctx context.Context, stdout, stderr io.ReadCloser, splitFunc bufio.SplitFunc, split byte) chan types.StdStreamMessage { 167 ch := make(chan types.StdStreamMessage) 168 169 wg := sync.WaitGroup{} 170 171 wg.Add(1) 172 _ = c.pool.Invoke(func() { 173 defer wg.Done() 174 for data := range c.processVirtualizationOutStream(ctx, stdout, splitFunc, split) { 175 ch <- types.StdStreamMessage{Data: data, StdStreamType: types.Stdout} 176 } 177 }) 178 179 wg.Add(1) 180 _ = c.pool.Invoke(func() { 181 defer wg.Done() 182 for data := range c.processVirtualizationOutStream(ctx, stderr, splitFunc, split) { 183 ch <- types.StdStreamMessage{Data: data, StdStreamType: types.Stderr} 184 } 185 }) 186 187 _ = c.pool.Invoke(func() { 188 defer close(ch) 189 wg.Wait() 190 }) 191 192 return ch 193 }