From 5a486ea48ee2ba2930b2d3781e6b95d9e7988bf1 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 27 Dec 2019 08:51:59 -0500 Subject: [PATCH] online mapping configuration --- config/config.go | 103 ++++++++++++++++++++++++++++++++++++++++++--- lameralert.go | 55 ++++++++++++++++++------ mapping/mapping.go | 71 +++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 19 deletions(-) create mode 100644 mapping/mapping.go diff --git a/config/config.go b/config/config.go index 77f561d..bdcbc96 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,8 @@ package config import ( + "bytes" + "encoding/gob" "encoding/json" "fmt" "sync" @@ -9,6 +11,8 @@ import ( "github.com/boltdb/bolt" "github.com/gorilla/mux" "github.com/rs/zerolog/log" + + "code.chrissexton.org/cws/lameralert/mapping" ) var ( @@ -24,10 +28,29 @@ func (value configValue) getBytes() ([]byte, error) { return json.Marshal(value) } -func decodeConfig(in []byte) (configValue, error) { - var out configValue - err := json.Unmarshal(in, &out) - return out, err +func decodeConfig(in []byte, target *configValue) error { + err := json.Unmarshal(in, target) + return err +} + +func encodeMapping(in mapping.Mapping) ([]byte, error) { + var out []byte + buf := bytes.NewBuffer(out) + enc := gob.NewEncoder(buf) + err := enc.Encode(in) + return buf.Bytes(), err +} + +func decodeMapping(in []byte) (mapping.Mapping, error) { + var target mapping.Mapping + dec := gob.NewDecoder(bytes.NewBuffer(in)) + err := dec.Decode(&target) + log.Debug(). + Interface("err", err). + Str("in", string(in)). + Interface("target", target). + Msg("decodeMapping") + return target, err } type Config struct { @@ -65,7 +88,8 @@ func (c *Config) getAll() (map[string]configValue, error) { err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) err := b.ForEach(func(k, v []byte) error { - value, err := decodeConfig(v) + var value configValue + err := decodeConfig(v, &value) if err != nil { return fmt.Errorf("could not convert value: %w", err) } @@ -80,6 +104,31 @@ func (c *Config) getAll() (map[string]configValue, error) { return vals, err } +func (c *Config) getMapping(key string) (mapping.Mapping, error) { + log.Debug().Msgf("getMapping(%s)", key) + c.Lock() + defer c.Unlock() + db := c.db() + defer db.Close() + var err error + var out mapping.Mapping + err = db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + out, err = decodeMapping(b.Get([]byte(key))) + log.Debug().AnErr("err", err).Interface("out", out).Msgf("decode %s", key) + if err != nil { + log.Error().Msgf("Error decoding: %w", err) + return err + } + return nil + }) + if err != nil { + log.Error().Msgf("Error getting: %w", err) + return mapping.Mapping{}, err + } + return out, nil +} + func (c *Config) get(key string) (configValue, error) { c.Lock() defer c.Unlock() @@ -88,11 +137,10 @@ func (c *Config) get(key string) (configValue, error) { var out configValue err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) - value, err := decodeConfig(b.Get([]byte(key))) + err := decodeConfig(b.Get([]byte(key)), &out) if err != nil { return err } - out = value return nil }) if err != nil { @@ -101,6 +149,26 @@ func (c *Config) get(key string) (configValue, error) { return out, nil } +func (c *Config) setMapping(key string, value mapping.Mapping) error { + c.Lock() + defer c.Unlock() + db := c.db() + defer db.Close() + err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + v, err := encodeMapping(value) + if err != nil { + return err + } + err = b.Put([]byte(key), v) + return err + }) + if err != nil { + log.Error().Msgf("Error setting key: %s", err) + } + return err +} + func (c *Config) set(key string, value configValue) error { c.Lock() defer c.Unlock() @@ -192,6 +260,27 @@ func (c *Config) GetString(key, defaultValue string) string { return v.Value.(string) } +func (c *Config) GetMapping(key string, defaultValue mapping.Mapping) mapping.Mapping { + m, err := c.getMapping(key) + if err != nil { + log.Error().Msgf("Error: %w", err) + return defaultValue + } + return m +} + +func (c *Config) GetGeneric(key string, defaultValue interface{}) interface{} { + v, err := c.get(key) + if err != nil { + return defaultValue + } + return v.Value +} + +func (c *Config) SetMapping(key string, value mapping.Mapping) error { + return c.setMapping(key, value) +} + func (c *Config) Set(key string, value interface{}) error { return c.set(key, configValue{key, value}) } diff --git a/lameralert.go b/lameralert.go index a294d52..29b3ef7 100644 --- a/lameralert.go +++ b/lameralert.go @@ -7,7 +7,7 @@ import ( "github.com/rs/zerolog/log" "code.chrissexton.org/cws/lameralert/config" - "code.chrissexton.org/cws/lameralert/event" + "code.chrissexton.org/cws/lameralert/mapping" "code.chrissexton.org/cws/lameralert/sinks" "code.chrissexton.org/cws/lameralert/sources" ) @@ -28,8 +28,6 @@ func NewConnection(from sources.Source, to sinks.Sink) Connection { } } -var mapping = map[chan event.Event][]chan event.Event{} - func logger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here @@ -38,22 +36,55 @@ func logger(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + func Setup() chan bool { r := mux.NewRouter() r.Use(logger) c := config.New(r.PathPrefix("/config").Subrouter()) ch := make(chan bool) - h := sources.NewGenericRest(r.PathPrefix("/rest").Subrouter()) - NewConnection(h, sinks.NewConsoleID(c, "00")) - NewConnection(h, sinks.NewConsoleID(c, "01")) + mappings := c.GetMapping("mappings", mapping.Example()) - rss := sources.NewRSS(c, r.PathPrefix("/rss/webshit").Subrouter(), "webshit", "http://n-gate.com/hackernews/index.rss") - NewConnection(rss, sinks.NewConsoleID(c, "webshit-sink")) - if push, err := sinks.NewPushover(c, "webshit-push"); err != nil { - log.Fatal().Msgf("error: %s", err) - } else { - NewConnection(rss, push) + //mappings := mapping.Example() + //c.SetMapping("mappings", mappings) + + srcs := map[string]sources.Source{} + snks := map[string]sinks.Sink{} + for _, source := range mappings.Sources { + var src sources.Source + // TODO: Move this logic out to the sources package + switch source.Type { + case "GenericRest": + path := source.Config["prefix"] + src = sources.NewGenericRest(r.PathPrefix(path).Subrouter()) + case "RSS": + path := source.Config["prefix"] + url := source.Config["URL"] + src = sources.NewRSS(c, r.PathPrefix(path).Subrouter(), source.Name, url) + } + srcs[source.Name] = src + } + + for _, sink := range mappings.Sinks { + var snk sinks.Sink + // TODO: Move this logic out to the sinks package + switch sink.Type { + case "Console": + snk = sinks.NewConsoleID(c, sink.Config["ID"]) + case "Pushover": + var err error + snk, err = sinks.NewPushover(c, sink.Name) + if err != nil { + log.Fatal().Msgf("Error setting up pushover: %w", err) + } + } + snks[sink.Name] = snk + } + + for _, m := range mappings.Mappings { + src := srcs[m.From] + snk := snks[m.To] + NewConnection(src, snk) } http.ListenAndServe(":9090", r) diff --git a/mapping/mapping.go b/mapping/mapping.go new file mode 100644 index 0000000..cdae81a --- /dev/null +++ b/mapping/mapping.go @@ -0,0 +1,71 @@ +package mapping + +type Mapping struct { + Sources []MapItem + Sinks []MapItem + Mappings []MapPair +} + +type MapPair struct { + From string + To string +} + +type MapItem struct { + Name string + Type string + Config map[string]string +} + +// TODO: Make a web interface to configure these horrible structures +func Example() Mapping { + mappings := Mapping{} + + webshit := MapItem{ + Name: "webshit", + Type: "RSS", + Config: map[string]string{ + "prefix": "/rss/webshit", + "URL": "http://n-gate.com/hackernews/index.rss", + }, + } + mappings.Sources = append(mappings.Sources, webshit) + genericRest := MapItem{ + Name: "GenericRest", + Type: "GenericRest", + Config: map[string]string{ + "prefix": "/rest", + "name": "GenericRest", + }, + } + mappings.Sources = append(mappings.Sources, genericRest) + consoleZero := MapItem{ + Name: "Console-00", + Type: "Console", + Config: map[string]string{ + "ID": "00", + }, + } + mappings.Sinks = append(mappings.Sinks, consoleZero) + consoleOne := MapItem{ + Name: "Console-01", + Type: "Console", + Config: map[string]string{ + "ID": "01", + }, + } + mappings.Sinks = append(mappings.Sinks, consoleOne) + + pushOver := MapItem{ + Name: "Pushover", + Type: "Pushover", + Config: map[string]string{}, + } + mappings.Sinks = append(mappings.Sinks, pushOver) + + mappings.Mappings = append(mappings.Mappings, MapPair{"GenericRest", "Console-00"}) + mappings.Mappings = append(mappings.Mappings, MapPair{"GenericRest", "Console-01"}) + mappings.Mappings = append(mappings.Mappings, MapPair{"webshit", "Pushover"}) + + return mappings +}