diff --git a/cmd/server.go b/cmd/server.go index 553298d19..9e5c14cbe 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -28,9 +28,12 @@ import ( "github.com/flanksource/incident-commander/events" "github.com/flanksource/incident-commander/jobs" "github.com/flanksource/incident-commander/logs" + "github.com/flanksource/incident-commander/notification" "github.com/flanksource/incident-commander/playbook" "github.com/flanksource/incident-commander/rbac" + "github.com/flanksource/incident-commander/responder" "github.com/flanksource/incident-commander/snapshot" + "github.com/flanksource/incident-commander/teams" "github.com/flanksource/incident-commander/upstream" "github.com/flanksource/incident-commander/utils" ) @@ -258,6 +261,8 @@ var Serve = &cobra.Command{ UpstreamPush: api.UpstreamConf, }) + go tableUpdatesHandler(api.DefaultContext) + go playbook.StartPlaybookRunConsumer(api.DefaultContext) go playbook.ListenPlaybookPGNotify(api.DefaultContext) @@ -335,3 +340,23 @@ func ServerCache(next echo.HandlerFunc) echo.HandlerFunc { return next(c) } } + +// tableUpdatesHandler handles all "table_activity" pg notifications. +func tableUpdatesHandler(ctx api.Context) { + notifyRouter := events.NewPgNotifyRouter() + go notifyRouter.Run(ctx, "table_activity") + + notificationUpdateCh := notifyRouter.RegisterRoutes("notifications") + teamsUpdateChan := notifyRouter.RegisterRoutes("teams") + + for { + select { + case id := <-notificationUpdateCh: + notification.PurgeCache(id) + + case id := <-teamsUpdateChan: + responder.PurgeCache(id) + teams.PurgeCache(id) + } + } +} diff --git a/db/notifications.go b/db/notifications.go index aa11da8db..b86080d29 100644 --- a/db/notifications.go +++ b/db/notifications.go @@ -80,3 +80,7 @@ func PersistNotificationFromCRD(obj *v1.Notification) error { func DeleteNotification(id string) error { return Gorm.Delete(&models.Notification{}, "id = ?", id).Error } + +func UpdateNotificationError(id string, err string) error { + return Gorm.Model(&models.Notification{}).Where("id = ?", id).Update("error", err).Error +} diff --git a/events/event_queue.go b/events/event_queue.go index 5146c205d..c1d99bf91 100644 --- a/events/event_queue.go +++ b/events/event_queue.go @@ -34,9 +34,6 @@ type ( // // These events are generated by the database in response to updates on some of the tables. const ( - EventTeamUpdate = "team.update" - EventTeamDelete = "team.delete" - EventCheckPassed = "check.passed" EventCheckFailed = "check.failed" @@ -46,9 +43,6 @@ const ( EventComponentStatusWarning = "component.status.warning" EventComponentStatusError = "component.status.error" - EventNotificationUpdate = "notification.update" - EventNotificationDelete = "notification.delete" - EventPlaybookSpecApprovalUpdated = "playbook.spec.approval.updated" EventPlaybookApprovalInserted = "playbook.approval.inserted" @@ -95,18 +89,16 @@ type Config struct { func StartConsumers(ctx api.Context, config Config) { // We listen to all PG Notifications on one channel and distribute it to other consumers // based on the events. - notifyRouter := newPgNotifyRouter() + notifyRouter := NewPgNotifyRouter() go notifyRouter.Run(ctx, eventQueueUpdateChannel) uniqEvents := make(map[string]struct{}) allSyncHandlers := []SyncEventConsumer{ - NewTeamConsumerSync(), NewCheckConsumerSync(), NewComponentConsumerSync(), NewResponderConsumerSync(), NewCommentConsumerSync(), NewNotificationSaveConsumerSync(), - NewNotificationUpdatesConsumerSync(), NewPlaybookApprovalConsumerSync(), NewPlaybookApprovalSpecUpdatedConsumerSync(), } @@ -120,7 +112,7 @@ func StartConsumers(ctx api.Context, config Config) { uniqEvents[event] = struct{}{} } - pgNotifyChannel := notifyRouter.RegisterRoutes(allSyncHandlers[i].watchEvents) + pgNotifyChannel := notifyRouter.RegisterRoutes(allSyncHandlers[i].watchEvents...) go allSyncHandlers[i].EventConsumer().Listen(ctx, pgNotifyChannel) } @@ -141,7 +133,7 @@ func StartConsumers(ctx api.Context, config Config) { uniqEvents[event] = struct{}{} } - pgNotifyChannel := notifyRouter.RegisterRoutes(asyncConsumers[i].watchEvents) + pgNotifyChannel := notifyRouter.RegisterRoutes(asyncConsumers[i].watchEvents...) go asyncConsumers[i].EventConsumer().Listen(ctx, pgNotifyChannel) } } diff --git a/events/notifications.go b/events/notifications.go index 4c4fd1157..d76d330d0 100644 --- a/events/notifications.go +++ b/events/notifications.go @@ -1,32 +1,21 @@ package events import ( - "encoding/json" "fmt" - "net/url" "strings" - "time" - "github.com/flanksource/commons/template" - cUtils "github.com/flanksource/commons/utils" "github.com/flanksource/duty" "github.com/flanksource/duty/models" "github.com/flanksource/incident-commander/api" + "github.com/flanksource/incident-commander/db" "github.com/flanksource/incident-commander/logs" - pkgNotification "github.com/flanksource/incident-commander/notification" + "github.com/flanksource/incident-commander/notification" pkgResponder "github.com/flanksource/incident-commander/responder" - "github.com/flanksource/incident-commander/teams" - "github.com/google/uuid" + "github.com/flanksource/incident-commander/utils/expression" ) -func NewNotificationUpdatesConsumerSync() SyncEventConsumer { - return SyncEventConsumer{ - watchEvents: []string{EventNotificationUpdate, EventNotificationDelete}, - consumers: []SyncEventHandlerFunc{ - handleNotificationUpdates, - }, - } -} +// List of all possible variables for any expression related to notifications +var allEnvVars = []string{"check", "canary", "incident", "team", "responder", "comment", "evidence", "hypothesis"} func NewNotificationSaveConsumerSync() SyncEventConsumer { return SyncEventConsumer{ @@ -59,216 +48,11 @@ func NewNotificationSendConsumerAsync() AsyncEventConsumer { } } -func sendNotifications(ctx api.Context, events []api.Event) []api.Event { - var failedEvents []api.Event - for _, e := range events { - var props NotificationEventPayload - props.FromMap(e.Properties) - - notificationContext := pkgNotification.NewContext(ctx, props.NotificationID) - notificationContext.WithSource(props.EventName, props.ID) - logs.IfError(notificationContext.StartLog(), "error persisting start of notification send history") - - if err := sendNotification(notificationContext, e); err != nil { - e.Error = err.Error() - failedEvents = append(failedEvents, e) - notificationContext.WithError(err.Error()) - } - - logs.IfError(notificationContext.EndLog(), "error persisting end of notification send history") - } - - return failedEvents -} - -// NotificationEventPayload holds data to create a notification -type NotificationEventPayload struct { - ID uuid.UUID `json:"id"` // Resource id. depends what it is based on the original event. - EventName string `json:"event_name"` // The name of the original event this notification is for. - PersonID *uuid.UUID `json:"person_id,omitempty"` // The person recipient. - TeamID string `json:"team_id,omitempty"` // The team recipient. - NotificationName string `json:"notification_name,omitempty"` // Name of the notification of a team or a custom service of the notification. - NotificationID uuid.UUID `json:"notification_id,omitempty"` // ID of the notification. - EventCreatedAt time.Time `json:"event_created_at"` // Timestamp at which the original event was created -} - -// NotificationTemplate holds in data for notification -// that'll be used by struct templater. -type NotificationTemplate struct { - Title string `template:"true"` - Message string `template:"true"` - Properties map[string]string `template:"true"` -} - -func (t *NotificationEventPayload) AsMap() map[string]string { - m := make(map[string]string) - b, _ := json.Marshal(&t) - _ = json.Unmarshal(b, &m) - return m -} - -func (t *NotificationEventPayload) FromMap(m map[string]string) { - b, _ := json.Marshal(m) - _ = json.Unmarshal(b, &t) -} - -// labelsTemplate is a helper func to generate the template for displaying labels -func labelsTemplate(field string) string { - return fmt.Sprintf("{{if %s}}### Labels: \n{{range $k, $v := %s}}**{{$k}}**: {{$v}} \n{{end}}{{end}}", field, field) -} - -// defaultTitleAndBody returns the default title and body for notification -// based on the given event. -func defaultTitleAndBody(event string) (title string, body string) { - switch event { - case EventCheckPassed: - title = "Check {{.check.name}} has passed" - body = fmt.Sprintf(`Canary: {{.canary.name}} -{{if .agent}}Agent: {{.agent.name}}{{end}} -{{if .status.message}}Message: {{.status.message}} {{end}} -%s - -[Reference]({{.permalink}})`, labelsTemplate(".check.labels")) - - case EventCheckFailed: - title = "Check {{.check.name}} has failed" - body = fmt.Sprintf(`Canary: {{.canary.name}} -{{if .agent}}Agent: {{.agent.name}}{{end}} -Error: {{.status.error}} -%s - -[Reference]({{.permalink}})`, labelsTemplate(".check.labels")) - - case EventComponentStatusHealthy, EventComponentStatusUnhealthy, EventComponentStatusInfo, EventComponentStatusWarning, EventComponentStatusError: - title = "Component {{.component.name}} status updated to {{.component.status}}" - body = fmt.Sprintf("%s\n[Reference]({{.permalink}})", labelsTemplate(".component.labels")) - - case EventIncidentCommentAdded: - title = "{{.author.name}} left a comment on {{.incident.incident_id}}: {{.incident.title}}" - body = "{{.comment.comment}}\n\n[Reference]({{.permalink}})" - - case EventIncidentCreated: - title = "{{.incident.incident_id}}: {{.incident.title}} ({{.incident.severity}}) created" - body = "Type: {{.incident.type}}\n\n[Reference]({{.permalink}})" - - case EventIncidentDODAdded: - title = "Definition of Done added | {{.incident.incident_id}}: {{.incident.title}}" - body = "Evidence: {{.evidence.description}}\n\n[Reference]({{.permalink}})" - - case EventIncidentDODPassed, EventIncidentDODRegressed: - title = "Definition of Done {{if .evidence.done}}passed{{else}}regressed{{end}} | {{.incident.incident_id}}: {{.incident.title}}" - body = `Evidence: {{.evidence.description}} -Hypothesis: {{.hypothesis.title}} - -[Reference]({{.permalink}})` - - case EventIncidentResponderAdded: - title = "New responder added to {{.incident.incident_id}}: {{.incident.title}}" - body = "Responder {{.responder.name}}\n\n[Reference]({{.permalink}})" - - case EventIncidentResponderRemoved: - title = "Responder removed from {{.incident.incident_id}}: {{.incident.title}}" - body = "Responder {{.responder.name}}\n\n[Reference]({{.permalink}})" - - case EventIncidentStatusCancelled, EventIncidentStatusClosed, EventIncidentStatusInvestigating, EventIncidentStatusMitigated, EventIncidentStatusOpen, EventIncidentStatusResolved: - title = "{{.incident.title}} status updated" - body = "New Status: {{.incident.status}}\n\n[Reference]({{.permalink}})" - - case EventTeamUpdate, EventTeamDelete, EventNotificationUpdate, EventNotificationDelete, EventPlaybookSpecApprovalUpdated, EventPlaybookApprovalInserted: - // Not applicable - } - - return title, body -} - -func sendNotification(ctx *pkgNotification.Context, event api.Event) error { - var props NotificationEventPayload - props.FromMap(event.Properties) - - originalEvent := api.Event{Name: props.EventName, CreatedAt: props.EventCreatedAt} - celEnv, err := getEnvForEvent(ctx.Context, originalEvent, event.Properties) - if err != nil { - return err - } - - templater := template.StructTemplater{ - Values: celEnv, - ValueFunctions: true, - DelimSets: []template.Delims{ - {Left: "{{", Right: "}}"}, - {Left: "$(", Right: ")"}, - }, - } - - notification, err := pkgNotification.GetNotification(ctx.Context, props.NotificationID.String()) - if err != nil { - return err - } - - defaultTitle, defaultBody := defaultTitleAndBody(props.EventName) - - data := NotificationTemplate{ - Title: cUtils.Coalesce(notification.Title, defaultTitle), - Message: cUtils.Coalesce(notification.Template, defaultBody), - Properties: notification.Properties, - } - - if err := templater.Walk(&data); err != nil { - return fmt.Errorf("error templating notification: %w", err) - } - - if props.PersonID != nil { - ctx.WithPersonID(props.PersonID).WithRecipientType(pkgNotification.RecipientTypePerson) - var emailAddress string - if err := ctx.DB().Model(&models.Person{}).Select("email").Where("id = ?", props.PersonID).Find(&emailAddress).Error; err != nil { - return fmt.Errorf("failed to get email of person(id=%s); %v", props.PersonID, err) - } - - smtpURL := fmt.Sprintf("%s?ToAddresses=%s", api.SystemSMTP, url.QueryEscape(emailAddress)) - return pkgNotification.Send(ctx, "", smtpURL, data.Title, data.Message, data.Properties) - } - - if props.TeamID != "" { - ctx.WithRecipientType(pkgNotification.RecipientTypeTeam) - teamSpec, err := teams.GetTeamSpec(ctx.Context, props.TeamID) - if err != nil { - return fmt.Errorf("failed to get team(id=%s); %v", props.TeamID, err) - } - - for _, cn := range teamSpec.Notifications { - if cn.Name != props.NotificationName { - continue - } - - if err := templater.Walk(&cn); err != nil { - return fmt.Errorf("error templating notification: %w", err) - } - - return pkgNotification.Send(ctx, cn.Connection, cn.URL, data.Title, data.Message, data.Properties, cn.Properties) - } - } - - for _, cn := range notification.CustomNotifications { - ctx.WithRecipientType(pkgNotification.RecipientTypeCustom) - if cn.Name != props.NotificationName { - continue - } - - if err := templater.Walk(&cn); err != nil { - return fmt.Errorf("error templating notification: %w", err) - } - - return pkgNotification.Send(ctx, cn.Connection, cn.URL, data.Title, data.Message, data.Properties, cn.Properties) - } - - return nil -} - -// addNotificationEvent responds to a event that can possible generate a notification. +// addNotificationEvent responds to a event that can possibly generate a notification. // If a notification is found for the given event and passes all the filters, then -// a new notification event is created. +// a new `notification.send` event is created. func addNotificationEvent(ctx api.Context, event api.Event) error { - notificationIDs, err := pkgNotification.GetNotificationIDs(ctx, event.Name) + notificationIDs, err := notification.GetNotificationIDsForEvent(ctx, event.Name) if err != nil { return err } @@ -283,7 +67,7 @@ func addNotificationEvent(ctx api.Context, event api.Event) error { } for _, id := range notificationIDs { - n, err := pkgNotification.GetNotification(ctx, id) + n, err := notification.GetNotification(ctx, id) if err != nil { return err } @@ -292,113 +76,66 @@ func addNotificationEvent(ctx api.Context, event api.Event) error { continue } - expressionRunner := pkgNotification.ExpressionRunner{ - ResourceID: id, - ResourceType: "notification", - CelEnv: celEnv, + if n.Error != nil { + // A notification that currently has errors is skipped. + continue } - if valid, err := expressionRunner.Eval(ctx, n.Filter); err != nil || !valid { - // We consider an error in filter evaluation is a failed filter check. - // Mostly, the filter check returns an error if the variable isn't defined. - // Example: If the filter makes use of `check` variable but the event is for - // incident creation, then the expression evaluation returns an error. + if valid, err := expression.Eval(n.Filter, celEnv, allEnvVars); err != nil { + logs.IfError(db.UpdateNotificationError(id, err.Error()), "failed to update notification") + } else if !valid { continue } - if n.PersonID != nil { - resourceID, err := uuid.Parse(event.Properties["id"]) - if err != nil { - return fmt.Errorf("failed to parse resource id: %v", err) - } - - prop := NotificationEventPayload{ - EventName: event.Name, - NotificationID: n.ID, - ID: resourceID, - PersonID: n.PersonID, - EventCreatedAt: event.CreatedAt, - } - - newEvent := &api.Event{ - Name: EventNotificationSend, - Properties: prop.AsMap(), - } - if err := ctx.DB().Clauses(eventQueueOnConflictClause).Create(newEvent).Error; err != nil { - return fmt.Errorf("failed to create notification event for person(id=%s): %v", n.PersonID, err) - } + payloads, err := notification.CreateNotificationSendPayloads(ctx, event, n, celEnv) + if err != nil { + return err } - if n.TeamID != nil { - teamSpec, err := teams.GetTeamSpec(ctx, n.TeamID.String()) - if err != nil { - return fmt.Errorf("failed to get team(id=%s); %v", n.TeamID, err) - } - - expressionRunner := pkgNotification.ExpressionRunner{ - ResourceID: id, - ResourceType: "notification", - CelEnv: celEnv, + for _, payload := range payloads { + newEvent := api.Event{ + Name: EventNotificationSend, + Properties: payload.AsMap(), } - for _, cn := range teamSpec.Notifications { - if valid, err := expressionRunner.Eval(ctx, cn.Filter); err != nil || !valid { - continue - } - - resourceID, err := uuid.Parse(event.Properties["id"]) - if err != nil { - return fmt.Errorf("failed to parse resource id: %v", err) - } - - prop := NotificationEventPayload{ - EventName: event.Name, - NotificationID: n.ID, - ID: resourceID, - TeamID: n.TeamID.String(), - NotificationName: cn.Name, - } - - newEvent := &api.Event{ - Name: EventNotificationSend, - Properties: prop.AsMap(), - } - - if err := ctx.DB().Clauses(eventQueueOnConflictClause).Create(newEvent).Error; err != nil { - return fmt.Errorf("failed to create notification event for team(id=%s): %v", n.TeamID, err) - } + if err := ctx.DB().Clauses(eventQueueOnConflictClause).Create(&newEvent).Error; err != nil { + return fmt.Errorf("failed to saved `notification.send` event for payload (%v): %w", payload.AsMap(), err) } } + } - for _, cn := range n.CustomNotifications { - if valid, err := expressionRunner.Eval(ctx, cn.Filter); err != nil || !valid { - continue - } + return nil +} - resourceID, err := uuid.Parse(event.Properties["id"]) - if err != nil { - return fmt.Errorf("failed to parse resource id: %v", err) - } +// sendNotifications sends a notification for each of the given events - one at a time. +// It returns any events that failed to send. +func sendNotifications(ctx api.Context, events []api.Event) []api.Event { + var failedEvents []api.Event + for _, e := range events { + var payload notification.NotificationEventPayload + payload.FromMap(e.Properties) - prop := NotificationEventPayload{ - EventName: event.Name, - NotificationID: n.ID, - ID: resourceID, - NotificationName: cn.Name, - } + notificationContext := notification.NewContext(ctx, payload.NotificationID) + notificationContext.WithSource(payload.EventName, payload.ID) - newEvent := &api.Event{ - Name: EventNotificationSend, - Properties: prop.AsMap(), - } + logs.IfError(notificationContext.StartLog(), "error persisting start of notification send history") - if err := ctx.DB().Clauses(eventQueueOnConflictClause).Create(newEvent).Error; err != nil { - return fmt.Errorf("failed to create notification event for custom service (name:%s): %v", cn.Name, err) - } + originalEvent := api.Event{Name: payload.EventName, CreatedAt: payload.EventCreatedAt} + celEnv, err := getEnvForEvent(ctx, originalEvent, e.Properties) + if err != nil { + e.Error = err.Error() + failedEvents = append(failedEvents, e) + notificationContext.WithError(err.Error()) + } else if err := notification.SendNotification(notificationContext, payload, celEnv); err != nil { + e.Error = err.Error() + failedEvents = append(failedEvents, e) + notificationContext.WithError(err.Error()) } + + logs.IfError(notificationContext.EndLog(), "error persisting end of notification send history") } - return nil + return failedEvents } // getEnvForEvent gets the environment variables for the given event @@ -560,11 +297,3 @@ func getEnvForEvent(ctx api.Context, event api.Event, properties map[string]stri return env, nil } - -func handleNotificationUpdates(ctx api.Context, event api.Event) error { - if id, ok := event.Properties["id"]; ok { - pkgNotification.PurgeCache(id) - } - - return nil -} diff --git a/events/pg_notify_router.go b/events/pg_notify_router.go index f77b393e0..a39fd6b32 100644 --- a/events/pg_notify_router.go +++ b/events/pg_notify_router.go @@ -1,6 +1,8 @@ package events import ( + "strings" + "github.com/flanksource/commons/logger" "github.com/flanksource/duty/duty/pg" "github.com/flanksource/incident-commander/api" @@ -12,14 +14,14 @@ type pgNotifyRouter struct { registry map[string]chan string } -func newPgNotifyRouter() *pgNotifyRouter { +func NewPgNotifyRouter() *pgNotifyRouter { return &pgNotifyRouter{ registry: make(map[string]chan string), } } // RegisterRoutes creates a single channel for the given routes and returns it. -func (t *pgNotifyRouter) RegisterRoutes(routes []string) <-chan string { +func (t *pgNotifyRouter) RegisterRoutes(routes ...string) <-chan string { pgNotifyChannel := make(chan string) for _, we := range routes { t.registry[we] = pgNotifyChannel @@ -33,10 +35,21 @@ func (t *pgNotifyRouter) Run(ctx api.Context, channel string) { logger.Debugf("running pg notify router") for payload := range eventQueueNotifyChannel { - if ch, ok := t.registry[payload]; ok { - ch <- payload - } else if payload != EventPushQueueCreate { // Ignore push queue events cuz that'll pollute the logs - logger.Warnf("notify router:: received notification for an unregistered event: %s", payload) + if payload == "" || payload == EventPushQueueCreate { + // Ignore push queue events cuz that'll pollute the logs + continue + } + + // The original payload is expected to be in the form of + // <...optional payload> + fields := strings.Fields(payload) + route := fields[0] + derivedPayload := strings.Join(fields[1:], " ") + + if ch, ok := t.registry[route]; ok { + ch <- derivedPayload + } else { + logger.Warnf("notify router:: received notification for an unregistered event: %s", route) } } } diff --git a/events/teams.go b/events/teams.go deleted file mode 100644 index aa0782d5b..000000000 --- a/events/teams.go +++ /dev/null @@ -1,70 +0,0 @@ -package events - -import ( - "fmt" - - "github.com/flanksource/commons/logger" - "github.com/flanksource/incident-commander/api" - "github.com/flanksource/incident-commander/responder" - "github.com/flanksource/incident-commander/teams" - "github.com/google/uuid" -) - -func NewTeamConsumerSync() SyncEventConsumer { - consumer := SyncEventConsumer{ - watchEvents: []string{EventTeamUpdate, EventTeamDelete}, - consumers: []SyncEventHandlerFunc{handleTeamEvent}, - } - - return consumer -} - -func handleTeamEvent(ctx api.Context, event api.Event) error { - switch event.Name { - case EventTeamUpdate: - return handleTeamUpdate(ctx, event) - case EventTeamDelete: - return handleTeamDelete(ctx, event) - default: - return fmt.Errorf("unrecognized event name: %s", event.Name) - } -} - -// handleTeamDelete makes the necessary changes when a team is deleted. -func handleTeamDelete(ctx api.Context, event api.Event) error { - var teamID uuid.UUID - if _teamID, ok := event.Properties["team_id"]; !ok { - logger.Warnf("event has invalid property. missing 'team_id'") - return nil - } else { - var err error - teamID, err = uuid.Parse(_teamID) - if err != nil { - logger.Warnf("event has invalid team_id=%q. It's not a UUID", _teamID) - return nil - } - } - - responder.PurgeCache(teamID.String()) - teams.PurgeCache(teamID.String()) - return nil -} - -// handleTeamUpdate makes the necessary changes when a team spec is updated. -func handleTeamUpdate(ctx api.Context, event api.Event) error { - var teamID uuid.UUID - if _teamID, ok := event.Properties["team_id"]; !ok { - logger.Warnf("event has invalid property. missing 'team_id'") - return nil - } else { - var err error - teamID, err = uuid.Parse(_teamID) - if err != nil { - return err - } - } - - responder.PurgeCache(teamID.String()) - teams.PurgeCache(teamID.String()) - return nil -} diff --git a/go.mod b/go.mod index 5fc9d64e3..333ada7ba 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/flanksource/gomplate/v3 v3.20.12 github.com/flanksource/kopper v1.0.6 github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 - github.com/google/cel-go v0.18.0 + github.com/google/cel-go v0.18.1 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.1 github.com/jackc/pgx/v5 v5.4.3 @@ -30,7 +30,6 @@ require ( github.com/onsi/gomega v1.27.7 github.com/ory/client-go v1.1.41 github.com/prometheus/client_golang v1.16.0 - github.com/sethvargo/go-retry v0.2.4 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df @@ -41,7 +40,7 @@ require ( ) require ( - ariga.io/atlas v0.14.0 // indirect + ariga.io/atlas v0.14.1 // indirect cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.2 // indirect @@ -74,7 +73,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce // indirect github.com/hashicorp/hcl/v2 v2.18.0 // indirect @@ -96,9 +95,10 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/robertkrimen/otto v0.2.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/tidwall/gjson v1.16.0 // indirect + github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect @@ -112,8 +112,8 @@ require ( golang.org/x/term v0.12.0 // indirect golang.org/x/tools v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/flanksource/yaml.v3 v3.2.3 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect @@ -143,8 +143,8 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/TomOnTime/utfutil v0.0.0-20230223141146-125e65197b36 - github.com/antonmedv/expr v1.15.2 // indirect - github.com/aws/aws-sdk-go v1.45.13 // indirect + github.com/antonmedv/expr v1.15.3 // indirect + github.com/aws/aws-sdk-go v1.45.16 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -212,10 +212,10 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.142.0 // indirect + google.golang.org/api v0.143.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/grpc v1.58.1 // indirect + google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/grpc v1.58.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 8570bffb2..d6465ce77 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -ariga.io/atlas v0.14.0 h1:2gpshFCwvlbXxRJHahUbIpDVdsbJtZVVeuqL410LSTY= -ariga.io/atlas v0.14.0/go.mod h1:+TR129FJZ5Lvzms6dvCeGWh1yR6hMvmXBhug4hrNIGk= +ariga.io/atlas v0.14.1 h1:mun+I5QiFaKVJBfHNnlTqa0PCj6qCZsp/M3dxFC9WPg= +ariga.io/atlas v0.14.1/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -646,8 +646,8 @@ github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIi github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/antonmedv/expr v1.15.2 h1:afFXpDWIC2n3bF+kTZE1JvFo+c34uaM3sTqh8z0xfdU= -github.com/antonmedv/expr v1.15.2/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE= +github.com/antonmedv/expr v1.15.3 h1:q3hOJZNvLvhqE8OHBs1cFRdbXFNKuA+bHmRaI+AmRmI= +github.com/antonmedv/expr v1.15.3/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= @@ -656,8 +656,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.45.13 h1:LwD/G+PX7FQnbU8wXekx12e90i1GuKJQC2+pl4IlPAs= -github.com/aws/aws-sdk-go v1.45.13/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.16 h1:spca2z7UJgoQ5V2fX6XiHDCj2E65kOJAfbUPozSkE24= +github.com/aws/aws-sdk-go v1.45.16/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= @@ -870,8 +870,8 @@ github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGc github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= -github.com/google/cel-go v0.18.0 h1:u74MPiEC8mejBrkXqrTWT102g5IFEUjxOngzQIijMzU= -github.com/google/cel-go v0.18.0/go.mod h1:PVAybmSnWkNMUZR/tEWFUiJ1Np4Hz0MHsZJcgC4zln4= +github.com/google/cel-go v0.18.1 h1:V/lAXKq4C3BYLDy/ARzMtpkEEYfHQpZzVyzy69nEUjs= +github.com/google/cel-go v0.18.1/go.mod h1:PVAybmSnWkNMUZR/tEWFUiJ1Np4Hz0MHsZJcgC4zln4= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -935,8 +935,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -1231,8 +1231,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= -github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -1760,8 +1760,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.142.0 h1:mf+7EJ94fi5ZcnpPy+m0Yv2dkz8bKm+UL0snTCuwXlY= -google.golang.org/api v0.142.0/go.mod h1:zJAN5o6HRqR7O+9qJUFOWrZkYE66RH+efPBdTLA4xBA= +google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA= +google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1905,16 +1905,16 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1954,8 +1954,8 @@ google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= -google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/hack/generate-schemas/go.mod b/hack/generate-schemas/go.mod index 440930768..40a9533a6 100644 --- a/hack/generate-schemas/go.mod +++ b/hack/generate-schemas/go.mod @@ -10,7 +10,7 @@ require ( ) require ( - ariga.io/atlas v0.14.0 // indirect + ariga.io/atlas v0.14.1 // indirect cloud.google.com/go v0.110.8 // indirect cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect @@ -20,9 +20,9 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect - github.com/antonmedv/expr v1.15.2 // indirect + github.com/antonmedv/expr v1.15.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/aws/aws-sdk-go v1.45.13 // indirect + github.com/aws/aws-sdk-go v1.45.16 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -41,13 +41,13 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/cel-go v0.18.0 // indirect + github.com/google/cel-go v0.18.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.3.1 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gosimple/slug v1.13.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect @@ -95,7 +95,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/tidwall/gjson v1.16.0 // indirect + github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect @@ -117,12 +117,12 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.142.0 // indirect + google.golang.org/api v0.143.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/grpc v1.58.1 // indirect + google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/grpc v1.58.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/flanksource/yaml.v3 v3.2.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/hack/generate-schemas/go.sum b/hack/generate-schemas/go.sum index 43ece36df..7650cf514 100644 --- a/hack/generate-schemas/go.sum +++ b/hack/generate-schemas/go.sum @@ -1,5 +1,5 @@ -ariga.io/atlas v0.14.0 h1:2gpshFCwvlbXxRJHahUbIpDVdsbJtZVVeuqL410LSTY= -ariga.io/atlas v0.14.0/go.mod h1:+TR129FJZ5Lvzms6dvCeGWh1yR6hMvmXBhug4hrNIGk= +ariga.io/atlas v0.14.1 h1:mun+I5QiFaKVJBfHNnlTqa0PCj6qCZsp/M3dxFC9WPg= +ariga.io/atlas v0.14.1/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -629,8 +629,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/antonmedv/expr v1.15.2 h1:afFXpDWIC2n3bF+kTZE1JvFo+c34uaM3sTqh8z0xfdU= -github.com/antonmedv/expr v1.15.2/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE= +github.com/antonmedv/expr v1.15.3 h1:q3hOJZNvLvhqE8OHBs1cFRdbXFNKuA+bHmRaI+AmRmI= +github.com/antonmedv/expr v1.15.3/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= @@ -639,8 +639,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.45.13 h1:LwD/G+PX7FQnbU8wXekx12e90i1GuKJQC2+pl4IlPAs= -github.com/aws/aws-sdk-go v1.45.13/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.16 h1:spca2z7UJgoQ5V2fX6XiHDCj2E65kOJAfbUPozSkE24= +github.com/aws/aws-sdk-go v1.45.16/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -801,8 +801,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= -github.com/google/cel-go v0.18.0 h1:u74MPiEC8mejBrkXqrTWT102g5IFEUjxOngzQIijMzU= -github.com/google/cel-go v0.18.0/go.mod h1:PVAybmSnWkNMUZR/tEWFUiJ1Np4Hz0MHsZJcgC4zln4= +github.com/google/cel-go v0.18.1 h1:V/lAXKq4C3BYLDy/ARzMtpkEEYfHQpZzVyzy69nEUjs= +github.com/google/cel-go v0.18.1/go.mod h1:PVAybmSnWkNMUZR/tEWFUiJ1Np4Hz0MHsZJcgC4zln4= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -863,8 +863,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -1099,8 +1099,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= -github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -1606,8 +1606,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.142.0 h1:mf+7EJ94fi5ZcnpPy+m0Yv2dkz8bKm+UL0snTCuwXlY= -google.golang.org/api v0.142.0/go.mod h1:zJAN5o6HRqR7O+9qJUFOWrZkYE66RH+efPBdTLA4xBA= +google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA= +google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1751,16 +1751,16 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb h1:Isk1sSH7bovx8Rti2wZK0UZF6oraBDK74uoyLEEVFN0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1800,8 +1800,8 @@ google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= -google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/notification/events.go b/notification/events.go new file mode 100644 index 000000000..58584b8e8 --- /dev/null +++ b/notification/events.go @@ -0,0 +1,287 @@ +package notification + +import ( + "encoding/json" + "fmt" + "net/url" + "time" + + "github.com/flanksource/commons/template" + "github.com/flanksource/commons/utils" + "github.com/flanksource/duty/models" + "github.com/google/uuid" + + "github.com/flanksource/incident-commander/api" + "github.com/flanksource/incident-commander/db" + "github.com/flanksource/incident-commander/logs" + "github.com/flanksource/incident-commander/teams" + "github.com/flanksource/incident-commander/utils/expression" +) + +// List of all events that can create notifications ... +const ( + EventCheckPassed = "check.passed" + EventCheckFailed = "check.failed" + + EventComponentStatusHealthy = "component.status.healthy" + EventComponentStatusUnhealthy = "component.status.unhealthy" + EventComponentStatusInfo = "component.status.info" + EventComponentStatusWarning = "component.status.warning" + EventComponentStatusError = "component.status.error" + + EventIncidentCommentAdded = "incident.comment.added" + EventIncidentCreated = "incident.created" + EventIncidentDODAdded = "incident.dod.added" + EventIncidentDODPassed = "incident.dod.passed" + EventIncidentDODRegressed = "incident.dod.regressed" + EventIncidentResponderAdded = "incident.responder.added" + EventIncidentResponderRemoved = "incident.responder.removed" + EventIncidentStatusCancelled = "incident.status.cancelled" + EventIncidentStatusClosed = "incident.status.closed" + EventIncidentStatusInvestigating = "incident.status.investigating" + EventIncidentStatusMitigated = "incident.status.mitigated" + EventIncidentStatusOpen = "incident.status.open" + EventIncidentStatusResolved = "incident.status.resolved" +) + +// List of all possible variables for any expression related to notifications +var allEnvVars = []string{"check", "canary", "incident", "team", "responder", "comment", "evidence", "hypothesis"} + +// NotificationTemplate holds in data for notification +// that'll be used by struct templater. +type NotificationTemplate struct { + Title string `template:"true"` + Message string `template:"true"` + Properties map[string]string `template:"true"` +} + +// NotificationEventPayload holds data to create a notification. +type NotificationEventPayload struct { + ID uuid.UUID `json:"id"` // Resource id. depends what it is based on the original event. + EventName string `json:"event_name"` // The name of the original event this notification is for. + PersonID *uuid.UUID `json:"person_id,omitempty"` // The person recipient. + TeamID string `json:"team_id,omitempty"` // The team recipient. + NotificationName string `json:"notification_name,omitempty"` // Name of the notification of a team or a custom service of the notification. + NotificationID uuid.UUID `json:"notification_id,omitempty"` // ID of the notification. + EventCreatedAt time.Time `json:"event_created_at"` // Timestamp at which the original event was created +} + +func (t *NotificationEventPayload) AsMap() map[string]string { + m := make(map[string]string) + b, _ := json.Marshal(&t) + _ = json.Unmarshal(b, &m) + return m +} + +func (t *NotificationEventPayload) FromMap(m map[string]string) { + b, _ := json.Marshal(m) + _ = json.Unmarshal(b, &t) +} + +// SendNotification generates the notification from the given event and sends it. +func SendNotification(ctx *Context, payload NotificationEventPayload, celEnv map[string]any) error { + templater := template.StructTemplater{ + Values: celEnv, + ValueFunctions: true, + DelimSets: []template.Delims{ + {Left: "{{", Right: "}}"}, + {Left: "$(", Right: ")"}, + }, + } + + notification, err := GetNotification(ctx.Context, payload.NotificationID.String()) + if err != nil { + return err + } + + defaultTitle, defaultBody := defaultTitleAndBody(payload.EventName) + + data := NotificationTemplate{ + Title: utils.Coalesce(notification.Title, defaultTitle), + Message: utils.Coalesce(notification.Template, defaultBody), + Properties: notification.Properties, + } + + if err := templater.Walk(&data); err != nil { + return fmt.Errorf("error templating notification: %w", err) + } + + if payload.PersonID != nil { + ctx.WithPersonID(payload.PersonID).WithRecipientType(RecipientTypePerson) + var emailAddress string + if err := ctx.DB().Model(&models.Person{}).Select("email").Where("id = ?", payload.PersonID).Find(&emailAddress).Error; err != nil { + return fmt.Errorf("failed to get email of person(id=%s); %v", payload.PersonID, err) + } + + smtpURL := fmt.Sprintf("%s?ToAddresses=%s", api.SystemSMTP, url.QueryEscape(emailAddress)) + return Send(ctx, "", smtpURL, data.Title, data.Message, data.Properties) + } + + if payload.TeamID != "" { + ctx.WithRecipientType(RecipientTypeTeam) + teamSpec, err := teams.GetTeamSpec(ctx.Context, payload.TeamID) + if err != nil { + return fmt.Errorf("failed to get team(id=%s); %v", payload.TeamID, err) + } + + for _, cn := range teamSpec.Notifications { + if cn.Name != payload.NotificationName { + continue + } + + if err := templater.Walk(&cn); err != nil { + return fmt.Errorf("error templating notification: %w", err) + } + + return Send(ctx, cn.Connection, cn.URL, data.Title, data.Message, data.Properties, cn.Properties) + } + } + + for _, cn := range notification.CustomNotifications { + ctx.WithRecipientType(RecipientTypeCustom) + if cn.Name != payload.NotificationName { + continue + } + + if err := templater.Walk(&cn); err != nil { + return fmt.Errorf("error templating notification: %w", err) + } + + return Send(ctx, cn.Connection, cn.URL, data.Title, data.Message, data.Properties, cn.Properties) + } + + return nil +} + +// labelsTemplate is a helper func to generate the template for displaying labels +func labelsTemplate(field string) string { + return fmt.Sprintf("{{if %s}}### Labels: \n{{range $k, $v := %s}}**{{$k}}**: {{$v}} \n{{end}}{{end}}", field, field) +} + +// defaultTitleAndBody returns the default title and body for notification +// based on the given event. +func defaultTitleAndBody(event string) (title string, body string) { + switch event { + case EventCheckPassed: + title = "Check {{.check.name}} has passed" + body = fmt.Sprintf(`Canary: {{.canary.name}} +{{if .agent}}Agent: {{.agent.name}}{{end}} +{{if .status.message}}Message: {{.status.message}} {{end}} +%s + +[Reference]({{.permalink}})`, labelsTemplate(".check.labels")) + + case EventCheckFailed: + title = "Check {{.check.name}} has failed" + body = fmt.Sprintf(`Canary: {{.canary.name}} +{{if .agent}}Agent: {{.agent.name}}{{end}} +Error: {{.status.error}} +%s + +[Reference]({{.permalink}})`, labelsTemplate(".check.labels")) + + case EventComponentStatusHealthy, EventComponentStatusUnhealthy, EventComponentStatusInfo, EventComponentStatusWarning, EventComponentStatusError: + title = "Component {{.component.name}} status updated to {{.component.status}}" + body = fmt.Sprintf("%s\n[Reference]({{.permalink}})", labelsTemplate(".component.labels")) + + case EventIncidentCommentAdded: + title = "{{.author.name}} left a comment on {{.incident.incident_id}}: {{.incident.title}}" + body = "{{.comment.comment}}\n\n[Reference]({{.permalink}})" + + case EventIncidentCreated: + title = "{{.incident.incident_id}}: {{.incident.title}} ({{.incident.severity}}) created" + body = "Type: {{.incident.type}}\n\n[Reference]({{.permalink}})" + + case EventIncidentDODAdded: + title = "Definition of Done added | {{.incident.incident_id}}: {{.incident.title}}" + body = "Evidence: {{.evidence.description}}\n\n[Reference]({{.permalink}})" + + case EventIncidentDODPassed, EventIncidentDODRegressed: + title = "Definition of Done {{if .evidence.done}}passed{{else}}regressed{{end}} | {{.incident.incident_id}}: {{.incident.title}}" + body = `Evidence: {{.evidence.description}} +Hypothesis: {{.hypothesis.title}} + +[Reference]({{.permalink}})` + + case EventIncidentResponderAdded: + title = "New responder added to {{.incident.incident_id}}: {{.incident.title}}" + body = "Responder {{.responder.name}}\n\n[Reference]({{.permalink}})" + + case EventIncidentResponderRemoved: + title = "Responder removed from {{.incident.incident_id}}: {{.incident.title}}" + body = "Responder {{.responder.name}}\n\n[Reference]({{.permalink}})" + + case EventIncidentStatusCancelled, EventIncidentStatusClosed, EventIncidentStatusInvestigating, EventIncidentStatusMitigated, EventIncidentStatusOpen, EventIncidentStatusResolved: + title = "{{.incident.title}} status updated" + body = "New Status: {{.incident.status}}\n\n[Reference]({{.permalink}})" + } + + return title, body +} + +func CreateNotificationSendPayloads(ctx api.Context, event api.Event, n *NotificationWithSpec, celEnv map[string]any) ([]NotificationEventPayload, error) { + var payloads []NotificationEventPayload + + resourceID, err := uuid.Parse(event.Properties["id"]) + if err != nil { + return nil, fmt.Errorf("failed to parse resource id: %v", err) + } + + if n.PersonID != nil { + payload := NotificationEventPayload{ + EventName: event.Name, + NotificationID: n.ID, + ID: resourceID, + PersonID: n.PersonID, + EventCreatedAt: event.CreatedAt, + } + + payloads = append(payloads, payload) + } + + if n.TeamID != nil { + teamSpec, err := teams.GetTeamSpec(ctx, n.TeamID.String()) + if err != nil { + return nil, fmt.Errorf("failed to get team (id=%s); %v", n.TeamID, err) + } + + for _, cn := range teamSpec.Notifications { + if valid, err := expression.Eval(cn.Filter, celEnv, allEnvVars); err != nil { + logs.IfError(db.UpdateNotificationError(n.ID.String(), err.Error()), "failed to update notification") + } else if !valid { + continue + } + + payload := NotificationEventPayload{ + EventName: event.Name, + NotificationID: n.ID, + ID: resourceID, + TeamID: n.TeamID.String(), + NotificationName: cn.Name, + EventCreatedAt: event.CreatedAt, + } + + payloads = append(payloads, payload) + } + } + + for _, cn := range n.CustomNotifications { + if valid, err := expression.Eval(cn.Filter, celEnv, allEnvVars); err != nil { + logs.IfError(db.UpdateNotificationError(n.ID.String(), err.Error()), "failed to update notification") + } else if !valid { + continue + } + + payload := NotificationEventPayload{ + EventName: event.Name, + NotificationID: n.ID, + ID: resourceID, + NotificationName: cn.Name, + EventCreatedAt: event.CreatedAt, + } + + payloads = append(payloads, payload) + } + + return payloads, nil +} diff --git a/notification/expression.go b/notification/expression.go deleted file mode 100644 index 8553f8f2d..000000000 --- a/notification/expression.go +++ /dev/null @@ -1,106 +0,0 @@ -package notification - -import ( - "fmt" - "strconv" - "time" - - "github.com/flanksource/commons/logger" - "github.com/flanksource/duty/models" - "github.com/flanksource/incident-commander/api" - "github.com/flanksource/incident-commander/db" - "github.com/google/cel-go/cel" - "github.com/patrickmn/go-cache" -) - -var ( - prgCache = cache.New(1*time.Hour, 1*time.Hour) - - allEnvVars = []string{"check", "canary", "incident", "team", "responder", "comment", "evidence", "hypothesis"} -) - -type programCache struct { - program *cel.Program - err error -} - -type ExpressionRunner struct { - ResourceID string - ResourceType string - CelEnv map[string]any -} - -func (t ExpressionRunner) logToJobHistory(ctx api.Context, name, errMsg string) { - jobHistory := models.NewJobHistory(name, t.ResourceType, t.ResourceID) - jobHistory.Start() - jobHistory.AddError(errMsg) - if err := db.PersistJobHistory(ctx, jobHistory.End()); err != nil { - logger.Errorf("error persisting job history: %v", err) - } -} - -// Eval evaluates the given expression into a boolean. -// The expression should return a boolean value that's supported by strconv.ParseBool. -func (t ExpressionRunner) Eval(ctx api.Context, expression string) (bool, error) { - if expression == "" { - return true, nil - } - - prg, err := t.GetOrCompileCELProgram(ctx, expression) - if err != nil { - return false, err - } - - out, _, err := (*prg).Eval(t.CelEnv) - if err != nil { - t.logToJobHistory(ctx, "NotificationFilterEval", fmt.Sprintf("%s: %s", expression, err.Error())) - return false, err - } - - return strconv.ParseBool(fmt.Sprint(out)) -} - -// GetOrCompileCELProgram returns a cached or compiled cel.Program for the given cel expression. -func (t ExpressionRunner) GetOrCompileCELProgram(ctx api.Context, expression string) (*cel.Program, error) { - if prg, exists := prgCache.Get(expression); exists { - val := prg.(*programCache) - if val.err != nil { - return nil, val.err - } - - return val.program, nil - } - - var cachedData programCache - defer func() { - prgCache.SetDefault(expression, &cachedData) - if cachedData.err != nil { - t.logToJobHistory(ctx, "NotificationFilterCompile", fmt.Sprintf("%s: %s", expression, cachedData.err.Error())) - } - }() - - celOpts := make([]cel.EnvOption, len(allEnvVars)) - for i := range allEnvVars { - celOpts[i] = cel.Variable(allEnvVars[i], cel.AnyType) - } - env, err := cel.NewEnv(celOpts...) - if err != nil { - cachedData.err = err - return nil, err - } - - ast, iss := env.Compile(expression) - if iss.Err() != nil { - cachedData.err = iss.Err() - return nil, iss.Err() - } - - prg, err := env.Program(ast) - if err != nil { - cachedData.err = err - return nil, err - } - - cachedData.program = &prg - return &prg, nil -} diff --git a/notification/notification.go b/notification/notification.go index 924f048b6..e84783cc5 100644 --- a/notification/notification.go +++ b/notification/notification.go @@ -21,7 +21,9 @@ func PurgeCache(notificationID string) { notificationByIDCache.Delete(notificationID) } -func GetNotificationIDs(ctx api.Context, eventName string) ([]string, error) { +// GetNotificationIDsForEvent returns ids of all the notifications +// that are watching the given event. +func GetNotificationIDsForEvent(ctx api.Context, eventName string) ([]string, error) { if val, found := notificationByEventCache.Get(eventName); found { return val.([]string), nil } diff --git a/utils/expression/expression.go b/utils/expression/expression.go new file mode 100644 index 000000000..2c022fa8e --- /dev/null +++ b/utils/expression/expression.go @@ -0,0 +1,86 @@ +package expression + +import ( + "fmt" + "strconv" + "time" + + "github.com/google/cel-go/cel" + "github.com/patrickmn/go-cache" +) + +var prgCache = cache.New(1*time.Hour, 1*time.Hour) + +type programCache struct { + program *cel.Program + err error +} + +// Eval evaluates the given expression. +// The expression should return a boolean value that's supported by strconv.ParseBool. +func Eval(expression string, celEnv map[string]any, allEnvVars []string) (bool, error) { + if expression == "" { + return true, nil + } + + prg, err := getOrCompileCELProgram(expression, allEnvVars) + if err != nil { + return false, fmt.Errorf("failed to compile program: %w", err) + } + + out, _, err := (*prg).Eval(celEnv) + if err != nil { + return false, fmt.Errorf("failed to evaluate program: %w", err) + } + + result, err := strconv.ParseBool(fmt.Sprint(out)) + if err != nil { + return false, fmt.Errorf("program result is not of a supported boolean type: %w", err) + } + + return result, nil +} + +// getOrCompileCELProgram returns a cached or compiled cel.Program for the given cel expression. +func getOrCompileCELProgram(expression string, allEnvVars []string) (*cel.Program, error) { + if prg, exists := prgCache.Get(expression); exists { + val := prg.(*programCache) + if val.err != nil { + return nil, val.err + } + + return val.program, nil + } + + prg, err := compileCELProgram(expression, allEnvVars) + if err != nil { + prgCache.SetDefault(expression, &programCache{err: err}) + return nil, err + } + + prgCache.SetDefault(expression, &programCache{program: prg}) + return prg, nil +} + +func compileCELProgram(expression string, allEnvVars []string) (*cel.Program, error) { + celOpts := make([]cel.EnvOption, len(allEnvVars)) + for i := range allEnvVars { + celOpts[i] = cel.Variable(allEnvVars[i], cel.AnyType) + } + env, err := cel.NewEnv(celOpts...) + if err != nil { + return nil, fmt.Errorf("failed to create cel environment: %w", err) + } + + ast, iss := env.Compile(expression) + if iss.Err() != nil { + return nil, fmt.Errorf("failed to compile expression: %w", iss.Err()) + } + + prg, err := env.Program(ast) + if err != nil { + return nil, fmt.Errorf("failed to create program: %w", err) + } + + return &prg, nil +} diff --git a/utils/pg_notify.go b/utils/pg_notify.go deleted file mode 100644 index 30d4f4655..000000000 --- a/utils/pg_notify.go +++ /dev/null @@ -1,52 +0,0 @@ -package utils - -import ( - "context" - "fmt" - "time" - - "github.com/flanksource/commons/logger" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/sethvargo/go-retry" -) - -// listenToPostgresNotify listens to postgres notifications -// and will retry on failure for dbReconnectMaxAttempt times -func ListenToPostgresNotify(pool *pgxpool.Pool, channel string, dbReconnectMaxDuration, dbReconnectBackoffBaseDuration time.Duration, pgNotify chan<- string) { - var listen = func(ctx context.Context, pgNotify chan<- string) error { - conn, err := pool.Acquire(ctx) - if err != nil { - return fmt.Errorf("error acquiring database connection: %v", err) - } - defer conn.Release() - - _, err = conn.Exec(ctx, fmt.Sprintf("LISTEN %s", channel)) - if err != nil { - return fmt.Errorf("error listening to database notifications: %v", err) - } - logger.Debugf("listening to database notifications: %s", channel) - - for { - n, err := conn.Conn().WaitForNotification(ctx) - if err != nil { - return fmt.Errorf("error listening to database notifications: %v", err) - } - - pgNotify <- n.Payload - } - } - - // retry on failure. - for { - backoff := retry.WithMaxDuration(dbReconnectMaxDuration, retry.NewExponential(dbReconnectBackoffBaseDuration)) - err := retry.Do(context.TODO(), backoff, func(ctx context.Context) error { - if err := listen(ctx, pgNotify); err != nil { - return retry.RetryableError(err) - } - - return nil - }) - - logger.Errorf("failed to connect to database: %v", err) - } -}