github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/exec/exec.go (about) 1 package exec 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "os/exec" 10 "strings" 11 12 "github.com/hashicorp/otto/ui" 13 ) 14 15 // Run runs the given command and streams all the output to the 16 // given UI. It also connects stdin properly so that input works as 17 // expected. 18 func Run(uiVal ui.Ui, cmd *exec.Cmd) error { 19 out_r, out_w := io.Pipe() 20 cmd.Stdin = os.Stdin 21 cmd.Stdout = out_w 22 cmd.Stderr = out_w 23 24 // Copy output to the UI until we can't. 25 output := false 26 uiDone := make(chan struct{}) 27 go func() { 28 defer close(uiDone) 29 var buf [1024]byte 30 for { 31 n, err := out_r.Read(buf[:]) 32 if n > 0 { 33 output = true 34 uiVal.Raw(string(buf[:n])) 35 } 36 37 // We just break on any error. io.EOF is not an error and 38 // is our true exit case, but any other error we don't really 39 // handle here. It probably means something went wrong 40 // somewhere else anyways. 41 if err != nil { 42 break 43 } 44 } 45 }() 46 47 // Run the command 48 log.Printf("[DEBUG] execDir: %s", cmd.Dir) 49 log.Printf("[DEBUG] exec: %s %s", cmd.Path, strings.Join(cmd.Args[1:], " ")) 50 51 // Build a runnable command that we can log out to make things easier 52 // for debugging. This lets debuging devs just copy and paste the command. 53 logRunnableCommand(cmd) 54 55 // Run 56 err := Runner(cmd) 57 58 // Wait for all the output to finish 59 out_w.Close() 60 <-uiDone 61 62 if output { 63 // Output one extra newline to separate output from Otto. We only 64 // do this if there was any output to begin with. 65 uiVal.Message("") 66 } 67 68 // Return the output from the command 69 return err 70 } 71 72 // OttoSkipCleanupEnvVar, when set, tells Otto to avoid cleaning up its 73 // temporary workspace files, which can be helpful for debugging. 74 const OttoSkipCleanupEnvVar = "OTTO_SKIP_CLEANUP" 75 76 // ShouldCleanup returns true for normal operation. It returns false if the 77 // user requested that Otto avoid cleaning up its temporary files for 78 // debugging purposes. 79 func ShouldCleanup() bool { 80 return os.Getenv(OttoSkipCleanupEnvVar) == "" 81 } 82 83 func logRunnableCommand(cmd *exec.Cmd) { 84 // Create the struct of the global available environment variables 85 // 86 // We could probably cache this, but I'm not sure this really matters. 87 // The bottleneck should always be subprocess execution and not this. 88 global := make(map[string]struct{}) 89 for _, env := range os.Environ() { 90 idx := strings.Index(env, "=") 91 if idx == -1 { 92 panic("env var doesn't contain = " + env) 93 } 94 95 global[env[:idx]] = struct{}{} 96 } 97 98 // Build the env vars 99 var debugBuf bytes.Buffer 100 for _, env := range cmd.Env { 101 // Split the env var, if we have it globally, then don't put it 102 // in the output in order to save some sanity. 103 parts := strings.SplitN(env, "=", 2) 104 if _, ok := global[parts[0]]; ok { 105 continue 106 } 107 108 debugBuf.WriteString(fmt.Sprintf("%s=%q ", parts[0], parts[1])) 109 } 110 111 // Write the command and the arguments 112 debugBuf.WriteString(cmd.Path + " ") 113 for _, arg := range cmd.Args[1:] { 114 // If the argument contains a space, then we want to quote it 115 if strings.Contains(arg, " ") { 116 debugBuf.WriteString(fmt.Sprintf("'%s' ", arg)) 117 } else { 118 debugBuf.WriteString(fmt.Sprintf("%s ", arg)) 119 } 120 } 121 122 log.Printf("[DEBUG] exec runnable: %s", debugBuf.String()) 123 }