github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/k8s/client/cmd.go (about) 1 package client 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "io" 8 "os/exec" 9 "strings" 10 11 "github.com/rs/zerolog/log" 12 ) 13 14 func ExecCmd(command string) error { 15 return ExecCmdWithContext(context.Background(), command) 16 } 17 18 func ExecCmdWithContext(ctx context.Context, command string) error { 19 return ExecCmdWithOptions(ctx, command, func(m string) { 20 log.Debug().Str("Text", m).Msg("Std Pipe") 21 }) 22 } 23 24 // readStdPipe continuously reads from a given pipe (either stdout or stderr) 25 // and processes the output line by line using the provided outputFunction. 26 // It handles lines of any length dynamically without the need for a large predefined buffer. 27 func readStdPipe(pipe io.ReadCloser, outputFunction func(string)) { 28 reader := bufio.NewReader(pipe) 29 var output []rune 30 31 for { 32 // ReadLine tries to return a single line, not including the end-of-line bytes. 33 // The returned line may be incomplete if the line's too long for the buffer. 34 // isPrefix will be true if the line is longer than the buffer. 35 chunk, isPrefix, err := reader.ReadLine() 36 37 // Handle any errors that occurred during the read. 38 if err != nil { 39 // Log any error that's not an EOF (end of file). 40 if err != io.EOF { 41 log.Warn().Err(err).Msg("Error while reading standard pipe, this can be caused by really long logs and can be ignored if nothing else is wrong.") 42 } 43 break 44 } 45 46 // Append the chunk to the output buffer. 47 // bytes.Runes converts the byte slice to a slice of runes, handling multi-byte characters. 48 output = append(output, bytes.Runes(chunk)...) 49 50 // If isPrefix is false, we've reached the end of the line and can process it. 51 if !isPrefix { 52 // Call the output function with the complete line if it's defined. 53 if outputFunction != nil { 54 outputFunction(string(output)) 55 } 56 // Reset output to an empty slice for reading the next line. 57 output = output[:0] 58 } 59 } 60 } 61 62 func ExecCmdWithOptions(ctx context.Context, command string, outputFunction func(string)) error { 63 c := strings.Split(command, " ") 64 cmd := exec.CommandContext(ctx, c[0], c[1:]...) // #nosec: G204 65 stderr, err := cmd.StderrPipe() 66 if err != nil { 67 return err 68 } 69 stdout, err := cmd.StdoutPipe() 70 if err != nil { 71 return err 72 } 73 if err := cmd.Start(); err != nil { 74 return err 75 } 76 go readStdPipe(stderr, outputFunction) 77 go readStdPipe(stdout, outputFunction) 78 return cmd.Wait() 79 }