github.com/kubeshop/testkube@v1.17.23/pkg/process/exec.go (about)

     1  package process
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os/exec"
     8  	"strings"
     9  )
    10  
    11  type Options struct {
    12  	Command string
    13  	Args    []string
    14  	DryRun  bool
    15  }
    16  
    17  // Execute runs system command and returns whole output also in case of error
    18  func ExecuteWithOptions(options Options) (out []byte, err error) {
    19  	if options.DryRun {
    20  		fmt.Println("$ " + strings.Join(append([]string{options.Command}, options.Args...), " "))
    21  		return []byte{}, nil
    22  	}
    23  	return ExecuteInDir("", options.Command, options.Args...)
    24  }
    25  
    26  // Execute runs system command and returns whole output also in case of error
    27  func Execute(command string, arguments ...string) (out []byte, err error) {
    28  	return ExecuteInDir("", command, arguments...)
    29  }
    30  
    31  // ExecuteInDir runs system command and returns whole output also in case of error in a specific directory
    32  func ExecuteInDir(dir string, command string, arguments ...string) (out []byte, err error) {
    33  	cmd := exec.Command(command, arguments...)
    34  	if dir != "" {
    35  		cmd.Dir = dir
    36  	}
    37  
    38  	buffer := new(bytes.Buffer)
    39  	cmd.Stdout = buffer
    40  	cmd.Stderr = buffer
    41  
    42  	if err = cmd.Start(); err != nil {
    43  		rErr := fmt.Errorf("could not start process with command: %s  error: %w\noutput: %s", command, err, buffer.String())
    44  		if cmd.ProcessState != nil {
    45  			rErr = fmt.Errorf("could not start process with command: %s, exited with code:%d  error: %w\noutput: %s", command, cmd.ProcessState.ExitCode(), err, buffer.String())
    46  		}
    47  		return buffer.Bytes(), rErr
    48  	}
    49  
    50  	if err = cmd.Wait(); err != nil {
    51  		// TODO clean error output (currently it has buffer too - need to refactor in cmd)
    52  		rErr := fmt.Errorf("could not start process with command: %s  error: %w\noutput: %s", command, err, buffer.String())
    53  		if cmd.ProcessState != nil {
    54  			rErr = fmt.Errorf("could not start process with command: %s, exited with code:%d  error: %w\noutput: %s", command, cmd.ProcessState.ExitCode(), err, buffer.String())
    55  		}
    56  		return buffer.Bytes(), rErr
    57  	}
    58  
    59  	return buffer.Bytes(), nil
    60  }
    61  
    62  // LoggedExecuteInDir runs system command and returns whole output also in case of error in a specific directory with logging to writer
    63  func LoggedExecuteInDir(dir string, writer io.Writer, command string, arguments ...string) (out []byte, err error) {
    64  	cmd := exec.Command(command, arguments...)
    65  	if dir != "" {
    66  		cmd.Dir = dir
    67  	}
    68  	buffer := new(bytes.Buffer)
    69  
    70  	// set multiwriter write to writer and to buffer in parallel
    71  	w := io.MultiWriter(buffer, writer)
    72  	cmd.Stdout = w
    73  	cmd.Stderr = w
    74  
    75  	if err = cmd.Start(); err != nil {
    76  		rErr := fmt.Errorf("could not start process with command: %s  error: %w\noutput: %s", command, err, buffer.String())
    77  		if cmd.ProcessState != nil {
    78  			rErr = fmt.Errorf("could not start process with command: %s, exited with code:%d  error: %w\noutput: %s", command, cmd.ProcessState.ExitCode(), err, buffer.String())
    79  		}
    80  		return buffer.Bytes(), rErr
    81  	}
    82  
    83  	if err = cmd.Wait(); err != nil {
    84  		rErr := fmt.Errorf("process started with command: %s  error: %w\noutput: %s", command, err, buffer.String())
    85  		if cmd.ProcessState != nil {
    86  			rErr = fmt.Errorf("process started with command: %s, exited with code:%d  error: %w\noutput: %s", command, cmd.ProcessState.ExitCode(), err, buffer.String())
    87  		}
    88  		return buffer.Bytes(), rErr
    89  	}
    90  
    91  	return buffer.Bytes(), nil
    92  }
    93  
    94  // ExecuteAsync runs system command and doesn't wait when it's completed
    95  func ExecuteAsync(command string, arguments ...string) (cmd *exec.Cmd, err error) {
    96  	return ExecuteAsyncInDir("", command, arguments...)
    97  }
    98  
    99  // ExecuteAsyncInDir runs system command and doesn't wait when it's completed for specific directory
   100  func ExecuteAsyncInDir(dir string, command string, arguments ...string) (cmd *exec.Cmd, err error) {
   101  	cmd = exec.Command(command, arguments...)
   102  	if dir != "" {
   103  		cmd.Dir = dir
   104  	}
   105  
   106  	if err = cmd.Start(); err != nil {
   107  		output := ""
   108  		out, errOut := cmd.Output()
   109  		if errOut == nil {
   110  			output = string(out)
   111  		}
   112  		rErr := fmt.Errorf("process started with command: %s  error: %w\noutput: %s", command, err, output)
   113  		if cmd.ProcessState != nil {
   114  			rErr = fmt.Errorf("process started with command: %s, exited with code:%d  error: %w\noutput: %s", command, cmd.ProcessState.ExitCode(), err, output)
   115  		}
   116  		return cmd, rErr
   117  	}
   118  
   119  	return cmd, nil
   120  }
   121  
   122  // ExecuteString executes string based command
   123  func ExecuteString(command string) (out []byte, err error) {
   124  	parts := strings.Split(command, " ")
   125  	if len(parts) == 1 {
   126  		out, err = Execute(parts[0])
   127  	} else if len(parts) > 1 {
   128  		out, err = Execute(parts[0], parts[1:]...)
   129  	} else {
   130  		return out, fmt.Errorf("invalid command to run '%s'", command)
   131  	}
   132  
   133  	if err != nil {
   134  		return out, fmt.Errorf("error: %w, output: %s", err, out)
   135  	}
   136  
   137  	return out, nil
   138  }