From c0acfe1f063965d73ffcba3f6e3f1c822d360a71 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Mon, 16 Oct 2023 20:35:26 +0200 Subject: [PATCH] add basic notifer scaffolding and new config file --- .gitignore | 2 +- cmd/whawty-alerts/config.go | 68 ++++++++++++++++++++++++++++++++++ cmd/whawty-alerts/main.go | 39 ++++++++++--------- cmd/whawty-alerts/web.go | 31 ++-------------- contrib/sample-cfg.yml | 2 + notifier/notifier.go | 74 +++++++++++++++++++++++++++++++++++++ notifier/types.go | 45 ++++++++++++++++++++++ store/store.go | 22 +++++++++-- store/types.go | 6 +++ 9 files changed, 239 insertions(+), 50 deletions(-) create mode 100644 cmd/whawty-alerts/config.go create mode 100644 contrib/sample-cfg.yml create mode 100644 notifier/notifier.go create mode 100644 notifier/types.go diff --git a/.gitignore b/.gitignore index b73600a..5ce3605 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /whawty-alerts /cmd/whawty-alerts/whawty-alerts .coverprofile -/test.db +/contrib/test.db diff --git a/cmd/whawty-alerts/config.go b/cmd/whawty-alerts/config.go new file mode 100644 index 0000000..25a498d --- /dev/null +++ b/cmd/whawty-alerts/config.go @@ -0,0 +1,68 @@ +// +// Copyright (c) 2023 whawty contributors (see AUTHORS file) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of whawty.alerts nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +package main + +import ( + "fmt" + "os" + + "github.com/spreadspace/tlsconfig" + "github.com/whawty/alerts/notifier" + "github.com/whawty/alerts/store" + "gopkg.in/yaml.v3" +) + +type WebConfig struct { + TLS *tlsconfig.TLSConfig `yaml:"tls"` +} + +type Config struct { + Store store.Config `yaml:"store"` + Notifier notifier.Config `yaml:"notifer"` + Web WebConfig `yaml:"web"` +} + +func readConfig(configfile string) (*Config, error) { + file, err := os.Open(configfile) + if err != nil { + return nil, fmt.Errorf("Error opening config file: %s", err) + } + defer file.Close() + + decoder := yaml.NewDecoder(file) + decoder.KnownFields(true) + + c := &Config{} + if err = decoder.Decode(c); err != nil { + return nil, fmt.Errorf("Error parsing config file: %s", err) + } + return c, nil +} diff --git a/cmd/whawty-alerts/main.go b/cmd/whawty-alerts/main.go index d16ba57..76319ec 100644 --- a/cmd/whawty-alerts/main.go +++ b/cmd/whawty-alerts/main.go @@ -38,6 +38,7 @@ import ( "sync" "github.com/urfave/cli" + "github.com/whawty/alerts/notifier" "github.com/whawty/alerts/store" ) @@ -53,27 +54,30 @@ func init() { } func cmdRun(c *cli.Context) error { - s, err := store.Open("test.db") + conf, err := readConfig(c.GlobalString("config")) if err != nil { - return cli.NewExitError(err.Error(), 3) + return cli.NewExitError(err.Error(), 1) } - var webc *webConfig - if c.String("web-config") != "" { - webc, err = readWebConfig(c.String("web-config")) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } + s, err := store.Open(&conf.Store, wl, wdl) + if err != nil { + return cli.NewExitError(err.Error(), 3) } - webAddrs := c.StringSlice("web-addr") + defer s.Close() + n, err := notifier.NewNotifier(&conf.Notifier, s, wl, wdl) + if err != nil { + return cli.NewExitError(err.Error(), 3) + } + defer n.Close() + webAddrs := c.StringSlice("web-addr") var wg sync.WaitGroup for _, webAddr := range webAddrs { a := webAddr wg.Add(1) go func() { defer wg.Done() - if err := runWebAddr(a, webc, s); err != nil { + if err := runWebAddr(a, &conf.Web, s); err != nil { fmt.Printf("warning running web interface(%s) failed: %s\n", a, err) } }() @@ -88,18 +92,19 @@ func main() { app.Name = "whawty-alerts" app.Version = "0.1" app.Usage = "simple alert manager" - app.Flags = []cli.Flag{} + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config", + Value: "/etc/whawty/alerts.yaml", + Usage: "path to the configuration file", + EnvVar: "WHAWTY_ALERTS_CONFIG", + }, + } app.Commands = []cli.Command{ { Name: "run", Usage: "run the alert manager", Flags: []cli.Flag{ - cli.StringFlag{ - Name: "web-config", - Value: "", - Usage: "path to the web configuration file", - EnvVar: "WHAWTY_ALERTS_WEB_CONFIG", - }, cli.StringSliceFlag{ Name: "web-addr", Usage: "address to listen on for web API", diff --git a/cmd/whawty-alerts/web.go b/cmd/whawty-alerts/web.go index cb8bcce..ef4c304 100644 --- a/cmd/whawty-alerts/web.go +++ b/cmd/whawty-alerts/web.go @@ -31,18 +31,14 @@ package main import ( - "fmt" "net" "net/http" - "os" "time" "github.com/gin-gonic/gin" - "github.com/spreadspace/tlsconfig" apiV1 "github.com/whawty/alerts/api/v1" "github.com/whawty/alerts/store" "github.com/whawty/alerts/ui" - "gopkg.in/yaml.v3" ) const ( @@ -50,28 +46,7 @@ const ( WebAPIv1Prefix = "/api/v1/" ) -type webConfig struct { - TLS *tlsconfig.TLSConfig `yaml:"tls"` -} - -func readWebConfig(configfile string) (*webConfig, error) { - file, err := os.Open(configfile) - if err != nil { - return nil, err - } - defer file.Close() - - decoder := yaml.NewDecoder(file) - decoder.KnownFields(true) - - c := &webConfig{} - if err = decoder.Decode(c); err != nil { - return nil, fmt.Errorf("Error parsing config file: %s", err) - } - return c, nil -} - -func runWeb(listener net.Listener, config *webConfig, st *store.Store) (err error) { +func runWeb(listener net.Listener, config *WebConfig, st *store.Store) (err error) { gin.SetMode(gin.ReleaseMode) r := gin.New() @@ -97,7 +72,7 @@ func runWeb(listener net.Listener, config *webConfig, st *store.Store) (err erro return server.Serve(listener) } -func runWebAddr(addr string, config *webConfig, store *store.Store) (err error) { +func runWebAddr(addr string, config *WebConfig, store *store.Store) (err error) { if addr == "" { addr = ":http" } @@ -108,6 +83,6 @@ func runWebAddr(addr string, config *webConfig, store *store.Store) (err error) return runWeb(ln.(*net.TCPListener), config, store) } -func runWebListener(listener *net.TCPListener, config *webConfig, store *store.Store) (err error) { +func runWebListener(listener *net.TCPListener, config *WebConfig, store *store.Store) (err error) { return runWeb(listener, config, store) } diff --git a/contrib/sample-cfg.yml b/contrib/sample-cfg.yml new file mode 100644 index 0000000..83b33d0 --- /dev/null +++ b/contrib/sample-cfg.yml @@ -0,0 +1,2 @@ +store: + path: ./contrib/test.db diff --git a/notifier/notifier.go b/notifier/notifier.go new file mode 100644 index 0000000..3666609 --- /dev/null +++ b/notifier/notifier.go @@ -0,0 +1,74 @@ +// +// Copyright (c) 2023 whawty contributors (see AUTHORS file) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of whawty.alerts nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +package notifier + +import ( + "context" + "io" + "log" + "time" + + "github.com/whawty/alerts/store" +) + +type Notifier struct { + conf *Config + store *store.Store + infoLog *log.Logger + dbgLog *log.Logger + ctx context.Context + cancel context.CancelFunc +} + +func (n *Notifier) Close() error { + n.cancel() + return nil +} + +func NewNotifier(conf *Config, st *store.Store, infoLog, dbgLog *log.Logger) (n *Notifier, err error) { + if infoLog == nil { + infoLog = log.New(io.Discard, "", 0) + } + if dbgLog == nil { + dbgLog = log.New(io.Discard, "", 0) + } + + n = &Notifier{conf: conf, store: st, infoLog: infoLog, dbgLog: dbgLog} + n.ctx, n.cancel = context.WithCancel(context.Background()) + if n.conf.Interval <= 0 { + n.conf.Interval = 1 * time.Minute + } + + // TODO: start go-routine to handle notfications + + infoLog.Printf("notifier: started with evaluation interval %s", conf.Interval.String()) + return +} diff --git a/notifier/types.go b/notifier/types.go new file mode 100644 index 0000000..7704fd5 --- /dev/null +++ b/notifier/types.go @@ -0,0 +1,45 @@ +// +// Copyright (c) 2023 whawty contributors (see AUTHORS file) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of whawty.alerts nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +package notifier + +import ( + "time" +) + +type NotifierTarget struct { + Type string `yaml:"type"` + // TODO: other fields? +} + +type Config struct { + Interval time.Duration `yaml:"interval"` + Targets []NotifierTarget `yaml:"targets"` +} diff --git a/store/store.go b/store/store.go index 10c4015..c2737ad 100644 --- a/store/store.go +++ b/store/store.go @@ -31,16 +31,30 @@ package store import ( + "io" + "log" + bolt "go.etcd.io/bbolt" ) type Store struct { - db *bolt.DB + conf *Config + db *bolt.DB + infoLog *log.Logger + dbgLog *log.Logger } -func Open(path string) (s *Store, err error) { - s = &Store{} - s.db, err = bolt.Open(path, 0600, nil) +func Open(conf *Config, infoLog, dbgLog *log.Logger) (s *Store, err error) { + if infoLog == nil { + infoLog = log.New(io.Discard, "", 0) + } + if dbgLog == nil { + dbgLog = log.New(io.Discard, "", 0) + } + + s = &Store{conf: conf, infoLog: infoLog, dbgLog: dbgLog} + s.db, err = bolt.Open(conf.Path, 0600, nil) + infoLog.Printf("store: opened database %s", s.conf.Path) return } diff --git a/store/types.go b/store/types.go index d564f02..18b87b7 100644 --- a/store/types.go +++ b/store/types.go @@ -36,6 +36,12 @@ import ( "time" ) +// Configuration + +type Config struct { + Path string `yaml:"path"` +} + // Errors var (