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 }