// Run will execute the command in workingDir, by applying the expansions to // the script and then invoking it with sh -c, and logging all of the command's // stdout/stderr using the Logger. // It will block until the command either finishes, or is aborted prematurely // via the kill channel. func (ac *AgentCommand) Run(workingDir string) error { ac.LogTask(slogger.INFO, "Running script task for command \n%v in directory %v", ac.ScriptLine, workingDir) logWriterInfo := &evergreen.LoggingWriter{ac.Task, slogger.INFO} logWriterErr := &evergreen.LoggingWriter{ac.Task, slogger.ERROR} ignoreErrors := false if strings.HasPrefix(ac.ScriptLine, "-") { ac.ScriptLine = ac.ScriptLine[1:] ignoreErrors = true } outBufferWriter := util.NewLineBufferingWriter(logWriterInfo) errorBufferWriter := util.NewLineBufferingWriter(logWriterErr) defer outBufferWriter.Flush() defer errorBufferWriter.Flush() cmd := &command.LocalCommand{ CmdString: ac.ScriptLine, WorkingDirectory: workingDir, Stdout: outBufferWriter, Stderr: errorBufferWriter, Environment: os.Environ(), } err := cmd.PrepToRun(ac.Expansions) if err != nil { ac.LogTask(slogger.ERROR, "Failed to prepare command: %v", err) } ac.LogTask(slogger.INFO, "Running command (expanded): %v", cmd.CmdString) doneStatus := make(chan error) go func() { err := cmd.Run() doneStatus <- err }() select { case err = <-doneStatus: if ignoreErrors { return nil } else { return err } case _ = <-ac.KillChan: // try and kill the process ac.LogExecution(slogger.INFO, "Got kill signal, stopping process: %v", cmd.Cmd.Process.Pid) if err := cmd.Stop(); err != nil { ac.LogExecution(slogger.ERROR, "Error occurred stopping process: %v", err) } return InterruptedCmdError } return err }
// Execute starts the shell with its given parameters. func (self *ShellExecCommand) Execute(pluginLogger plugin.Logger, pluginCom plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error { pluginLogger.LogExecution(slogger.DEBUG, "Preparing script...") logWriterInfo := pluginLogger.GetTaskLogWriter(slogger.INFO) logWriterErr := pluginLogger.GetTaskLogWriter(slogger.ERROR) outBufferWriter := util.NewLineBufferingWriter(logWriterInfo) errorBufferWriter := util.NewLineBufferingWriter(logWriterErr) defer outBufferWriter.Flush() defer errorBufferWriter.Flush() localCmd := &command.LocalCommand{ CmdString: self.Script, Stdout: outBufferWriter, Stderr: errorBufferWriter, ScriptMode: true, } if self.WorkingDir != "" { localCmd.WorkingDirectory = filepath.Join(conf.WorkDir, self.WorkingDir) } else { localCmd.WorkingDirectory = conf.WorkDir } err := localCmd.PrepToRun(conf.Expansions) if err != nil { return fmt.Errorf("Failed to apply expansions: %v", err) } if self.Silent { pluginLogger.LogExecution(slogger.INFO, "Executing script (source hidden)...") } else { pluginLogger.LogExecution(slogger.INFO, "Executing script: %v", localCmd.CmdString) } doneStatus := make(chan error) go func() { var err error env := os.Environ() env = append(env, fmt.Sprintf("EVR_TASK_ID=%v", conf.Task.Id)) env = append(env, fmt.Sprintf("EVR_AGENT_PID=%v", os.Getpid())) localCmd.Environment = env err = localCmd.Start() if err == nil { pluginLogger.LogSystem(slogger.DEBUG, "spawned shell process with pid %v", localCmd.Cmd.Process.Pid) // Call the platform's process-tracking function. On some OSes this will be a noop, // on others this may need to do some additional work to track the process so that // it can be cleaned up later. if trackedTask != "" && trackedTask == conf.Task.Id { trackProcess(conf.Task.Id, localCmd.Cmd.Process.Pid, pluginLogger) } if !self.Background { err = localCmd.Cmd.Wait() } } else { pluginLogger.LogSystem(slogger.DEBUG, "error spawning shell process: %v", err) } doneStatus <- err }() defer pluginLogger.Flush() select { case err = <-doneStatus: if err != nil { if self.ContinueOnError { pluginLogger.LogExecution(slogger.INFO, "(ignoring) Script finished with error: %v", err) return nil } else { pluginLogger.LogExecution(slogger.INFO, "Script finished with error: %v", err) return err } } else { pluginLogger.LogExecution(slogger.INFO, "Script execution complete.") } case <-stop: pluginLogger.LogExecution(slogger.INFO, "Got kill signal") // need to check command has started if localCmd.Cmd != nil { pluginLogger.LogExecution(slogger.INFO, "Stopping process: %v", localCmd.Cmd.Process.Pid) // try and stop the process if err := localCmd.Stop(); err != nil { pluginLogger.LogExecution(slogger.ERROR, "Error occurred stopping process: %v", err) } } return fmt.Errorf("Shell command interrupted.") } return nil }