-
Notifications
You must be signed in to change notification settings - Fork 0
/
command.go
146 lines (122 loc) · 3.2 KB
/
command.go
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package gar
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
)
type Command struct {
Command string // The command to run
Args []string // The args to pass to command
WorkingDir string // The directory where to run command
Env map[string]string // The environments variables added to command
Logger Logger // The logger to use fo command's output
}
func generateCommand(options Options, args ...string) Command {
return Command{
Command: options.AnsibleRunnerBinary,
Args: args,
WorkingDir: options.AnsibleRunnerDir,
Env: options.EnvVars,
Logger: options.Logger,
}
}
func runAnsibleRunnerCommand(options Options, args ...string) (string, error) {
cmd := generateCommand(options, args...)
description := fmt.Sprintf("%s %v", options.AnsibleRunnerBinary, args)
return runCommandAndGetOutput(description, cmd)
}
func runCommandAndGetOutput(description string, cmd Command) (string, error) {
cmd.Logger.Debug(description)
output, err := runCommand(cmd)
if err != nil {
if output != nil {
return output.Combined(), &ErrWithCmdOutput{err, output}
}
return "", &ErrWithCmdOutput{err, output}
}
return output.Combined(), nil
}
func runCommand(command Command) (*output, error) {
command.Logger.Debug("Running command %s with args %s\n", command.Command, command.Args)
cmd := exec.Command(command.Command, command.Args...)
cmd.Dir = command.WorkingDir
cmd.Stdin = os.Stdin
cmd.Env = formatEnvVars(command)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
output, err := readStdoutAndStderr(command, stdout, stderr)
if err != nil {
return output, err
}
return output, cmd.Wait()
}
func readStdoutAndStderr(command Command, stdout, stderr io.ReadCloser) (*output, error) {
out := newOutput()
stdoutReader := bufio.NewReader(stdout)
stderrReader := bufio.NewReader(stderr)
wg := &sync.WaitGroup{}
wg.Add(2)
var stdoutErr, stderrErr error
go func() {
defer wg.Done()
stdoutErr = readData(command, false, stdoutReader, out.stdout)
}()
go func() {
defer wg.Done()
stderrErr = readData(command, true, stderrReader, out.stderr)
}()
wg.Wait()
if stdoutErr != nil {
return out, stdoutErr
}
if stderrErr != nil {
return out, stderrErr
}
return out, nil
}
func readData(command Command, isStderr bool, reader *bufio.Reader, writer io.StringWriter) error {
var line string
var readErr error
for {
line, readErr = reader.ReadString('\n')
// remove newline, our output is in a slice,
// one element per line.
line = strings.TrimSuffix(line, "\n")
// only return early if the line does not have
// any contents. We could have a line that does
// not not have a newline before io.EOF, we still
// need to add it to the output.
if len(line) == 0 && readErr == io.EOF {
break
}
if isStderr {
command.Logger.Warn(line)
} else {
command.Logger.Info(line)
}
if _, err := writer.WriteString(line); err != nil {
return err
}
if readErr != nil {
break
}
}
if readErr != io.EOF {
return readErr
}
return nil
}