blob: a371eb1d992e7bdccefef6d1558cb84752317b3c [file] [log] [blame]
package notifier
import (
"context"
"errors"
"fmt"
"cloud.google.com/go/pubsub"
"go.skia.org/infra/go/chatbot"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/email"
"go.skia.org/infra/go/util"
)
const (
EMAIL_FROM_ADDRESS = "noreply@skia.org"
)
// Notifier is an interface used for sending notifications from an AutoRoller.
type Notifier interface {
// Send the given message to the given thread. This should be safe to
// run in a goroutine.
Send(ctx context.Context, thread string, msg *Message) error
}
// Configuration for a Notifier.
type Config struct {
// Required fields.
// Configuration for filtering out messages.
Filter string `json:"filter"`
// Exactly one of these should be specified.
Email *EmailNotifierConfig `json:"email,omitempty"`
Chat *ChatNotifierConfig `json:"chat,omitempty"`
PubSub *PubSubNotifierConfig `json:"pubsub,omitempty"`
// Optional fields.
// If present, all messages inherit this subject line.
Subject string `json:"subject,omitempty"`
}
// Validate the Config.
func (c *Config) Validate() error {
if c.Filter == "" {
return errors.New("Filter is required.")
}
if _, err := ParseFilter(c.Filter); err != nil {
return err
}
n := []util.Validator{}
if c.Email != nil {
n = append(n, c.Email)
}
if c.Chat != nil {
n = append(n, c.Chat)
}
if c.PubSub != nil {
n = append(n, c.PubSub)
}
if len(n) != 1 {
return fmt.Errorf("Exactly one notification config must be supplied, but got %d", len(n))
}
return n[0].Validate()
}
// Create a Notifier from the Config.
func (c *Config) Create(ctx context.Context, emailer *email.GMail, chatBotConfigReader chatbot.ConfigReader) (Notifier, Filter, string, error) {
if err := c.Validate(); err != nil {
return nil, FILTER_SILENT, "", err
}
filter, err := ParseFilter(c.Filter)
if err != nil {
return nil, FILTER_SILENT, "", err
}
var n Notifier
if c.Email != nil {
n, err = EmailNotifier(c.Email.Emails, emailer, "")
} else if c.Chat != nil {
n, err = ChatNotifier(c.Chat.RoomID, chatBotConfigReader)
} else if c.PubSub != nil {
n, err = PubSubNotifier(ctx, c.PubSub.Topic)
} else {
return nil, FILTER_SILENT, "", fmt.Errorf("No config specified!")
}
if err != nil {
return nil, FILTER_SILENT, "", err
}
return n, filter, c.Subject, nil
}
// Create a copy of this Config.
func (c *Config) Copy(ctx context.Context) *Config {
configCopy := &Config{
Filter: c.Filter,
Subject: c.Subject,
}
if c.Email != nil {
configCopy.Email = &EmailNotifierConfig{
Emails: c.Email.Emails,
}
}
if c.Chat != nil {
configCopy.Chat = &ChatNotifierConfig{
RoomID: c.Chat.RoomID,
}
}
if c.PubSub != nil {
configCopy.PubSub = &PubSubNotifierConfig{
Topic: c.PubSub.Topic,
}
}
return configCopy
}
// Configuration for EmailNotifier.
type EmailNotifierConfig struct {
// List of email addresses to notify. Required.
Emails []string `json:"emails"`
}
// Validate the EmailNotifierConfig.
func (c *EmailNotifierConfig) Validate() error {
if c.Emails == nil || len(c.Emails) == 0 {
return fmt.Errorf("Emails is required.")
}
return nil
}
// emailNotifier is a Notifier implementation which sends email to interested
// parties.
type emailNotifier struct {
from string
gmail *email.GMail
markup string
to []string
}
// See documentation for Notifier interface.
func (n *emailNotifier) Send(_ context.Context, subject string, msg *Message) error {
if n.gmail == nil {
return nil
}
return n.gmail.SendWithMarkup(n.from, n.to, subject, msg.Body, n.markup)
}
// EmailNotifier returns a Notifier which sends email to interested parties.
// Sends the same ViewAction markup with each message.
func EmailNotifier(emails []string, emailer *email.GMail, markup string) (Notifier, error) {
return &emailNotifier{
from: EMAIL_FROM_ADDRESS,
gmail: emailer,
markup: markup,
to: emails,
}, nil
}
// Configuration for ChatNotifier.
type ChatNotifierConfig struct {
RoomID string `json:"room"`
}
// Validate the ChatNotifierConfig.
func (c *ChatNotifierConfig) Validate() error {
if c.RoomID == "" {
return fmt.Errorf("RoomID is required.")
}
return nil
}
// chatNotifier is a Notifier implementation which sends chat messages.
type chatNotifier struct {
configReader chatbot.ConfigReader
roomId string
}
// See documentation for Notifier interface.
func (n *chatNotifier) Send(_ context.Context, thread string, msg *Message) error {
return chatbot.SendUsingConfig(msg.Body, n.roomId, thread, n.configReader)
}
// ChatNotifier returns a Notifier which sends email to interested parties.
func ChatNotifier(roomId string, configReader chatbot.ConfigReader) (Notifier, error) {
return &chatNotifier{
configReader: configReader,
roomId: roomId,
}, nil
}
// Configuration for a PubSubNotifier.
type PubSubNotifierConfig struct {
Topic string `json:"topic"`
}
// Validate the PubSubNotifierConfig.
func (c *PubSubNotifierConfig) Validate() error {
if c.Topic == "" {
return errors.New("Topic is required.")
}
return nil
}
// pubSubNotifier is a Notifier implementation which sends pub/sub messages.
type pubSubNotifier struct {
topic *pubsub.Topic
}
// See documentation for Notifier interface.
func (n *pubSubNotifier) Send(ctx context.Context, subject string, msg *Message) error {
res := n.topic.Publish(ctx, &pubsub.Message{
Attributes: map[string]string{
"severity": msg.Severity.String(),
"subject": subject,
},
Data: []byte(msg.Body),
})
_, err := res.Get(ctx)
return err
}
// PubSubNotifier returns a Notifier which sends messages via PubSub.
func PubSubNotifier(ctx context.Context, topic string) (Notifier, error) {
client, err := pubsub.NewClient(ctx, common.PROJECT_ID)
if err != nil {
return nil, err
}
// Create the topic if it doesn't exist.
t := client.Topic(topic)
if exists, err := t.Exists(ctx); err != nil {
return nil, err
} else if !exists {
t, err = client.CreateTopic(ctx, topic)
if err != nil {
return nil, err
}
}
return &pubSubNotifier{
topic: t,
}, nil
}