aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/dispatch/main.go120
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")
+ }
+}