...

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

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

     1  package component
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
     9  	devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    10  	"github.com/redhat-developer/odo/pkg/exec"
    11  	"github.com/redhat-developer/odo/pkg/log"
    12  	"github.com/redhat-developer/odo/pkg/machineoutput"
    13  	"github.com/redhat-developer/odo/pkg/platform"
    14  	"github.com/redhat-developer/odo/pkg/util"
    15  	"k8s.io/klog"
    16  	"k8s.io/utils/pointer"
    17  )
    18  
    19  const ShellExecutable string = "/bin/sh"
    20  
    21  func ExecuteTerminatingCommand(
    22  	ctx context.Context,
    23  	execClient exec.Client,
    24  	platformClient platform.Client,
    25  	command devfilev1.Command,
    26  	componentExists bool,
    27  	podName string,
    28  	appName string,
    29  	componentName string,
    30  	msg string,
    31  	directRun bool,
    32  ) error {
    33  
    34  	if componentExists && command.Exec != nil && pointer.BoolDeref(command.Exec.HotReloadCapable, false) {
    35  		klog.V(2).Infof("command is hot-reload capable, not executing %q again", command.Id)
    36  		return nil
    37  	}
    38  
    39  	// Spinner is displayed only if no outputs are displayed
    40  	var spinner *log.Status
    41  	var stdoutWriter, stderrWriter *io.PipeWriter
    42  	var stdoutChannel, stderrChannel chan interface{}
    43  
    44  	if !directRun {
    45  		if msg == "" {
    46  			msg = fmt.Sprintf("Executing %s command on container %q", command.Id, command.Exec.Component)
    47  		} else {
    48  			msg += " (command: " + command.Id + ")"
    49  		}
    50  		spinner = log.Spinner(msg)
    51  		defer spinner.End(false)
    52  
    53  		logger := machineoutput.NewMachineEventLoggingClient()
    54  		stdoutWriter, stdoutChannel, stderrWriter, stderrChannel = logger.CreateContainerOutputWriter()
    55  	}
    56  
    57  	cmdline := getCmdline(command, !directRun)
    58  	_, _, err := execClient.ExecuteCommand(ctx, cmdline, podName, command.Exec.Component, directRun, stdoutWriter, stderrWriter)
    59  
    60  	if !directRun {
    61  		closeWriterAndWaitForAck(stdoutWriter, stdoutChannel, stderrWriter, stderrChannel)
    62  		spinner.End(err == nil)
    63  
    64  		if err != nil {
    65  			rd, errLog := Log(platformClient, componentName, appName, false, command)
    66  			if errLog != nil {
    67  				return fmt.Errorf("unable to log error %v: %w", err, errLog)
    68  			}
    69  
    70  			// Use GetStderr in order to make sure that colour output is correct
    71  			// on non-TTY terminals
    72  			errLog = util.DisplayLog(false, rd, log.GetStderr(), componentName, -1)
    73  			if errLog != nil {
    74  				return fmt.Errorf("unable to log error %v: %w", err, errLog)
    75  			}
    76  		}
    77  	}
    78  	return err
    79  }
    80  
    81  func getCmdline(command v1alpha2.Command, redirectToPid1 bool) []string {
    82  	// deal with environment variables
    83  	var cmdLine string
    84  	setEnvVariable := util.GetCommandStringFromEnvs(command.Exec.Env)
    85  
    86  	if setEnvVariable == "" {
    87  		cmdLine = command.Exec.CommandLine
    88  	} else {
    89  		cmdLine = setEnvVariable + " && " + command.Exec.CommandLine
    90  	}
    91  
    92  	// Change to the workdir and execute the command
    93  	// Redirecting to /proc/1/fd/* allows to redirect the process output to the output streams of PID 1 process inside the container.
    94  	// This way, returning the container logs with 'odo logs' or 'kubectl logs' would work seamlessly.
    95  	// See https://stackoverflow.com/questions/58716574/where-exactly-do-the-logs-of-kubernetes-pods-come-from-at-the-container-level
    96  	redirectString := ""
    97  	if redirectToPid1 {
    98  		redirectString = "1>>/proc/1/fd/1 2>>/proc/1/fd/2"
    99  	}
   100  	var cmd []string
   101  	if command.Exec.WorkingDir != "" {
   102  		// since we are using /bin/sh -c, the command needs to be within a single double quote instance, for example "cd /tmp && pwd"
   103  		cmd = []string{ShellExecutable, "-c", "cd " + command.Exec.WorkingDir + " && (" + cmdLine + ") " + redirectString}
   104  	} else {
   105  		cmd = []string{ShellExecutable, "-c", "(" + cmdLine + ") " + redirectString}
   106  	}
   107  	return cmd
   108  }
   109  
   110  func closeWriterAndWaitForAck(stdoutWriter *io.PipeWriter, stdoutChannel chan interface{}, stderrWriter *io.PipeWriter, stderrChannel chan interface{}) {
   111  	if stdoutWriter != nil {
   112  		_ = stdoutWriter.Close()
   113  		<-stdoutChannel
   114  	}
   115  	if stderrWriter != nil {
   116  		_ = stderrWriter.Close()
   117  		<-stderrChannel
   118  	}
   119  }
   120  

View as plain text