| 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 |
| } |