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
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer/common/iochan"
|
"github.com/hashicorp/packer/helper/builder/localexec"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
|
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)
|
args := make([]string, len(cmd.Args)-1)
|
||||||
copy(args, cmd.Args[1:])
|
copy(args, cmd.Args[1:])
|
||||||
|
|
||||||
// Scrub password from the log output.
|
// Scrub password from the log output.
|
||||||
|
capturedPassword := ""
|
||||||
for i, v := range args {
|
for i, v := range args {
|
||||||
if v == "-p" || v == "--password" {
|
if v == "-p" || v == "--password" {
|
||||||
args[i+1] = "<Filtered>"
|
capturedPassword = args[i+1]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Executing: %s %v", cmd.Path, args)
|
// run local command and stream output to UI.
|
||||||
cmd.Stdout = stdout_w
|
return localexec.RunAndStream(cmd, ui, []string{capturedPassword})
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
import (
|
||||||
"testing"
|
"testing"
|
Loading…
Reference in New Issue