2016-06-11 03:13:33 -05:00
package alerting
import (
2021-10-18 10:06:19 -05:00
"context"
2020-07-23 01:17:20 -05:00
"encoding/json"
2016-06-11 03:13:33 -05:00
"errors"
2016-11-03 01:25:00 -05:00
"fmt"
2016-06-11 03:13:33 -05:00
"github.com/grafana/grafana/pkg/components/simplejson"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2019-05-14 01:15:05 -05:00
"github.com/grafana/grafana/pkg/models"
2022-02-28 02:54:56 -06:00
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
2016-06-11 03:13:33 -05:00
)
2022-02-28 02:54:56 -06:00
type DashAlertExtractor interface {
GetAlerts ( ctx context . Context , dashAlertInfo DashAlertInfo ) ( [ ] * models . Alert , error )
ValidateAlerts ( ctx context . Context , dashAlertInfo DashAlertInfo ) error
2016-06-11 03:13:33 -05:00
}
2022-02-28 02:54:56 -06:00
// DashAlertExtractorService extracts alerts from the dashboard json.
type DashAlertExtractorService struct {
datasourcePermissionsService permissions . DatasourcePermissionsService
datasourceService datasources . DataSourceService
2022-04-08 07:30:25 -05:00
alertStore AlertStore
2022-02-28 02:54:56 -06:00
log log . Logger
}
2022-08-03 10:17:26 -05:00
func ProvideDashAlertExtractorService ( datasourcePermissionsService permissions . DatasourcePermissionsService , datasourceService datasources . DataSourceService , store AlertStore ) * DashAlertExtractorService {
2022-02-28 02:54:56 -06:00
return & DashAlertExtractorService {
datasourcePermissionsService : datasourcePermissionsService ,
datasourceService : datasourceService ,
2022-08-03 10:17:26 -05:00
alertStore : store ,
2022-02-28 02:54:56 -06:00
log : log . New ( "alerting.extractor" ) ,
2016-06-11 03:13:33 -05:00
}
}
2022-06-27 11:23:15 -05:00
func ( e * DashAlertExtractorService ) lookupQueryDataSource ( ctx context . Context , panel * simplejson . Json , panelQuery * simplejson . Json , orgID int64 ) ( * datasources . DataSource , error ) {
2021-10-29 12:57:24 -05:00
dsName := ""
dsUid := ""
datasource , ok := panelQuery . CheckGet ( "datasource" )
if ! ok {
datasource = panel . Get ( "datasource" )
}
if name , err := datasource . String ( ) ; err == nil {
dsName = name
} else if uid , ok := datasource . CheckGet ( "uid" ) ; ok {
dsUid = uid . MustString ( )
}
if dsName == "" && dsUid == "" {
2022-06-27 11:23:15 -05:00
query := & datasources . GetDefaultDataSourceQuery { OrgId : orgID }
2022-02-28 02:54:56 -06:00
if err := e . datasourceService . GetDefaultDataSource ( ctx , query ) ; err != nil {
2016-06-20 04:44:06 -05:00
return nil , err
2018-03-28 04:31:33 -05:00
}
return query . Result , nil
2016-06-11 03:13:33 -05:00
}
2022-06-27 11:23:15 -05:00
query := & datasources . GetDataSourceQuery { Name : dsName , Uid : dsUid , OrgId : orgID }
2022-02-28 02:54:56 -06:00
if err := e . datasourceService . GetDataSource ( ctx , query ) ; err != nil {
2021-01-07 14:33:17 -06:00
return nil , err
}
return query . Result , nil
2016-06-11 03:13:33 -05:00
}
2018-03-28 04:31:33 -05:00
func findPanelQueryByRefID ( panel * simplejson . Json , refID string ) * simplejson . Json {
2016-07-19 14:00:41 -05:00
for _ , targetsObj := range panel . Get ( "targets" ) . MustArray ( ) {
target := simplejson . NewFromAny ( targetsObj )
2018-03-28 04:31:33 -05:00
if target . Get ( "refId" ) . MustString ( ) == refID {
2016-07-19 14:00:41 -05:00
return target
}
}
return nil
}
2020-07-23 01:17:20 -05:00
func copyJSON ( in json . Marshaler ) ( * simplejson . Json , error ) {
2018-03-28 04:31:33 -05:00
rawJSON , err := in . MarshalJSON ( )
2017-01-13 08:46:23 -06:00
if err != nil {
2021-03-17 10:06:10 -05:00
return nil , fmt . Errorf ( "JSON marshaling failed: %w" , err )
2017-01-13 08:46:23 -06:00
}
2018-03-28 04:31:33 -05:00
return simplejson . NewJson ( rawJSON )
2017-01-13 08:46:23 -06:00
}
2021-12-02 09:41:24 -06:00
// UAEnabled takes a context and returns true if Unified Alerting is enabled
// and false if it is disabled or the setting is not present in the context
type uaEnabledKeyType string
const uaEnabledKey uaEnabledKeyType = "unified_alerting_enabled"
func WithUAEnabled ( ctx context . Context , enabled bool ) context . Context {
retCtx := context . WithValue ( ctx , uaEnabledKey , enabled )
return retCtx
}
func UAEnabled ( ctx context . Context ) bool {
enabled , ok := ctx . Value ( uaEnabledKey ) . ( bool )
if ! ok {
return false
}
return enabled
}
2022-02-28 02:54:56 -06:00
func ( e * DashAlertExtractorService ) getAlertFromPanels ( ctx context . Context , jsonWithPanels * simplejson . Json , validateAlertFunc func ( * models . Alert ) bool , logTranslationFailures bool , dashAlertInfo DashAlertInfo ) ( [ ] * models . Alert , error ) {
2019-05-14 01:15:05 -05:00
alerts := make ( [ ] * models . Alert , 0 )
2016-06-11 03:13:33 -05:00
2017-12-19 04:19:52 -06:00
for _ , panelObj := range jsonWithPanels . Get ( "panels" ) . MustArray ( ) {
panel := simplejson . NewFromAny ( panelObj )
2018-03-13 16:23:37 -05:00
2018-03-28 04:31:33 -05:00
collapsedJSON , collapsed := panel . CheckGet ( "collapsed" )
2018-03-13 16:23:37 -05:00
// check if the panel is collapsed
2018-03-28 04:31:33 -05:00
if collapsed && collapsedJSON . MustBool ( ) {
2018-03-13 16:23:37 -05:00
// extract alerts from sub panels for collapsed panels
2022-02-28 02:54:56 -06:00
alertSlice , err := e . getAlertFromPanels ( ctx , panel , validateAlertFunc , logTranslationFailures , dashAlertInfo )
2018-03-13 16:23:37 -05:00
if err != nil {
return nil , err
}
2018-09-21 04:51:26 -05:00
alerts = append ( alerts , alertSlice ... )
2018-03-13 16:23:37 -05:00
continue
}
2017-12-19 04:19:52 -06:00
jsonAlert , hasAlert := panel . CheckGet ( "alert" )
2016-06-11 03:13:33 -05:00
2017-12-19 04:19:52 -06:00
if ! hasAlert {
continue
}
2016-06-11 03:13:33 -05:00
2018-03-28 04:31:33 -05:00
panelID , err := panel . Get ( "id" ) . Int64 ( )
2017-12-19 04:19:52 -06:00
if err != nil {
2018-10-13 00:53:28 -05:00
return nil , ValidationError { Reason : "A numeric panel id property is missing" }
2017-12-19 04:19:52 -06:00
}
2016-06-11 03:13:33 -05:00
2021-06-16 07:56:55 -05:00
addIdentifiersToValidationError := func ( err error ) error {
if err == nil {
return nil
}
var validationErr ValidationError
if ok := errors . As ( err , & validationErr ) ; ok {
ve := ValidationError {
Reason : validationErr . Reason ,
Err : validationErr . Err ,
PanelID : panelID ,
}
2022-02-28 02:54:56 -06:00
if dashAlertInfo . Dash != nil {
ve . DashboardID = dashAlertInfo . Dash . Id
2021-06-16 07:56:55 -05:00
}
return ve
}
return err
}
2017-12-19 04:19:52 -06:00
// backward compatibility check, can be removed later
enabled , hasEnabled := jsonAlert . CheckGet ( "enabled" )
Simplify comparison to bool constant (gosimple)
This fixes:
build.go:553:6: should omit comparison to bool constant, can be simplified to !strings.Contains(path, ".sha256") (S1002)
pkg/cmd/grafana-cli/commands/ls_command.go:27:5: should omit comparison to bool constant, can be simplified to !pluginDirInfo.IsDir() (S1002)
pkg/components/dynmap/dynmap_test.go:24:5: should omit comparison to bool constant, can be simplified to !value (S1002)
pkg/components/dynmap/dynmap_test.go:122:14: should omit comparison to bool constant, can be simplified to b (S1002)
pkg/components/dynmap/dynmap_test.go:125:14: should omit comparison to bool constant, can be simplified to !b (S1002)
pkg/components/dynmap/dynmap_test.go:128:14: should omit comparison to bool constant, can be simplified to !b (S1002)
pkg/models/org_user.go:51:5: should omit comparison to bool constant, can be simplified to !(*r).IsValid() (S1002)
pkg/plugins/datasource/wrapper/datasource_plugin_wrapper_test.go:77:12: should omit comparison to bool constant, can be simplified to !haveBool (S1002)
pkg/services/alerting/conditions/evaluator.go:23:9: should omit comparison to bool constant, can be simplified to !reducedValue.Valid (S1002)
pkg/services/alerting/conditions/evaluator.go:48:5: should omit comparison to bool constant, can be simplified to !reducedValue.Valid (S1002)
pkg/services/alerting/conditions/evaluator.go:91:5: should omit comparison to bool constant, can be simplified to !reducedValue.Valid (S1002)
pkg/services/alerting/conditions/query.go:56:6: should omit comparison to bool constant, can be simplified to !reducedValue.Valid (S1002)
pkg/services/alerting/extractor.go:107:20: should omit comparison to bool constant, can be simplified to !enabled.MustBool() (S1002)
pkg/services/alerting/notifiers/telegram.go:222:41: should omit comparison to bool constant, can be simplified to this.UploadImage (S1002)
pkg/services/sqlstore/apikey.go:58:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/apikey.go:72:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/dashboard.go:66:33: should omit comparison to bool constant, can be simplified to !cmd.Overwrite (S1002)
pkg/services/sqlstore/dashboard.go:175:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/dashboard.go:311:13: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/dashboard.go:444:12: should omit comparison to bool constant, can be simplified to !exists (S1002)
pkg/services/sqlstore/dashboard.go:472:12: should omit comparison to bool constant, can be simplified to !exists (S1002)
pkg/services/sqlstore/dashboard.go:554:32: should omit comparison to bool constant, can be simplified to !cmd.Overwrite (S1002)
pkg/services/sqlstore/dashboard_snapshot.go:83:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/plugin_setting.go:39:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/quota.go:34:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/quota.go:111:6: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/quota.go:136:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/quota.go:213:6: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/temp_user.go:129:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/user.go:157:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/user.go:182:5: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/user.go:191:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/user.go:212:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/services/sqlstore/user.go:307:12: should omit comparison to bool constant, can be simplified to !has (S1002)
pkg/social/generic_oauth.go:185:5: should omit comparison to bool constant, can be simplified to !s.extractToken(&data, token) (S1002)
pkg/tsdb/mssql/mssql.go:148:39: should omit comparison to bool constant, can be simplified to ok (S1002)
pkg/tsdb/mssql/mssql.go:212:6: should omit comparison to bool constant, can be simplified to !query.Model.Get("fillNull").MustBool(false) (S1002)
pkg/tsdb/mssql/mssql.go:247:56: should omit comparison to bool constant, can be simplified to ok (S1002)
pkg/tsdb/mssql/mssql.go:274:7: should omit comparison to bool constant, can be simplified to !exist (S1002)
pkg/tsdb/mssql/mssql.go:282:8: should omit comparison to bool constant, can be simplified to !exist (S1002)
pkg/tsdb/mysql/mysql.go:221:6: should omit comparison to bool constant, can be simplified to !query.Model.Get("fillNull").MustBool(false) (S1002)
pkg/tsdb/mysql/mysql.go:256:56: should omit comparison to bool constant, can be simplified to ok (S1002)
pkg/tsdb/mysql/mysql.go:283:7: should omit comparison to bool constant, can be simplified to !exist (S1002)
pkg/tsdb/mysql/mysql.go:291:8: should omit comparison to bool constant, can be simplified to !exist (S1002)
pkg/tsdb/postgres/postgres.go:134:39: should omit comparison to bool constant, can be simplified to ok (S1002)
pkg/tsdb/postgres/postgres.go:201:6: should omit comparison to bool constant, can be simplified to !query.Model.Get("fillNull").MustBool(false) (S1002)
pkg/tsdb/postgres/postgres.go:236:56: should omit comparison to bool constant, can be simplified to ok (S1002)
pkg/tsdb/postgres/postgres.go:263:7: should omit comparison to bool constant, can be simplified to !exist (S1002)
pkg/tsdb/postgres/postgres.go:271:8: should omit comparison to bool constant, can be simplified to !exist (S1002)
2018-04-16 13:12:59 -05:00
if hasEnabled && ! enabled . MustBool ( ) {
2017-12-19 04:19:52 -06:00
continue
}
2016-06-11 03:13:33 -05:00
2017-12-19 04:19:52 -06:00
frequency , err := getTimeDurationStringToSeconds ( jsonAlert . Get ( "frequency" ) . MustString ( ) )
if err != nil {
2021-06-16 07:56:55 -05:00
return nil , addIdentifiersToValidationError ( ValidationError { Reason : err . Error ( ) } )
2017-12-19 04:19:52 -06:00
}
2018-11-05 06:14:02 -06:00
rawFor := jsonAlert . Get ( "for" ) . MustString ( )
2021-04-12 07:53:51 -05:00
forValue , err := getForValue ( rawFor )
if err != nil {
2021-06-16 07:56:55 -05:00
return nil , addIdentifiersToValidationError ( err )
2018-11-02 04:38:02 -05:00
}
2019-05-14 01:15:05 -05:00
alert := & models . Alert {
2022-02-28 02:54:56 -06:00
DashboardId : dashAlertInfo . Dash . Id ,
OrgId : dashAlertInfo . OrgID ,
2018-03-28 04:31:33 -05:00
PanelId : panelID ,
2017-12-19 04:19:52 -06:00
Id : jsonAlert . Get ( "id" ) . MustInt64 ( ) ,
Name : jsonAlert . Get ( "name" ) . MustString ( ) ,
Handler : jsonAlert . Get ( "handler" ) . MustInt64 ( ) ,
Message : jsonAlert . Get ( "message" ) . MustString ( ) ,
Frequency : frequency ,
2018-11-05 04:05:30 -06:00
For : forValue ,
2017-12-19 04:19:52 -06:00
}
for _ , condition := range jsonAlert . Get ( "conditions" ) . MustArray ( ) {
jsonCondition := simplejson . NewFromAny ( condition )
jsonQuery := jsonCondition . Get ( "query" )
2018-03-28 04:31:33 -05:00
queryRefID := jsonQuery . Get ( "params" ) . MustArray ( ) [ 0 ] . ( string )
panelQuery := findPanelQueryByRefID ( panel , queryRefID )
2017-09-18 03:31:45 -05:00
2017-12-19 04:19:52 -06:00
if panelQuery == nil {
2021-12-02 09:41:24 -06:00
var reason string
if UAEnabled ( ctx ) {
reason = fmt . Sprintf ( "Alert on PanelId: %v refers to query(%s) that cannot be found. Legacy alerting queries are not able to be removed at this time in order to preserve the ability to rollback to previous versions of Grafana" , alert . PanelId , queryRefID )
} else {
reason = fmt . Sprintf ( "Alert on PanelId: %v refers to query(%s) that cannot be found" , alert . PanelId , queryRefID )
}
2017-12-19 04:19:52 -06:00
return nil , ValidationError { Reason : reason }
2016-06-15 04:39:25 -05:00
}
2022-02-28 02:54:56 -06:00
datasource , err := e . lookupQueryDataSource ( ctx , panel , panelQuery , dashAlertInfo . OrgID )
2018-03-28 04:31:33 -05:00
if err != nil {
2021-10-29 12:57:24 -05:00
return nil , err
2016-06-11 03:13:33 -05:00
}
2022-06-27 11:23:15 -05:00
dsFilterQuery := datasources . DatasourcesPermissionFilterQuery {
2022-02-28 02:54:56 -06:00
User : dashAlertInfo . User ,
2022-06-27 11:23:15 -05:00
Datasources : [ ] * datasources . DataSource { datasource } ,
2018-11-05 07:25:19 -06:00
}
2022-02-28 02:54:56 -06:00
if err := e . datasourcePermissionsService . FilterDatasourcesBasedOnQueryPermissions ( ctx , & dsFilterQuery ) ; err != nil {
2022-03-02 04:04:29 -06:00
if ! errors . Is ( err , permissions . ErrNotImplemented ) {
return nil , err
}
} else if len ( dsFilterQuery . Result ) == 0 {
2022-06-27 11:23:15 -05:00
return nil , datasources . ErrDataSourceAccessDenied
2018-11-05 07:25:19 -06:00
}
2018-03-28 04:31:33 -05:00
jsonQuery . SetPath ( [ ] string { "datasourceId" } , datasource . Id )
2017-12-19 04:19:52 -06:00
if interval , err := panel . Get ( "interval" ) . String ( ) ; err == nil {
panelQuery . Set ( "interval" , interval )
}
2016-07-19 14:00:41 -05:00
2017-12-19 04:19:52 -06:00
jsonQuery . Set ( "model" , panelQuery . Interface ( ) )
}
2016-07-19 14:00:41 -05:00
2017-12-19 04:19:52 -06:00
alert . Settings = jsonAlert
2016-07-19 14:00:41 -05:00
2017-12-19 04:19:52 -06:00
// validate
2022-04-08 07:30:25 -05:00
_ , err = NewRuleFromDBAlert ( ctx , e . alertStore , alert , logTranslationFailures )
2018-03-07 09:20:05 -06:00
if err != nil {
return nil , err
}
2018-03-28 04:31:33 -05:00
if ! validateAlertFunc ( alert ) {
2018-10-13 00:53:28 -05:00
return nil , ValidationError { Reason : fmt . Sprintf ( "Panel id is not correct, alertName=%v, panelId=%v" , alert . Name , alert . PanelId ) }
2017-12-19 04:19:52 -06:00
}
2018-03-28 04:31:33 -05:00
alerts = append ( alerts , alert )
2017-12-19 04:19:52 -06:00
}
2016-07-19 14:00:41 -05:00
2017-12-19 04:19:52 -06:00
return alerts , nil
}
2016-07-19 14:00:41 -05:00
2019-05-14 01:15:05 -05:00
func validateAlertRule ( alert * models . Alert ) bool {
2018-03-28 04:31:33 -05:00
return alert . ValidToSave ( )
}
2019-05-20 05:13:32 -05:00
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data.
2022-02-28 02:54:56 -06:00
func ( e * DashAlertExtractorService ) GetAlerts ( ctx context . Context , dashAlertInfo DashAlertInfo ) ( [ ] * models . Alert , error ) {
return e . extractAlerts ( ctx , validateAlertRule , true , dashAlertInfo )
2018-03-28 04:31:33 -05:00
}
2016-10-13 02:43:05 -05:00
2022-02-28 02:54:56 -06:00
func ( e * DashAlertExtractorService ) extractAlerts ( ctx context . Context , validateFunc func ( alert * models . Alert ) bool , logTranslationFailures bool , dashAlertInfo DashAlertInfo ) ( [ ] * models . Alert , error ) {
dashboardJSON , err := copyJSON ( dashAlertInfo . Dash . Data )
2017-12-19 04:19:52 -06:00
if err != nil {
return nil , err
}
2016-06-11 03:13:33 -05:00
2019-05-14 01:15:05 -05:00
alerts := make ( [ ] * models . Alert , 0 )
2016-06-11 03:13:33 -05:00
2017-12-19 04:19:52 -06:00
// We extract alerts from rows to be backwards compatible
// with the old dashboard json model.
2018-03-28 04:31:33 -05:00
rows := dashboardJSON . Get ( "rows" ) . MustArray ( )
2017-12-19 04:19:52 -06:00
if len ( rows ) > 0 {
for _ , rowObj := range rows {
row := simplejson . NewFromAny ( rowObj )
2022-02-28 02:54:56 -06:00
a , err := e . getAlertFromPanels ( ctx , row , validateFunc , logTranslationFailures , dashAlertInfo )
2017-12-19 04:19:52 -06:00
if err != nil {
2016-07-21 06:09:12 -05:00
return nil , err
2016-06-11 03:13:33 -05:00
}
2017-12-19 04:19:52 -06:00
alerts = append ( alerts , a ... )
}
} else {
2022-02-28 02:54:56 -06:00
a , err := e . getAlertFromPanels ( ctx , dashboardJSON , validateFunc , logTranslationFailures , dashAlertInfo )
2017-12-19 04:19:52 -06:00
if err != nil {
return nil , err
2016-06-11 03:13:33 -05:00
}
2017-12-19 04:19:52 -06:00
alerts = append ( alerts , a ... )
2016-06-11 03:13:33 -05:00
}
2016-06-11 04:54:46 -05:00
e . log . Debug ( "Extracted alerts from dashboard" , "alertCount" , len ( alerts ) )
2016-06-11 03:54:24 -05:00
return alerts , nil
2016-06-11 03:13:33 -05:00
}
2018-03-28 04:31:33 -05:00
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
2019-05-20 05:13:32 -05:00
// in the first validation pass.
2022-02-28 02:54:56 -06:00
func ( e * DashAlertExtractorService ) ValidateAlerts ( ctx context . Context , dashAlertInfo DashAlertInfo ) error {
2021-11-12 07:35:38 -06:00
_ , err := e . extractAlerts ( ctx , func ( alert * models . Alert ) bool {
2021-03-17 10:06:10 -05:00
return alert . OrgId != 0 && alert . PanelId != 0
2022-02-28 02:54:56 -06:00
} , false , dashAlertInfo )
2018-03-28 04:31:33 -05:00
return err
}