github.com/sneal/packer@v0.5.2/builder/docker/exec.go (about) 1 package docker 2 3 import ( 4 "fmt" 5 "github.com/mitchellh/iochan" 6 "github.com/mitchellh/packer/packer" 7 "io" 8 "log" 9 "os/exec" 10 "regexp" 11 "strings" 12 "sync" 13 "syscall" 14 ) 15 16 func runAndStream(cmd *exec.Cmd, ui packer.Ui) error { 17 stdout_r, stdout_w := io.Pipe() 18 stderr_r, stderr_w := io.Pipe() 19 defer stdout_w.Close() 20 defer stderr_w.Close() 21 22 log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:]) 23 cmd.Stdout = stdout_w 24 cmd.Stderr = stderr_w 25 if err := cmd.Start(); err != nil { 26 return err 27 } 28 29 // Create the channels we'll use for data 30 exitCh := make(chan int, 1) 31 stdoutCh := iochan.DelimReader(stdout_r, '\n') 32 stderrCh := iochan.DelimReader(stderr_r, '\n') 33 34 // Start the goroutine to watch for the exit 35 go func() { 36 defer stdout_w.Close() 37 defer stderr_w.Close() 38 exitStatus := 0 39 40 err := cmd.Wait() 41 if exitErr, ok := err.(*exec.ExitError); ok { 42 exitStatus = 1 43 44 // There is no process-independent way to get the REAL 45 // exit status so we just try to go deeper. 46 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 47 exitStatus = status.ExitStatus() 48 } 49 } 50 51 exitCh <- exitStatus 52 }() 53 54 // This waitgroup waits for the streaming to end 55 var streamWg sync.WaitGroup 56 streamWg.Add(2) 57 58 streamFunc := func(ch <-chan string) { 59 defer streamWg.Done() 60 61 for data := range ch { 62 data = cleanOutputLine(data) 63 if data != "" { 64 ui.Message(data) 65 } 66 } 67 } 68 69 // Stream stderr/stdout 70 go streamFunc(stderrCh) 71 go streamFunc(stdoutCh) 72 73 // Wait for the process to end and then wait for the streaming to end 74 exitStatus := <-exitCh 75 streamWg.Wait() 76 77 if exitStatus != 0 { 78 return fmt.Errorf("Bad exit status: %d", exitStatus) 79 } 80 81 return nil 82 } 83 84 // cleanOutputLine cleans up a line so that '\r' don't muck up the 85 // UI output when we're reading from a remote command. 86 func cleanOutputLine(line string) string { 87 // Build a regular expression that will get rid of shell codes 88 re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]") 89 line = re.ReplaceAllString(line, "") 90 91 // Trim surrounding whitespace 92 line = strings.TrimSpace(line) 93 94 // Trim up to the first carriage return, since that text would be 95 // lost anyways. 96 idx := strings.LastIndex(line, "\r") 97 if idx > -1 { 98 line = line[idx+1:] 99 } 100 101 return line 102 }