129 lines
3 KiB
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")
|
|
}
|