184 lines
3.6 KiB
Go
184 lines
3.6 KiB
Go
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...")
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
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...")
|
|
|
|
err := fn()
|
|
|
|
if err != nil {
|
|
errorChannel <- err
|
|
fmt.Println(err)
|
|
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))
|
|
|
|
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)
|
|
}
|