mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
* Change NewRunHandler params with Options * Pass an options to RunHandler to show logs * Hide spinner and std output since outputs are displayed * Integration tests with failing command * Fix outputs * use raw terminal and local standard i/o streams * Fix podman i/o * Fix stdout/err * Test if in/out are terminal * command reference doc
127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
package exec
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"k8s.io/klog"
|
|
"k8s.io/kubectl/pkg/util/term"
|
|
|
|
"github.com/redhat-developer/odo/pkg/log"
|
|
"github.com/redhat-developer/odo/pkg/platform"
|
|
)
|
|
|
|
type ExecClient struct {
|
|
platformClient platform.Client
|
|
}
|
|
|
|
func NewExecClient(platformClient platform.Client) *ExecClient {
|
|
return &ExecClient{
|
|
platformClient: platformClient,
|
|
}
|
|
}
|
|
|
|
// ExecuteCommand executes the given command in the pod's container,
|
|
// writing the output to the specified respective pipe writers
|
|
// when directRun is true, will execute the command with terminal in Raw mode and connected to local standard I/Os
|
|
// so input, including Ctrl-c, is sent to the remote process
|
|
func (o ExecClient) ExecuteCommand(ctx context.Context, command []string, podName string, containerName string, directRun bool, stdoutWriter *io.PipeWriter, stderrWriter *io.PipeWriter) (stdout []string, stderr []string, err error) {
|
|
if !directRun {
|
|
soutReader, soutWriter := io.Pipe()
|
|
serrReader, serrWriter := io.Pipe()
|
|
|
|
klog.V(2).Infof("Executing command %v for pod: %v in container: %v", command, podName, containerName)
|
|
|
|
// Read stdout and stderr, store their output in cmdOutput, and also pass output to consoleOutput Writers (if non-nil)
|
|
stdoutCompleteChannel := startReaderGoroutine(os.Stdout, soutReader, directRun, &stdout, stdoutWriter)
|
|
stderrCompleteChannel := startReaderGoroutine(os.Stderr, serrReader, directRun, &stderr, stderrWriter)
|
|
|
|
err = o.platformClient.ExecCMDInContainer(ctx, containerName, podName, command, soutWriter, serrWriter, nil, false)
|
|
|
|
// Block until we have received all the container output from each stream
|
|
_ = soutWriter.Close()
|
|
<-stdoutCompleteChannel
|
|
_ = serrWriter.Close()
|
|
<-stderrCompleteChannel
|
|
|
|
// Details are displayed only if no outputs are displayed
|
|
if err != nil && !directRun {
|
|
// It is safe to read from stdout and stderr here, as the goroutines are guaranteed to have terminated at this point.
|
|
klog.V(2).Infof("ExecuteCommand returned an an err: %v. for command '%v'\nstdout: %v\nstderr: %v",
|
|
err, command, stdout, stderr)
|
|
|
|
msg := fmt.Sprintf("unable to exec command %v", command)
|
|
if len(stdout) != 0 {
|
|
msg += fmt.Sprintf("\n=== stdout===\n%s", strings.Join(stdout, "\n"))
|
|
}
|
|
if len(stderr) != 0 {
|
|
msg += fmt.Sprintf("\n=== stderr===\n%s", strings.Join(stderr, "\n"))
|
|
}
|
|
return stdout, stderr, fmt.Errorf("%s: %w", msg, err)
|
|
}
|
|
|
|
return stdout, stderr, err
|
|
}
|
|
|
|
tty := setupTTY()
|
|
|
|
fn := func() error {
|
|
return o.platformClient.ExecCMDInContainer(ctx, containerName, podName, command, tty.Out, os.Stderr, tty.In, tty.Raw)
|
|
}
|
|
|
|
return nil, nil, tty.Safe(fn)
|
|
}
|
|
|
|
// This goroutine will automatically pipe the output from the writer (passed into ExecCMDInContainer) to
|
|
// the loggers.
|
|
// The returned channel will contain a single nil entry once the reader has closed.
|
|
func startReaderGoroutine(logWriter io.Writer, reader io.Reader, show bool, cmdOutput *[]string, consoleOutput *io.PipeWriter) chan interface{} {
|
|
result := make(chan interface{})
|
|
|
|
go func() {
|
|
scanner := bufio.NewScanner(reader)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
if show {
|
|
_, err := fmt.Fprintln(logWriter, line)
|
|
if err != nil {
|
|
log.Errorf("Unable to print to stdout: %s", err.Error())
|
|
}
|
|
} else {
|
|
klog.V(2).Infof(line)
|
|
}
|
|
|
|
if cmdOutput != nil {
|
|
*cmdOutput = append(*cmdOutput, line)
|
|
}
|
|
|
|
if consoleOutput != nil {
|
|
_, err := consoleOutput.Write([]byte(line + "\n"))
|
|
if err != nil {
|
|
log.Errorf("Error occurred on writing string to consoleOutput writer: %s", err.Error())
|
|
}
|
|
}
|
|
}
|
|
result <- nil
|
|
}()
|
|
|
|
return result
|
|
}
|
|
|
|
func setupTTY() term.TTY {
|
|
tty := term.TTY{
|
|
In: os.Stdin,
|
|
Out: os.Stdout,
|
|
}
|
|
if !tty.IsTerminalIn() || !tty.IsTerminalOut() {
|
|
return tty
|
|
}
|
|
tty.Raw = true
|
|
return tty
|
|
}
|