diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/dispatch/main.go | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/cmd/dispatch/main.go b/cmd/dispatch/main.go new file mode 100644 index 0000000..53aec92 --- /dev/null +++ b/cmd/dispatch/main.go @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2020 Ethel Morgan +// +// SPDX-License-Identifier: MIT + +// Binary dispatch is a webhook dispatch server. +package main + +import ( + "context" + "net" + "net/http" + + "github.com/gorilla/mux" + "go.eth.moe/dispatch/config" + "go.eth.moe/flag" + "go.eth.moe/httputil" + "go.eth.moe/logger" +) + +var ( + configPath = flag.Custom("config-path", "", "path to config.json", flag.RequiredString) + + listen = flag.Custom("listen", "", "either a unix socket path (e.g. /run/nginx/catbus.sock), a port prefixed by a colon (e.g. :8080), or an IP and port (e.g. 192.168.1.1:8080)", func(raw string) (interface{}, error) { + if raw == "" { + return nil, flag.ErrRequired + } + if conn, err := net.ResolveTCPAddr("tcp", raw); err == nil { + return conn, err + } + return net.ResolveUnixAddr("unix", raw) + }) +) + +func main() { + flag.Parse() + + configPath := (*configPath).(string) + listen := (*listen).(net.Addr) + + log := logger.Background() + + config, err := config.ParseFile(configPath) + if err != nil { + log.AddField("config-path", configPath) + log.WithError(err).Fatal("could not parse config") + } + + log.AddField("http.listen", listen) + conn, err := net.Listen(listen.Network(), listen.String()) + if err != nil { + log.WithError(err).Fatal("could not listen") + } + defer conn.Close() + + m := mux.NewRouter() + m.NotFoundHandler = httputil.NotFoundHandler + + m.PathPrefix("/actions/{action}"). + Methods("POST"). + Handler(http.StripPrefix("/actions", triggerAction(config))) + + m.Use(httputil.Logger) + + log.Info("starting HTTP server") + if err := http.Serve(conn, m); err != nil { + log.WithError(err).Fatal("could not start HTTP server") + } +} + +func triggerAction(config *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + found := false + for _, actionConfig := range config.Actions { + if matchAction(actionConfig, r) { + found = true + runAction(ctx, actionConfig) + } + } + if !found { + httputil.NotFound(w, r) + return + } + } +} + +func matchAction(action config.Action, r *http.Request) bool { + for _, trigger := range action.Triggers { + if trigger.URL.Path != r.URL.Path { + return false + } + for k := range trigger.FormValues { + want := trigger.FormValues.Get(k) + got := r.FormValue(k) + if got != want { + return false + } + } + return true + } + return false +} + +func runAction(ctx context.Context, action config.Action) { + log, _ := logger.FromContext(ctx) + + for _, output := range action.Outputs { + if output.Kind != config.HTTPOutput { + log.Warning("only supports HTTP for now") + continue + } + + if _, err := http.PostForm(output.URL.String(), output.FormValues); err != nil { + log.WithError(err).Error("could not POST form") + continue + } + log.Info("POSTed to URL") + } +} |