github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/cmd.go (about) 1 // Copyright 2020 (c) Cognizant Digital Business, Evolutionary AI. All rights reserved. Issued under the Apache 2.0 License. 2 3 package runner 4 5 import ( 6 "bufio" 7 "context" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path" 13 "path/filepath" 14 "time" 15 16 "github.com/go-stack/stack" 17 "github.com/jjeffery/kv" 18 ) 19 20 // CmdRun contains a function that will run a bash script without argument and will pass results back using 21 // the channel supplied by the caller. Exit codes will be communicated via the err return. The output 22 // channel will be closed on completion. 23 // 24 func CmdRun(ctx context.Context, bashScript string, output chan *string) (err kv.Error) { 25 26 script := filepath.Clean(bashScript) 27 defer close(output) 28 29 if _, errGo := os.Stat(script); os.IsNotExist(errGo) { 30 return kv.Wrap(errGo).With("script", script, "stack", stack.Trace().TrimRuntime()) 31 } 32 33 // Create a new TMPDIR because the python pip tends to leave dirt behind 34 // when doing pip builds etc 35 tmpDir, errGo := ioutil.TempDir("", "") 36 if errGo != nil { 37 return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 38 } 39 defer os.RemoveAll(tmpDir) 40 41 // Move to starting the process that we will monitor with the experiment running within 42 // it 43 44 // #nosec 45 cmd := exec.CommandContext(ctx, "/bin/bash", "-c", "export TMPDIR="+tmpDir+"; "+filepath.Clean(script)) 46 cmd.Dir = path.Dir(script) 47 48 stdout, errGo := cmd.StdoutPipe() 49 if errGo != nil { 50 return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 51 } 52 stderr, errGo := cmd.StderrPipe() 53 if errGo != nil { 54 return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 55 } 56 57 if errGo = cmd.Start(); errGo != nil { 58 return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 59 } 60 61 merged := io.MultiReader(stderr, stdout) 62 scanner := bufio.NewScanner(merged) 63 for scanner.Scan() { 64 aLine := scanner.Text()[:] 65 select { 66 case output <- &aLine: 67 continue 68 case <-time.After(time.Second): 69 continue 70 } 71 } 72 73 // Wait for the process to exit, and store any error code if possible 74 // before we continue to wait on the processes output devices finishing 75 if errGo = cmd.Wait(); errGo != nil { 76 if err == nil { 77 err = kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 78 } 79 } 80 81 return err 82 }