github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/goutils/exec/exec.go (about)

     1  /*
     2   * Copyright (c) 2023-present unTill Software Development Group B. V.  and Contributors
     3   * @author Maxim Geraskin
     4   *
     5   * This source code is licensed under the MIT license found in the
     6   * LICENSE file in the root directory of this source tree.
     7   */
     8  
     9  /*
    10  Links
    11  
    12  - https://stackoverflow.com/questions/25190971/golang-copy-exec-output-to-log
    13  
    14  */
    15  
    16  package exec
    17  
    18  import (
    19  	"bytes"
    20  	"errors"
    21  	"io"
    22  	"os"
    23  	"os/exec"
    24  	"sync"
    25  
    26  	"github.com/voedger/voedger/pkg/goutils/logger"
    27  )
    28  
    29  // https://github.com/b4b4r07/go-pipe/blob/master/README.md
    30  
    31  // PipedExec allows to execute commands in pipe
    32  type PipedExec struct {
    33  	cmds []*pipedCmd
    34  }
    35  
    36  // Stderr redirection
    37  const (
    38  	StderrRedirectNone = iota
    39  	StderrRedirectStdout
    40  	StderrRedirectNull
    41  )
    42  
    43  type pipedCmd struct {
    44  	stderrRedirection int
    45  	cmd               *exec.Cmd
    46  }
    47  
    48  func (pe *PipedExec) command(name string, stderrRedirection int, args ...string) *PipedExec {
    49  	cmd := exec.Command(name, args...)
    50  	lastIdx := len(pe.cmds) - 1
    51  	if lastIdx > -1 {
    52  		var err error
    53  		cmd.Stdin, err = pe.cmds[lastIdx].cmd.StdoutPipe()
    54  		// notest
    55  		if err != nil {
    56  			panic(err)
    57  		}
    58  	} else {
    59  		cmd.Stdin = os.Stdin
    60  	}
    61  	pe.cmds = append(pe.cmds, &pipedCmd{stderrRedirection, cmd})
    62  	return pe
    63  }
    64  
    65  // GetCmd returns cmd with given index
    66  func (pe *PipedExec) GetCmd(idx int) *exec.Cmd {
    67  	return pe.cmds[idx].cmd
    68  }
    69  
    70  // Command adds a command to a pipe
    71  func (pe *PipedExec) Command(name string, args ...string) *PipedExec {
    72  	return pe.command(name, StderrRedirectNone, args...)
    73  }
    74  
    75  // WorkingDir sets working directory for the last command
    76  func (pe *PipedExec) WorkingDir(wd string) *PipedExec {
    77  	pipedCmd := pe.cmds[len(pe.cmds)-1]
    78  	pipedCmd.cmd.Dir = wd
    79  	return pe
    80  }
    81  
    82  // Wait until all cmds finish
    83  func (pe *PipedExec) Wait() error {
    84  	var firstErr error
    85  	for _, cmd := range pe.cmds {
    86  		err := cmd.cmd.Wait()
    87  		if nil != err && firstErr == nil {
    88  			firstErr = err
    89  		}
    90  	}
    91  	return firstErr
    92  }
    93  
    94  // Start all cmds
    95  func (pe *PipedExec) Start(out io.Writer, err io.Writer) error {
    96  	for _, cmd := range pe.cmds {
    97  		if cmd.stderrRedirection == StderrRedirectNone && nil != err {
    98  			cmd.cmd.Stderr = err
    99  		}
   100  	}
   101  	lastIdx := len(pe.cmds) - 1
   102  	if lastIdx < 0 {
   103  		return errors.New("empty command list")
   104  	}
   105  	if nil != out {
   106  		pe.cmds[lastIdx].cmd.Stdout = out
   107  	}
   108  
   109  	for _, cmd := range pe.cmds {
   110  		logger.Verbose(cmd.cmd.Path, cmd.cmd.Args)
   111  		err := cmd.cmd.Start()
   112  		if nil != err {
   113  			return err
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  // Run starts the pipe
   120  func (pe *PipedExec) Run(out io.Writer, err io.Writer) error {
   121  	e := pe.Start(out, err)
   122  	if nil != e {
   123  		return e
   124  	}
   125  	return pe.Wait()
   126  }
   127  
   128  // RunToStrings runs the pipe and saves outputs to strings
   129  func (pe *PipedExec) RunToStrings() (stdout string, stderr string, err error) {
   130  	// _, stdoutw := io.Pipe()
   131  	// _, stderrw := io.Pipe()
   132  
   133  	var wg sync.WaitGroup
   134  
   135  	wg.Add(2)
   136  
   137  	lastCmd := pe.cmds[len(pe.cmds)-1]
   138  	stdoutPipe, err := lastCmd.cmd.StdoutPipe()
   139  	// notest
   140  	if nil != err {
   141  		return "", "", err
   142  	}
   143  	stderrPipe, err := lastCmd.cmd.StderrPipe()
   144  	// notest
   145  	if nil != err {
   146  		return "", "", err
   147  	}
   148  
   149  	err = pe.Start(nil, nil)
   150  	if nil != err {
   151  		return "", "", err
   152  	}
   153  
   154  	go func() {
   155  		defer wg.Done()
   156  		buf := new(bytes.Buffer)
   157  		_, errr := buf.ReadFrom(stdoutPipe)
   158  		// notestdept
   159  		if nil != errr {
   160  			panic(errr)
   161  		}
   162  		stdout = buf.String()
   163  	}()
   164  
   165  	go func() {
   166  		defer wg.Done()
   167  		buf := new(bytes.Buffer)
   168  		_, errr := buf.ReadFrom(stderrPipe)
   169  		// notestdept
   170  		if nil != errr {
   171  			panic(errr)
   172  		}
   173  		stderr = buf.String()
   174  	}()
   175  
   176  	wg.Wait()
   177  
   178  	return stdout, stderr, pe.Wait()
   179  
   180  }