mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-15 14:40:41 +01:00
Compare commits
82 Commits
feat/ai-tr
...
feat/alert
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0565363083 | ||
|
|
c2f6ffb423 | ||
|
|
b6208538bf | ||
|
|
db708385ad | ||
|
|
83b1b90f29 | ||
|
|
47cf0847b1 | ||
|
|
685a02e72d | ||
|
|
b5fcd935a7 | ||
|
|
fa105e640c | ||
|
|
014e1fc86a | ||
|
|
6d3fbb9903 | ||
|
|
758443f42a | ||
|
|
847eb52883 | ||
|
|
17574db3dc | ||
|
|
34482d8cc1 | ||
|
|
a541d2687c | ||
|
|
6ab70762a7 | ||
|
|
8989e0a3a1 | ||
|
|
0d150554c7 | ||
|
|
bee75c8e34 | ||
|
|
eb54542ca1 | ||
|
|
c92fe4969a | ||
|
|
97a2fefe61 | ||
|
|
c059d3b8f6 | ||
|
|
37d342eb90 | ||
|
|
d60da7b8a7 | ||
|
|
8d5111bc7d | ||
|
|
e2578c15fb | ||
|
|
6c5016bdd9 | ||
|
|
3b13ae9681 | ||
|
|
d55e4a7243 | ||
|
|
6ff779d5ab | ||
|
|
a446a832ae | ||
|
|
c89c19ce9e | ||
|
|
e543b3ef32 | ||
|
|
3f7531f444 | ||
|
|
abfa9bee1e | ||
|
|
2f497108df | ||
|
|
1fa1e292e9 | ||
|
|
a328988a15 | ||
|
|
7e53bd607d | ||
|
|
4d82eb8436 | ||
|
|
95c11d0550 | ||
|
|
b2810ec117 | ||
|
|
7556dd2868 | ||
|
|
339a10217e | ||
|
|
cd0eb62734 | ||
|
|
a667007991 | ||
|
|
208fc1e8ec | ||
|
|
54eb67392a | ||
|
|
0e0d97c16f | ||
|
|
659f9c7ecf | ||
|
|
ce8442c17a | ||
|
|
7ab21678b5 | ||
|
|
951bfb66cd | ||
|
|
113685fbdb | ||
|
|
b9c306dd26 | ||
|
|
7e71d4507c | ||
|
|
e13cb08097 | ||
|
|
1e7aaa9f14 | ||
|
|
09ab9f4081 | ||
|
|
f945b5f513 | ||
|
|
8b0ab0ff26 | ||
|
|
f2679a6866 | ||
|
|
253a117849 | ||
|
|
c9538be38f | ||
|
|
be51c37317 | ||
|
|
66796d1767 | ||
|
|
8e19b17855 | ||
|
|
8885497e14 | ||
|
|
626e047485 | ||
|
|
cc3d86c3ec | ||
|
|
0100083284 | ||
|
|
648e945471 | ||
|
|
7323d5ae0b | ||
|
|
578b9172ba | ||
|
|
2110006d17 | ||
|
|
01dda19d17 | ||
|
|
b519574b89 | ||
|
|
629ecabce7 | ||
|
|
131e302c55 | ||
|
|
239c92ba67 |
@@ -12964,9 +12964,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- planned-maintenance:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- planned-maintenance:list
|
||||
summary: List downtime schedules
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13021,9 +13021,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- planned-maintenance:create
|
||||
- planned-maintenance:attach
|
||||
- rule:attach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- planned-maintenance:create
|
||||
- planned-maintenance:attach
|
||||
- rule:attach
|
||||
summary: Create downtime schedule
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13068,9 +13072,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- planned-maintenance:delete
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- planned-maintenance:delete
|
||||
summary: Delete downtime schedule
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13126,9 +13130,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- planned-maintenance:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- planned-maintenance:read
|
||||
summary: Get downtime schedule by ID
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13183,9 +13187,17 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- planned-maintenance:update
|
||||
- planned-maintenance:attach
|
||||
- planned-maintenance:detach
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- planned-maintenance:update
|
||||
- planned-maintenance:attach
|
||||
- planned-maintenance:detach
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
summary: Update downtime schedule
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -14407,9 +14419,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- route-policy:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- route-policy:list
|
||||
summary: List route policies
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14464,9 +14476,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- route-policy:create
|
||||
- route-policy:attach
|
||||
- notification-channel:attach
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- route-policy:create
|
||||
- route-policy:attach
|
||||
- notification-channel:attach
|
||||
summary: Create route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14511,9 +14527,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- route-policy:delete
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- route-policy:delete
|
||||
summary: Delete route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14569,9 +14585,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- route-policy:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- route-policy:read
|
||||
summary: Get route policy by ID
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14638,9 +14654,17 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- route-policy:update
|
||||
- route-policy:attach
|
||||
- route-policy:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- route-policy:update
|
||||
- route-policy:attach
|
||||
- route-policy:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
summary: Update route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -22040,9 +22064,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: List rule saved views
|
||||
tags:
|
||||
- rules
|
||||
@@ -22098,9 +22122,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Create rule saved view
|
||||
tags:
|
||||
- rules
|
||||
@@ -22152,9 +22176,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Delete rule saved view
|
||||
tags:
|
||||
- rules
|
||||
@@ -22221,9 +22245,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Update rule saved view
|
||||
tags:
|
||||
- rules
|
||||
@@ -22272,9 +22296,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: List alert rules
|
||||
tags:
|
||||
- rules
|
||||
@@ -23113,9 +23137,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
summary: Create alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -23160,9 +23188,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:delete
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:delete
|
||||
summary: Delete alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -23218,9 +23246,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get alert rule by ID
|
||||
tags:
|
||||
- rules
|
||||
@@ -24071,9 +24099,17 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
summary: Patch alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -24912,9 +24948,17 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
summary: Update alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -25014,9 +25058,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule history filter keys
|
||||
tags:
|
||||
- rules
|
||||
@@ -25124,9 +25168,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule history filter values
|
||||
tags:
|
||||
- rules
|
||||
@@ -25199,9 +25243,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule overall status timeline
|
||||
tags:
|
||||
- rules
|
||||
@@ -25271,9 +25315,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule history stats
|
||||
tags:
|
||||
- rules
|
||||
@@ -25363,9 +25407,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule history timeline
|
||||
tags:
|
||||
- rules
|
||||
@@ -25438,9 +25482,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get top contributors to rule firing
|
||||
tags:
|
||||
- rules
|
||||
@@ -26280,9 +26324,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
summary: Test alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -28148,9 +28196,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: List alert rules (v3)
|
||||
tags:
|
||||
- rules
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/rulerauthz"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@@ -327,88 +328,167 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.GetAllRoutePolicies), handler.OpenAPIDef{
|
||||
ID: "GetAllRoutePolicies",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "List route policies",
|
||||
Description: "This endpoint lists all route policies for the organization",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*alertmanagertypes.GettableRoutePolicy, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.alertmanagerHandler.GetAllRoutePolicies, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetAllRoutePolicies",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "List route policies",
|
||||
Description: "This endpoint lists all route policies for the organization",
|
||||
Response: make([]*alertmanagertypes.GettableRoutePolicy, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.alertmanagerHandler.GetRoutePolicyByID), handler.OpenAPIDef{
|
||||
ID: "GetRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Get route policy by ID",
|
||||
Description: "This endpoint returns a route policy by ID",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.alertmanagerHandler.GetRoutePolicyByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Get route policy by ID",
|
||||
Description: "This endpoint returns a route policy by ID",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.CreateRoutePolicy), handler.OpenAPIDef{
|
||||
ID: "CreateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Create route policy",
|
||||
Description: "This endpoint creates a route policy",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.alertmanagerHandler.CreateRoutePolicy, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Create route policy",
|
||||
Description: "This endpoint creates a route policy",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbCreate),
|
||||
coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.ResponseJSONPath("data.id"),
|
||||
Selector: coretypes.WildcardSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
SourceIDs: coretypes.OneID(coretypes.ResponseJSONPath("data.id")),
|
||||
SourceSelector: coretypes.WildcardSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewCreateRoutePolicyChannelAttachExtractor(provider.alertmanagerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.UpdateRoutePolicy), handler.OpenAPIDef{
|
||||
ID: "UpdateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Update route policy",
|
||||
Description: "This endpoint updates a route policy by ID",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.alertmanagerHandler.UpdateRoutePolicy, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateRoutePolicy",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Update route policy",
|
||||
Description: "This endpoint updates a route policy by ID",
|
||||
Request: new(alertmanagertypes.PostableRoutePolicy),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.GettableRoutePolicy),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbUpdate),
|
||||
coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbDetach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbDetach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewUpdateRoutePolicyChannelAttachExtractor(provider.alertmanagerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbDetach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewUpdateRoutePolicyChannelDetachExtractor(provider.alertmanagerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(provider.authzMiddleware.AdminAccess(provider.alertmanagerHandler.DeleteRoutePolicyByID), handler.OpenAPIDef{
|
||||
ID: "DeleteRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Delete route policy",
|
||||
Description: "This endpoint deletes a route policy by ID",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/route_policies/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.alertmanagerHandler.DeleteRoutePolicyByID, authtypes.SigNozAdminRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRoutePolicyByID",
|
||||
Tags: []string{"routepolicies"},
|
||||
Summary: "Delete route policy",
|
||||
Description: "This endpoint deletes a route policy by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRoutePolicy.Scope(coretypes.VerbDelete)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRoutePolicy,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,11 @@ type provider struct {
|
||||
ruleStateHistoryHandler rulestatehistory.Handler
|
||||
spanMapperHandler spanmapper.Handler
|
||||
alertmanagerHandler alertmanager.Handler
|
||||
alertmanagerService alertmanager.Alertmanager
|
||||
prometheusHandler prometheus.Handler
|
||||
traceDetailHandler tracedetail.Handler
|
||||
rulerHandler ruler.Handler
|
||||
rulerService ruler.Ruler
|
||||
llmPricingRuleHandler llmpricingrule.Handler
|
||||
statsHandler statsreporter.Handler
|
||||
savedViewHandler savedview.Handler
|
||||
@@ -136,10 +138,12 @@ func NewFactory(
|
||||
ruleStateHistoryHandler rulestatehistory.Handler,
|
||||
spanMapperHandler spanmapper.Handler,
|
||||
alertmanagerHandler alertmanager.Handler,
|
||||
alertmanagerService alertmanager.Alertmanager,
|
||||
prometheusHandler prometheus.Handler,
|
||||
llmPricingRuleHandler llmpricingrule.Handler,
|
||||
traceDetailHandler tracedetail.Handler,
|
||||
rulerHandler ruler.Handler,
|
||||
rulerService ruler.Ruler,
|
||||
statsHandler statsreporter.Handler,
|
||||
savedViewHandler savedview.Handler,
|
||||
globalConfig global.Config,
|
||||
@@ -189,10 +193,12 @@ func NewFactory(
|
||||
ruleStateHistoryHandler,
|
||||
spanMapperHandler,
|
||||
alertmanagerHandler,
|
||||
alertmanagerService,
|
||||
prometheusHandler,
|
||||
llmPricingRuleHandler,
|
||||
traceDetailHandler,
|
||||
rulerHandler,
|
||||
rulerService,
|
||||
statsHandler,
|
||||
savedViewHandler,
|
||||
globalConfig,
|
||||
@@ -244,10 +250,12 @@ func newProvider(
|
||||
ruleStateHistoryHandler rulestatehistory.Handler,
|
||||
spanMapperHandler spanmapper.Handler,
|
||||
alertmanagerHandler alertmanager.Handler,
|
||||
alertmanagerService alertmanager.Alertmanager,
|
||||
prometheusHandler prometheus.Handler,
|
||||
llmPricingRuleHandler llmpricingrule.Handler,
|
||||
traceDetailHandler tracedetail.Handler,
|
||||
rulerHandler ruler.Handler,
|
||||
rulerService ruler.Ruler,
|
||||
statsHandler statsreporter.Handler,
|
||||
savedViewHandler savedview.Handler,
|
||||
globalConfig global.Config,
|
||||
@@ -299,9 +307,11 @@ func newProvider(
|
||||
ruleStateHistoryHandler: ruleStateHistoryHandler,
|
||||
spanMapperHandler: spanMapperHandler,
|
||||
alertmanagerHandler: alertmanagerHandler,
|
||||
alertmanagerService: alertmanagerService,
|
||||
prometheusHandler: prometheusHandler,
|
||||
traceDetailHandler: traceDetailHandler,
|
||||
rulerHandler: rulerHandler,
|
||||
rulerService: rulerService,
|
||||
llmPricingRuleHandler: llmPricingRuleHandler,
|
||||
statsHandler: statsHandler,
|
||||
savedViewHandler: savedViewHandler,
|
||||
@@ -532,8 +542,5 @@ func newAnonymousSecuritySchemes(scopes []string) []handler.OpenAPISecuritySchem
|
||||
}
|
||||
|
||||
func newScopedSecuritySchemes(scopes []string) []handler.OpenAPISecurityScheme {
|
||||
return []handler.OpenAPISecurityScheme{
|
||||
{Name: authtypes.IdentNProviderAPIKey.StringValue(), Scopes: scopes},
|
||||
{Name: authtypes.IdentNProviderTokenizer.StringValue(), Scopes: scopes},
|
||||
}
|
||||
return handler.NewScopedSecuritySchemes(scopes)
|
||||
}
|
||||
|
||||
@@ -4,259 +4,554 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/rulerauthz"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListRules), handler.OpenAPIDef{
|
||||
ID: "ListRules",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List alert rules",
|
||||
Description: "This endpoint lists all alert rules with their current evaluation state. Deprecated: use ListRulesV3, which supports filtering, sorting and pagination.",
|
||||
Response: make([]*ruletypes.Rule, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.ListRules, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListRules",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List alert rules",
|
||||
Description: "This endpoint lists all alert rules with their current evaluation state. Deprecated: use ListRulesV3, which supports filtering, sorting and pagination.",
|
||||
Response: make([]*ruletypes.Rule, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v3/rules", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListRulesV3), handler.OpenAPIDef{
|
||||
ID: "ListRulesV3",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List alert rules (v3)",
|
||||
Description: "Returns a page of alert rules with their current evaluation state, trimmed to the fields the list page renders. Supports a filter DSL (`query`), a repeated `states` filter applied after the state overlay, sort (`updated_at`/`created_at`/`name`/`state`/`severity`), order (`asc`/`desc`), and offset-based pagination (`limit`/`offset`). In the filter DSL, a non-reserved key is matched as a rule label directly (`team = infra`); a key that collides with a reserved keyword matches either interpretation (negative operators exclude both), and `labels.<key>` targets only the label. The response also carries the org's label pairs and the reserved filter keys for building filter suggestions.",
|
||||
RequestQuery: new(ruletypes.ListRulesParams),
|
||||
Response: new(ruletypes.ListableRules),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v3/rules", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.ListRulesV3, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListRulesV3",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List alert rules (v3)",
|
||||
Description: "Returns a page of alert rules with their current evaluation state, trimmed to the fields the list page renders. Supports a filter DSL (`query`), a repeated `states` filter applied after the state overlay, sort (`updated_at`/`created_at`/`name`/`state`/`severity`), order (`asc`/`desc`), and offset-based pagination (`limit`/`offset`). In the filter DSL, a non-reserved key is matched as a rule label directly (`team = infra`); a key that collides with a reserved keyword matches either interpretation (negative operators exclude both), and `labels.<key>` targets only the label. The response also carries the org's label pairs and the reserved filter keys for building filter suggestions.",
|
||||
RequestQuery: new(ruletypes.ListRulesParams),
|
||||
Response: new(ruletypes.ListableRules),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.GetRuleByID), handler.OpenAPIDef{
|
||||
ID: "GetRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Get alert rule by ID",
|
||||
Description: "This endpoint returns an alert rule by ID",
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.GetRuleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Get alert rule by ID",
|
||||
Description: "This endpoint returns an alert rule by ID",
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateRule), handler.OpenAPIDef{
|
||||
ID: "CreateRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create alert rule",
|
||||
Description: "This endpoint creates a new alert rule",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.CreateRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create alert rule",
|
||||
Description: "This endpoint creates a new alert rule",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbCreate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.ResponseJSONPath("data.id"),
|
||||
Selector: coretypes.WildcardSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.ResponseJSONPath("data.id")),
|
||||
SourceSelector: coretypes.WildcardSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewCreateChannelAttachExtractor(provider.alertmanagerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateRuleByID), handler.OpenAPIDef{
|
||||
ID: "UpdateRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update alert rule",
|
||||
Description: "This endpoint updates an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.UpdateRuleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update alert rule",
|
||||
Description: "This endpoint updates an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbUpdate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDetach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbDetach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewReplaceChannelAttachExtractor(provider.alertmanagerService, provider.rulerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbDetach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewReplaceChannelDetachExtractor(provider.alertmanagerService, provider.rulerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteRuleByID), handler.OpenAPIDef{
|
||||
ID: "DeleteRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete alert rule",
|
||||
Description: "This endpoint deletes an alert rule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.DeleteRuleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete alert rule",
|
||||
Description: "This endpoint deletes an alert rule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDelete)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.PatchRuleByID), handler.OpenAPIDef{
|
||||
ID: "PatchRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Patch alert rule",
|
||||
Description: "This endpoint applies a partial update to an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.PatchRuleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PatchRuleByID",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Patch alert rule",
|
||||
Description: "This endpoint applies a partial update to an alert rule by ID",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.Rule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbUpdate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDetach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbDetach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewPatchChannelAttachExtractor(provider.alertmanagerService, provider.rulerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbDetach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewPatchChannelDetachExtractor(provider.alertmanagerService, provider.rulerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/test", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.TestRule), handler.OpenAPIDef{
|
||||
ID: "TestRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Test alert rule",
|
||||
Description: "This endpoint fires a test notification for the given rule definition",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.GettableTestRule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rules/test", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.TestRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "TestRule",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Test alert rule",
|
||||
Description: "This endpoint fires a test notification for the given rule definition",
|
||||
Request: new(ruletypes.PostableRule),
|
||||
RequestContentType: "application/json",
|
||||
RequestExamples: postableRuleExamples(),
|
||||
Response: new(ruletypes.GettableTestRule),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbCreate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceSelector: coretypes.WildcardSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewCreateChannelAttachExtractor(provider.alertmanagerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListRuleViews), handler.OpenAPIDef{
|
||||
ID: "ListRuleViews",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List rule saved views",
|
||||
Description: "Returns every saved view in the calling user's org. Saved views are shared org-wide.",
|
||||
Response: new(ruletypes.ListableRuleViews),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rule_views", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.ListRuleViews, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListRuleViews",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List rule saved views",
|
||||
Description: "Returns every saved view in the calling user's org. Saved views are shared org-wide.",
|
||||
Response: new(ruletypes.ListableRuleViews),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.CreateRuleView), handler.OpenAPIDef{
|
||||
ID: "CreateRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create rule saved view",
|
||||
Description: "Persists the calling user's rule listing state (query, states, sort, order) as a named, reusable view shared across the org.",
|
||||
Request: new(ruletypes.PostableRuleView),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.RuleView),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rule_views", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.CreateRuleView, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create rule saved view",
|
||||
Description: "Persists the calling user's rule listing state (query, states, sort, order) as a named, reusable view shared across the org.",
|
||||
Request: new(ruletypes.PostableRuleView),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.RuleView),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.UpdateRuleView), handler.OpenAPIDef{
|
||||
ID: "UpdateRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update rule saved view",
|
||||
Description: "Replaces a saved view's name and data. Saved views are shared org-wide.",
|
||||
Request: new(ruletypes.UpdatableRuleView),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.RuleView),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rule_views/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.UpdateRuleView, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update rule saved view",
|
||||
Description: "Replaces a saved view's name and data. Saved views are shared org-wide.",
|
||||
Request: new(ruletypes.UpdatableRuleView),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.RuleView),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.DeleteRuleView), handler.OpenAPIDef{
|
||||
ID: "DeleteRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete rule saved view",
|
||||
Description: "Removes a saved view. Saved views are shared org-wide. Deleting a non-existent view returns 404.",
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v2/rule_views/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.DeleteRuleView, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete rule saved view",
|
||||
Description: "Removes a saved view. Saved views are shared org-wide. Deleting a non-existent view returns 404.",
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListDowntimeSchedules), handler.OpenAPIDef{
|
||||
ID: "ListDowntimeSchedules",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "List downtime schedules",
|
||||
Description: "This endpoint lists all planned maintenance / downtime schedules",
|
||||
RequestQuery: new(alertmanagertypes.ListPlannedMaintenanceParams),
|
||||
Response: make([]*alertmanagertypes.PlannedMaintenance, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.ListDowntimeSchedules, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListDowntimeSchedules",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "List downtime schedules",
|
||||
Description: "This endpoint lists all planned maintenance / downtime schedules",
|
||||
RequestQuery: new(alertmanagertypes.ListPlannedMaintenanceParams),
|
||||
Response: make([]*alertmanagertypes.PlannedMaintenance, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.GetDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "GetDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Get downtime schedule by ID",
|
||||
Description: "This endpoint returns a downtime schedule by ID",
|
||||
Response: new(alertmanagertypes.PlannedMaintenance),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.GetDowntimeScheduleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Get downtime schedule by ID",
|
||||
Description: "This endpoint returns a downtime schedule by ID",
|
||||
Response: new(alertmanagertypes.PlannedMaintenance),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.CreateDowntimeSchedule), handler.OpenAPIDef{
|
||||
ID: "CreateDowntimeSchedule",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Create downtime schedule",
|
||||
Description: "This endpoint creates a new planned maintenance / downtime schedule",
|
||||
Request: new(alertmanagertypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.PlannedMaintenance),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.CreateDowntimeSchedule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateDowntimeSchedule",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Create downtime schedule",
|
||||
Description: "This endpoint creates a new planned maintenance / downtime schedule",
|
||||
Request: new(alertmanagertypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(alertmanagertypes.PlannedMaintenance),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbCreate),
|
||||
coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.ResponseJSONPath("data.id"),
|
||||
Selector: coretypes.WildcardSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
SourceIDs: coretypes.OneID(coretypes.ResponseJSONPath("data.id")),
|
||||
SourceSelector: coretypes.WildcardSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceRule,
|
||||
TargetIDs: rulerauthz.DowntimeRuleIDsExtractor,
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.UpdateDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "UpdateDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Update downtime schedule",
|
||||
Description: "This endpoint updates a downtime schedule by ID",
|
||||
Request: new(alertmanagertypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.UpdateDowntimeScheduleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Update downtime schedule",
|
||||
Description: "This endpoint updates a downtime schedule by ID",
|
||||
Request: new(alertmanagertypes.PostablePlannedMaintenance),
|
||||
RequestContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbUpdate),
|
||||
coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbDetach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDetach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceRule,
|
||||
TargetIDs: rulerauthz.NewDowntimeRuleAttachExtractor(provider.rulerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbDetach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceRule,
|
||||
TargetIDs: rulerauthz.NewDowntimeRuleDetachExtractor(provider.rulerService),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(provider.authzMiddleware.EditAccess(provider.rulerHandler.DeleteDowntimeScheduleByID), handler.OpenAPIDef{
|
||||
ID: "DeleteDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Delete downtime schedule",
|
||||
Description: "This endpoint deletes a downtime schedule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
if err := router.Handle("/api/v1/downtime_schedules/{id}", handler.New(
|
||||
provider.authzMiddleware.CheckResources(provider.rulerHandler.DeleteDowntimeScheduleByID, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteDowntimeScheduleByID",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
Summary: "Delete downtime schedule",
|
||||
Description: "This endpoint deletes a downtime schedule by ID",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourcePlannedMaintenance.Scope(coretypes.VerbDelete)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourcePlannedMaintenance,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/rulestatehistorytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/stats", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryStats),
|
||||
provider.authzMiddleware.CheckResources(provider.ruleStateHistoryHandler.GetRuleHistoryStats, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryStats",
|
||||
Tags: []string{"rules"},
|
||||
@@ -24,13 +25,21 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/timeline", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryTimeline),
|
||||
provider.authzMiddleware.CheckResources(provider.ruleStateHistoryHandler.GetRuleHistoryTimeline, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryTimeline",
|
||||
Tags: []string{"rules"},
|
||||
@@ -41,13 +50,21 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/top_contributors", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryContributors),
|
||||
provider.authzMiddleware.CheckResources(provider.ruleStateHistoryHandler.GetRuleHistoryContributors, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryTopContributors",
|
||||
Tags: []string{"rules"},
|
||||
@@ -58,13 +75,21 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/filter_keys", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryFilterKeys),
|
||||
provider.authzMiddleware.CheckResources(provider.ruleStateHistoryHandler.GetRuleHistoryFilterKeys, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryFilterKeys",
|
||||
Tags: []string{"rules"},
|
||||
@@ -75,13 +100,21 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/filter_values", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryFilterValues),
|
||||
provider.authzMiddleware.CheckResources(provider.ruleStateHistoryHandler.GetRuleHistoryFilterValues, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryFilterValues",
|
||||
Tags: []string{"rules"},
|
||||
@@ -92,13 +125,21 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rules/{id}/history/overall_status", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.ruleStateHistoryHandler.GetRuleHistoryOverallStatus),
|
||||
provider.authzMiddleware.CheckResources(provider.ruleStateHistoryHandler.GetRuleHistoryOverallStatus, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleHistoryOverallStatus",
|
||||
Tags: []string{"rules"},
|
||||
@@ -109,8 +150,16 @@ func (provider *provider) addRuleStateHistoryRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
@@ -64,6 +65,13 @@ type OpenAPISecurityScheme struct {
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
func NewScopedSecuritySchemes(scopes []string) []OpenAPISecurityScheme {
|
||||
return []OpenAPISecurityScheme{
|
||||
{Name: authtypes.IdentNProviderAPIKey.StringValue(), Scopes: scopes},
|
||||
{Name: authtypes.IdentNProviderTokenizer.StringValue(), Scopes: scopes},
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAPICollector is a collector for OpenAPI operations.
|
||||
type OpenAPICollector struct {
|
||||
collector *openapi.Collector
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
@@ -57,6 +58,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
@@ -72,6 +74,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/rulerauthz"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
)
|
||||
|
||||
@@ -358,22 +361,271 @@ func (aH *APIHandler) Respond(w http.ResponseWriter, data interface{}) {
|
||||
writeHttpResponse(w, data)
|
||||
}
|
||||
|
||||
func newRuleHistoryV1OpenAPIDef(id string) handler.OpenAPIDef {
|
||||
return handler.OpenAPIDef{
|
||||
ID: id,
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Get rule history (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use the v2 rule history routes.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
}
|
||||
}
|
||||
|
||||
func newRuleHistoryV1ResourceDefs() handler.Option {
|
||||
return handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterRoutes registers routes for this handler on the given router
|
||||
func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/query_range", am.ViewAccess(aH.queryRangeMetrics)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/query", am.ViewAccess(aH.queryMetrics)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/rules", am.ViewAccess(aH.listRules)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/rules/{id}", am.ViewAccess(aH.getRule)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/rules", am.EditAccess(aH.createRule)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/rules/{id}", am.EditAccess(aH.editRule)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/rules/{id}", am.EditAccess(aH.deleteRule)).Methods(http.MethodDelete)
|
||||
router.HandleFunc("/api/v1/rules/{id}", am.EditAccess(aH.patchRule)).Methods(http.MethodPatch)
|
||||
router.HandleFunc("/api/v1/testRule", am.EditAccess(aH.testRule)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/rules/{id}/history/stats", am.ViewAccess(aH.getRuleStats)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/rules/{id}/history/timeline", am.ViewAccess(aH.getRuleStateHistory)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/rules/{id}/history/top_contributors", am.ViewAccess(aH.getRuleStateHistoryTopContributors)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/rules/{id}/history/overall_status", am.ViewAccess(aH.getOverallStateTransitions)).Methods(http.MethodPost)
|
||||
router.Handle("/api/v1/rules", handler.New(
|
||||
am.CheckResources(aH.listRules, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListRulesV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List alert rules (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use ListRulesV3.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbList)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet)
|
||||
router.Handle("/api/v1/rules/{id}", handler.New(
|
||||
am.CheckResources(aH.getRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetRuleV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Get alert rule (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use GetRuleByID.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbRead)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbRead,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodGet)
|
||||
router.Handle("/api/v1/rules", handler.New(
|
||||
am.CheckResources(aH.createRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "CreateRuleV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create alert rule (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use CreateRule.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbCreate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.ResponseJSONPath("data.id"),
|
||||
Selector: coretypes.WildcardSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.ResponseJSONPath("data.id")),
|
||||
SourceSelector: coretypes.WildcardSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewCreateChannelAttachExtractor(aH.Signoz.Alertmanager),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost)
|
||||
router.Handle("/api/v1/rules/{id}", handler.New(
|
||||
am.CheckResources(aH.editRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "UpdateRuleV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update alert rule (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use UpdateRuleByID.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbUpdate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDetach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbDetach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewReplaceChannelAttachExtractor(aH.Signoz.Alertmanager, aH.ruleManager),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbDetach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewReplaceChannelDetachExtractor(aH.Signoz.Alertmanager, aH.ruleManager),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPut)
|
||||
router.Handle("/api/v1/rules/{id}", handler.New(
|
||||
am.CheckResources(aH.deleteRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "DeleteRuleV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete alert rule (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use DeleteRuleByID.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDelete)}),
|
||||
},
|
||||
handler.WithResourceDefs(handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbDelete,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
}),
|
||||
)).Methods(http.MethodDelete)
|
||||
router.Handle("/api/v1/rules/{id}", handler.New(
|
||||
am.CheckResources(aH.patchRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "PatchRuleV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Patch alert rule (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use PatchRuleByID.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbUpdate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbDetach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbDetach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
ID: coretypes.PathParam("id"),
|
||||
Selector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewPatchChannelAttachExtractor(aH.Signoz.Alertmanager, aH.ruleManager),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbDetach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceIDs: coretypes.OneID(coretypes.PathParam("id")),
|
||||
SourceSelector: coretypes.IDSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewPatchChannelDetachExtractor(aH.Signoz.Alertmanager, aH.ruleManager),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPatch)
|
||||
router.Handle("/api/v1/testRule", handler.New(
|
||||
am.CheckResources(aH.testRule, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName),
|
||||
handler.OpenAPIDef{
|
||||
ID: "TestRuleV1",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Test alert rule (v1, deprecated)",
|
||||
Description: "Deprecated legacy endpoint. Use TestRule.",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: handler.NewScopedSecuritySchemes([]string{
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbCreate),
|
||||
coretypes.ResourceMetaResourceRule.Scope(coretypes.VerbAttach),
|
||||
coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbAttach),
|
||||
}),
|
||||
},
|
||||
handler.WithResourceDefs(
|
||||
handler.BasicResourceDef{
|
||||
Resource: coretypes.ResourceMetaResourceRule,
|
||||
Verb: coretypes.VerbCreate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
},
|
||||
handler.AttachDetachSiblingResourceDef{
|
||||
Verb: coretypes.VerbAttach,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
SourceResource: coretypes.ResourceMetaResourceRule,
|
||||
SourceSelector: coretypes.WildcardSelector,
|
||||
TargetResource: coretypes.ResourceMetaResourceNotificationChannel,
|
||||
TargetIDs: rulerauthz.NewCreateChannelAttachExtractor(aH.Signoz.Alertmanager),
|
||||
TargetSelector: coretypes.IDSelector,
|
||||
},
|
||||
),
|
||||
)).Methods(http.MethodPost)
|
||||
router.Handle("/api/v1/rules/{id}/history/stats", handler.New(
|
||||
am.CheckResources(aH.getRuleStats, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
newRuleHistoryV1OpenAPIDef("GetRuleHistoryStatsV1"),
|
||||
newRuleHistoryV1ResourceDefs(),
|
||||
)).Methods(http.MethodPost)
|
||||
router.Handle("/api/v1/rules/{id}/history/timeline", handler.New(
|
||||
am.CheckResources(aH.getRuleStateHistory, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
newRuleHistoryV1OpenAPIDef("GetRuleHistoryTimelineV1"),
|
||||
newRuleHistoryV1ResourceDefs(),
|
||||
)).Methods(http.MethodPost)
|
||||
router.Handle("/api/v1/rules/{id}/history/top_contributors", handler.New(
|
||||
am.CheckResources(aH.getRuleStateHistoryTopContributors, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
newRuleHistoryV1OpenAPIDef("GetRuleHistoryTopContributorsV1"),
|
||||
newRuleHistoryV1ResourceDefs(),
|
||||
)).Methods(http.MethodPost)
|
||||
router.Handle("/api/v1/rules/{id}/history/overall_status", handler.New(
|
||||
am.CheckResources(aH.getOverallStateTransitions, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName),
|
||||
newRuleHistoryV1OpenAPIDef("GetRuleHistoryOverallStatusV1"),
|
||||
newRuleHistoryV1ResourceDefs(),
|
||||
)).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
|
||||
|
||||
|
||||
171
pkg/ruler/rulerauthz/channel.go
Normal file
171
pkg/ruler/rulerauthz/channel.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package rulerauthz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func NewCreateChannelAttachExtractor(alertmanagerService alertmanager.Alertmanager) coretypes.ResourceIDsExtractor {
|
||||
return coretypes.ResourceIDsExtractor{Phase: coretypes.PhaseRequest, Fn: func(ec coretypes.ExtractorContext) ([]string, error) {
|
||||
if ec.Request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx := ec.Request.Context()
|
||||
orgID, err := orgIDFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names, err := channelsFromBody(ec.RequestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resolveChannelIDs(ctx, alertmanagerService, orgID, names)
|
||||
}}
|
||||
}
|
||||
|
||||
func NewReplaceChannelAttachExtractor(alertmanagerService alertmanager.Alertmanager, rulerService ruler.Ruler) coretypes.ResourceIDsExtractor {
|
||||
return newChannelDiffExtractor(alertmanagerService, rulerService, replacedChannels, attachedSide)
|
||||
}
|
||||
|
||||
func NewReplaceChannelDetachExtractor(alertmanagerService alertmanager.Alertmanager, rulerService ruler.Ruler) coretypes.ResourceIDsExtractor {
|
||||
return newChannelDiffExtractor(alertmanagerService, rulerService, replacedChannels, detachedSide)
|
||||
}
|
||||
|
||||
func NewPatchChannelAttachExtractor(alertmanagerService alertmanager.Alertmanager, rulerService ruler.Ruler) coretypes.ResourceIDsExtractor {
|
||||
return newChannelDiffExtractor(alertmanagerService, rulerService, patchedChannels, attachedSide)
|
||||
}
|
||||
|
||||
func NewPatchChannelDetachExtractor(alertmanagerService alertmanager.Alertmanager, rulerService ruler.Ruler) coretypes.ResourceIDsExtractor {
|
||||
return newChannelDiffExtractor(alertmanagerService, rulerService, patchedChannels, detachedSide)
|
||||
}
|
||||
|
||||
const (
|
||||
attachedSide diffSide = iota
|
||||
detachedSide
|
||||
)
|
||||
|
||||
type diffSide int
|
||||
|
||||
type incomingChannelsFunc func(stored *ruletypes.GettableRule, body []byte) ([]string, error)
|
||||
|
||||
func newChannelDiffExtractor(alertmanagerService alertmanager.Alertmanager, rulerService ruler.Ruler, incoming incomingChannelsFunc, side diffSide) coretypes.ResourceIDsExtractor {
|
||||
return coretypes.ResourceIDsExtractor{Phase: coretypes.PhaseRequest, Fn: func(ec coretypes.ExtractorContext) ([]string, error) {
|
||||
if ec.Request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx := ec.Request.Context()
|
||||
orgID, err := orgIDFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ruleID, err := valuer.NewUUID(mux.Vars(ec.Request)["id"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stored, err := rulerService.GetRule(ctx, ruleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storedNames := stored.Channels()
|
||||
incomingNames, err := incoming(stored, ec.RequestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attached, detached := coretypes.DiffLinks(storedNames, incomingNames)
|
||||
if side == detachedSide {
|
||||
return resolveChannelIDs(ctx, alertmanagerService, orgID, detached)
|
||||
}
|
||||
|
||||
return resolveChannelIDs(ctx, alertmanagerService, orgID, attached)
|
||||
}}
|
||||
}
|
||||
|
||||
func replacedChannels(_ *ruletypes.GettableRule, body []byte) ([]string, error) {
|
||||
return channelsFromBody(body)
|
||||
}
|
||||
|
||||
func patchedChannels(stored *ruletypes.GettableRule, body []byte) ([]string, error) {
|
||||
patched, err := clonePostableRule(stored.PostableRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, patched); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return patched.Channels(), nil
|
||||
}
|
||||
|
||||
func clonePostableRule(rule ruletypes.PostableRule) (*ruletypes.PostableRule, error) {
|
||||
encoded, err := json.Marshal(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clone := new(ruletypes.PostableRule)
|
||||
if err := json.Unmarshal(encoded, clone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
func channelsFromBody(body []byte) ([]string, error) {
|
||||
rule := new(ruletypes.PostableRule)
|
||||
if err := json.Unmarshal(body, rule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rule.Channels(), nil
|
||||
}
|
||||
|
||||
func resolveChannelIDs(ctx context.Context, alertmanagerService alertmanager.Alertmanager, orgID valuer.UUID, names []string) ([]string, error) {
|
||||
if len(names) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
channels, err := alertmanagerService.ListChannels(ctx, orgID.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idByName := make(map[string]string, len(channels))
|
||||
for _, channel := range channels {
|
||||
idByName[channel.DisplayName] = channel.ID.StringValue()
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if id, ok := idByName[name]; ok {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func orgIDFromContext(ctx context.Context) (valuer.UUID, error) {
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
return valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return valuer.NewUUID(claims.OrgID)
|
||||
}
|
||||
174
pkg/ruler/rulerauthz/channel_test.go
Normal file
174
pkg/ruler/rulerauthz/channel_test.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package rulerauthz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func v1RuleJSON(preferredChannels string) string {
|
||||
return `{
|
||||
"alert": "TestAlert",
|
||||
"version": "v5",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queryType": "builder",
|
||||
"queries": [{
|
||||
"type": "builder_query",
|
||||
"spec": {
|
||||
"name": "A",
|
||||
"signal": "metrics",
|
||||
"aggregations": [{"metricName": "cpu", "spaceAggregation": "p50"}],
|
||||
"stepInterval": "5m"
|
||||
}
|
||||
}]
|
||||
},
|
||||
"target": 10.0,
|
||||
"matchType": "1",
|
||||
"op": "1"
|
||||
}` + preferredChannels + `
|
||||
}`
|
||||
}
|
||||
|
||||
func v2RuleJSON(channels string) string {
|
||||
return `{
|
||||
"alert": "TestAlert",
|
||||
"version": "v5",
|
||||
"schemaVersion": "v2alpha1",
|
||||
"ruleType": "threshold_rule",
|
||||
"condition": {
|
||||
"compositeQuery": {
|
||||
"queryType": "builder",
|
||||
"queries": [{
|
||||
"type": "builder_query",
|
||||
"spec": {
|
||||
"name": "A",
|
||||
"signal": "metrics",
|
||||
"aggregations": [{"metricName": "cpu", "spaceAggregation": "p50"}],
|
||||
"stepInterval": "5m"
|
||||
}
|
||||
}]
|
||||
},
|
||||
"thresholds": {
|
||||
"kind": "basic",
|
||||
"spec": [{"name": "critical", "target": 90.0, "matchType": "1", "op": "1", "channels": ` + channels + `}]
|
||||
}
|
||||
},
|
||||
"evaluation": {
|
||||
"kind": "rolling",
|
||||
"spec": {"evalWindow": "5m", "frequency": "1m"}
|
||||
},
|
||||
"notificationSettings": {
|
||||
"renotify": {"enabled": true, "interval": "4h", "alertStates": ["firing"]}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
func TestChannelsFromBody(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
body string
|
||||
wantChannels []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "V1PreferredChannels_FoldedIntoThresholds",
|
||||
body: v1RuleJSON(`, "preferredChannels": ["slack", "pager"]`),
|
||||
wantChannels: []string{"slack", "pager"},
|
||||
},
|
||||
{
|
||||
name: "V1NoChannels_Empty",
|
||||
body: v1RuleJSON(""),
|
||||
wantChannels: nil,
|
||||
},
|
||||
{
|
||||
name: "V2ThresholdChannels_ReadDirectly",
|
||||
body: v2RuleJSON(`["email"]`),
|
||||
wantChannels: []string{"email"},
|
||||
},
|
||||
{
|
||||
name: "V2EmptyChannels_Empty",
|
||||
body: v2RuleJSON(`[]`),
|
||||
wantChannels: nil,
|
||||
},
|
||||
{
|
||||
name: "NotJSON_Errors",
|
||||
body: "not json",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
channels, err := channelsFromBody([]byte(testCase.body))
|
||||
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.wantChannels, channels)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncomingChannels(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
incoming incomingChannelsFunc
|
||||
stored string
|
||||
body string
|
||||
wantChannels []string
|
||||
wantStoredAfter []string
|
||||
}{
|
||||
{
|
||||
name: "Replaced_IgnoresStored",
|
||||
incoming: replacedChannels,
|
||||
stored: v2RuleJSON(`["slack"]`),
|
||||
body: v2RuleJSON(`["pager"]`),
|
||||
wantChannels: []string{"pager"},
|
||||
wantStoredAfter: []string{"slack"},
|
||||
},
|
||||
{
|
||||
name: "Patched_OmitsCondition_StoredChannelsSurvive",
|
||||
incoming: patchedChannels,
|
||||
stored: v2RuleJSON(`["slack"]`),
|
||||
body: `{"disabled": true}`,
|
||||
wantChannels: []string{"slack"},
|
||||
wantStoredAfter: []string{"slack"},
|
||||
},
|
||||
{
|
||||
name: "Patched_SetsChannels_StoredChannelsReplaced",
|
||||
incoming: patchedChannels,
|
||||
stored: v2RuleJSON(`["slack"]`),
|
||||
body: v2RuleJSON(`["pager"]`),
|
||||
wantChannels: []string{"pager"},
|
||||
wantStoredAfter: []string{"slack"},
|
||||
},
|
||||
{
|
||||
name: "Patched_EmptiesChannels_NoChannelsLeft",
|
||||
incoming: patchedChannels,
|
||||
stored: v2RuleJSON(`["slack"]`),
|
||||
body: v2RuleJSON(`[]`),
|
||||
wantChannels: nil,
|
||||
wantStoredAfter: []string{"slack"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
stored := new(ruletypes.GettableRule)
|
||||
require.NoError(t, json.Unmarshal([]byte(testCase.stored), &stored.PostableRule))
|
||||
|
||||
channels, err := testCase.incoming(stored, []byte(testCase.body))
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.wantChannels, channels)
|
||||
assert.Equal(t, testCase.wantStoredAfter, stored.Channels())
|
||||
})
|
||||
}
|
||||
}
|
||||
48
pkg/ruler/rulerauthz/downtime.go
Normal file
48
pkg/ruler/rulerauthz/downtime.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package rulerauthz
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var DowntimeRuleIDsExtractor = coretypes.BodyJSONArray("alertIds")
|
||||
|
||||
func NewDowntimeRuleAttachExtractor(rulerService ruler.Ruler) coretypes.ResourceIDsExtractor {
|
||||
return newDowntimeDiffExtractor(rulerService, attachedSide)
|
||||
}
|
||||
|
||||
func NewDowntimeRuleDetachExtractor(rulerService ruler.Ruler) coretypes.ResourceIDsExtractor {
|
||||
return newDowntimeDiffExtractor(rulerService, detachedSide)
|
||||
}
|
||||
|
||||
func newDowntimeDiffExtractor(rulerService ruler.Ruler, side diffSide) coretypes.ResourceIDsExtractor {
|
||||
return coretypes.ResourceIDsExtractor{Phase: coretypes.PhaseRequest, Fn: func(ec coretypes.ExtractorContext) ([]string, error) {
|
||||
if ec.Request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
scheduleID, err := valuer.NewUUID(mux.Vars(ec.Request)["id"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stored, err := rulerService.MaintenanceStore().GetPlannedMaintenanceByID(ec.Request.Context(), scheduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
incoming, err := DowntimeRuleIDsExtractor.Fn(ec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attached, detached := coretypes.DiffLinks(stored.RuleIDs, incoming)
|
||||
if side == detachedSide {
|
||||
return detached, nil
|
||||
}
|
||||
|
||||
return attached, nil
|
||||
}}
|
||||
}
|
||||
79
pkg/ruler/rulerauthz/route_policy.go
Normal file
79
pkg/ruler/rulerauthz/route_policy.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package rulerauthz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func NewCreateRoutePolicyChannelAttachExtractor(alertmanagerService alertmanager.Alertmanager) coretypes.ResourceIDsExtractor {
|
||||
return coretypes.ResourceIDsExtractor{Phase: coretypes.PhaseRequest, Fn: func(ec coretypes.ExtractorContext) ([]string, error) {
|
||||
if ec.Request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx := ec.Request.Context()
|
||||
orgID, err := orgIDFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names, err := routePolicyChannelsFromBody(ec.RequestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resolveChannelIDs(ctx, alertmanagerService, orgID, names)
|
||||
}}
|
||||
}
|
||||
|
||||
func NewUpdateRoutePolicyChannelAttachExtractor(alertmanagerService alertmanager.Alertmanager) coretypes.ResourceIDsExtractor {
|
||||
return newRoutePolicyDiffExtractor(alertmanagerService, attachedSide)
|
||||
}
|
||||
|
||||
func NewUpdateRoutePolicyChannelDetachExtractor(alertmanagerService alertmanager.Alertmanager) coretypes.ResourceIDsExtractor {
|
||||
return newRoutePolicyDiffExtractor(alertmanagerService, detachedSide)
|
||||
}
|
||||
|
||||
func newRoutePolicyDiffExtractor(alertmanagerService alertmanager.Alertmanager, side diffSide) coretypes.ResourceIDsExtractor {
|
||||
return coretypes.ResourceIDsExtractor{Phase: coretypes.PhaseRequest, Fn: func(ec coretypes.ExtractorContext) ([]string, error) {
|
||||
if ec.Request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx := ec.Request.Context()
|
||||
orgID, err := orgIDFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stored, err := alertmanagerService.GetRoutePolicyByID(ctx, mux.Vars(ec.Request)["id"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
incoming, err := routePolicyChannelsFromBody(ec.RequestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attached, detached := coretypes.DiffLinks(stored.Channels, incoming)
|
||||
if side == detachedSide {
|
||||
return resolveChannelIDs(ctx, alertmanagerService, orgID, detached)
|
||||
}
|
||||
|
||||
return resolveChannelIDs(ctx, alertmanagerService, orgID, attached)
|
||||
}}
|
||||
}
|
||||
|
||||
func routePolicyChannelsFromBody(body []byte) ([]string, error) {
|
||||
policy := new(alertmanagertypes.PostableRoutePolicy)
|
||||
if err := json.Unmarshal(body, policy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policy.Channels, nil
|
||||
}
|
||||
@@ -100,10 +100,12 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
|
||||
struct{ rulestatehistory.Handler }{},
|
||||
struct{ spanmapper.Handler }{},
|
||||
struct{ alertmanager.Handler }{},
|
||||
struct{ alertmanager.Alertmanager }{},
|
||||
struct{ prometheus.Handler }{},
|
||||
struct{ llmpricingrule.Handler }{},
|
||||
struct{ tracedetail.Handler }{},
|
||||
struct{ ruler.Handler }{},
|
||||
struct{ ruler.Ruler }{},
|
||||
struct{ statsreporter.Handler }{},
|
||||
struct{ savedview.Handler }{},
|
||||
global.Config{},
|
||||
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/prometheus/clickhouseprometheusv2"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/querier/signozquerier"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
|
||||
"github.com/SigNoz/signoz/pkg/sharder/singlesharder"
|
||||
@@ -262,6 +263,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewAddRuleViewFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddFieldTuplesFactory(sqlstore),
|
||||
sqlmigration.NewKeepRelevantLLMPricingRulesFactory(),
|
||||
sqlmigration.NewAddAlertTuplesFactory(sqlstore),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -326,7 +328,7 @@ func NewQuerierProviderFactories(telemetryStore telemetrystore.TelemetryStore, p
|
||||
)
|
||||
}
|
||||
|
||||
func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.AuthZ, modules Modules, handlers Handlers, globalConfig global.Config, gatewayService gateway.Gateway, identNResolver identn.IdentNResolver, sharder sharder.Sharder, auditor auditor.Auditor, web web.Web) factory.NamedMap[factory.ProviderFactory[apiserver.APIServer, apiserver.Config]] {
|
||||
func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.AuthZ, modules Modules, handlers Handlers, globalConfig global.Config, gatewayService gateway.Gateway, identNResolver identn.IdentNResolver, sharder sharder.Sharder, auditor auditor.Auditor, web web.Web, alertmanagerService alertmanager.Alertmanager, rulerService ruler.Ruler) factory.NamedMap[factory.ProviderFactory[apiserver.APIServer, apiserver.Config]] {
|
||||
return factory.MustNewNamedMap(
|
||||
signozapiserver.NewFactory(
|
||||
orgGetter,
|
||||
@@ -363,10 +365,12 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
|
||||
handlers.RuleStateHistory,
|
||||
handlers.SpanMapperHandler,
|
||||
handlers.AlertmanagerHandler,
|
||||
alertmanagerService,
|
||||
handlers.PrometheusHandler,
|
||||
handlers.LLMPricingRuleHandler,
|
||||
handlers.TraceDetail,
|
||||
handlers.RulerHandler,
|
||||
rulerService,
|
||||
handlers.StatsHandler,
|
||||
handlers.SavedView,
|
||||
globalConfig,
|
||||
|
||||
@@ -106,6 +106,8 @@ func TestNewProviderFactories(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ func New(
|
||||
ctx,
|
||||
providerSettings,
|
||||
config.APIServer,
|
||||
NewAPIServerProviderFactories(orgGetter, authz, modules, handlers, config.Global, gateway, identNResolver, sharder, auditor, web),
|
||||
NewAPIServerProviderFactories(orgGetter, authz, modules, handlers, config.Global, gateway, identNResolver, sharder, auditor, web, alertmanager, rulerInstance),
|
||||
"signoz",
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
192
pkg/sqlmigration/136_add_alert_tuples.go
Normal file
192
pkg/sqlmigration/136_add_alert_tuples.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addAlertTuples struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewAddAlertTuplesFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_alert_tuples"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addAlertTuples{sqlstore: sqlstore}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addAlertTuples) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *addAlertTuples) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
var storeID string
|
||||
err = tx.QueryRowContext(ctx, `SELECT id FROM store WHERE name = ? LIMIT 1`, "signoz").Scan(&storeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var orgIDs []string
|
||||
err = tx.NewSelect().
|
||||
Table("organizations").
|
||||
Column("id").
|
||||
Scan(ctx, &orgIDs)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
isPG := migration.sqlstore.BunDB().Dialect().Name() == dialect.PG
|
||||
|
||||
writeVerbs := []string{"create", "read", "update", "delete", "list"}
|
||||
readVerbs := []string{"read", "list"}
|
||||
linkVerbs := []string{"attach", "detach"}
|
||||
|
||||
tuples := make([]migrationTuple, 0, 47)
|
||||
for _, objectName := range []string{"rule", "planned-maintenance"} {
|
||||
for _, verb := range writeVerbs {
|
||||
tuples = append(tuples,
|
||||
migrationTuple{authtypes.SigNozAdminRoleName, "metaresource", objectName, verb},
|
||||
migrationTuple{authtypes.SigNozEditorRoleName, "metaresource", objectName, verb},
|
||||
)
|
||||
}
|
||||
for _, verb := range linkVerbs {
|
||||
tuples = append(tuples,
|
||||
migrationTuple{authtypes.SigNozAdminRoleName, "metaresource", objectName, verb},
|
||||
migrationTuple{authtypes.SigNozEditorRoleName, "metaresource", objectName, verb},
|
||||
)
|
||||
}
|
||||
for _, verb := range readVerbs {
|
||||
tuples = append(tuples, migrationTuple{authtypes.SigNozViewerRoleName, "metaresource", objectName, verb})
|
||||
}
|
||||
}
|
||||
for _, verb := range writeVerbs {
|
||||
tuples = append(tuples, migrationTuple{authtypes.SigNozAdminRoleName, "metaresource", "route-policy", verb})
|
||||
}
|
||||
for _, verb := range linkVerbs {
|
||||
tuples = append(tuples, migrationTuple{authtypes.SigNozAdminRoleName, "metaresource", "route-policy", verb})
|
||||
}
|
||||
for _, verb := range readVerbs {
|
||||
tuples = append(tuples,
|
||||
migrationTuple{authtypes.SigNozEditorRoleName, "metaresource", "route-policy", verb},
|
||||
migrationTuple{authtypes.SigNozViewerRoleName, "metaresource", "route-policy", verb},
|
||||
)
|
||||
}
|
||||
for _, verb := range linkVerbs {
|
||||
tuples = append(tuples,
|
||||
migrationTuple{authtypes.SigNozAdminRoleName, "metaresource", "notification-channel", verb},
|
||||
migrationTuple{authtypes.SigNozEditorRoleName, "metaresource", "notification-channel", verb},
|
||||
)
|
||||
}
|
||||
|
||||
for _, orgID := range orgIDs {
|
||||
for _, tuple := range tuples {
|
||||
entropy := ulid.DefaultEntropy()
|
||||
now := time.Now().UTC()
|
||||
tupleID := ulid.MustNew(ulid.Timestamp(now), entropy).String()
|
||||
|
||||
objectID := "organization/" + orgID + "/" + tuple.objectName + "/*"
|
||||
roleSubject := "organization/" + orgID + "/role/" + tuple.roleName
|
||||
|
||||
if isPG {
|
||||
user := "role:" + roleSubject + "#assignee"
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, user, "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
continue
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, user, 0, tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
continue
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", 0, tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
managedRoleGroups := make(map[string]string, len(coretypes.ManagedRoleToTransactions))
|
||||
for roleName, transactions := range coretypes.ManagedRoleToTransactions {
|
||||
data, err := json.Marshal(authtypes.NewTransactionGroupsFromTransactions(transactions))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
managedRoleGroups[roleName] = string(data)
|
||||
}
|
||||
|
||||
for _, orgID := range orgIDs {
|
||||
for roleName, data := range managedRoleGroups {
|
||||
if _, err := tx.NewUpdate().
|
||||
Model(new(roles)).
|
||||
Set("transaction_groups = ?", data).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("type = ?", authtypes.RoleTypeManaged.StringValue()).
|
||||
Where("name = ?", roleName).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *addAlertTuples) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
41
pkg/types/coretypes/link.go
Normal file
41
pkg/types/coretypes/link.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package coretypes
|
||||
|
||||
func DiffLinks(stored, incoming []string) (attached, detached []string) {
|
||||
storedSet := make(map[string]struct{}, len(stored))
|
||||
for _, id := range stored {
|
||||
storedSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
incomingSet := make(map[string]struct{}, len(incoming))
|
||||
for _, id := range incoming {
|
||||
incomingSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(incoming))
|
||||
attached = make([]string, 0, len(incoming))
|
||||
for _, id := range incoming {
|
||||
if _, ok := storedSet[id]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
attached = append(attached, id)
|
||||
}
|
||||
|
||||
clear(seen)
|
||||
detached = make([]string, 0, len(stored))
|
||||
for _, id := range stored {
|
||||
if _, ok := incomingSet[id]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
detached = append(detached, id)
|
||||
}
|
||||
|
||||
return attached, detached
|
||||
}
|
||||
77
pkg/types/coretypes/link_test.go
Normal file
77
pkg/types/coretypes/link_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package coretypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDiffLinks(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
stored []string
|
||||
incoming []string
|
||||
wantAttached []string
|
||||
wantDetached []string
|
||||
}{
|
||||
{
|
||||
name: "BothEmpty_NothingChanges",
|
||||
wantAttached: []string{},
|
||||
wantDetached: []string{},
|
||||
},
|
||||
{
|
||||
name: "NoStored_EverythingAttaches",
|
||||
incoming: []string{"slack", "pager"},
|
||||
wantAttached: []string{"slack", "pager"},
|
||||
wantDetached: []string{},
|
||||
},
|
||||
{
|
||||
name: "NoIncoming_EverythingDetaches",
|
||||
stored: []string{"slack", "pager"},
|
||||
wantAttached: []string{},
|
||||
wantDetached: []string{"slack", "pager"},
|
||||
},
|
||||
{
|
||||
name: "Unchanged_NothingChanges",
|
||||
stored: []string{"slack", "pager"},
|
||||
incoming: []string{"pager", "slack"},
|
||||
wantAttached: []string{},
|
||||
wantDetached: []string{},
|
||||
},
|
||||
{
|
||||
name: "OneAddedOneRemoved_BothSidesReported",
|
||||
stored: []string{"slack", "pager"},
|
||||
incoming: []string{"slack", "email"},
|
||||
wantAttached: []string{"email"},
|
||||
wantDetached: []string{"pager"},
|
||||
},
|
||||
{
|
||||
name: "DuplicateIncoming_CollapsedOnce",
|
||||
incoming: []string{"slack", "slack"},
|
||||
wantAttached: []string{"slack"},
|
||||
wantDetached: []string{},
|
||||
},
|
||||
{
|
||||
name: "DuplicateStored_CollapsedOnce",
|
||||
stored: []string{"pager", "pager"},
|
||||
wantAttached: []string{},
|
||||
wantDetached: []string{"pager"},
|
||||
},
|
||||
{
|
||||
name: "IncomingOrderPreserved",
|
||||
stored: []string{"keep"},
|
||||
incoming: []string{"zebra", "keep", "apple"},
|
||||
wantAttached: []string{"zebra", "apple"},
|
||||
wantDetached: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
attached, detached := DiffLinks(testCase.stored, testCase.incoming)
|
||||
|
||||
assert.Equal(t, testCase.wantAttached, attached)
|
||||
assert.Equal(t, testCase.wantDetached, detached)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -134,12 +134,16 @@ var ManagedRoleToTransactions = map[string][]Transaction{
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
// rule — full CRUD
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
// saved-view — full CRUD
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindSavedView}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindSavedView}, WildCardSelectorString)},
|
||||
@@ -172,12 +176,16 @@ var ManagedRoleToTransactions = map[string][]Transaction{
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
// route-policy — admin writes, viewer reads
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
// apdex-setting — admin updates, viewer reads
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindApdexSetting}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindApdexSetting}, WildCardSelectorString)},
|
||||
@@ -237,12 +245,16 @@ var ManagedRoleToTransactions = map[string][]Transaction{
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindPlannedMaintenance}, WildCardSelectorString)},
|
||||
// rule — full CRUD
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbDelete, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbCreate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRule}, WildCardSelectorString)},
|
||||
// saved-view — full CRUD
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindSavedView}, WildCardSelectorString)},
|
||||
{Verb: VerbUpdate, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindSavedView}, WildCardSelectorString)},
|
||||
@@ -278,6 +290,8 @@ var ManagedRoleToTransactions = map[string][]Transaction{
|
||||
// notification-channel — read only (admin writes)
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbAttach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
{Verb: VerbDetach, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindNotificationChannel}, WildCardSelectorString)},
|
||||
// route-policy — read only (admin writes)
|
||||
{Verb: VerbRead, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
{Verb: VerbList, Object: *MustNewObject(ResourceRef{Type: TypeMetaResource, Kind: KindRoutePolicy}, WildCardSelectorString)},
|
||||
|
||||
@@ -47,8 +47,8 @@ var (
|
||||
ResourceRole = NewResourceRole()
|
||||
ResourceServiceAccount = NewResourceServiceAccount()
|
||||
ResourceUser = NewResourceUser()
|
||||
ResourceMetaResourceNotificationChannel = NewResourceMetaResource(KindNotificationChannel, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete)
|
||||
ResourceMetaResourceRoutePolicy = NewResourceMetaResource(KindRoutePolicy)
|
||||
ResourceMetaResourceNotificationChannel = NewResourceMetaResource(KindNotificationChannel, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete, VerbAttach, VerbDetach)
|
||||
ResourceMetaResourceRoutePolicy = NewResourceMetaResource(KindRoutePolicy, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete, VerbAttach, VerbDetach)
|
||||
ResourceMetaResourceApdexSetting = NewResourceMetaResource(KindApdexSetting)
|
||||
ResourceMetaResourceAuthDomain = NewResourceMetaResource(KindAuthDomain)
|
||||
ResourceMetaResourceSession = NewResourceMetaResource(KindSession)
|
||||
@@ -64,8 +64,8 @@ var (
|
||||
ResourceMetaResourceOrgPreference = NewResourceMetaResource(KindOrgPreference)
|
||||
ResourceMetaResourceQuickFilter = NewResourceMetaResource(KindQuickFilter, VerbList, VerbRead, VerbUpdate)
|
||||
ResourceMetaResourceTTLSetting = NewResourceMetaResource(KindTTLSetting)
|
||||
ResourceMetaResourceRule = NewResourceMetaResource(KindRule)
|
||||
ResourceMetaResourcePlannedMaintenance = NewResourceMetaResource(KindPlannedMaintenance)
|
||||
ResourceMetaResourceRule = NewResourceMetaResource(KindRule, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete, VerbAttach, VerbDetach)
|
||||
ResourceMetaResourcePlannedMaintenance = NewResourceMetaResource(KindPlannedMaintenance, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete, VerbAttach, VerbDetach)
|
||||
ResourceMetaResourceSavedView = NewResourceMetaResource(KindSavedView, VerbCreate, VerbList, VerbRead, VerbUpdate, VerbDelete)
|
||||
ResourceMetaResourceTraceFunnel = NewResourceMetaResource(KindTraceFunnel)
|
||||
ResourceMetaResourceFactorPassword = NewResourceMetaResource(KindFactorPassword)
|
||||
|
||||
@@ -188,7 +188,9 @@
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -199,7 +201,9 @@
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -256,7 +260,9 @@
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -267,7 +273,9 @@
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -407,7 +415,9 @@
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -418,7 +428,9 @@
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -483,7 +495,9 @@
|
||||
"kind": "notification-channel",
|
||||
"verbs": [
|
||||
"read",
|
||||
"list"
|
||||
"list",
|
||||
"attach",
|
||||
"detach"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
790
tests/integration/tests/ruler/07_authz.py
Normal file
790
tests/integration/tests/ruler/07_authz.py
Normal file
@@ -0,0 +1,790 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
from wiremock.resources.mappings import Mapping
|
||||
|
||||
from fixtures.auth import (
|
||||
USER_ADMIN_EMAIL,
|
||||
USER_ADMIN_PASSWORD,
|
||||
add_license,
|
||||
change_user_role,
|
||||
create_active_user,
|
||||
)
|
||||
from fixtures.role import find_role_by_name, transaction_group
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker
|
||||
|
||||
V1_RULES_URL = "/api/v1/rules"
|
||||
V2_RULES_URL = "/api/v2/rules"
|
||||
V3_RULES_URL = "/api/v3/rules"
|
||||
RULE_VIEWS_URL = "/api/v2/rule_views"
|
||||
DOWNTIME_URL = "/api/v1/downtime_schedules"
|
||||
ROUTE_POLICIES_URL = "/api/v1/route_policies"
|
||||
|
||||
_EDITOR_EMAIL = "editor+alertauthz@integration.test"
|
||||
_EDITOR_PASSWORD = "password123Z$"
|
||||
_VIEWER_EMAIL = "viewer+alertauthz@integration.test"
|
||||
_VIEWER_PASSWORD = "password123Z$"
|
||||
|
||||
_TARGET_RULE = "alert authz target"
|
||||
_EDITOR_RULE = "alert authz editor rule"
|
||||
_EDITOR_RULE_V1 = "alert authz editor rule v1"
|
||||
_ACTOR_EMAIL = "actor+alertauthz@integration.test"
|
||||
_ACTOR_PASSWORD = "password123Z$"
|
||||
_ACTOR_ROLE_NAME = "alert-authz-actor"
|
||||
_ACTOR_RULE = "alert authz actor rule"
|
||||
_ACTOR_SWAP_RULE = "alert authz actor swap rule"
|
||||
_ACTOR_DOWNTIME_NAME = "alert-authz-actor-downtime"
|
||||
_DOWNTIME_NAME = "alert-authz-downtime"
|
||||
_ROUTE_POLICY_NAME = "alert-authz-route-policy"
|
||||
_VIEW_NAME = "alert-authz-view"
|
||||
|
||||
SEED_CHANNEL = {"name": "alert-authz-channel", "email_configs": [{"to": "alert-authz@integration.test"}]}
|
||||
SEED_CHANNEL_ALT = {"name": "alert-authz-channel-alt", "email_configs": [{"to": "alert-authz-alt@integration.test"}]}
|
||||
|
||||
_ACTOR_BASE_TRANSACTION_GROUPS = [
|
||||
transaction_group("create", "metaresource", "rule", ["*"]),
|
||||
transaction_group("read", "metaresource", "rule", ["*"]),
|
||||
transaction_group("list", "metaresource", "rule", ["*"]),
|
||||
transaction_group("update", "metaresource", "rule", ["*"]),
|
||||
transaction_group("delete", "metaresource", "rule", ["*"]),
|
||||
transaction_group("attach", "metaresource", "rule", ["*"]),
|
||||
transaction_group("create", "metaresource", "planned-maintenance", ["*"]),
|
||||
transaction_group("read", "metaresource", "planned-maintenance", ["*"]),
|
||||
transaction_group("list", "metaresource", "planned-maintenance", ["*"]),
|
||||
transaction_group("update", "metaresource", "planned-maintenance", ["*"]),
|
||||
transaction_group("attach", "metaresource", "planned-maintenance", ["*"]),
|
||||
]
|
||||
|
||||
RULE_CONDITION = {
|
||||
"thresholds": {
|
||||
"kind": "basic",
|
||||
"spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above", "channels": ["alert-authz-channel"]}],
|
||||
},
|
||||
"compositeQuery": {
|
||||
"queryType": "builder",
|
||||
"panelType": "graph",
|
||||
"queries": [
|
||||
{
|
||||
"type": "builder_query",
|
||||
"spec": {
|
||||
"name": "A",
|
||||
"signal": "metrics",
|
||||
"aggregations": [{"metricName": "alert_authz_cpu", "timeAggregation": "avg", "spaceAggregation": "max"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
"selectedQueryName": "A",
|
||||
}
|
||||
|
||||
RULE_TEMPLATE = {
|
||||
"description": "authz coverage rule",
|
||||
"alertType": "METRIC_BASED_ALERT",
|
||||
"ruleType": "threshold_rule",
|
||||
"condition": RULE_CONDITION,
|
||||
"labels": {"severity": "critical"},
|
||||
"annotations": {"summary": "s", "description": "d"},
|
||||
"evaluation": {"kind": "rolling", "spec": {"evalWindow": "5m0s", "frequency": "1m"}},
|
||||
"notificationSettings": {"groupBy": [], "usePolicy": False, "renotify": {"enabled": False, "interval": "30m", "alertStates": []}},
|
||||
"version": "v5",
|
||||
"schemaVersion": "v2alpha1",
|
||||
}
|
||||
|
||||
|
||||
def test_setup_users_and_target(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
existing_emails = {user["email"] for user in response.json()["data"]}
|
||||
|
||||
for email, role, password, name in (
|
||||
(_EDITOR_EMAIL, "signoz-editor", _EDITOR_PASSWORD, "alert authz editor"),
|
||||
(_VIEWER_EMAIL, "signoz-viewer", _VIEWER_PASSWORD, "alert authz viewer"),
|
||||
):
|
||||
if email not in existing_emails:
|
||||
create_active_user(signoz, admin_token, email=email, role=role, password=password, name=name)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/channels"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
existing_channel_names = [channel["name"] for channel in response.json()["data"] or []]
|
||||
for channel in (SEED_CHANNEL, SEED_CHANNEL_ALT):
|
||||
if channel["name"] not in existing_channel_names:
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/channels"),
|
||||
json=channel,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V3_RULES_URL}?limit=200"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
ids = {rule["alert"]: rule["id"] for rule in response.json()["data"]["rules"]}
|
||||
for name in (_TARGET_RULE, _EDITOR_RULE, _EDITOR_RULE_V1, _ACTOR_SWAP_RULE):
|
||||
if name in ids:
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{ids[name]}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _TARGET_RULE},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
|
||||
def test_viewer_allowed_on_reads(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(_VIEWER_EMAIL, _VIEWER_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V3_RULES_URL}?limit=200"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
ids = {rule["alert"]: rule["id"] for rule in response.json()["data"]["rules"]}
|
||||
target_id = ids[_TARGET_RULE]
|
||||
|
||||
for path in (
|
||||
V2_RULES_URL,
|
||||
f"{V2_RULES_URL}/{target_id}",
|
||||
V1_RULES_URL,
|
||||
f"{V1_RULES_URL}/{target_id}",
|
||||
DOWNTIME_URL,
|
||||
ROUTE_POLICIES_URL,
|
||||
):
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(path),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, f"GET {path}: expected 200, got {response.status_code}: {response.text}"
|
||||
|
||||
|
||||
def test_viewer_allowed_on_rule_views(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(_VIEWER_EMAIL, _VIEWER_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(RULE_VIEWS_URL),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(RULE_VIEWS_URL),
|
||||
json={"name": _VIEW_NAME, "data": {"version": "v1", "sort": "updated_at", "order": "desc"}},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
view_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{RULE_VIEWS_URL}/{view_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_viewer_forbidden_on_mutations(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_VIEWER_EMAIL, _VIEWER_PASSWORD)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V3_RULES_URL}?limit=200"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
ids = {rule["alert"]: rule["id"] for rule in response.json()["data"]["rules"]}
|
||||
target_id = ids[_TARGET_RULE]
|
||||
|
||||
for method, path in (
|
||||
("post", V2_RULES_URL),
|
||||
("put", f"{V2_RULES_URL}/{target_id}"),
|
||||
("patch", f"{V2_RULES_URL}/{target_id}"),
|
||||
("delete", f"{V2_RULES_URL}/{target_id}"),
|
||||
("post", f"{V2_RULES_URL}/test"),
|
||||
("post", V1_RULES_URL),
|
||||
("put", f"{V1_RULES_URL}/{target_id}"),
|
||||
("patch", f"{V1_RULES_URL}/{target_id}"),
|
||||
("delete", f"{V1_RULES_URL}/{target_id}"),
|
||||
("post", "/api/v1/testRule"),
|
||||
("post", DOWNTIME_URL),
|
||||
("put", f"{DOWNTIME_URL}/{target_id}"),
|
||||
("delete", f"{DOWNTIME_URL}/{target_id}"),
|
||||
("post", ROUTE_POLICIES_URL),
|
||||
("put", f"{ROUTE_POLICIES_URL}/{target_id}"),
|
||||
("delete", f"{ROUTE_POLICIES_URL}/{target_id}"),
|
||||
):
|
||||
response = getattr(requests, method)(
|
||||
signoz.self.host_configs["8080"].get(path),
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, f"{method.upper()} {path}: expected 403, got {response.status_code}: {response.text}"
|
||||
|
||||
|
||||
def test_editor_allowed_on_rule_and_downtime_mutations(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(_EDITOR_EMAIL, _EDITOR_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _EDITOR_RULE},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
rule_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V1_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _EDITOR_RULE_V1},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
v1_rule_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V1_RULES_URL}/{v1_rule_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(DOWNTIME_URL),
|
||||
json={
|
||||
"name": _DOWNTIME_NAME,
|
||||
"description": "authz coverage window",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-22T00:00:00Z"},
|
||||
"alertIds": [],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
downtime_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
json={
|
||||
"name": _DOWNTIME_NAME,
|
||||
"description": "authz coverage window updated",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-23T00:00:00Z"},
|
||||
"alertIds": [],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_editor_forbidden_on_route_policy_writes(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(_EDITOR_EMAIL, _EDITOR_PASSWORD)
|
||||
|
||||
for method, path in (
|
||||
("post", ROUTE_POLICIES_URL),
|
||||
("put", f"{ROUTE_POLICIES_URL}/00000000-0000-0000-0000-000000000000"),
|
||||
("delete", f"{ROUTE_POLICIES_URL}/00000000-0000-0000-0000-000000000000"),
|
||||
):
|
||||
response = getattr(requests, method)(
|
||||
signoz.self.host_configs["8080"].get(path),
|
||||
json={},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, f"{method.upper()} {path}: expected 403, got {response.status_code}: {response.text}"
|
||||
|
||||
|
||||
def test_admin_allowed_on_route_policy_writes(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(ROUTE_POLICIES_URL),
|
||||
json={
|
||||
"name": _ROUTE_POLICY_NAME,
|
||||
"expression": 'threshold.name == "critical"',
|
||||
"channels": ["alert-authz-channel"],
|
||||
"description": "authz coverage policy",
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
policy_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{ROUTE_POLICIES_URL}/{policy_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_editor_allowed_on_downtime_naming_rules(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_EDITOR_EMAIL, _EDITOR_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V3_RULES_URL}?limit=200"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
target_id = {rule["alert"]: rule["id"] for rule in response.json()["data"]["rules"]}[_TARGET_RULE]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(DOWNTIME_URL),
|
||||
json={
|
||||
"name": _DOWNTIME_NAME,
|
||||
"description": "authz coverage window with rules",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-22T00:00:00Z"},
|
||||
"alertIds": [target_id],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
downtime_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
json={
|
||||
"name": _DOWNTIME_NAME,
|
||||
"description": "authz coverage window without rules",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-22T00:00:00Z"},
|
||||
"alertIds": [],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_editor_allowed_on_rule_channel_changes(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(_EDITOR_EMAIL, _EDITOR_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _EDITOR_RULE},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
rule_id = response.json()["data"]["id"]
|
||||
|
||||
swapped_condition = {
|
||||
**RULE_CONDITION,
|
||||
"thresholds": {
|
||||
"kind": "basic",
|
||||
"spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above", "channels": [SEED_CHANNEL_ALT["name"]]}],
|
||||
},
|
||||
}
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
json={**RULE_TEMPLATE, "alert": _EDITOR_RULE, "condition": swapped_condition},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.patch(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
json={"description": "patched without touching channels"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
assert response.json()["data"]["condition"]["thresholds"]["spec"][0]["channels"] == [SEED_CHANNEL_ALT["name"]], response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_apply_license(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
make_http_mocks: Callable[[TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
add_license(signoz, make_http_mocks, get_token)
|
||||
|
||||
|
||||
def test_setup_channel_scoped_actor(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_role: Callable[..., str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/roles"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
existing_role_ids = {role["name"]: role["id"] for role in response.json()["data"]}
|
||||
|
||||
if _ACTOR_ROLE_NAME in existing_role_ids:
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{existing_role_ids[_ACTOR_ROLE_NAME]}"),
|
||||
json={"description": "", "transactionGroups": _ACTOR_BASE_TRANSACTION_GROUPS},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
else:
|
||||
create_role(admin_token, _ACTOR_ROLE_NAME, _ACTOR_BASE_TRANSACTION_GROUPS)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v2/users"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
if _ACTOR_EMAIL not in {user["email"] for user in response.json()["data"]}:
|
||||
user_id = create_active_user(signoz, admin_token, email=_ACTOR_EMAIL, role="signoz-viewer", password=_ACTOR_PASSWORD, name="alert authz actor")
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", _ACTOR_ROLE_NAME)
|
||||
|
||||
|
||||
def test_rule_without_channels_allowed_without_channel_attach(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_ACTOR_EMAIL, _ACTOR_PASSWORD)
|
||||
|
||||
condition = {
|
||||
**RULE_CONDITION,
|
||||
"thresholds": {"kind": "basic", "spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above", "channels": []}]},
|
||||
}
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={
|
||||
**RULE_TEMPLATE,
|
||||
"alert": _ACTOR_RULE,
|
||||
"condition": condition,
|
||||
"notificationSettings": {**RULE_TEMPLATE["notificationSettings"], "usePolicy": True},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{response.json()['data']['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_rule_naming_channel_forbidden_without_channel_attach(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(_ACTOR_EMAIL, _ACTOR_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _ACTOR_RULE},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, response.text
|
||||
|
||||
|
||||
def test_rule_naming_channel_allowed_once_channel_attach_granted(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{find_role_by_name(signoz, admin_token, _ACTOR_ROLE_NAME)}"),
|
||||
json={
|
||||
"description": "",
|
||||
"transactionGroups": _ACTOR_BASE_TRANSACTION_GROUPS + [transaction_group("attach", "metaresource", "notification-channel", ["*"])],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
token = get_token(_ACTOR_EMAIL, _ACTOR_PASSWORD)
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _ACTOR_RULE},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{response.json()['data']['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_channel_swap_forbidden_without_channel_detach(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_ACTOR_EMAIL, _ACTOR_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(V2_RULES_URL),
|
||||
json={**RULE_TEMPLATE, "alert": _ACTOR_SWAP_RULE},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
rule_id = response.json()["data"]["id"]
|
||||
|
||||
swapped_condition = {
|
||||
**RULE_CONDITION,
|
||||
"thresholds": {
|
||||
"kind": "basic",
|
||||
"spec": [{"name": "critical", "target": 90, "matchType": "at_least_once", "op": "above", "channels": [SEED_CHANNEL_ALT["name"]]}],
|
||||
},
|
||||
}
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
json={**RULE_TEMPLATE, "alert": _ACTOR_SWAP_RULE, "condition": swapped_condition},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, response.text
|
||||
|
||||
response = requests.patch(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
json={"description": "patched without touching channels"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{rule_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_downtime_rule_removal_forbidden_without_rule_detach(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
token = get_token(_ACTOR_EMAIL, _ACTOR_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V3_RULES_URL}?limit=200"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
target_id = {rule["alert"]: rule["id"] for rule in response.json()["data"]["rules"]}[_TARGET_RULE]
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(DOWNTIME_URL),
|
||||
json={
|
||||
"name": _ACTOR_DOWNTIME_NAME,
|
||||
"description": "scoped actor window",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-22T00:00:00Z"},
|
||||
"alertIds": [target_id],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
downtime_id = response.json()["data"]["id"]
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
json={
|
||||
"name": _ACTOR_DOWNTIME_NAME,
|
||||
"description": "scoped actor window without rules",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-22T00:00:00Z"},
|
||||
"alertIds": [],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN, response.text
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
json={
|
||||
"name": _ACTOR_DOWNTIME_NAME,
|
||||
"description": "scoped actor window without rules",
|
||||
"schedule": {"timezone": "UTC", "startTime": "2026-09-21T00:00:00Z", "endTime": "2026-09-22T00:00:00Z"},
|
||||
"alertIds": [],
|
||||
},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{downtime_id}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
|
||||
def test_alert_authz_cleanup(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(DOWNTIME_URL),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
for schedule in response.json()["data"] or []:
|
||||
if schedule["name"] not in (_DOWNTIME_NAME, _ACTOR_DOWNTIME_NAME):
|
||||
continue
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{schedule['id']}"),
|
||||
json={"name": schedule["name"], "description": schedule["description"], "schedule": schedule["schedule"], "alertIds": []},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{DOWNTIME_URL}/{schedule['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, response.text
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(f"{V3_RULES_URL}?limit=200"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
ids = {rule["alert"]: rule["id"] for rule in response.json()["data"]["rules"]}
|
||||
for name in (_TARGET_RULE, _EDITOR_RULE, _EDITOR_RULE_V1, _ACTOR_SWAP_RULE):
|
||||
if name in ids:
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{V2_RULES_URL}/{ids[name]}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, f"delete {name}: {response.text}"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/channels"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
for channel in response.json()["data"] or []:
|
||||
if channel["name"] in (SEED_CHANNEL["name"], SEED_CHANNEL_ALT["name"]):
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/channels/{channel['id']}"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.NO_CONTENT, f"delete channel: {response.text}"
|
||||
Reference in New Issue
Block a user