package command import ( "bytes" "fmt" "io" "os" "os/exec" "sync" "github.com/pkg/errors" ) // Command defines the information required for executing a call to any executable type Command struct { dir string Stdout io.Writer Stderr io.Writer } // Dir sets the working directory for the execution func (c *Command) Dir(d string) { c.dir = d } // ExecCommand defines how to execute os commands var ExecCommand = exec.Command // RunShell runs the specified command on the shell func (c *Command) RunShell(shell, script string) error { _out, _err := prepareOut(c.Stdout, c.Stderr) cmd := ExecCommand(shell) cmd.Dir = c.dir in := bytes.Buffer{} in.Write([]byte(script)) cmd.Stdin = &in if err := runCmd(cmd, _out, _err); err != nil { return errors.Wrapf(err, "running shell script failed with %v", shell) } return nil } // RunExecutable runs the specified executable with parameters func (c *Command) RunExecutable(executable string, params ...string) error { _out, _err := prepareOut(c.Stdout, c.Stderr) cmd := ExecCommand(executable, params...) if len(c.dir) > 0 { cmd.Dir = c.dir } if err := runCmd(cmd, _out, _err); err != nil { return errors.Wrapf(err, "running command '%v' failed", executable) } return nil } func runCmd(cmd *exec.Cmd, _out, _err io.Writer) error { stdout, stderr, err := cmdPipes(cmd) if err != nil { return errors.Wrap(err, "getting commmand pipes failed") } err = cmd.Start() if err != nil { return errors.Wrap(err, "starting command failed") } var wg sync.WaitGroup wg.Add(2) var errStdout, errStderr error go func() { _, errStdout = io.Copy(_out, stdout) wg.Done() }() go func() { _, errStderr = io.Copy(_err, stderr) wg.Done() }() wg.Wait() err = cmd.Wait() if err != nil { return errors.Wrap(err, "cmd.Run() failed") } if errStdout != nil || errStderr != nil { return fmt.Errorf("failed to capture stdout/stderr: '%v'/'%v'", errStdout, errStderr) } return nil } func prepareOut(stdout, stderr io.Writer) (io.Writer, io.Writer) { //ToDo: check use of multiwriter instead to always write into os.Stdout and os.Stdin? //stdout := io.MultiWriter(os.Stdout, &stdoutBuf) //stderr := io.MultiWriter(os.Stderr, &stderrBuf) if stdout == nil { stdout = os.Stdout } if stderr == nil { stderr = os.Stderr } return stdout, stderr } func cmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) { stdout, err := cmd.StdoutPipe() if err != nil { return nil, nil, errors.Wrap(err, "getting Stdout pipe failed") } stderr, err := cmd.StderrPipe() if err != nil { return nil, nil, errors.Wrap(err, "getting Stderr pipe failed") } return stdout, stderr, nil }