-
Notifications
You must be signed in to change notification settings - Fork 1
/
cli_exec.go
127 lines (107 loc) · 2.36 KB
/
cli_exec.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
package clitool
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
// CLIExecutor runs a command with arguments using the os.Exec function.
type CLIExecutor struct {
verbose bool
}
func NewCLIExecutor(verbose bool) *CLIExecutor {
return &CLIExecutor{verbose: verbose}
}
func (e *CLIExecutor) ExecCollectOutput(
ctx context.Context,
c Command,
args *Args,
) (string, error) {
var buf strings.Builder
_, err := e.Exec(ctx, c, args, &buf, os.Stderr)
return buf.String(), err
}
func (e *CLIExecutor) Exec(
ctx context.Context,
c Command,
args *Args,
stdout, stderr io.Writer,
) (bool, error) {
command := c.Path
if command == "" {
return false, errors.New("No command configured")
}
command = os.Expand(command, func(s string) string {
if tmp, ok := args.Environment[s]; ok {
return tmp
}
return os.Getenv(s)
})
env := os.Environ()
for k, v := range args.Environment {
env = append(env, k+"="+v)
}
arguments := args.Build()
if len(c.SubCommand) > 0 {
tmp := make([]string, 0, len(arguments)+len(c.SubCommand))
tmp = append(tmp, c.SubCommand...)
tmp = append(tmp, arguments...)
arguments = tmp
}
osCommand := exec.CommandContext(ctx, c.Path, arguments...)
osCommand.Env = env
osCommand.Dir = c.WorkingDir
osCommand.Stdout = stdout
osCommand.Stderr = stderr
osCommand.Stdin = os.Stdin
if e.verbose {
fmt.Printf("Exec (working dir '%v'): `%v %v`\n",
c.WorkingDir,
command,
strings.Join(arguments, " "))
}
didRun, exitCode, err := checkError(osCommand.Run())
if err == nil {
return didRun, nil
}
if e.verbose {
fmt.Println(" => exit code:", exitCode)
}
if !didRun {
return false, fmt.Errorf("failed to run command: %+v", err)
}
return true, fmt.Errorf("command %v failed with %v: %+v", command, exitCode, err)
}
func checkError(err error) (bool, int, error) {
if err == nil {
return true, 0, nil
}
switch e := err.(type) {
case *exec.ExitError:
return e.Exited(), exitStatus(err), err
case interface{ ExitStatus() int }:
return false, exitStatus(err), err
default:
return false, 1, err
}
}
func exitStatus(err error) int {
type exitStatus interface {
ExitStatus() int
}
if err == nil {
return 0
}
switch e := err.(type) {
case exitStatus:
return e.ExitStatus()
case *exec.ExitError:
if sysErr, ok := e.Sys().(exitStatus); ok {
return sysErr.ExitStatus()
}
}
return 1
}