Alex Ellis (OpenFaaS Ltd) 2685c1db06 Upgrade to go-execute/v2
Upgrades to go-execute/v2 and updates various other
dependencies.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2023-10-23 11:02:37 +01:00

194 lines
4.7 KiB
Go
Generated

package execute
import (
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
type ExecTask struct {
// Command is the command to execute. This can be the path to an executable
// or the executable with arguments. The arguments are detected by looking for
// a space.
//
// Any arguments must be given via Args
Command string
// Args are the arguments to pass to the command. These are ignored if the
// Command contains arguments.
Args []string
// Shell run the command in a bash shell.
// Note that the system must have `bash` installed in the PATH or in /bin/bash
Shell bool
// Env is a list of environment variables to add to the current environment,
// these are used to override any existing environment variables.
Env []string
// Cwd is the working directory for the command
Cwd string
// Stdin connect a reader to stdin for the command
// being executed.
Stdin io.Reader
// PrintCommand prints the command before executing
PrintCommand bool
// StreamStdio prints stdout and stderr directly to os.Stdout/err as
// the command runs.
StreamStdio bool
// DisableStdioBuffer prevents any output from being saved in the
// TaskResult, which is useful for when the result is very large, or
// when you want to stream the output to another writer exclusively.
DisableStdioBuffer bool
// StdoutWriter when set will receive a copy of stdout from the command
StdOutWriter io.Writer
// StderrWriter when set will receive a copy of stderr from the command
StdErrWriter io.Writer
}
type ExecResult struct {
Stdout string
Stderr string
ExitCode int
Cancelled bool
}
func (et ExecTask) Execute(ctx context.Context) (ExecResult, error) {
argsSt := ""
if len(et.Args) > 0 {
argsSt = strings.Join(et.Args, " ")
}
if et.PrintCommand {
fmt.Println("exec: ", et.Command, argsSt)
}
// don't try to run if the context is already cancelled
if ctx.Err() != nil {
return ExecResult{
// the exec package returns -1 for cancelled commands
ExitCode: -1,
Cancelled: ctx.Err() == context.Canceled,
}, ctx.Err()
}
var command string
var commandArgs []string
if et.Shell {
// On a NixOS system, /bin/bash doesn't exist at /bin/bash
// the default behavior of exec.Command is to look for the
// executable in PATH.
command = "bash"
// There is a chance that PATH is not populate or propagated, therefore
// when bash cannot be resolved, set it to /bin/bash instead.
if _, err := exec.LookPath(command); err != nil {
command = "/bin/bash"
}
if len(et.Args) == 0 {
// use Split and Join to remove any extra whitespace?
startArgs := strings.Split(et.Command, " ")
script := strings.Join(startArgs, " ")
commandArgs = append([]string{"-c"}, script)
} else {
script := strings.Join(et.Args, " ")
commandArgs = append([]string{"-c"}, fmt.Sprintf("%s %s", et.Command, script))
}
} else {
command = et.Command
commandArgs = et.Args
// AE: This had to be removed to fix: #117 where Windows users
// have spaces in their paths, which are misinterpreted as
// arguments for the command.
// if strings.Contains(et.Command, " ") {
// parts := strings.Split(et.Command, " ")
// command = parts[0]
// commandArgs = parts[1:]
// }
}
cmd := exec.CommandContext(ctx, command, commandArgs...)
cmd.Dir = et.Cwd
if len(et.Env) > 0 {
overrides := map[string]bool{}
for _, env := range et.Env {
key := strings.Split(env, "=")[0]
overrides[key] = true
cmd.Env = append(cmd.Env, env)
}
for _, env := range os.Environ() {
key := strings.Split(env, "=")[0]
if _, ok := overrides[key]; !ok {
cmd.Env = append(cmd.Env, env)
}
}
}
if et.Stdin != nil {
cmd.Stdin = et.Stdin
}
stdoutBuff := bytes.Buffer{}
stderrBuff := bytes.Buffer{}
var stdoutWriters []io.Writer
var stderrWriters []io.Writer
if !et.DisableStdioBuffer {
stdoutWriters = append(stdoutWriters, &stdoutBuff)
stderrWriters = append(stderrWriters, &stderrBuff)
}
if et.StreamStdio {
stdoutWriters = append(stdoutWriters, os.Stdout)
stderrWriters = append(stderrWriters, os.Stderr)
}
if et.StdOutWriter != nil {
stdoutWriters = append(stdoutWriters, et.StdOutWriter)
}
if et.StdErrWriter != nil {
stderrWriters = append(stderrWriters, et.StdErrWriter)
}
cmd.Stdout = io.MultiWriter(stdoutWriters...)
cmd.Stderr = io.MultiWriter(stderrWriters...)
startErr := cmd.Start()
if startErr != nil {
return ExecResult{}, startErr
}
exitCode := 0
execErr := cmd.Wait()
if execErr != nil {
if exitError, ok := execErr.(*exec.ExitError); ok {
exitCode = exitError.ExitCode()
}
}
return ExecResult{
Stdout: stdoutBuff.String(),
Stderr: stderrBuff.String(),
ExitCode: exitCode,
Cancelled: ctx.Err() == context.Canceled,
}, ctx.Err()
}