File: exec.go

package info (click to toggle)
packer 1.3.4%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 8,324 kB
  • sloc: python: 619; sh: 557; makefile: 111
file content (114 lines) | stat: -rw-r--r-- 2,476 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package docker

import (
	"fmt"
	"io"
	"log"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"syscall"

	"github.com/hashicorp/packer/packer"
	"github.com/mitchellh/iochan"
)

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.
	for i, v := range args {
		if v == "-p" || v == "--password" {
			args[i+1] = "<Filtered>"
			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.DelimReader(stdout_r, '\n')
	stderrCh := iochan.DelimReader(stderr_r, '\n')

	// 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
}