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 }