httpwrap/main.go

129 lines
3 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"os/exec"
"os/signal"
"strings"
"time"
"github.com/tblyler/httpwrap/config"
)
// CommandResponse to be returned from an endpoint
type CommandResponse struct {
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
StdOut string `json:"stdout,omitempty"`
StdErr string `json:"stderr,omitempty"`
ExitCode int `json:"exit_code,omitempty"`
Error string `json:"error,omitempty"`
}
func main() {
configFilePath := os.Getenv("CONFIG_FILE_PATH")
if configFilePath == "" {
fmt.Fprintln(os.Stderr, "CONFIG_FILE_PATH environment variable must be set")
os.Exit(1)
}
config, err := config.NewJSONFileSource(configFilePath).Config(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(2)
}
for route, endpoint := range config.Endpoints {
endpoint := endpoint
http.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
if endpoint.HTTPMethod == "" {
endpoint.HTTPMethod = http.MethodGet
}
if strings.ToUpper(endpoint.HTTPMethod) != strings.ToUpper(r.Method) {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "unconfigured HTTP method:", r.Method)
return
}
args := endpoint.Arguments
if endpoint.AllowExternalArguments {
externalArgs, _ := r.URL.Query()["args"]
args = append(args, externalArgs...)
}
command := exec.CommandContext(
r.Context(),
endpoint.Command,
args...,
)
if endpoint.AllowStdin {
command.Stdin = r.Body
}
stdErr := bytes.NewBuffer(nil)
if !endpoint.DiscardStderr {
command.Stderr = stdErr
}
stdOut := bytes.NewBuffer(nil)
if !endpoint.DiscardStdout {
command.Stdout = stdOut
}
response := &CommandResponse{
StartTime: time.Now(),
}
err := command.Start()
if err != nil {
response.EndTime = time.Now()
response.Error = fmt.Sprintf("failed to start command: %v", err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(response)
return
}
command.Wait()
response.EndTime = time.Now()
response.ExitCode = command.ProcessState.ExitCode()
response.StdErr = stdErr.String()
response.StdOut = stdOut.String()
json.NewEncoder(w).Encode(response)
})
}
httpServer := &http.Server{
Addr: fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort),
}
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
go func() {
<-ctx.Done()
fmt.Println("got interrupt signal, waiting up to 5 seconds to gracefully shut down")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(5))
defer cancel()
httpServer.Shutdown(ctx)
}()
fmt.Println("listening on:", httpServer.Addr)
err = httpServer.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
fmt.Println("got error from HTTP server", err.Error())
os.Exit(3)
}
fmt.Println("successfully shut down")
}