1 package exec
2
3 import (
4 "bufio"
5 "context"
6 "fmt"
7 "io"
8 "os"
9 "strings"
10
11 "k8s.io/klog"
12 "k8s.io/kubectl/pkg/util/term"
13
14 "github.com/redhat-developer/odo/pkg/log"
15 "github.com/redhat-developer/odo/pkg/platform"
16 )
17
18 type ExecClient struct {
19 platformClient platform.Client
20 }
21
22 func NewExecClient(platformClient platform.Client) *ExecClient {
23 return &ExecClient{
24 platformClient: platformClient,
25 }
26 }
27
28
29
30
31
32 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) {
33 if !directRun {
34 soutReader, soutWriter := io.Pipe()
35 serrReader, serrWriter := io.Pipe()
36
37 klog.V(2).Infof("Executing command %v for pod: %v in container: %v", command, podName, containerName)
38
39
40 stdoutCompleteChannel := startReaderGoroutine(os.Stdout, soutReader, directRun, &stdout, stdoutWriter)
41 stderrCompleteChannel := startReaderGoroutine(os.Stderr, serrReader, directRun, &stderr, stderrWriter)
42
43 err = o.platformClient.ExecCMDInContainer(ctx, containerName, podName, command, soutWriter, serrWriter, nil, false)
44
45
46 _ = soutWriter.Close()
47 <-stdoutCompleteChannel
48 _ = serrWriter.Close()
49 <-stderrCompleteChannel
50
51
52 if err != nil && !directRun {
53
54 klog.V(2).Infof("ExecuteCommand returned an an err: %v. for command '%v'\nstdout: %v\nstderr: %v",
55 err, command, stdout, stderr)
56
57 msg := fmt.Sprintf("unable to exec command %v", command)
58 if len(stdout) != 0 {
59 msg += fmt.Sprintf("\n=== stdout===\n%s", strings.Join(stdout, "\n"))
60 }
61 if len(stderr) != 0 {
62 msg += fmt.Sprintf("\n=== stderr===\n%s", strings.Join(stderr, "\n"))
63 }
64 return stdout, stderr, fmt.Errorf("%s: %w", msg, err)
65 }
66
67 return stdout, stderr, err
68 }
69
70 tty := setupTTY()
71
72 fn := func() error {
73 return o.platformClient.ExecCMDInContainer(ctx, containerName, podName, command, tty.Out, os.Stderr, tty.In, tty.Raw)
74 }
75
76 return nil, nil, tty.Safe(fn)
77 }
78
79
80
81
82 func startReaderGoroutine(logWriter io.Writer, reader io.Reader, show bool, cmdOutput *[]string, consoleOutput *io.PipeWriter) chan interface{} {
83 result := make(chan interface{})
84
85 go func() {
86 scanner := bufio.NewScanner(reader)
87 for scanner.Scan() {
88 line := scanner.Text()
89
90 if show {
91 _, err := fmt.Fprintln(logWriter, line)
92 if err != nil {
93 log.Errorf("Unable to print to stdout: %s", err.Error())
94 }
95 } else {
96 klog.V(2).Infof(line)
97 }
98
99 if cmdOutput != nil {
100 *cmdOutput = append(*cmdOutput, line)
101 }
102
103 if consoleOutput != nil {
104 _, err := consoleOutput.Write([]byte(line + "\n"))
105 if err != nil {
106 log.Errorf("Error occurred on writing string to consoleOutput writer: %s", err.Error())
107 }
108 }
109 }
110 result <- nil
111 }()
112
113 return result
114 }
115
116 func setupTTY() term.TTY {
117 tty := term.TTY{
118 In: os.Stdin,
119 Out: os.Stdout,
120 }
121 if !tty.IsTerminalIn() || !tty.IsTerminalOut() {
122 return tty
123 }
124 tty.Raw = true
125 return tty
126 }
127
View as plain text