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  }