...

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

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

     1  package component
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    10  	"k8s.io/klog"
    11  
    12  	"github.com/redhat-developer/odo/pkg/exec"
    13  	"github.com/redhat-developer/odo/pkg/log"
    14  	"github.com/redhat-developer/odo/pkg/platform"
    15  	"github.com/redhat-developer/odo/pkg/remotecmd"
    16  	"github.com/redhat-developer/odo/pkg/task"
    17  	"github.com/redhat-developer/odo/pkg/util"
    18  )
    19  
    20  const numberOfLinesToOutputLog = 100
    21  
    22  // ExecuteRunCommand executes a Devfile command in the specified pod
    23  // If componentExists, the previous instance of the command will be stopped before (if hotReloadCapable is not set)
    24  func ExecuteRunCommand(ctx context.Context, execClient exec.Client, platformClient platform.Client, devfileCmd devfilev1.Command, componentExists bool, podName string, appName string, componentName string) error {
    25  	remoteProcessHandler := remotecmd.NewKubeExecProcessHandler(execClient)
    26  
    27  	statusHandlerFunc := func(s *log.Status) remotecmd.CommandOutputHandler {
    28  		return func(status remotecmd.RemoteProcessStatus, stdout []string, stderr []string, err error) {
    29  			switch status {
    30  			case remotecmd.Starting:
    31  				// Creating with no spin because the command could be long-running, and we cannot determine when it will end.
    32  				s.Start(fmt.Sprintf("Executing the application (command: %s)", devfileCmd.Id), true)
    33  			case remotecmd.Stopped, remotecmd.Errored:
    34  				s.EndWithStatus(fmt.Sprintf("Finished executing the application (command: %s)", devfileCmd.Id), status == remotecmd.Stopped)
    35  				if err != nil {
    36  					klog.V(2).Infof("error while running background command: %v", err)
    37  				}
    38  			}
    39  		}
    40  	}
    41  
    42  	// Spinner created but not started yet.
    43  	// It will be displayed when the statusHandlerFunc function is called with the "Starting" state.
    44  	spinner := log.NewStatus(log.GetStdout())
    45  
    46  	// if we need to restart, issue the remote process handler command to stop all running commands first.
    47  	// We do not need to restart Hot reload capable commands.
    48  	if componentExists {
    49  		if devfileCmd.Exec == nil || !util.SafeGetBool(devfileCmd.Exec.HotReloadCapable) {
    50  			klog.V(2).Infof("restart required for command %s", devfileCmd.Id)
    51  
    52  			cmdDef, err := devfileCommandToRemoteCmdDefinition(devfileCmd)
    53  			if err != nil {
    54  				return err
    55  			}
    56  
    57  			err = remoteProcessHandler.StopProcessForCommand(ctx, cmdDef, podName, devfileCmd.Exec.Component)
    58  			if err != nil {
    59  				return err
    60  			}
    61  
    62  			if err = remoteProcessHandler.StartProcessForCommand(ctx, cmdDef, podName, devfileCmd.Exec.Component, statusHandlerFunc(spinner)); err != nil {
    63  				return err
    64  			}
    65  		} else {
    66  			klog.V(2).Infof("command is hot-reload capable, not restarting %s", devfileCmd.Id)
    67  		}
    68  	} else {
    69  		cmdDef, err := devfileCommandToRemoteCmdDefinition(devfileCmd)
    70  		if err != nil {
    71  			return err
    72  		}
    73  
    74  		if err := remoteProcessHandler.StartProcessForCommand(ctx, cmdDef, podName, devfileCmd.Exec.Component, statusHandlerFunc(spinner)); err != nil {
    75  			return err
    76  		}
    77  	}
    78  
    79  	retrySchedule := []time.Duration{
    80  		5 * time.Second,
    81  		6 * time.Second,
    82  		9 * time.Second,
    83  	}
    84  	var totalWaitTime float64
    85  	for _, s := range retrySchedule {
    86  		totalWaitTime += s.Seconds()
    87  	}
    88  
    89  	_, err := task.NewRetryable(fmt.Sprintf("process for command %q", devfileCmd.Id), func() (bool, interface{}, error) {
    90  		klog.V(4).Infof("checking if process for command %q is running", devfileCmd.Id)
    91  		remoteProcess, err := remoteProcessHandler.GetProcessInfoForCommand(ctx, remotecmd.CommandDefinition{Id: devfileCmd.Id}, podName, devfileCmd.Exec.Component)
    92  		if err != nil {
    93  			return false, nil, err
    94  		}
    95  		isRunningOrDone := remoteProcess.Status == remotecmd.Running ||
    96  			remoteProcess.Status == remotecmd.Stopped ||
    97  			remoteProcess.Status == remotecmd.Errored
    98  		return isRunningOrDone, nil, err
    99  	}).RetryWithSchedule(retrySchedule, false)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	return checkRemoteCommandStatus(ctx, execClient, platformClient, devfileCmd, podName, appName, componentName, fmt.Sprintf("Devfile command %q exited with an error status in %.0f second(s)", devfileCmd.Id, totalWaitTime))
   105  }
   106  
   107  // devfileCommandToRemoteCmdDefinition builds and returns a new remotecmd.CommandDefinition object from the specified devfileCmd.
   108  // An error is returned for non-exec Devfile commands.
   109  func devfileCommandToRemoteCmdDefinition(devfileCmd devfilev1.Command) (remotecmd.CommandDefinition, error) {
   110  	if devfileCmd.Exec == nil {
   111  		return remotecmd.CommandDefinition{}, errors.New(" only Exec commands are supported")
   112  	}
   113  
   114  	envVars := make([]remotecmd.CommandEnvVar, 0, len(devfileCmd.Exec.Env))
   115  	for _, e := range devfileCmd.Exec.Env {
   116  		envVars = append(envVars, remotecmd.CommandEnvVar{Key: e.Name, Value: e.Value})
   117  	}
   118  
   119  	return remotecmd.CommandDefinition{
   120  		Id:         devfileCmd.Id,
   121  		WorkingDir: devfileCmd.Exec.WorkingDir,
   122  		EnvVars:    envVars,
   123  		CmdLine:    devfileCmd.Exec.CommandLine,
   124  	}, nil
   125  }
   126  
   127  // checkRemoteCommandStatus checks if the command is running .
   128  // if the command is not in a running state, we fetch the last 20 lines of the component's log and display it
   129  func checkRemoteCommandStatus(ctx context.Context, execClient exec.Client, platformClient platform.Client, command devfilev1.Command, podName string, appName string, componentName string, notRunningMessage string) error {
   130  	remoteProcessHandler := remotecmd.NewKubeExecProcessHandler(execClient)
   131  	remoteProcess, err := remoteProcessHandler.GetProcessInfoForCommand(ctx, remotecmd.CommandDefinition{Id: command.Id}, podName, command.Exec.Component)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	if remoteProcess.Status != remotecmd.Running && remoteProcess.Status != remotecmd.Stopped {
   137  		log.Warningf(notRunningMessage)
   138  		log.Warningf("Last %d lines of log:", numberOfLinesToOutputLog)
   139  
   140  		rd, err := Log(platformClient, componentName, appName, false, command)
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		// Use GetStderr in order to make sure that colour output is correct
   146  		// on non-TTY terminals
   147  		err = util.DisplayLog(false, rd, log.GetStderr(), componentName, numberOfLinesToOutputLog)
   148  		if err != nil {
   149  			return err
   150  		}
   151  	}
   152  	return nil
   153  }
   154  

View as plain text