Merge pull request #9845 from hashicorp/run_and_stream_helper_func
move runAndStream into a generalized helper function to enable easy l…
This commit is contained in:
commit
dfe8aa51b0
|
@ -1,114 +1,26 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/packer/common/iochan"
|
||||
"github.com/hashicorp/packer/helper/builder/localexec"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
|
||||
stdout_r, stdout_w := io.Pipe()
|
||||
stderr_r, stderr_w := io.Pipe()
|
||||
defer stdout_w.Close()
|
||||
defer stderr_w.Close()
|
||||
|
||||
args := make([]string, len(cmd.Args)-1)
|
||||
copy(args, cmd.Args[1:])
|
||||
|
||||
// Scrub password from the log output.
|
||||
capturedPassword := ""
|
||||
for i, v := range args {
|
||||
if v == "-p" || v == "--password" {
|
||||
args[i+1] = "<Filtered>"
|
||||
capturedPassword = args[i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// run local command and stream output to UI.
|
||||
return localexec.RunAndStream(cmd, ui, []string{capturedPassword})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package docker
|
||||
package localexec
|
||||
|
||||
import (
|
||||
"testing"
|
Loading…
Reference in New Issue