110 lines
2.4 KiB
Go
110 lines
2.4 KiB
Go
package localexec
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/hashicorp/packer/common/iochan"
|
|
"github.com/hashicorp/packer/packer"
|
|
)
|
|
|
|
func RunAndStream(cmd *exec.Cmd, ui packer.Ui, sensitive []string) error {
|
|
stdout_r, stdout_w := io.Pipe()
|
|
stderr_r, stderr_w := io.Pipe()
|
|
defer stdout_w.Close()
|
|
defer stderr_w.Close()
|
|
|
|
// Scrub any sensitive values from being printed to Packer ui.
|
|
packer.LogSecretFilter.Set(sensitive...)
|
|
|
|
args := make([]string, len(cmd.Args)-1)
|
|
copy(args, cmd.Args[1:])
|
|
|
|
log.Printf("Executing: %s %v", cmd.Path, args)
|
|
cmd.Stdout = stdout_w
|
|
cmd.Stderr = stderr_w
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the channels we'll use for data
|
|
exitCh := make(chan int, 1)
|
|
stdoutCh := iochan.LineReader(stdout_r)
|
|
stderrCh := iochan.LineReader(stderr_r)
|
|
|
|
// Start the goroutine to watch for the exit
|
|
go func() {
|
|
defer stdout_w.Close()
|
|
defer stderr_w.Close()
|
|
exitStatus := 0
|
|
|
|
err := cmd.Wait()
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
exitStatus = 1
|
|
|
|
// There is no process-independent way to get the REAL
|
|
// exit status so we just try to go deeper.
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
exitStatus = status.ExitStatus()
|
|
}
|
|
}
|
|
|
|
exitCh <- exitStatus
|
|
}()
|
|
|
|
// This waitgroup waits for the streaming to end
|
|
var streamWg sync.WaitGroup
|
|
streamWg.Add(2)
|
|
|
|
streamFunc := func(ch <-chan string) {
|
|
defer streamWg.Done()
|
|
|
|
for data := range ch {
|
|
data = cleanOutputLine(data)
|
|
if data != "" {
|
|
ui.Message(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stream stderr/stdout
|
|
go streamFunc(stderrCh)
|
|
go streamFunc(stdoutCh)
|
|
|
|
// Wait for the process to end and then wait for the streaming to end
|
|
exitStatus := <-exitCh
|
|
streamWg.Wait()
|
|
|
|
if exitStatus != 0 {
|
|
return fmt.Errorf("Bad exit status: %d", exitStatus)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// cleanOutputLine cleans up a line so that '\r' don't muck up the
|
|
// UI output when we're reading from a remote command.
|
|
func cleanOutputLine(line string) string {
|
|
// Build a regular expression that will get rid of shell codes
|
|
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
|
|
line = re.ReplaceAllString(line, "")
|
|
|
|
// Trim surrounding whitespace
|
|
line = strings.TrimSpace(line)
|
|
|
|
// Trim up to the first carriage return, since that text would be
|
|
// lost anyways.
|
|
idx := strings.LastIndex(line, "\r")
|
|
if idx > -1 {
|
|
line = line[idx+1:]
|
|
}
|
|
|
|
return line
|
|
}
|