From 1b63df4e4e8988b46576c7e03e7342994b17d979 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 8 Feb 2022 11:25:22 +0100 Subject: [PATCH] First prototype --- Makefile | 2 +- cmd/weblug/config.go | 35 +++++++++++++++ cmd/weblug/weblug.go | 102 ++++++++++++++++++++++++++++++++++++++++++- go.mod | 5 +++ go.sum | 4 ++ weblug.yaml | 19 ++++++++ 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 cmd/weblug/config.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 weblug.yaml diff --git a/Makefile b/Makefile index 616f5d7..8704420 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ default: all all: weblug -weblug: cmd/weblug/weblug.go +weblug: cmd/weblug/weblug.go cmd/weblug/config.go go build -o $@ $^ diff --git a/cmd/weblug/config.go b/cmd/weblug/config.go new file mode 100644 index 0000000..6d40f07 --- /dev/null +++ b/cmd/weblug/config.go @@ -0,0 +1,35 @@ +package main + +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +type Config struct { + Settings ConfigSettings `yaml:"settings"` + Hooks []Hook `yaml:"hooks"` +} + +type ConfigSettings struct { + BindAddress string // Bind address for the webserver +} + +type Hook struct { + Name string + Route string + Command string + Background bool +} + +func (cf *Config) SetDefaults() { + cf.Settings.BindAddress = ":2088" +} + +func (cf *Config) LoadYAML(filename string) error { + content, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + return yaml.Unmarshal(content, cf) +} diff --git a/cmd/weblug/weblug.go b/cmd/weblug/weblug.go index a4a5ca2..39a53b1 100644 --- a/cmd/weblug/weblug.go +++ b/cmd/weblug/weblug.go @@ -5,8 +5,108 @@ package main import ( "fmt" + "log" + "net/http" + "os" + "os/exec" + "strings" ) +var cf Config +var hooks []Hook + +type Handler func(http.ResponseWriter, *http.Request) + func main() { - fmt.Println("weblug") + cf.SetDefaults() + if err := cf.LoadYAML(("../../weblug.yaml")); err != nil { + fmt.Fprintf(os.Stderr, "yaml error: %s\n", err) + os.Exit(1) + } + + if len(cf.Hooks) == 0 { + fmt.Fprintf(os.Stderr, "error: no webhooks defined\n") + os.Exit(2) + } + + // Create default handlers + http.HandleFunc("/health", createHealthHandler()) + http.HandleFunc("/health.json", createHealthHandler()) + http.HandleFunc("/index", createDefaultHandler()) + http.HandleFunc("/index.htm", createDefaultHandler()) + http.HandleFunc("/index.html", createDefaultHandler()) + http.HandleFunc("/robots.txt", createRobotsHandler()) + + // Register hooks + for i, hook := range cf.Hooks { + if hook.Route == "" { + fmt.Fprintf(os.Stderr, "Invalid hook %s: No route defined\n", hook.Name) + } + log.Printf("Webhook %d: '%s' [%s] \"%s\"\n", i, hook.Name, hook.Route, hook.Command) + http.HandleFunc(hook.Route, createHandler(hook)) + } + + // TODO: Await termination signal + + log.Printf("Launching webserver on %s", cf.Settings.BindAddress) + log.Fatal(http.ListenAndServe(cf.Settings.BindAddress, nil)) +} + +// Execute the given command and return it's return code +func execute(command string) error { + split := strings.Split(command, " ") + args := make([]string, 0) + if len(split) > 1 { + args = split[1:] + } + cmd := exec.Command(split[0], args...) + return cmd.Run() +} + +// create a http handler function from the given hook +func createHandler(hook Hook) Handler { + return func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s", r.RemoteAddr, hook.Name) + if hook.Background { // Execute command in background + w.WriteHeader(200) + fmt.Fprintf(w, "{\"status\":\"ok\"}") + go func() { + if err := execute(hook.Command); err != nil { + log.Printf("Hook %s failed: %s", hook.Name, err) + } else { + log.Printf("Hook %s completed", hook.Name) + } + }() + } else { + if err := execute(hook.Command); err != nil { + log.Printf("Hook %s failed: %s", hook.Name, err) + w.WriteHeader(500) + fmt.Fprintf(w, "{\"status\":\"fail\"}") + } else { + w.WriteHeader(200) + fmt.Fprintf(w, "{\"status\":\"ok\"}") + } + } + } +} + +func createHealthHandler() Handler { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + fmt.Fprintf(w, "{\"status\":\"ok\"}") + } +} + +func createDefaultHandler() Handler { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + fmt.Fprintf(w, "weblug") + } +} + +func createRobotsHandler() Handler { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + fmt.Fprintf(w, "User-agent: *\nDisallow: /") + } } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..40e07b1 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module weblug/m/v2 + +go 1.17 + +require gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dd0bc19 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/weblug.yaml b/weblug.yaml new file mode 100644 index 0000000..9ccad0a --- /dev/null +++ b/weblug.yaml @@ -0,0 +1,19 @@ +--- +## Weblug example config + +settings: + bind: ":2088" + +# hook definition. A hook needs to define the HTTP endpoint ("route") and the +# command that will be executed, once this route is executed +hooks: + - name: 'hook one' + route: "/webhooks/1" + command: "sleep 5" + background: True # Terminate http request immediately + - name: 'hook two' + route: "/webhooks/2" + command: "sleep 2" + - name: 'hook 3' + route: "/webhooks/data/3" + command: "/srv/fetch-new-data.sh"