...

Source file src/github.com/redhat-developer/odo/pkg/exec/exec.go

Documentation: github.com/redhat-developer/odo/pkg/exec

     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  // ExecuteCommand executes the given command in the pod's container,
    29  // writing the output to the specified respective pipe writers
    30  // when directRun is true, will execute the command with terminal in Raw mode and connected to local standard I/Os
    31  // so input, including Ctrl-c, is sent to the remote process
    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  		// Read stdout and stderr, store their output in cmdOutput, and also pass output to consoleOutput Writers (if non-nil)
    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  		// Block until we have received all the container output from each stream
    46  		_ = soutWriter.Close()
    47  		<-stdoutCompleteChannel
    48  		_ = serrWriter.Close()
    49  		<-stderrCompleteChannel
    50  
    51  		// Details are displayed only if no outputs are displayed
    52  		if err != nil && !directRun {
    53  			// It is safe to read from stdout and stderr here, as the goroutines are guaranteed to have terminated at this point.
    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  // This goroutine will automatically pipe the output from the writer (passed into ExecCMDInContainer) to
    80  // the loggers.
    81  // The returned channel will contain a single nil entry once the reader has closed.
    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