Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XMPP support #3

Merged
merged 1 commit into from
May 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- More detailed errors for Redmine import

### Added
- XMPP output support

## [0.0.1] - 2019-05-13
### Added
- Import user mails for project members from Redmine
Expand Down
9 changes: 9 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@
[[constraint]]
branch = "master"
name = "github.com/bluele/slack"

[[constraint]]
branch = "master"
name = "github.com/mattn/go-xmpp"
21 changes: 21 additions & 0 deletions cmd/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,31 @@ func init() {
"http://s3-us-west-2.amazonaws.com/sensuapp.org/sensu.png",
"A URL to an image to use as the user avatar")

eventCmd.PersistentFlags().String(
"xmpp-server",
"",
"The XMPP server to send messages to")

eventCmd.PersistentFlags().String(
"xmpp-username",
"",
"The XMPP username used to send messages")

eventCmd.PersistentFlags().String(
"xmpp-password",
"",
"The XMPP password used for authentication")

_ = viper.BindPFlag("outputs", eventCmd.PersistentFlags().Lookup("outputs"))
_ = viper.BindPFlag("annotation-prefix", eventCmd.PersistentFlags().Lookup("annotation-prefix"))
_ = viper.BindPFlag("smtp-address", eventCmd.PersistentFlags().Lookup("smtp-address"))
_ = viper.BindPFlag("mail-from", eventCmd.PersistentFlags().Lookup("mail-from"))
_ = viper.BindPFlag("slack-webhook-url", eventCmd.PersistentFlags().Lookup("slack-webhook-url"))
_ = viper.BindPFlag("slack-username", eventCmd.PersistentFlags().Lookup("slack-username"))
_ = viper.BindPFlag("slack-icon-url", eventCmd.PersistentFlags().Lookup("slack-icon-url"))
_ = viper.BindPFlag("xmpp-server", eventCmd.PersistentFlags().Lookup("xmpp-server"))
_ = viper.BindPFlag("xmpp-username", eventCmd.PersistentFlags().Lookup("xmpp-username"))
_ = viper.BindPFlag("xmpp-password", eventCmd.PersistentFlags().Lookup("xmpp-password"))
}

func loadEvent() (*types.Event, error) {
Expand Down Expand Up @@ -142,6 +160,9 @@ func handleEvent(event *types.Event) error {
SlackWebhookURL: viper.GetString("slack-webhook-url"),
SlackUsername: viper.GetString("slack-username"),
SlackIconURL: viper.GetString("slack-icon-url"),
XMPPServer: viper.GetString("xmpp-server"),
XMPPUsername: viper.GetString("xmpp-username"),
XMPPPassword: viper.GetString("xmpp-password"),
}

recipients := recipient.Parse(redisClient, val)
Expand Down
36 changes: 22 additions & 14 deletions output/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ import (
sensu "github.com/sensu/sensu-go/types"
)

func resolveTemplate(templateValue string, event *sensu.Event) (string, error) {
func extendedEventFromEvent(event *sensu.Event) *ExtendedEvent {
return &ExtendedEvent{
Event: event,
Status: messageEventStatus(event),
EventAction: formattedEventAction(event),
EventKey: eventKey(event),
Output: formattedEventOutput(event, 100),
FullOutput: event.Check.Output,
FormattedMessage: formattedMessage(event),
}
}

func resolveTemplate(templateValue string, event *ExtendedEvent) (string, error) {
var resolved bytes.Buffer

tmpl, err := template.New("tmpl").Parse(templateValue)
Expand All @@ -27,11 +39,7 @@ func resolveTemplate(templateValue string, event *sensu.Event) (string, error) {
return resolved.String(), nil
}

func chomp(s string) string {
return strings.Trim(strings.Trim(strings.Trim(s, "\n"), "\r"), "\r\n")
}

func messageStatus(event *sensu.Event) string {
func messageEventStatus(event *sensu.Event) string {
switch event.Check.Status {
case 0:
return "Resolved"
Expand All @@ -51,20 +59,20 @@ func formattedEventAction(event *sensu.Event) string {
}
}

func eventKey(event *sensu.Event) string {
return fmt.Sprintf("%s/%s", event.Entity.Name, event.Check.Name)
}

func eventSummary(event *sensu.Event, maxLength int) string {
output := chomp(event.Check.Output)
func formattedEventOutput(event *sensu.Event, maxLength int) string {
output := strings.Trim(strings.Trim(strings.Trim(event.Check.Output, "\n"), "\r"), "\r\n")

if len(event.Check.Output) > maxLength {
output = output[0:maxLength] + "..."
}

return fmt.Sprintf("%s:%s", eventKey(event), output)
return output
}

func eventKey(event *sensu.Event) string {
return fmt.Sprintf("%s/%s", event.Entity.Name, event.Check.Name)
}

func formattedMessage(event *sensu.Event) string {
return fmt.Sprintf("%s - %s", formattedEventAction(event), eventSummary(event, 100))
return fmt.Sprintf("[%s] %s - %s", formattedEventAction(event), eventKey(event), formattedEventOutput(event, 100))
}
8 changes: 3 additions & 5 deletions output/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ import (
"net/mail"
"net/smtp"

sensu "github.com/sensu/sensu-go/types"

"sensu-sic-handler/recipient"
)

var (
mailSubjectTemplate = "[Sensu] {{.Entity.Name}}/{{.Check.Name}}: {{.Check.State}}"
mailBodyTemplate = "{{.Check.Output}}"
mailSubjectTemplate = "[Sensu] [{{ .EventAction }}] /{{ .EventKey }}: {{ .Status }}"
mailBodyTemplate = "{{ .FullOutput }}"
)

// Mail handles mail recipients (recipient.HandlerTypeMail)
func Mail(recipient *recipient.Recipient, event *sensu.Event, config *Config) (rerr error) {
func Mail(recipient *recipient.Recipient, event *ExtendedEvent, config *Config) (rerr error) {
if len(config.MailFrom) == 0 {
return errors.New("from email is empty")
}
Expand Down
8 changes: 5 additions & 3 deletions output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ import (
func Notify(recipients []*recipient.Recipient, event *sensu.Event, config *Config) error {
recipientMap := make(map[string]bool)

extendedEvent := extendedEventFromEvent(event)

for _, rcpt := range recipients {
if _, ok := recipientMap[rcpt.ID]; !ok {
var err error

switch rcpt.Type {
case recipient.OutputTypeNone:
case recipient.OutputTypeMail:
err = Mail(rcpt, event, config)
err = Mail(rcpt, extendedEvent, config)
case recipient.OutputTypeXMPP:
err = XMPP(rcpt, event, config)
err = XMPP(rcpt, extendedEvent, config)
case recipient.OutputTypeSlack:
err = Slack(rcpt, event, config)
err = Slack(rcpt, extendedEvent, config)
default:
fmt.Fprintln(os.Stderr, fmt.Sprintf("unsupported handler: %q", rcpt))
}
Expand Down
19 changes: 9 additions & 10 deletions output/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import (
"errors"

"github.com/bluele/slack"
sensu "github.com/sensu/sensu-go/types"

"sensu-sic-handler/recipient"
)

// Slack handles slack recipients (recipient.HandlerTypeSlack)
func Slack(recipient *recipient.Recipient, event *sensu.Event, config *Config) error {
func Slack(recipient *recipient.Recipient, event *ExtendedEvent, config *Config) error {
if len(config.SlackWebhookURL) == 0 {
return errors.New("webhook url is empty")
}
Expand All @@ -28,8 +27,8 @@ func Slack(recipient *recipient.Recipient, event *sensu.Event, config *Config) e
})
}

func slackMessageColor(event *sensu.Event) string {
switch event.Check.Status {
func slackMessageColor(event *ExtendedEvent) string {
switch event.Event.Check.Status {
case 0:
return "good"
case 2:
Expand All @@ -39,26 +38,26 @@ func slackMessageColor(event *sensu.Event) string {
}
}

func slackMessageAttachment(event *sensu.Event) *slack.Attachment {
func slackMessageAttachment(event *ExtendedEvent) *slack.Attachment {
return &slack.Attachment{
Title: "Description",
Text: event.Check.Output,
Fallback: formattedMessage(event),
Text: event.Event.Check.Output,
Fallback: event.FormattedMessage,
Color: slackMessageColor(event),
Fields: []*slack.AttachmentField{
{
Title: "Status",
Value: messageStatus(event),
Value: messageEventStatus(event.Event),
Short: false,
},
{
Title: "Entity",
Value: event.Entity.Name,
Value: event.Event.Entity.Name,
Short: true,
},
{
Title: "Check",
Value: event.Check.Name,
Value: event.Event.Check.Name,
Short: true,
},
},
Expand Down
18 changes: 18 additions & 0 deletions output/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,29 @@

package output

import (
sensu "github.com/sensu/sensu-go/types"
)

// Config configuration for handlers
type Config struct {
SMTPAddress string
MailFrom string
SlackWebhookURL string
SlackUsername string
SlackIconURL string
XMPPServer string
XMPPUsername string
XMPPPassword string
}

// ExtendedEvent is a helper type for template resolution
type ExtendedEvent struct {
Event *sensu.Event
Status string
EventAction string
EventKey string
Output string
FullOutput string
FormattedMessage string
}
77 changes: 75 additions & 2 deletions output/xmpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,85 @@
package output

import (
sensu "github.com/sensu/sensu-go/types"
"crypto/tls"
"errors"

"github.com/mattn/go-xmpp"

"sensu-sic-handler/recipient"
)

var xmppMessageTemplate = "{{ .FormattedMessage }}"

// XMPP handles XMPP recipients (recipient.HandlerTypeXMPP)
func XMPP(recipient *recipient.Recipient, event *sensu.Event, config *Config) error {
func XMPP(recipient *recipient.Recipient, event *ExtendedEvent, config *Config) (rerr error) {
if len(config.XMPPServer) == 0 {
return errors.New("hostname is empty")
}

if len(config.XMPPUsername) == 0 {
return errors.New("username is empty")
}

if len(config.XMPPPassword) == 0 {
return errors.New("password is empty")
}

xmpp.DefaultConfig = tls.Config{
//ServerName: config.XMPPServer,
//InsecureSkipVerify: false,
}

clientOptions := xmpp.Options{
Host: config.XMPPServer,
User: config.XMPPUsername,
Password: config.XMPPPassword,
NoTLS: true,
}

client, err := clientOptions.NewClient()
if err != nil {
return err
}

msg, err := resolveTemplate(xmppMessageTemplate, event)
if err != nil {
return err
}

switch recipient.Args["type"] {
case "user":
err = xmppSendUser(client, recipient.Args["user"], msg)
case "muc":
err = xmppSendMUC(client, recipient.Args["room"], msg)
}

if err != nil {
return err
}

return nil
}

func xmppSendUser(client *xmpp.Client, remote string, msg string) error {
_, err := client.Send(xmpp.Chat{Remote: remote, Type: "chat", Text: msg})
if err != nil {
return err
}

return nil
}

func xmppSendMUC(client *xmpp.Client, remote string, msg string) error {
_, err := client.JoinMUCNoHistory(remote, "sensu")
if err != nil {
return err
}

_, err = client.Send(xmpp.Chat{Remote: remote, Type: "groupchat", Text: msg})
if err != nil {
return err
}

return nil
}
2 changes: 1 addition & 1 deletion redmine/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"fmt"

"github.com/go-redis/redis"
redmine "github.com/mattn/go-redmine"
"github.com/mattn/go-redmine"
)

// Import imports memberships and users into redis
Expand Down
2 changes: 1 addition & 1 deletion redmine/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package redmine

import (
redmine "github.com/mattn/go-redmine"
"github.com/mattn/go-redmine"
)

type projectMemberships struct {
Expand Down