diff --git a/api/notifications.go b/api/notifications.go index 2e1399114..6d0b78ce6 100644 --- a/api/notifications.go +++ b/api/notifications.go @@ -10,6 +10,11 @@ import ( "gorm.io/gorm/schema" ) +// SystemSMTP indicates that the shoutrrr URL for smtp should use +// the system's SMTP credentials. +const SystemSMTP = "smtp://system/" + +// +kubebuilder:object:generate=true type NotificationConfig struct { Name string `json:"name"` // A unique name to identify this notification configuration. Filter string `json:"filter,omitempty"` // Filter is a CEL-expression used to decide whether this notification client should send the notification diff --git a/api/v1/notification_types.go b/api/v1/notification_types.go new file mode 100644 index 000000000..fc92cbcd7 --- /dev/null +++ b/api/v1/notification_types.go @@ -0,0 +1,80 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type NotificationRecipientSpec struct { + // ID or email of the person + Person string `json:"person,omitempty" yaml:"person,omitempty"` + + // name or ID of the recipient team + Team string `json:"team,omitempty" yaml:"team,omitempty"` + + // Email of the recipient + Email string `json:"email,omitempty" yaml:"email,omitempty"` + + // Specify connection string for an external service. + // Should be in the format of connection:///name + // or the id of the connection. + Connection string `json:"connection,omitempty" yaml:"connection,omitempty"` + + // Specify shoutrrr URL + URL string `json:"url,omitempty" yaml:"url,omitempty"` + + // Properties for Shoutrrr + Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"` +} + +// Empty returns true if none of the receivers are set +func (t *NotificationRecipientSpec) Empty() bool { + return t.Person == "" && t.Team == "" && t.Email == "" && t.Connection == "" && t.URL == "" +} + +// +kubebuilder:object:generate=true +type NotificationSpec struct { + // List of events that can trigger this notification + Events []string `json:"events" yaml:"events"` + + // The title for the notification + Title string `json:"title,omitempty" yaml:"title,omitempty"` + + // Template is the notification body in markdown + Template string `json:"template,omitempty" yaml:"template,omitempty"` + + // Cel-expression used to decide whether this notification client should send the notification + Filter string `json:"filter,omitempty" yaml:"filter,omitempty"` + + // Specify the recipient + To NotificationRecipientSpec `json:"to" yaml:"to"` +} + +// NotificationStatus defines the observed state of Notification +type NotificationStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Notification is the Schema for the Notification API +type Notification struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + + Spec NotificationSpec `json:"spec,omitempty" yaml:"spec,omitempty"` + Status NotificationStatus `json:"status,omitempty" yaml:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// NotificationList contains a list of Notification +type NotificationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Notification `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Notification{}, &NotificationList{}) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 9e0cd78b8..f164d5e47 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -294,6 +294,123 @@ func (in *IncidentRuleStatus) DeepCopy() *IncidentRuleStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Notification) DeepCopyInto(out *Notification) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Notification. +func (in *Notification) DeepCopy() *Notification { + if in == nil { + return nil + } + out := new(Notification) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Notification) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NotificationList) DeepCopyInto(out *NotificationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Notification, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NotificationList. +func (in *NotificationList) DeepCopy() *NotificationList { + if in == nil { + return nil + } + out := new(NotificationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NotificationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NotificationRecipientSpec) DeepCopyInto(out *NotificationRecipientSpec) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NotificationRecipientSpec. +func (in *NotificationRecipientSpec) DeepCopy() *NotificationRecipientSpec { + if in == nil { + return nil + } + out := new(NotificationRecipientSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NotificationSpec) DeepCopyInto(out *NotificationSpec) { + *out = *in + if in.Events != nil { + in, out := &in.Events, &out.Events + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.To.DeepCopyInto(&out.To) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NotificationSpec. +func (in *NotificationSpec) DeepCopy() *NotificationSpec { + if in == nil { + return nil + } + out := new(NotificationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NotificationStatus) DeepCopyInto(out *NotificationStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NotificationStatus. +func (in *NotificationStatus) DeepCopy() *NotificationStatus { + if in == nil { + return nil + } + out := new(NotificationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Permission) DeepCopyInto(out *Permission) { *out = *in diff --git a/api/zz_generated.deepcopy.go b/api/zz_generated.deepcopy.go index e7b944e2f..f08a82ecb 100644 --- a/api/zz_generated.deepcopy.go +++ b/api/zz_generated.deepcopy.go @@ -242,3 +242,25 @@ func (in *IncidentTemplate) DeepCopy() *IncidentTemplate { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NotificationConfig) DeepCopyInto(out *NotificationConfig) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NotificationConfig. +func (in *NotificationConfig) DeepCopy() *NotificationConfig { + if in == nil { + return nil + } + out := new(NotificationConfig) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/server.go b/cmd/server.go index 1b7b6f761..1998ab961 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -205,6 +205,15 @@ func launchKopper() { logger.Fatalf("Unable to create controller for Playbook: %v", err) } + if err = kopper.SetupReconciler( + mgr, + db.PersistNotificationFromCRD, + db.DeleteNotification, + "notification.mission-control.flanksource.com", + ); err != nil { + logger.Fatalf("Unable to create controller for Notification: %v", err) + } + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { logger.Fatalf("error running manager: %v", err) } diff --git a/config/crds/mission-control.flanksource.com_notifications.yaml b/config/crds/mission-control.flanksource.com_notifications.yaml new file mode 100644 index 000000000..5e4f7f8ff --- /dev/null +++ b/config/crds/mission-control.flanksource.com_notifications.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: notifications.mission-control.flanksource.com +spec: + group: mission-control.flanksource.com + names: + kind: Notification + listKind: NotificationList + plural: notifications + singular: notification + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Notification is the Schema for the Notification API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + events: + description: List of events that can trigger this notification + items: + type: string + type: array + filter: + description: Cel-expression used to decide whether this notification + client should send the notification + type: string + template: + description: Template is the notification body in markdown + type: string + title: + description: The title for the notification + type: string + to: + description: Specify the recipient + properties: + connection: + description: Specify connection string for an external service. + Should be in the format of connection:///name or the id + of the connection. + type: string + email: + description: Email of the recipient + type: string + person: + description: ID or email of the person + type: string + properties: + additionalProperties: + type: string + description: Properties for Shoutrrr + type: object + team: + description: name or ID of the recipient team + type: string + url: + description: Specify shoutrrr URL + type: string + type: object + required: + - events + - to + type: object + status: + description: NotificationStatus defines the observed state of Notification + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/schemas/notification.schema.json b/config/schemas/notification.schema.json new file mode 100644 index 000000000..260f7c63c --- /dev/null +++ b/config/schemas/notification.schema.json @@ -0,0 +1 @@ +{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Notification","definitions":{"FieldsV1":{"properties":{},"additionalProperties":false,"type":"object"},"ManagedFieldsEntry":{"properties":{"manager":{"type":"string"},"operation":{"type":"string"},"apiVersion":{"type":"string"},"time":{"$ref":"#/definitions/Time"},"fieldsType":{"type":"string"},"fieldsV1":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/FieldsV1"},"subresource":{"type":"string"}},"additionalProperties":false,"type":"object"},"Notification":{"properties":{"kind":{"type":"string"},"apiVersion":{"type":"string"},"metadata":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ObjectMeta"},"spec":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/NotificationSpec"},"status":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/NotificationStatus"}},"additionalProperties":false,"type":"object"},"NotificationRecipientSpec":{"properties":{"person":{"type":"string"},"team":{"type":"string"},"email":{"type":"string"},"connection":{"type":"string"},"url":{"type":"string"},"properties":{"patternProperties":{".*":{"type":"string"}},"type":"object"}},"additionalProperties":false,"type":"object"},"NotificationSpec":{"required":["events","to"],"properties":{"events":{"items":{"type":"string"},"type":"array"},"title":{"type":"string"},"template":{"type":"string"},"filter":{"type":"string"},"to":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/NotificationRecipientSpec"}},"additionalProperties":false,"type":"object"},"NotificationStatus":{"properties":{},"additionalProperties":false,"type":"object"},"ObjectMeta":{"properties":{"name":{"type":"string"},"generateName":{"type":"string"},"namespace":{"type":"string"},"selfLink":{"type":"string"},"uid":{"type":"string"},"resourceVersion":{"type":"string"},"generation":{"type":"integer"},"creationTimestamp":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Time"},"deletionTimestamp":{"$ref":"#/definitions/Time"},"deletionGracePeriodSeconds":{"type":"integer"},"labels":{"patternProperties":{".*":{"type":"string"}},"type":"object"},"annotations":{"patternProperties":{".*":{"type":"string"}},"type":"object"},"ownerReferences":{"items":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/OwnerReference"},"type":"array"},"finalizers":{"items":{"type":"string"},"type":"array"},"managedFields":{"items":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ManagedFieldsEntry"},"type":"array"}},"additionalProperties":false,"type":"object"},"OwnerReference":{"required":["apiVersion","kind","name","uid"],"properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"},"controller":{"type":"boolean"},"blockOwnerDeletion":{"type":"boolean"}},"additionalProperties":false,"type":"object"},"Time":{"properties":{},"additionalProperties":false,"type":"object"}}} \ No newline at end of file diff --git a/db/notifications.go b/db/notifications.go new file mode 100644 index 000000000..0eed6b47f --- /dev/null +++ b/db/notifications.go @@ -0,0 +1,82 @@ +package db + +import ( + "encoding/json" + "fmt" + + "github.com/flanksource/duty" + "github.com/flanksource/duty/models" + "github.com/flanksource/incident-commander/api" + v1 "github.com/flanksource/incident-commander/api/v1" + "github.com/google/uuid" +) + +func PersistNotificationFromCRD(obj *v1.Notification) error { + ctx := api.NewContext(Gorm, nil) + + uid, err := uuid.Parse(string(obj.GetUID())) + if err != nil { + return err + } + + if obj.Spec.To.Empty() { + return fmt.Errorf("notification %s has no recipient", obj.Name) + } + + dbObj := models.Notification{ + ID: uid, + Events: obj.Spec.Events, + Title: obj.Spec.Title, + Template: obj.Spec.Template, + Filter: obj.Spec.Filter, + Properties: obj.Spec.To.Properties, + } + + switch { + case obj.Spec.To.Person != "": + person, err := duty.FindPerson(ctx, obj.Spec.To.Person) + if err != nil { + return err + } else if person == nil { + return fmt.Errorf("person (%s) not found", obj.Spec.To.Person) + } + + dbObj.PersonID = &person.ID + + case obj.Spec.To.Team != "": + team, err := duty.FindTeam(ctx, obj.Spec.To.Team) + if err != nil { + return err + } else if team == nil { + return fmt.Errorf("team (%s) not found", obj.Spec.To.Team) + } + + dbObj.TeamID = &team.ID + + default: + customService := api.NotificationConfig{ + Name: obj.Name, // Name is mandatory. We derive it from the spec. + } + + if len(obj.Spec.To.Email) != 0 { + customService.URL = fmt.Sprintf("smtp://system/?To=%s", obj.Spec.To.Email) + } else if len(obj.Spec.To.Connection) != 0 { + customService.Connection = obj.Spec.To.Connection + } else if len(obj.Spec.To.URL) != 0 { + customService.URL = obj.Spec.To.URL + } + + customServices, err := json.Marshal([]api.NotificationConfig{customService}) + if err != nil { + return err + } + + dbObj.CustomServices = customServices + } + + return Gorm.Save(&dbObj).Error +} + +func DeleteNotification(id string) error { + return Gorm.Delete(&models.Notification{}, "id = ?", id).Error +} diff --git a/events/notifications.go b/events/notifications.go index 71e491adc..98de5c1ad 100644 --- a/events/notifications.go +++ b/events/notifications.go @@ -210,7 +210,7 @@ func sendNotification(ctx *api.Context, event api.Event) error { return fmt.Errorf("failed to get email of person(id=%s); %v", props.PersonID, err) } - smtpURL := fmt.Sprintf("%s?ToAddresses=%s", pkgNotification.SystemSMTP, url.QueryEscape(emailAddress)) + smtpURL := fmt.Sprintf("%s?ToAddresses=%s", api.SystemSMTP, url.QueryEscape(emailAddress)) return pkgNotification.Send(ctx, "", smtpURL, data.Title, data.Message, data.Properties) } @@ -467,7 +467,7 @@ func getEnvForEvent(ctx *api.Context, event api.Event, properties map[string]str return nil, fmt.Errorf("incident(id=%s) not found", comment.IncidentID) } - author, err := duty.FindCachedPerson(ctx, comment.CreatedBy.String()) + author, err := duty.FindPerson(ctx, comment.CreatedBy.String()) if err != nil { return nil, fmt.Errorf("error getting comment author (id=%s)", comment.CreatedBy) } else if author == nil { diff --git a/fixtures/notifications/component-status.yaml b/fixtures/notifications/component-status.yaml new file mode 100644 index 000000000..037c3272c --- /dev/null +++ b/fixtures/notifications/component-status.yaml @@ -0,0 +1,21 @@ +apiVersion: mission-control.flanksource.com/v1 +kind: Notification +metadata: + name: component-error-or-warning +spec: + events: + - component.status.error + - component.status.warning + title: | + {{.component.name}} status updated to {{.component.status}} + template: | + {{range $k, $v := .component.labels}} + **{{$k}}**: {{$v}} + {{end}} + + [Reference]({{.permalink}}) + to: + connection: connection://Slack/incident-notifications + properties: + color: |- + {{if eq .component.status "error"}}bad{{else}}#FFA700{{end}} diff --git a/fixtures/notifications/health-check.yaml b/fixtures/notifications/health-check.yaml new file mode 100644 index 000000000..2cf0e5ea7 --- /dev/null +++ b/fixtures/notifications/health-check.yaml @@ -0,0 +1,10 @@ +apiVersion: mission-control.flanksource.com/v1 +kind: Notification +metadata: + name: http-check-passed +spec: + events: + - check.passed + filter: check.type == 'http' + to: + team: backend diff --git a/fixtures/notifications/incidents.yaml b/fixtures/notifications/incidents.yaml new file mode 100644 index 000000000..38b7423fc --- /dev/null +++ b/fixtures/notifications/incidents.yaml @@ -0,0 +1,22 @@ +apiVersion: mission-control.flanksource.com/v1 +kind: Notification +metadata: + name: incident-status-updates +spec: + events: + - incident.status.cancelled + - incident.status.closed + - incident.status.investigating + - incident.status.mitigated + - incident.status.open + - incident.status.resolved + filter: incident.severity == 'High' || incident.severity == 'Critical' + title: | + Incident "{{incident.title}}" status was updated to {{incident.status}} + template: | + Description: {{.incident.description}} + Has communicator: {{if .incident.communicator_id}}Yes{{else}}No{{end}} + + [Reference]({{.permalink}}) + to: + person: aditya@flanksource.com \ No newline at end of file diff --git a/go.mod b/go.mod index 76d3d8525..c407a830b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/containrrr/shoutrrr v0.8.0 github.com/fergusstrange/embedded-postgres v1.23.0 github.com/flanksource/commons v1.12.0 - github.com/flanksource/duty v1.0.177 + github.com/flanksource/duty v1.0.178 github.com/flanksource/gomplate/v3 v3.20.12 github.com/flanksource/kopper v1.0.6 github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 @@ -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-20230911183012-2d3300fd4832 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832 // 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 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 @@ -144,7 +144,7 @@ require ( 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.8 // indirect + github.com/aws/aws-sdk-go v1.45.9 // 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 @@ -214,7 +214,7 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.140.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20230911183012-2d3300fd4832 // indirect + google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect google.golang.org/grpc v1.58.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 83596e6e5..f4f507567 100644 --- a/go.sum +++ b/go.sum @@ -663,6 +663,8 @@ github.com/aws/aws-sdk-go v1.45.7 h1:k4QsvWZhm8409TYeRuTV1P6+j3lLKoe+giFA/j3VAps github.com/aws/aws-sdk-go v1.45.7/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.45.8 h1:QbOMBTuRYx11fBwNSAJuztXmQf47deFz+CVYjakqmRo= github.com/aws/aws-sdk-go v1.45.8/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.9 h1:ks4nMaagM/0jeOFUxWxx9C009vkrdgm3lgcnciet1YU= +github.com/aws/aws-sdk-go v1.45.9/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -754,6 +756,8 @@ github.com/flanksource/duty v1.0.175 h1:MIGYWUMA2riuHBMtXOTs5wr19XUGXuAlrhzDIqJq github.com/flanksource/duty v1.0.175/go.mod h1:C3eT1PfdqTdefpGRDfUzLDVjSKuYjqZbgbIqX757FbA= github.com/flanksource/duty v1.0.177 h1:8gcdnioDyAMMGi+5Mwy5+mcE5ECtVgWxluIvhEZKUVc= github.com/flanksource/duty v1.0.177/go.mod h1:C3eT1PfdqTdefpGRDfUzLDVjSKuYjqZbgbIqX757FbA= +github.com/flanksource/duty v1.0.178 h1:Z8fm2OLczGoMdehJDYJeAZSRwzVZ3ojg0vVSLEzTUIU= +github.com/flanksource/duty v1.0.178/go.mod h1:C3eT1PfdqTdefpGRDfUzLDVjSKuYjqZbgbIqX757FbA= github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc= github.com/flanksource/gomplate/v3 v3.20.12 h1:SLo8eLaYkUTizHIuntZ4LxxLzbRfV0NvC6DTpu9fj94= github.com/flanksource/gomplate/v3 v3.20.12/go.mod h1:1N1aptaAo0XUaGsyU5CWiwn9GMRpbIKX1AdsypfmZYo= @@ -1919,14 +1923,20 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= google.golang.org/genproto v0.0.0-20230911183012-2d3300fd4832 h1:/30npZKtUjXqju7ZA2MsvpkGKD4mQFtf+zPnZasABjg= google.golang.org/genproto v0.0.0-20230911183012-2d3300fd4832/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +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/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-20230911183012-2d3300fd4832 h1:4E7rZzBdR5LmiZx6n47Dg4AjH8JLhMQWywsYqvXNLcs= google.golang.org/genproto/googleapis/api v0.0.0-20230911183012-2d3300fd4832/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +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/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-20230911183012-2d3300fd4832 h1:o4LtQxebKIJ4vkzyhtD2rfUNZ20Zf0ik5YVP5E7G7VE= google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +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/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= @@ -2053,6 +2063,8 @@ k8s.io/api v0.28.0 h1:3j3VPWmN9tTDI68NETBWlDiA9qOiGJ7sdKeufehBYsM= k8s.io/api v0.28.0/go.mod h1:0l8NZJzB0i/etuWnIXcwfIv+xnDOhL3lLW919AWYDuY= k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= +k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= +k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= k8s.io/apiextensions-apiserver v0.27.4 h1:ie1yZG4nY/wvFMIR2hXBeSVq+HfNzib60FjnBYtPGSs= k8s.io/apiextensions-apiserver v0.27.4/go.mod h1:KHZaDr5H9IbGEnSskEUp/DsdXe1hMQ7uzpQcYUFt2bM= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= @@ -2061,10 +2073,14 @@ k8s.io/apimachinery v0.28.0 h1:ScHS2AG16UlYWk63r46oU3D5y54T53cVI5mMJwwqFNA= k8s.io/apimachinery v0.28.0/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= +k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= k8s.io/client-go v0.28.0 h1:ebcPRDZsCjpj62+cMk1eGNX1QkMdRmQ6lmz5BLoFWeM= k8s.io/client-go v0.28.0/go.mod h1:0Asy9Xt3U98RypWJmU1ZrRAGKhP6NqDPmptlAzK2kMc= k8s.io/client-go v0.28.1 h1:pRhMzB8HyLfVwpngWKE8hDcXRqifh1ga2Z/PU9SXVK8= k8s.io/client-go v0.28.1/go.mod h1:pEZA3FqOsVkCc07pFVzK076R+P/eXqsgx5zuuRWukNE= +k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= +k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c= k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= diff --git a/hack/generate-schemas/go.mod b/hack/generate-schemas/go.mod index 9b70a08a3..dccf6097e 100644 --- a/hack/generate-schemas/go.mod +++ b/hack/generate-schemas/go.mod @@ -22,12 +22,12 @@ require ( github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/antonmedv/expr v1.15.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/aws/aws-sdk-go v1.45.8 // indirect + github.com/aws/aws-sdk-go v1.45.9 // 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 github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/flanksource/duty v1.0.177 // indirect + github.com/flanksource/duty v1.0.178 // indirect github.com/flanksource/gomplate/v3 v3.20.12 // indirect github.com/flanksource/is-healthy v0.0.0-20230713150444-ad2a5ef4bb37 // indirect github.com/flanksource/mapstructure v1.6.0 // indirect @@ -119,9 +119,9 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.140.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20230911183012-2d3300fd4832 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230911183012-2d3300fd4832 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832 // 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.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/flanksource/yaml.v3 v3.2.3 // indirect diff --git a/hack/generate-schemas/go.sum b/hack/generate-schemas/go.sum index 209b1c860..d6a25b79a 100644 --- a/hack/generate-schemas/go.sum +++ b/hack/generate-schemas/go.sum @@ -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.8 h1:QbOMBTuRYx11fBwNSAJuztXmQf47deFz+CVYjakqmRo= -github.com/aws/aws-sdk-go v1.45.8/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.9 h1:ks4nMaagM/0jeOFUxWxx9C009vkrdgm3lgcnciet1YU= +github.com/aws/aws-sdk-go v1.45.9/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 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= @@ -704,8 +704,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fergusstrange/embedded-postgres v1.23.0 h1:ZYRD89nammxQDWDi6taJE2CYjDuAoVc1TpEqRIYQryc= github.com/flanksource/commons v1.12.0 h1:8B7+AbRbWH3KVFwbmXYkG3gS42pF+uVaF4lAgDY+ZJA= github.com/flanksource/commons v1.12.0/go.mod h1:zYEhi6E2+diQ+loVcROUHo/Bgv+Tn61W2NYmrb5MgVI= -github.com/flanksource/duty v1.0.177 h1:8gcdnioDyAMMGi+5Mwy5+mcE5ECtVgWxluIvhEZKUVc= -github.com/flanksource/duty v1.0.177/go.mod h1:C3eT1PfdqTdefpGRDfUzLDVjSKuYjqZbgbIqX757FbA= +github.com/flanksource/duty v1.0.178 h1:Z8fm2OLczGoMdehJDYJeAZSRwzVZ3ojg0vVSLEzTUIU= +github.com/flanksource/duty v1.0.178/go.mod h1:C3eT1PfdqTdefpGRDfUzLDVjSKuYjqZbgbIqX757FbA= github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc= github.com/flanksource/gomplate/v3 v3.20.12 h1:SLo8eLaYkUTizHIuntZ4LxxLzbRfV0NvC6DTpu9fj94= github.com/flanksource/gomplate/v3 v3.20.12/go.mod h1:1N1aptaAo0XUaGsyU5CWiwn9GMRpbIKX1AdsypfmZYo= @@ -1752,16 +1752,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-20230911183012-2d3300fd4832 h1:/30npZKtUjXqju7ZA2MsvpkGKD4mQFtf+zPnZasABjg= -google.golang.org/genproto v0.0.0-20230911183012-2d3300fd4832/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +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/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-20230911183012-2d3300fd4832 h1:4E7rZzBdR5LmiZx6n47Dg4AjH8JLhMQWywsYqvXNLcs= -google.golang.org/genproto/googleapis/api v0.0.0-20230911183012-2d3300fd4832/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +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/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-20230911183012-2d3300fd4832 h1:o4LtQxebKIJ4vkzyhtD2rfUNZ20Zf0ik5YVP5E7G7VE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +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/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= diff --git a/hack/generate-schemas/main.go b/hack/generate-schemas/main.go index 287af033e..89fe12774 100644 --- a/hack/generate-schemas/main.go +++ b/hack/generate-schemas/main.go @@ -12,6 +12,7 @@ import ( var schemas = map[string]any{ "connection": &v1.Connection{}, + "notification": &v1.Notification{}, "playbook": &v1.Playbook{}, "incident-rules": &v1.IncidentRule{}, } diff --git a/notification/shoutrrr.go b/notification/shoutrrr.go index 69f3f4fe6..8a8134a28 100644 --- a/notification/shoutrrr.go +++ b/notification/shoutrrr.go @@ -16,10 +16,6 @@ import ( icUtils "github.com/flanksource/incident-commander/utils" ) -// SystemSMTP indicates that the shoutrrr URL for smtp should use -// the system's SMTP credentials. -const SystemSMTP = "smtp://system/" - // setSystemSMTPCredential modifies the shoutrrrURL to use the system's SMTP credentials. func setSystemSMTPCredential(shoutrrrURL string) (string, error) { prefix := fmt.Sprintf("smtp://%s:%s@%s:%s/", @@ -28,7 +24,7 @@ func setSystemSMTPCredential(shoutrrrURL string) (string, error) { os.Getenv("SMTP_HOST"), os.Getenv("SMTP_PORT"), ) - shoutrrrURL = strings.ReplaceAll(shoutrrrURL, SystemSMTP, prefix) + shoutrrrURL = strings.ReplaceAll(shoutrrrURL, api.SystemSMTP, prefix) parsedURL, err := url.Parse(shoutrrrURL) if err != nil { @@ -54,7 +50,7 @@ func Send(ctx *api.Context, connectionName, shoutrrrURL, title, message string, properties = append([]map[string]string{connection.Properties}, properties...) } - if strings.HasPrefix(shoutrrrURL, SystemSMTP) { + if strings.HasPrefix(shoutrrrURL, api.SystemSMTP) { var err error shoutrrrURL, err = setSystemSMTPCredential(shoutrrrURL) if err != nil {