From f48f7f45d74a0b6ec01d3f2df6fc557caa3e978b Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Tue, 10 Sep 2024 18:01:55 +0545 Subject: [PATCH] feat: add endpoint to save notification silences --- fixtures/notifications/silence.yaml | 11 ---- notification/controllers.go | 21 ++++++- notification/events.go | 29 --------- notification/silence.go | 94 +++++++++++++++++++++++++++++ rbac/objects.go | 1 + rbac/policies.yaml | 2 +- rbac/policy.go | 60 +++++++++--------- 7 files changed, 147 insertions(+), 71 deletions(-) delete mode 100644 fixtures/notifications/silence.yaml create mode 100644 notification/silence.go diff --git a/fixtures/notifications/silence.yaml b/fixtures/notifications/silence.yaml deleted file mode 100644 index b846eceb0..000000000 --- a/fixtures/notifications/silence.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: mission-control.flanksource.com/v1 -kind: NotificationSilence -metadata: - name: silences - namespace: mc -spec: - matcher: .config.type == "Kubernetes::HelmRelease" && .config.name == "mission-control" - from: '2024-09-01T00:00:00Z' - until: '2024-09-30T00:00:00Z' - recursive: true diff --git a/notification/controllers.go b/notification/controllers.go index 0805f8588..b29d183aa 100644 --- a/notification/controllers.go +++ b/notification/controllers.go @@ -1,8 +1,10 @@ package notification import ( + "encoding/json" "net/http" + "github.com/flanksource/duty/context" echoSrv "github.com/flanksource/incident-commander/echo" "github.com/flanksource/incident-commander/rbac" "github.com/labstack/echo/v4" @@ -13,7 +15,24 @@ func init() { } func RegisterRoutes(e *echo.Echo) { - e.GET("/notification/events", func(c echo.Context) error { + g := e.Group("/notification") + + g.GET("/events", func(c echo.Context) error { return c.JSON(http.StatusOK, EventRing.Get()) }, rbac.Authorization(rbac.ObjectMonitor, rbac.ActionRead)) + + g.POST("/silence", func(c echo.Context) error { + ctx := c.Request().Context().(context.Context) + + var req SilenceSaveRequest + if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + return err + } + + if err := SaveNotificationSilence(ctx, req); err != nil { + return err + } + + return nil + }, rbac.Authorization(rbac.ObjectNotificationSilence, rbac.ActionCreate)) } diff --git a/notification/events.go b/notification/events.go index 3d7f0c787..6da3a5bb4 100644 --- a/notification/events.go +++ b/notification/events.go @@ -462,32 +462,3 @@ func getEnvForEvent(ctx context.Context, event models.Event, properties map[stri return env, nil } - -func getSilencedResourceFromCelEnv(celEnv map[string]any) models.NotificationSilenceResource { - var silencedResource models.NotificationSilenceResource - if v, ok := celEnv["config"]; ok { - if vv, ok := v.(map[string]any); ok { - silencedResource.ConfigID = lo.ToPtr(vv["id"].(string)) - } - } - - if v, ok := celEnv["check"]; ok { - if vv, ok := v.(map[string]any); ok { - silencedResource.CheckID = lo.ToPtr(vv["id"].(string)) - } - } - - if v, ok := celEnv["canary"]; ok { - if vv, ok := v.(map[string]any); ok { - silencedResource.CanaryID = lo.ToPtr(vv["id"].(string)) - } - } - - if v, ok := celEnv["component"]; ok { - if vv, ok := v.(map[string]any); ok { - silencedResource.ComponentID = lo.ToPtr(vv["id"].(string)) - } - } - - return silencedResource -} diff --git a/notification/silence.go b/notification/silence.go new file mode 100644 index 000000000..975da177e --- /dev/null +++ b/notification/silence.go @@ -0,0 +1,94 @@ +package notification + +import ( + "errors" + "time" + + "github.com/flanksource/commons/duration" + "github.com/flanksource/duty/context" + "github.com/flanksource/duty/models" + "github.com/samber/lo" +) + +type SilenceSaveRequest struct { + models.NotificationSilenceResource + From time.Time `json:"from"` + Until time.Time `json:"until"` + Duration string `json:"duration"` + Description string `json:"description"` +} + +func (t *SilenceSaveRequest) Validate() error { + if t.From.IsZero() { + return errors.New("`from` time is required") + } + + if t.Until.IsZero() { + if t.Duration == "" { + return errors.New("`until` or `duration` is required") + } + + if parsed, err := duration.ParseDuration(t.Duration); err != nil { + return err + } else { + t.Until = t.From.Add(time.Duration(parsed)) + } + } + + if t.From.After(t.Until) { + return errors.New("`from` time must be before `until` time") + } + + if t.NotificationSilenceResource.CanaryID == nil && t.NotificationSilenceResource.CheckID == nil && t.NotificationSilenceResource.ConfigID == nil && + t.NotificationSilenceResource.ComponentID == nil { + return errors.New("at least one of `config_id`, `canary_id`, `check_id` or `component_id` is required") + } + + return nil +} + +func SaveNotificationSilence(ctx context.Context, req SilenceSaveRequest) error { + if err := req.Validate(); err != nil { + return err + } + + silence := models.NotificationSilence{ + NotificationSilenceResource: req.NotificationSilenceResource, + From: req.From, + Until: req.Until, + Description: req.Description, + Source: models.SourceUI, + CreatedBy: lo.ToPtr(ctx.User().ID), + } + + return ctx.DB().Create(&silence).Error +} + +func getSilencedResourceFromCelEnv(celEnv map[string]any) models.NotificationSilenceResource { + var silencedResource models.NotificationSilenceResource + if v, ok := celEnv["config"]; ok { + if vv, ok := v.(map[string]any); ok { + silencedResource.ConfigID = lo.ToPtr(vv["id"].(string)) + } + } + + if v, ok := celEnv["check"]; ok { + if vv, ok := v.(map[string]any); ok { + silencedResource.CheckID = lo.ToPtr(vv["id"].(string)) + } + } + + if v, ok := celEnv["canary"]; ok { + if vv, ok := v.(map[string]any); ok { + silencedResource.CanaryID = lo.ToPtr(vv["id"].(string)) + } + } + + if v, ok := celEnv["component"]; ok { + if vv, ok := v.(map[string]any); ok { + silencedResource.ComponentID = lo.ToPtr(vv["id"].(string)) + } + } + + return silencedResource +} diff --git a/rbac/objects.go b/rbac/objects.go index 4e41e5920..e3b2aeb01 100644 --- a/rbac/objects.go +++ b/rbac/objects.go @@ -109,6 +109,7 @@ var dbResourceObjMap = map[string]string{ "networks": ObjectAuthConfidential, "notification_send_history": ObjectMonitor, "notifications_summary": ObjectMonitor, + "notification_silences": ObjectNotificationSilence, "notifications": ObjectDatabaseSettings, "people_roles": ObjectDatabasePublic, "people": ObjectPeople, diff --git a/rbac/policies.yaml b/rbac/policies.yaml index ea30d867d..ceda44f59 100644 --- a/rbac/policies.yaml +++ b/rbac/policies.yaml @@ -26,7 +26,7 @@ - viewer - principal: editor acl: - - objects: canaries,catalog,topology,playbooks,kubernetes-proxy + - objects: canaries,catalog,topology,playbooks,kubernetes-proxy,notifcation-silence actions: create,read,update,delete - objects: playbooks actions: run diff --git a/rbac/policy.go b/rbac/policy.go index ef368df46..c26189f0a 100644 --- a/rbac/policy.go +++ b/rbac/policy.go @@ -147,36 +147,38 @@ const ( RoleAgent = "agent" // Actions - ActionRead = "read" - ActionUpdate = "update" - ActionCreate = "create" - ActionDelete = "delete" - ActionRun = "run" - ActionApprove = "approve" - ActionAll = "*" - ActionCRUD = "create,read,update,delete" - ObjectKubernetesProxy = "kubernetes-proxy" + ActionRead = "read" + ActionUpdate = "update" + ActionCreate = "create" + ActionDelete = "delete" + ActionRun = "run" + ActionApprove = "approve" + ActionAll = "*" + ActionCRUD = "create,read,update,delete" + // Objects - ObjectLogs = "logs" - ObjectAgent = "agent" - ObjectAgentPush = "agent-push" - ObjectArtifact = "artifact" - ObjectAuth = "auth" - ObjectCanary = "canaries" - ObjectCatalog = "catalog" - ObjectConnection = "connection" - ObjectDatabase = "database" - ObjectDatabaseIdentity = "database.identities" - ObjectAuthConfidential = "database.kratos" - ObjectDatabasePublic = "database.public" - ObjectDatabaseSettings = "database.config_scrapers" - ObjectDatabaseSystem = "database.system" - ObjectIncident = "incident" - ObjectMonitor = "database.monitor" - ObjectPlaybooks = "playbooks" - ObjectRBAC = "rbac" - ObjectTopology = "topology" - ObjectPeople = "people" + ObjectKubernetesProxy = "kubernetes-proxy" + ObjectLogs = "logs" + ObjectAgent = "agent" + ObjectAgentPush = "agent-push" + ObjectArtifact = "artifact" + ObjectNotificationSilence = "notification-silence" + ObjectAuth = "auth" + ObjectCanary = "canaries" + ObjectCatalog = "catalog" + ObjectConnection = "connection" + ObjectDatabase = "database" + ObjectDatabaseIdentity = "database.identities" + ObjectAuthConfidential = "database.kratos" + ObjectDatabasePublic = "database.public" + ObjectDatabaseSettings = "database.config_scrapers" + ObjectDatabaseSystem = "database.system" + ObjectIncident = "incident" + ObjectMonitor = "database.monitor" + ObjectPlaybooks = "playbooks" + ObjectRBAC = "rbac" + ObjectTopology = "topology" + ObjectPeople = "people" ) var (