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  }