diff --git a/OsemClient.go b/OsemClient.go deleted file mode 100644 index c575057..0000000 --- a/OsemClient.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "net/http" - "github.com/dghubble/sling" -) - -type OsemClient struct { - sling *sling.Sling -} - -func NewOsemClient(client *http.Client) *OsemClient { - return &OsemClient{ - sling: sling.New().Client(client).Base("https://api.opensensemap.org/"), - } -} - -func (client *OsemClient) GetBox(boxId string) (Box, error) { - box := Box{} - _, err := client.sling.New().Path("boxes/").Path(boxId).ReceiveSuccess(&box) - return box, err -} diff --git a/cmd/args.go b/cmd/args.go new file mode 100644 index 0000000..9652f50 --- /dev/null +++ b/cmd/args.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "../core" + "fmt" + "github.com/spf13/cobra" + "regexp" +) + +func isValidBoxId(boxId string) bool { + // boxIds are UUIDs + r := regexp.MustCompile("^[0-9a-fA-F]{24}$") + return r.MatchString(boxId) +} + +func BoxIdValidator(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("requires at least 1 argument") + } + for _, boxId := range args { + if isValidBoxId(boxId) == false { + return fmt.Errorf("invalid boxId specified: %s", boxId) + } + } + return nil +} + +// TODO: actually to be read from arg / file +var defaultConf = &core.NotifyConfig{ + // Transports: struct { + // Slack: SlackConfig{ + // Channel: "asdf" + // Token: "qwer" + // } + // }, + Events: []core.NotifyEvent{ + core.NotifyEvent{ + Type: "measurementAge", + Target: "593bcd656ccf3b0011791f5d", + Threshold: "5h", + }, + }, +} + +// func parseNotifyConfig(conf string) NotifyConfig, error {} diff --git a/cmd/check.go b/cmd/check.go new file mode 100644 index 0000000..cb1a409 --- /dev/null +++ b/cmd/check.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "../core" + "fmt" + "github.com/spf13/cobra" +) + +func init() { + checkCmd.AddCommand(checkBoxCmd) + rootCmd.AddCommand(checkCmd) +} + +var checkCmd = &cobra.Command{ + Use: "check", + Short: "One-off check for events on boxes", + Long: "One-off check for events on boxes", +} + +var checkBoxCmd = &cobra.Command{ + Use: "boxes [...]", + Short: "one-off check on one or more box IDs", + Long: "specify box IDs to check them for events", + Args: BoxIdValidator, + RunE: func(cmd *cobra.Command, args []string) error { + notifications, err := core.CheckNotifications(args, defaultConf) + if err != nil { + return fmt.Errorf("error checking for notifications:", err) + } + fmt.Println(notifications) + + // logNotifications(notifications) + if shouldNotify { + // TODO + } + + return nil + }, +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..6fcf10d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "osem_notify", + Long: "Run healthchecks and send notifications for boxes on opensensemap.org", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var shouldNotify bool +var defaultConfig string + +func init() { + rootCmd.PersistentFlags().BoolVarP(&shouldNotify, "notify", "n", false, "if set, will send out notifications.\nOtherwise results are printed to stdout only") + rootCmd.PersistentFlags().StringVarP(&defaultConfig, "confdefault", "c", "", "default JSON config to use for event checking") +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + } +} diff --git a/cmd/watch.go b/cmd/watch.go new file mode 100644 index 0000000..0447a77 --- /dev/null +++ b/cmd/watch.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + "../core" +) + +var watchInterval int +var ticker <-chan time.Time + +func init() { + watchCmd.AddCommand(watchBoxesCmd) + watchCmd.PersistentFlags().IntVarP(&watchInterval, "interval", "i", 15, "interval to run checks in minutes") + rootCmd.AddCommand(watchCmd) +} + +var watchCmd = &cobra.Command{ + Use: "watch", + Aliases: []string{"serve"}, + Short: "Watch boxes for events at an interval", + Long: "Watch boxes for events at an interval", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + ticker = time.NewTicker(time.Duration(watchInterval) * time.Second).C + }, +} + +var watchBoxesCmd = &cobra.Command{ + Use: "boxes [...]", + Short: "watch a list of box IDs for events", + Long: "specify box IDs to watch them for events", + Args: BoxIdValidator, + RunE: func(cmd *cobra.Command, args []string) error { + exec := func() error { + notifications, err := core.CheckNotifications(args, defaultConf) + if err != nil { + return fmt.Errorf("error checking for notifications: ", err) + } + fmt.Println(notifications) + + // logNotifications(notifications) + if shouldNotify { + // TODO + } + return nil + } + + err := exec() + if err != nil { + return err + } + for { + <-ticker + err = exec() + if err != nil { + return err + } + } + + return nil + }, +} diff --git a/Box.go b/core/Box.go similarity index 84% rename from Box.go rename to core/Box.go index d75e23c..658a100 100644 --- a/Box.go +++ b/core/Box.go @@ -1,4 +1,10 @@ -package main +package core + +type NotifyEvent struct { + Type string `json:"type"` + Target string `json:"target"` + Threshold string `json:"threshold"` +} type NotifyConfig struct { // Transports interface{} `json:"transports"` diff --git a/core/OsemClient.go b/core/OsemClient.go new file mode 100644 index 0000000..568a0af --- /dev/null +++ b/core/OsemClient.go @@ -0,0 +1,34 @@ +package core + +import ( + "errors" + "github.com/dghubble/sling" + "net/http" +) + +type OsemError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type OsemClient struct { + sling *sling.Sling +} + +func NewOsemClient(client *http.Client) *OsemClient { + return &OsemClient{ + sling: sling.New().Client(client).Base("https://api.opensensemap.org/"), + } +} + +func (client *OsemClient) GetBox(boxId string) (Box, error) { + box := Box{} + fail := OsemError{} + client.sling.New().Path("boxes/").Path(boxId).Receive(&box, &fail) + if fail.Message != "" { + return box, errors.New("could not fetch box: " + fail.Message) + } + return box, nil +} + +var osem = NewOsemClient(&http.Client{}) // default client diff --git a/core/jobs.go b/core/jobs.go new file mode 100644 index 0000000..8d17bd1 --- /dev/null +++ b/core/jobs.go @@ -0,0 +1,74 @@ +package core + +import ( + log "github.com/sirupsen/logrus" + "os" +) + +func init() { + log.SetLevel(log.DebugLevel) + log.SetOutput(os.Stdout) + // log.SetFormatter(&log.JSONFormatter{}) +} + +func CheckNotifications(boxIds []string, defaultConf *NotifyConfig) ([]Notification, []error) { + log.Info("Checking notifications for ", len(boxIds), " box(es)") + + // TODO: return a map of Box: []Notification instead? + notifications := []Notification{} + errors := []error{} + for _, boxId := range boxIds { + n, err := checkBox(boxId, defaultConf) + if notifications != nil { + notifications = append(notifications, n...) + } + if err != nil { + errors = append(errors, err) + } + } + + if len(errors) == 0 { + errors = nil + } + + return notifications, errors +} + +func checkBox(boxId string, defaultConf *NotifyConfig) ([]Notification, error) { + boxLogger := log.WithFields(log.Fields{"boxId": boxId}) + boxLogger.Debug("checking box for due notifications") + + // get box data + box, err := osem.GetBox(boxId) + if err != nil { + boxLogger.Error(err) + return nil, err + } + + // if box has no notify config, we use the defaultConf + if box.NotifyConf == nil { + box.NotifyConf = defaultConf + } + boxLogger.Debug(box.NotifyConf) + + // run checks + notifications, err2 := box.runChecks() + if err2 != nil { + boxLogger.Error("could not run checks on box: ", err) + return notifications, err2 + } + if notifications == nil { + boxLogger.Debug("all is fine") + return nil, nil + } + + // store notifications for later submit + // notifier, err3 := box.getNotifier() + // if err3 != nil { + // boxLogger.Error("could not get notifier for box: ", err) + // return notifications, err3 + // } + // notifier.AddNotifications(notifications) + + return notifications, nil +} diff --git a/notifiers.go b/core/notifiers.go similarity index 67% rename from notifiers.go rename to core/notifiers.go index c8bdead..0186055 100644 --- a/notifiers.go +++ b/core/notifiers.go @@ -1,4 +1,4 @@ -package main +package core type AbstractNotifier interface { GetName() string @@ -12,9 +12,3 @@ type Notification struct { // TODO: multiple transports? one transport per event? (??) type SlackConfig struct{} - -type NotifyEvent struct { - Type string `json:"type"` - Target string `json:"target"` - Threshold string `json:"threshold"` -} diff --git a/main.go b/main.go index 8a9dd0f..231c3ff 100644 --- a/main.go +++ b/main.go @@ -1,81 +1,9 @@ package main import ( - "net/http" - "os" - "runtime" - - "github.com/carlescere/scheduler" - log "github.com/sirupsen/logrus" + "./cmd" ) -func checkBox(boxId string, defaultConf *NotifyConfig) { - boxLogger := log.WithFields(log.Fields{"boxId": boxId}) - boxLogger.Debug("checking box for due notifications") - - // get box data - box, err := osem.GetBox(boxId) - if err != nil { - boxLogger.Error("could not fetch box: ", err) - return - } - - // if box has no notify config, we use the defaultConf - if box.NotifyConf == nil { - box.NotifyConf = defaultConf - } - boxLogger.Debug(box.NotifyConf) - - // run checks - notifications, err2 := box.runChecks() - if err2 != nil { - boxLogger.Error("could not run checks on box: ", err) - return - } - if notifications == nil { - boxLogger.Debug("all is fine") - return - } - - // store notifications for later submit - // notifier, err3 := box.getNotifier() - // if err3 != nil { - // boxLogger.Error("could not get notifier for box: ", err) - // return - // } - // notifier.AddNotifications(notifications) -} - -func checkNotifications() { - log.Info("running job checkNotifications()") - checkBox("593bcd656ccf3b0011791f5a", defaultConf) -} - -var osem = NewOsemClient(&http.Client{}) -var defaultConf = &NotifyConfig{ - // Transports: struct { - // Slack: SlackConfig{ - // Channel: "asdf" - // Token: "qwer" - // } - // }, - Events: []NotifyEvent{ - NotifyEvent{ - Type: "measurementAge", - Target: "593bcd656ccf3b0011791f5d", - Threshold: "5h", - }, - }, -} - -func init() { - log.SetLevel(log.DebugLevel) - log.SetOutput(os.Stdout) - // log.SetFormatter(&log.JSONFormatter{}) -} - func main() { - scheduler.Every(15).Seconds().Run(checkNotifications) - // scheduler.Every(30).Seconds().Run(submitNotifications) - runtime.Goexit() // keep runtime running + cmd.Execute() }