/
exec.go
183 lines (138 loc) · 3.65 KB
/
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package utils
import (
"bufio"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
log "github.com/sirupsen/logrus"
)
// Command create a command at the project root.
func Command(name string, args ...string) *exec.Cmd {
cmd := exec.Command(name, args...)
// By default set the working directory to the project root directory.
wd, _ := os.Getwd()
for !strings.HasSuffix(wd, "authelia") {
wd = filepath.Dir(wd)
}
cmd.Dir = wd
return cmd
}
// CommandWithStdout create a command forwarding stdout and stderr to the OS streams.
func CommandWithStdout(name string, args ...string) *exec.Cmd {
cmd := Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd
}
// Shell create a shell command.
func Shell(command string) *exec.Cmd {
return CommandWithStdout("bash", "-c", command)
}
// RunCommandAndReturnOutput runs a shell command then returns the stdout and the exit code.
func RunCommandAndReturnOutput(command string) (output string, exitCode int, err error) {
cmd := Shell(command)
cmd.Stdout = nil
cmd.Stderr = nil
outputBytes, err := cmd.Output()
if err != nil {
return "", cmd.ProcessState.ExitCode(), err
}
return strings.Trim(string(outputBytes), "\n"), cmd.ProcessState.ExitCode(), nil
}
// RunCommandUntilCtrlC run a command until ctrl-c is hit.
func RunCommandUntilCtrlC(cmd *exec.Cmd) {
mutex := sync.Mutex{}
cond := sync.NewCond(&mutex)
signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
mutex.Lock()
go func() {
mutex.Lock()
f := bufio.NewWriter(os.Stdout)
defer f.Flush()
fmt.Println("Hit Ctrl+C to shutdown...") //nolint:forbidigo
err := cmd.Run()
if err != nil {
fmt.Println(err) //nolint:forbidigo
cond.Broadcast()
mutex.Unlock()
return
}
<-signalChannel
cond.Broadcast()
mutex.Unlock()
}()
cond.Wait()
}
// RunFuncUntilCtrlC run a function until ctrl-c is hit.
func RunFuncUntilCtrlC(fn func() error) error {
mutex := sync.Mutex{}
cond := sync.NewCond(&mutex)
errorChannel := make(chan error)
signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
mutex.Lock()
go func() {
mutex.Lock()
f := bufio.NewWriter(os.Stdout)
defer f.Flush()
fmt.Println("Hit Ctrl+C to shutdown...") //nolint:forbidigo
err := fn()
if err != nil {
errorChannel <- err
fmt.Println(err) //nolint:forbidigo
cond.Broadcast()
mutex.Unlock()
return
}
errorChannel <- nil
<-signalChannel
cond.Broadcast()
mutex.Unlock()
}()
cond.Wait()
return <-errorChannel
}
// RunCommandWithTimeout run a command with timeout.
func RunCommandWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
// Start a process.
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// Wait for the process to finish or kill it after a timeout (whichever happens first).
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-time.After(timeout):
fmt.Printf("Timeout of %ds reached... Killing process...\n", int64(timeout/time.Second)) //nolint:forbidigo
if err := cmd.Process.Kill(); err != nil {
return err
}
return ErrTimeoutReached
case err := <-done:
return err
}
}
// RunFuncWithRetry run a function for n attempts with a sleep of n duration between each attempt.
func RunFuncWithRetry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; ; i++ {
err = f()
if err == nil {
return
}
if i >= (attempts - 1) {
break
}
time.Sleep(sleep)
log.Printf("Retrying after error: %s", err)
}
return fmt.Errorf("failed after %d attempts, last error: %s", attempts, err)
}