func ExecuteCommand()

in agent/executers/executers.go [255:390]


func ExecuteCommand(
	context context.T,
	cancelFlag task.CancelFlag,
	workingDir string,
	stdoutWriter io.Writer,
	stderrWriter io.Writer,
	executionTimeout int,
	commandName string,
	commandArguments []string,
	envVars map[string]string,
) (exitCode int, err error) {
	log := context.Log()

	stdoutInterruptable, stopStdout := newWriter(stdoutWriter)
	stderrInterruptable, stopStderr := newWriter(stderrWriter)

	command := exec.Command(commandName, commandArguments...)
	command.Dir = workingDir
	exitCode = 0

	// If we assign the writers directly, the command may never exit even though a command.Process.Wait() does due to https://github.com/golang/go/issues/13155
	// However, if we run goroutines to copy from the StdoutPipe and StderrPipe we may lose the last write.
	command.Stdout = stdoutInterruptable
	command.Stderr = stderrInterruptable
	/*
		stdoutPipe, err := command.StdoutPipe()
		if err != nil {
			return 1, err
		}
		stderrPipe, err := command.StderrPipe()
		if err != nil {
			return 1, err
		}
		go io.Copy(stdoutInterruptable, stdoutPipe)
		go io.Copy(stderrInterruptable, stderrPipe)
	*/

	// configure OS-specific process settings
	prepareProcess(command)

	// configure environment variables
	prepareEnvironment(context, command, envVars)

	log.Debugf("Running in directory %v, command: %v %v", workingDir, commandName, commandArguments)

	quiesce()
	if err = command.Start(); err != nil {
		log.Error("error occurred starting the command", err)
		exitCode = 1
		return
	}

	signal := timeoutSignal{}

	cancelled := make(chan bool, 1)
	go func() {
		cancelState := cancelFlag.Wait()
		if cancelFlag.Canceled() {
			cancelled <- true
			log.Debug("Cancel flag set to cancelled")
		}
		log.Debugf("Cancel flag set to %v", cancelState)
	}()

	done := make(chan error, 1)
	go func() {
		done <- command.Wait()
	}()

	select {
	case <-time.After(time.Duration(executionTimeout) * time.Second):
		stopStdout <- true
		stopStderr <- true
		if err = killProcess(command.Process, &signal); err != nil {
			exitCode = 1
			log.Error(err)
		} else {
			// set appropriate exit code based on timeout
			exitCode = appconfig.CommandStoppedPreemptivelyExitCode
			err = &exec.ExitError{Stderr: []byte("Process timed out")}
			log.Infof("The execution of command was timedout.")
		}
	case <-cancelled:
		// task has been asked to cancel, kill process
		log.Debug("Process cancelled. Attempting to stop process.")
		stopStdout <- true
		stopStderr <- true
		if err = killProcess(command.Process, &signal); err != nil {
			exitCode = 1
			log.Error(err)
		} else {
			// set appropriate exit code based on cancel
			exitCode = appconfig.CommandStoppedPreemptivelyExitCode
			err = &exec.ExitError{Stderr: []byte("Cancelled process")}
			log.Infof("The execution of command was cancelled.")
		}
	case err = <-done:
		log.Debug("Process completed.")
		if err != nil {
			exitCode = 1
			log.Debugf("command returned error %v", err)
			if exiterr, ok := err.(*exec.ExitError); ok {
				// The program has exited with an exit code != 0
				if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
					exitCode = status.ExitStatus()

					if signal.execInterruptedOnWindows {
						log.Debug("command interrupted by cancel or timeout")
						exitCode = -1
					}

					// First try to handle Cancel and Timeout scenarios
					// SIGKILL will result in an exitcode of -1
					if exitCode == -1 {
						if cancelFlag.Canceled() {
							// set appropriate exit code based on cancel or timeout
							exitCode = appconfig.CommandStoppedPreemptivelyExitCode
							log.Infof("The execution of command was cancelled.")
						}
					} else {
						log.Infof("The execution of command returned Exit Status: %d", exitCode)
					}
				}
			}
		} else {
			// check if cancellation or timeout failed to kill the process
			// This will not occur as we do a SIGKILL, which is not recoverable.
			if cancelFlag.Canceled() {
				// This is when the cancellation failed and the command completed successfully
				log.Errorf("the cancellation failed to stop the process.")
				// do not return as the command could have been cancelled and also timedout
			}
		}
	}
	return
}