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  }