2023-10-02 23:47:59 +08:00
package api
import (
"errors"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2024-05-24 00:44:30 +08:00
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
2025-03-13 16:28:35 +08:00
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
2023-10-02 23:47:59 +08:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2025-03-13 16:28:35 +08:00
apivalidation "github.com/grafana/grafana/pkg/services/ngalert/api/validation"
2023-10-02 23:47:59 +08:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
)
// ExportFromPayload converts the rule groups from the argument `ruleGroupConfig` to export format. All rules are expected to be fully specified. The access to data sources mentioned in the rules is not enforced.
2024-01-17 17:07:39 +08:00
// Can return 403 StatusForbidden if user is not authorized to read folder `namespaceUID`
func ( srv RulerSrv ) ExportFromPayload ( c * contextmodel . ReqContext , ruleGroupConfig apimodels . PostableRuleGroupConfig , namespaceUID string ) response . Response {
2025-04-10 20:42:23 +08:00
namespace , err := srv . store . GetNamespaceByUID ( c . Req . Context ( ) , namespaceUID , c . GetOrgID ( ) , c . SignedInUser )
2023-10-02 23:47:59 +08:00
if err != nil {
return toNamespaceErrorResponse ( err )
}
2025-04-10 20:42:23 +08:00
rulesWithOptionals , err := apivalidation . ValidateRuleGroup ( & ruleGroupConfig , c . GetOrgID ( ) , namespace . UID , apivalidation . RuleLimitsFromConfig ( srv . cfg , srv . featureManager ) )
2023-10-02 23:47:59 +08:00
if err != nil {
return ErrResp ( http . StatusBadRequest , err , "" )
}
if len ( rulesWithOptionals ) == 0 {
return ErrResp ( http . StatusBadRequest , err , "" )
}
rules := make ( [ ] ngmodels . AlertRule , 0 , len ( rulesWithOptionals ) )
for _ , optional := range rulesWithOptionals {
rules = append ( rules , optional . AlertRule )
}
2024-05-31 16:09:20 +08:00
groupsWithFullpath := ngmodels . NewAlertRuleGroupWithFolderFullpath ( rules [ 0 ] . GetGroupKey ( ) , rules , namespace . Fullpath )
2023-10-02 23:47:59 +08:00
2024-05-31 16:09:20 +08:00
e , err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath ( [ ] ngmodels . AlertRuleGroupWithFolderFullpath { groupsWithFullpath } )
2023-10-02 23:47:59 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to create alerting file export" )
}
return exportResponse ( c , e )
}
// ExportRules reads alert rules that user has access to from database according to the filters.
func ( srv RulerSrv ) ExportRules ( c * contextmodel . ReqContext ) response . Response {
// The similar method exists in provisioning (see ProvisioningSrv.RouteGetAlertRulesExport).
// Modification to parameters and response format should be made in these two methods at the same time.
folderUIDs := c . QueryStrings ( "folderUid" )
group := c . Query ( "group" )
uid := c . Query ( "ruleUid" )
2024-05-31 16:09:20 +08:00
var groups [ ] ngmodels . AlertRuleGroupWithFolderFullpath
2023-10-02 23:47:59 +08:00
if uid != "" {
if group != "" || len ( folderUIDs ) > 0 {
return ErrResp ( http . StatusBadRequest , errors . New ( "group and folder should not be specified when a single rule is requested" ) , "" )
}
2024-05-31 16:09:20 +08:00
rulesGroup , err := srv . getRuleWithFolderFullpathByRuleUid ( c , uid )
2023-10-02 23:47:59 +08:00
if err != nil {
return errorToResponse ( err )
}
2024-05-31 16:09:20 +08:00
groups = [ ] ngmodels . AlertRuleGroupWithFolderFullpath { rulesGroup }
2023-10-02 23:47:59 +08:00
} else if group != "" {
if len ( folderUIDs ) != 1 || folderUIDs [ 0 ] == "" {
return ErrResp ( http . StatusBadRequest ,
fmt . Errorf ( "group name must be specified together with a single folder_uid parameter. Got %d" , len ( folderUIDs ) ) ,
"" ,
)
}
2024-05-31 16:09:20 +08:00
rulesGroup , err := srv . getRuleGroupWithFolderFullPath ( c , ngmodels . AlertRuleGroupKey {
2025-04-10 20:42:23 +08:00
OrgID : c . GetOrgID ( ) ,
2023-10-02 23:47:59 +08:00
NamespaceUID : folderUIDs [ 0 ] ,
RuleGroup : group ,
} )
if err != nil {
return errorToResponse ( err )
}
2024-05-31 16:09:20 +08:00
groups = [ ] ngmodels . AlertRuleGroupWithFolderFullpath { rulesGroup }
2023-10-02 23:47:59 +08:00
} else {
var err error
2024-05-31 16:09:20 +08:00
groups , err = srv . getRulesWithFolderFullPathInFolders ( c , folderUIDs )
2023-10-02 23:47:59 +08:00
if err != nil {
return errorToResponse ( err )
}
}
if len ( groups ) == 0 {
return response . Empty ( http . StatusNotFound )
}
// sort result so the response is always stable
ngmodels . SortAlertRuleGroupWithFolderTitle ( groups )
2024-05-31 16:09:20 +08:00
e , err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath ( groups )
2023-10-02 23:47:59 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to create alerting file export" )
}
return exportResponse ( c , e )
}
2024-05-31 16:09:20 +08:00
// getRuleWithFolderFullpathByRuleUid calls getAuthorizedRuleByUid and combines its result with folder (aka namespace) title.
func ( srv RulerSrv ) getRuleWithFolderFullpathByRuleUid ( c * contextmodel . ReqContext , ruleUID string ) ( ngmodels . AlertRuleGroupWithFolderFullpath , error ) {
2023-10-02 23:47:59 +08:00
rule , err := srv . getAuthorizedRuleByUid ( c . Req . Context ( ) , c , ruleUID )
if err != nil {
2024-05-31 16:09:20 +08:00
return ngmodels . AlertRuleGroupWithFolderFullpath { } , err
2023-10-02 23:47:59 +08:00
}
2025-04-10 20:42:23 +08:00
namespace , err := srv . store . GetNamespaceByUID ( c . Req . Context ( ) , rule . NamespaceUID , c . GetOrgID ( ) , c . SignedInUser )
2023-10-02 23:47:59 +08:00
if err != nil {
2024-05-31 16:09:20 +08:00
return ngmodels . AlertRuleGroupWithFolderFullpath { } , errors . Join ( errFolderAccess , err )
2023-10-02 23:47:59 +08:00
}
2024-05-31 16:09:20 +08:00
return ngmodels . NewAlertRuleGroupWithFolderFullpath ( rule . GetGroupKey ( ) , [ ] ngmodels . AlertRule { rule } , namespace . Fullpath ) , nil
2023-10-02 23:47:59 +08:00
}
2024-05-31 16:09:20 +08:00
// getRuleGroupWithFolderFullPath calls getAuthorizedRuleGroup and combines its result with folder (aka namespace) title.
func ( srv RulerSrv ) getRuleGroupWithFolderFullPath ( c * contextmodel . ReqContext , ruleGroupKey ngmodels . AlertRuleGroupKey ) ( ngmodels . AlertRuleGroupWithFolderFullpath , error ) {
2025-04-10 20:42:23 +08:00
namespace , err := srv . store . GetNamespaceByUID ( c . Req . Context ( ) , ruleGroupKey . NamespaceUID , c . GetOrgID ( ) , c . SignedInUser )
2023-10-02 23:47:59 +08:00
if err != nil {
2024-05-31 16:09:20 +08:00
return ngmodels . AlertRuleGroupWithFolderFullpath { } , errors . Join ( errFolderAccess , err )
2023-10-02 23:47:59 +08:00
}
rules , err := srv . getAuthorizedRuleGroup ( c . Req . Context ( ) , c , ruleGroupKey )
if err != nil {
2024-05-31 16:09:20 +08:00
return ngmodels . AlertRuleGroupWithFolderFullpath { } , err
2023-10-02 23:47:59 +08:00
}
if len ( rules ) == 0 {
2024-05-31 16:09:20 +08:00
return ngmodels . AlertRuleGroupWithFolderFullpath { } , ngmodels . ErrAlertRuleNotFound
2023-10-02 23:47:59 +08:00
}
2024-05-31 16:09:20 +08:00
return ngmodels . NewAlertRuleGroupWithFolderFullpathFromRulesGroup ( ruleGroupKey , rules , namespace . Fullpath ) , nil
2023-10-02 23:47:59 +08:00
}
2024-05-31 16:09:20 +08:00
// getRulesWithFolderFullPathInFolders gets list of folders to which user has access, and then calls searchAuthorizedAlertRules.
2023-10-02 23:47:59 +08:00
// If argument folderUIDs is not empty it intersects it with the list of folders available for user and then retrieves rules that are in those folders.
2024-05-31 16:09:20 +08:00
func ( srv RulerSrv ) getRulesWithFolderFullPathInFolders ( c * contextmodel . ReqContext , folderUIDs [ ] string ) ( [ ] ngmodels . AlertRuleGroupWithFolderFullpath , error ) {
2025-04-10 20:42:23 +08:00
folders , err := srv . store . GetUserVisibleNamespaces ( c . Req . Context ( ) , c . GetOrgID ( ) , c . SignedInUser )
2023-10-02 23:47:59 +08:00
if err != nil {
return nil , err
}
query := ngmodels . ListAlertRulesQuery {
2025-04-10 20:42:23 +08:00
OrgID : c . GetOrgID ( ) ,
2023-10-02 23:47:59 +08:00
NamespaceUIDs : nil ,
}
if len ( folderUIDs ) > 0 {
for _ , folderUID := range folderUIDs {
if _ , ok := folders [ folderUID ] ; ok {
query . NamespaceUIDs = append ( query . NamespaceUIDs , folderUID )
}
}
if len ( query . NamespaceUIDs ) == 0 {
2024-05-24 00:44:30 +08:00
return nil , authz . NewAuthorizationErrorGeneric ( "access rules in the specified folders" )
2023-10-02 23:47:59 +08:00
}
} else {
for _ , folder := range folders {
query . NamespaceUIDs = append ( query . NamespaceUIDs , folder . UID )
}
}
2024-05-24 00:44:30 +08:00
rulesByGroup , _ , err := srv . searchAuthorizedAlertRules ( c . Req . Context ( ) , authorizedRuleGroupQuery {
User : c . SignedInUser ,
NamespaceUIDs : folderUIDs ,
} )
2023-10-02 23:47:59 +08:00
if err != nil {
return nil , err
}
2024-05-31 16:09:20 +08:00
result := make ( [ ] ngmodels . AlertRuleGroupWithFolderFullpath , 0 , len ( rulesByGroup ) )
2023-10-02 23:47:59 +08:00
for groupKey , rulesGroup := range rulesByGroup {
namespace , ok := folders [ groupKey . NamespaceUID ]
if ! ok {
continue // user does not have access
}
2024-05-31 16:09:20 +08:00
result = append ( result , ngmodels . NewAlertRuleGroupWithFolderFullpathFromRulesGroup ( groupKey , rulesGroup , namespace . Fullpath ) )
2023-10-02 23:47:59 +08:00
}
return result , nil
}