11package utils
22
33import (
4+ "bufio"
45 "bytes"
56 "context"
67 "encoding/base64"
78 "fmt"
9+ "io"
810 "os"
911 "os/exec"
1012 "strings"
13+ "time"
1114
1215 . "github.com/onsi/ginkgo/v2"
1316 . "github.com/onsi/gomega"
1417
15- "github.com/werf/werf/v2/pkg/logging"
18+ "github.com/werf/werf/v2/pkg/util/option"
19+ werfExec "github.com/werf/werf/v2/pkg/werf/exec"
1620)
1721
1822func RunCommand (ctx context.Context , dir , command string , args ... string ) ([]byte , error ) {
@@ -33,10 +37,14 @@ type RunCommandOptions struct {
3337 ToStdin string
3438 ShouldSucceed bool
3539 NoStderr bool
40+
41+ CancelOnOutput string
42+ CancelOnOutputTimeout time.Duration
3643}
3744
3845func RunCommandWithOptions (ctx context.Context , dir , command string , args []string , options RunCommandOptions ) ([]byte , error ) {
39- cmd := exec .CommandContext (logging .WithLogger (ctx ), command , args ... )
46+ cmd := exec .CommandContext (ctx , command , args ... )
47+ cmd = werfExec .PrepareGracefulCancellation (cmd )
4048
4149 if dir != "" {
4250 cmd .Dir = dir
@@ -45,29 +53,65 @@ func RunCommandWithOptions(ctx context.Context, dir, command string, args []stri
4553 cmd .Env = append (os .Environ (), options .ExtraEnv ... )
4654
4755 if options .ToStdin != "" {
48- var b bytes.Buffer
49- b .Write ([]byte (options .ToStdin ))
50- cmd .Stdin = & b
56+ cmd .Stdin = bytes .NewReader ([]byte (options .ToStdin ))
5157 }
5258
53- var res []byte
54- var err error
59+ stdout , err := cmd .StdoutPipe ()
60+ Expect (err ).To (Succeed ())
61+
62+ stderr , err := cmd .StderrPipe ()
63+ Expect (err ).To (Succeed ())
64+
65+ var outputReader io.Reader
66+
5567 if options .NoStderr {
56- res , err = cmd . Output ()
68+ outputReader = stdout
5769 } else {
58- res , err = cmd . CombinedOutput ( )
70+ outputReader = io . MultiReader ( stdout , stderr )
5971 }
6072
61- _ , _ = GinkgoWriter .Write (res )
73+ res := & bytes.Buffer {}
74+
75+ Expect (cmd .Start ()).To (Succeed ())
76+
77+ if options .CancelOnOutput != "" {
78+ copyReader := io .TeeReader (outputReader , res )
79+ waitForOutput (copyReader , options .CancelOnOutput , options .CancelOnOutputTimeout )
80+ Expect (cmd .Cancel ()).To (Succeed ())
81+ }
82+
83+ _ , err = io .Copy (res , outputReader )
84+ Expect (err ).To (Succeed ())
85+
86+ err = cmd .Wait ()
87+
88+ _ , _ = GinkgoWriter .Write (res .Bytes ())
6289
6390 if options .ShouldSucceed {
6491 errorDesc := fmt .Sprintf ("%[2]s %[3]s (dir: %[1]s)" , dir , command , strings .Join (args , " " ))
6592 Expect (err ).ShouldNot (HaveOccurred (), errorDesc )
6693 }
6794
68- return res , err
95+ return res . Bytes () , err
6996}
7097
7198func ShelloutPack (command string ) string {
7299 return fmt .Sprintf ("eval $(echo %s | base64 -d)" , base64 .StdEncoding .EncodeToString ([]byte (command )))
73100}
101+
102+ // waitForOutput waits for output and exits early or exits by timeout
103+ func waitForOutput (reader io.Reader , output string , timeout time.Duration ) {
104+ scanner := bufio .NewScanner (reader )
105+ tmr := time .NewTimer (option .ValueOrDefault (timeout , time .Minute ))
106+
107+ for scanner .Scan () {
108+ select {
109+ case <- tmr .C :
110+ return
111+ default :
112+ if strings .Contains (scanner .Text (), output ) {
113+ return
114+ }
115+ }
116+ }
117+ }
0 commit comments