137 lines
3.4 KiB
Go
137 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"sync"
|
|
"time"
|
|
|
|
// we use pflag package because it understands double dashes
|
|
flags "github.com/spf13/pflag"
|
|
)
|
|
|
|
var (
|
|
servePort string
|
|
isHelp bool
|
|
cmd string
|
|
cmdArgs []string
|
|
commandExecDir string
|
|
)
|
|
|
|
// some args parsing
|
|
func init() {
|
|
flags.StringVarP(&servePort, "port", "p", "8080", "Port to listen for incoming connections.")
|
|
flags.BoolVarP(&isHelp, "help", "h", false, "Prints this help message and exits.")
|
|
|
|
flags.StringVarP(&cmd, "cmd", "c", "", "REQUIRED: The command to execute.")
|
|
flags.StringSliceVarP(&cmdArgs, "args", "a", []string{}, "Arguments for the command. Can be provided multiple times or as comma-separated string.")
|
|
flags.StringVarP(&commandExecDir, "dir", "d", "", "The Directory to execute the command.\nDefaults to the directory the tool was called on.")
|
|
flags.Parse()
|
|
}
|
|
|
|
// handles the webhook requests
|
|
func handler(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close() // so I don't forget to close it -> eventhough it's not read.
|
|
|
|
cmd := exec.CommandContext(r.Context(), cmd, cmdArgs...)
|
|
// set directory only if it's present.
|
|
if commandExecDir != "" {
|
|
cmd.Dir = commandExecDir
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
log.Println(err)
|
|
fmt.Fprint(w, err.Error())
|
|
w.WriteHeader(http.StatusBadGateway)
|
|
}
|
|
output := string(out)
|
|
log.Println("Command output:\n" + output)
|
|
fmt.Fprint(w, output)
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func main() {
|
|
if isHelp {
|
|
printHelp()
|
|
// printing help is treated as a successful execution.
|
|
os.Exit(0)
|
|
}
|
|
|
|
err := validateParams()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
// use the default mux because it implements the Handler interface
|
|
// which we need for the server struct.
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", handler) // only add one handler on root path
|
|
|
|
// custom server struct to set custom timeouts for better performance.
|
|
server := &http.Server{
|
|
Handler: mux,
|
|
Addr: fmt.Sprintf(":%s", servePort),
|
|
WriteTimeout: 15 * time.Second,
|
|
ReadTimeout: 15 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
// server start and stop management
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go startHTTPServer(server, &wg)
|
|
go listenToStopHTTPServer(server, &wg)
|
|
wg.Wait()
|
|
}
|
|
|
|
func startHTTPServer(server *http.Server, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
log.Printf("The HTTP server is running: http://localhost:%s/\n", servePort)
|
|
if err := server.ListenAndServe(); err != nil {
|
|
if errors.Is(err, http.ErrServerClosed) {
|
|
log.Println("Shutting down the server...")
|
|
return
|
|
}
|
|
log.Fatalf("An unexpected error happened while running the HTTP server: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func listenToStopHTTPServer(server *http.Server, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
stop := make(chan os.Signal, 1)
|
|
signal.Notify(stop, os.Interrupt)
|
|
<-stop // block til signal is captured.
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
log.Fatalf("An error happened on the shutdown of the server: %v", err)
|
|
}
|
|
}
|
|
|
|
func printHelp() {
|
|
fmt.Println(`A small server to listen for requests to execute some particular code.
|
|
|
|
USAGE:
|
|
whook [FLAGS] --cmd [COMMAND [--args [ARGS]]
|
|
|
|
FLAGS:`)
|
|
flags.PrintDefaults()
|
|
}
|
|
|
|
// validateParams if required params are present.
|
|
func validateParams() error {
|
|
if cmd == "" {
|
|
return errors.New("missing required 'cmd' parameter")
|
|
}
|
|
|
|
return nil
|
|
}
|