blob: 06985efbe8566d622b16324577b0a5aac3e55c35 [file] [log] [blame]
package notifier
import (
"context"
"fmt"
"net/http"
"go.skia.org/infra/email/go/emailclient"
"go.skia.org/infra/go/chatbot"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"golang.org/x/sync/errgroup"
)
// Message represents a message to be sent through one or more Notifiers.
type Message struct {
// Subject line of the message (required). This is ignored in favor of
// the default in single-thread mode.
Subject string
// Body of the message (required).
Body string
// Severity of the message. May cause the message not to be sent,
// depending on filter settings.
Severity Severity
// Type of message. This is used with the optional IncludeMsgTypes to
// send only specific types of messages.
Type string
// ExtraRecipients who should also be sent this Message. Not supported for
// all types of notification.
ExtraRecipients []string
}
// Validate the Message.
func (m *Message) Validate() error {
if m.Subject == "" {
return fmt.Errorf("Message.Subject is required.")
}
if m.Body == "" {
return fmt.Errorf("Message.Body is required.")
}
if m.Type == "" {
return fmt.Errorf("Message.Type is required.")
}
return nil
}
// filteredThreadedNotifier groups a Notifier with a Filter and an optional
// static subject line for all messages to this Notifier.
type filteredThreadedNotifier struct {
includeMsgTypes []string
notifier Notifier
filter Filter
singleThreadSubject string
}
// Router is a struct used for sending notification through zero or more
// Notifiers.
type Router struct {
client *http.Client
configReader chatbot.ConfigReader
emailer emailclient.Client
notifiers []*filteredThreadedNotifier
}
// Send a notification.
func (r *Router) Send(ctx context.Context, msg *Message) error {
if err := msg.Validate(); err != nil {
return err
}
var group errgroup.Group
for _, n := range r.notifiers {
n := n
group.Go(func() error {
subject := msg.Subject
if n.singleThreadSubject != "" {
subject = n.singleThreadSubject
}
msgLog := fmt.Sprintf("(%s; %s): %s\n\n%s", msg.Severity.String(), msg.Type, subject, msg.Body)
if n.includeMsgTypes != nil {
if !util.In(msg.Type, n.includeMsgTypes) {
sklog.Debugf("Not sending notification (%s not in %v): %s", msg.Type, n.includeMsgTypes, msgLog)
return nil
}
} else if !n.filter.ShouldSend(msg.Severity) {
sklog.Debugf("Not sending notification (%s < %s): %s", msg.Severity.String(), n.filter.String(), msgLog)
return nil
}
sklog.Infof("Sending notification %s", msgLog)
return n.notifier.Send(ctx, subject, msg)
})
}
return group.Wait()
}
// Return a Router instance.
func NewRouter(client *http.Client, emailer emailclient.Client, chatBotConfigReader chatbot.ConfigReader) *Router {
return &Router{
client: client,
configReader: chatBotConfigReader,
emailer: emailer,
notifiers: []*filteredThreadedNotifier{},
}
}
// Add a new Notifier, which filters according to the given Filter. If
// singleThreadSubject is provided, that will be used as the subject for all
// Messages, ignoring their Subject field.
func (r *Router) Add(n Notifier, f Filter, includeMsgTypes []string, singleThreadSubject string) {
r.notifiers = append(r.notifiers, &filteredThreadedNotifier{
includeMsgTypes: includeMsgTypes,
notifier: n,
filter: f,
singleThreadSubject: singleThreadSubject,
})
}
// Add a new Notifier based on the given Config.
func (r *Router) AddFromConfig(ctx context.Context, c *Config) error {
if err := c.Validate(); err != nil {
return err
}
n, f, wl, s, err := c.Create(ctx, r.client, r.emailer, r.configReader)
if err != nil {
return err
}
r.Add(n, f, wl, s)
return nil
}
// Add all of the Notifiers specified by the given Configs.
func (r *Router) AddFromConfigs(ctx context.Context, cfgs []*Config) error {
for _, c := range cfgs {
if err := r.AddFromConfig(ctx, c); err != nil {
return err
}
}
return nil
}