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