diff --git a/README.md b/README.md index af37e1a..e67b280 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Run `osem_notify help config` for details and an example configuration. `transport` | `options` ------------|------------ `email` | `recipients`: list of email addresses +`xmpp` | `recipients`: list of JIDs Want more? [add it](#contribute)! diff --git a/cmd/cmd_root.go b/cmd/cmd_root.go index c3549b5..3430d03 100644 --- a/cmd/cmd_root.go +++ b/cmd/cmd_root.go @@ -23,32 +23,32 @@ var configHelpCmd = &cobra.Command{ > Example configuration: - healthchecks: - # override default health checks for all boxes - default: - notifications: - transport: email - options: - recipients: - - fridolina@example.com - events: - - type: "measurement_age" - target: "all" # all sensors - threshold: "15m" # any duration - - type: "measurement_faulty" - target: "all" - threshold: "" - - # set health checks per box - 593bcd656ccf3b0011791f5a: - notifications: - options: - recipients: - - ruth.less@example.com - events: - - type: "measurement_max" - target: "593bcd656ccf3b0011791f5b" - threshold: "40" + healthchecks: + # override default health checks for all boxes + default: + notifications: + transport: email + options: + recipients: + - fridolina@example.com + events: + - type: "measurement_age" + target: "all" # all sensors + threshold: "15m" # any duration + - type: "measurement_faulty" + target: "all" + threshold: "" + + # set health checks per box + 593bcd656ccf3b0011791f5a: + notifications: + options: + recipients: + - ruth.less@example.com + events: + - type: "measurement_max" + target: "593bcd656ccf3b0011791f5b" + threshold: "40" # only needed when sending notifications via email email: @@ -58,12 +58,20 @@ var configHelpCmd = &cobra.Command{ pass: bar from: hildegunst@example.com + # only needed when sending notifications via XMPP + xmpp: + host: jabber.example.com:5222 + user: foo@jabber.example.com + pass: bar + startls: true + > possible values for healthchecks.*.notifications: transport | options ----------|------------------------------------- email | recipients: list of email addresses + xmpp | recipients: list of JIDs > possible values for healthchecks.*.events[]: diff --git a/core/notifier_xmpp.go b/core/notifier_xmpp.go new file mode 100644 index 0000000..fe6c38e --- /dev/null +++ b/core/notifier_xmpp.go @@ -0,0 +1,112 @@ +package core + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/spf13/viper" + xmpp "github.com/mattn/go-xmpp" +) + +// box config required for the XmppNotifier +type XmppNotifier struct { + Recipients []string +} + +func (n XmppNotifier) New(config interface{}) (AbstractNotifier, error) { + // assign configuration to the notifier after ensuring the correct type. + // lesson of this project: golang requires us to fuck around with type + // assertions, instead of providing us with proper inheritance. + + asserted, ok := config.(XmppNotifier) + if !ok || asserted.Recipients == nil { + // config did not contain valid options. + // first try fallback: parse result of viper is a map[string]interface{}, + // which requires a different assertion change + asserted2, ok := config.(map[string]interface{}) + if ok { + asserted3, ok := asserted2["recipients"].([]interface{}) + if ok { + asserted = XmppNotifier{Recipients: []string{}} + for _, rec := range asserted3 { + asserted.Recipients = append(asserted.Recipients, rec.(string)) + } + } + } + + if asserted.Recipients == nil { + return nil, errors.New("Invalid XmppNotifier options") + } + } + + return XmppNotifier{ + Recipients: asserted.Recipients, + }, nil +} + +func (n XmppNotifier) ComposeNotification(box *Box, checks []CheckResult) Notification { + errTexts := []string{} + resolvedTexts := []string{} + for _, check := range checks { + if check.Status == CheckErr { + errTexts = append(errTexts, check.String()) + } else { + resolvedTexts = append(resolvedTexts, check.String()) + } + } + + var ( + resolved string + resolvedList string + errList string + ) + if len(resolvedTexts) != 0 { + resolvedList = fmt.Sprintf("Resolved issue(s):\n\n%s\n\n", strings.Join(resolvedTexts, "\n")) + } + if len(errTexts) != 0 { + errList = fmt.Sprintf("New issue(s):\n\n%s\n\n", strings.Join(errTexts, "\n")) + } else { + resolved = "resolved " + } + + return Notification{ + Subject: fmt.Sprintf("Issues %swith your box \"%s\" on opensensemap.org!", resolved, box.Name), + Body: fmt.Sprintf("A check at %s identified the following updates for your box \"%s\":\n\n%s%sYou may visit https://opensensemap.org/explore/%s for more details.\n\n--\nSent automatically by osem_notify (https://github.com/noerw/osem_notify)", + time.Now().Round(time.Minute), box.Name, errList, resolvedList, box.Id), + } +} + +func (n XmppNotifier) Submit(notification Notification) error { + xmppOpts := xmpp.Options{ + Host: viper.GetString("xmpp.host"), + User: viper.GetString("xmpp.user"), + Password: viper.GetString("xmpp.pass"), + Resource: "osem_notify", + } + + if viper.GetBool("xmpp.starttls") { + xmppOpts.NoTLS = true + xmppOpts.StartTLS = true + } + + client, err := xmppOpts.NewClient() + if err != nil { + return err + } + + for _, recipient := range n.Recipients { + _, err = client.Send(xmpp.Chat{ + Remote: recipient, + Subject: notification.Subject, + Text: fmt.Sprintf("%s\n\n%s", notification.Subject, notification.Body), + }) + + if err != nil { + return err + } + } + + return err +} diff --git a/core/notifiers.go b/core/notifiers.go index 2b41503..2ae1198 100644 --- a/core/notifiers.go +++ b/core/notifiers.go @@ -10,6 +10,7 @@ import ( var Notifiers = map[string]AbstractNotifier{ "email": EmailNotifier{}, + "xmpp": XmppNotifier{}, } type AbstractNotifier interface {