whook/main.go
2022-03-13 15:40:55 +01:00

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
}