2025-01-23 23:19:50 +08:00
package datasourcecheck
import (
"context"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
2025-02-05 21:59:40 +08:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2025-01-23 23:19:50 +08:00
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/util"
)
2025-02-06 16:55:17 +08:00
type check struct {
DatasourceSvc datasources . DataSourceService
PluginStore pluginstore . Store
PluginContextProvider pluginContextProvider
PluginClient plugins . Client
}
2025-01-23 23:19:50 +08:00
func New (
datasourceSvc datasources . DataSourceService ,
pluginStore pluginstore . Store ,
2025-02-05 21:59:40 +08:00
pluginContextProvider pluginContextProvider ,
2025-01-23 23:19:50 +08:00
pluginClient plugins . Client ,
) checks . Check {
return & check {
DatasourceSvc : datasourceSvc ,
PluginStore : pluginStore ,
PluginContextProvider : pluginContextProvider ,
PluginClient : pluginClient ,
}
}
2025-02-06 16:55:17 +08:00
func ( c * check ) Items ( ctx context . Context ) ( [ ] any , error ) {
dss , err := c . DatasourceSvc . GetAllDataSources ( ctx , & datasources . GetAllDataSourcesQuery { } )
if err != nil {
return nil , err
}
res := make ( [ ] any , len ( dss ) )
for i , ds := range dss {
res [ i ] = ds
}
return res , nil
2025-01-23 23:19:50 +08:00
}
2025-02-06 16:55:17 +08:00
func ( c * check ) ID ( ) string {
2025-01-23 23:19:50 +08:00
return "datasource"
}
2025-02-06 16:55:17 +08:00
func ( c * check ) Steps ( ) [ ] checks . Step {
return [ ] checks . Step {
& uidValidationStep { } ,
& healthCheckStep {
PluginContextProvider : c . PluginContextProvider ,
PluginClient : c . PluginClient ,
} ,
2025-01-23 23:19:50 +08:00
}
2025-02-06 16:55:17 +08:00
}
type uidValidationStep struct { }
2025-01-23 23:19:50 +08:00
2025-02-06 16:55:17 +08:00
func ( s * uidValidationStep ) ID ( ) string {
return "uid-validation"
}
func ( s * uidValidationStep ) Title ( ) string {
return "UID validation"
}
func ( s * uidValidationStep ) Description ( ) string {
return "Check if the UID of each data source is valid."
}
2025-02-07 22:48:18 +08:00
func ( s * uidValidationStep ) Run ( ctx context . Context , obj * advisor . CheckSpec , i any ) ( * advisor . CheckReportFailure , error ) {
2025-02-07 17:57:26 +08:00
ds , ok := i . ( * datasources . DataSource )
if ! ok {
return nil , fmt . Errorf ( "invalid item type %T" , i )
2025-02-06 16:55:17 +08:00
}
2025-02-07 17:57:26 +08:00
// Data source UID validation
err := util . ValidateUID ( ds . UID )
if err != nil {
2025-02-07 22:48:18 +08:00
return checks . NewCheckReportFailure (
advisor . CheckReportFailureSeverityLow ,
2025-02-07 17:57:26 +08:00
fmt . Sprintf ( "Invalid UID '%s' for data source %s" , ds . UID , ds . Name ) ,
"Check the <a href='https://grafana.com/docs/grafana/latest/upgrade-guide/upgrade-v11.2/#grafana-data-source-uid-format-enforcement' target=_blank>documentation</a> for more information." ,
s . ID ( ) ,
ds . UID ,
) , nil
}
return nil , nil
2025-02-06 16:55:17 +08:00
}
type healthCheckStep struct {
PluginContextProvider pluginContextProvider
PluginClient plugins . Client
}
func ( s * healthCheckStep ) Title ( ) string {
return "Health check"
}
func ( s * healthCheckStep ) Description ( ) string {
return "Check if all data sources are healthy."
}
func ( s * healthCheckStep ) ID ( ) string {
return "health-check"
}
2025-02-07 22:48:18 +08:00
func ( s * healthCheckStep ) Run ( ctx context . Context , obj * advisor . CheckSpec , i any ) ( * advisor . CheckReportFailure , error ) {
2025-02-07 17:57:26 +08:00
ds , ok := i . ( * datasources . DataSource )
if ! ok {
return nil , fmt . Errorf ( "invalid item type %T" , i )
}
// Health check execution
requester , err := identity . GetRequester ( ctx )
if err != nil {
return nil , err
}
pCtx , err := s . PluginContextProvider . GetWithDataSource ( ctx , ds . Type , requester , ds )
if err != nil {
return nil , fmt . Errorf ( "failed to get plugin context: %w" , err )
}
req := & backend . CheckHealthRequest {
PluginContext : pCtx ,
Headers : map [ string ] string { } ,
}
resp , err := s . PluginClient . CheckHealth ( ctx , req )
if err != nil || resp . Status != backend . HealthStatusOk {
2025-02-07 22:48:18 +08:00
return checks . NewCheckReportFailure (
advisor . CheckReportFailureSeverityHigh ,
2025-02-07 17:57:26 +08:00
fmt . Sprintf ( "Health check failed for %s" , ds . Name ) ,
fmt . Sprintf (
"Go to the <a href='/connections/datasources/edit/%s'>data source configuration</a>" +
" and address the issues reported." , ds . UID ) ,
s . ID ( ) ,
ds . UID ,
) , nil
2025-01-23 23:19:50 +08:00
}
2025-02-07 17:57:26 +08:00
return nil , nil
2025-01-23 23:19:50 +08:00
}
2025-02-05 21:59:40 +08:00
type pluginContextProvider interface {
GetWithDataSource ( ctx context . Context , pluginID string , user identity . Requester , ds * datasources . DataSource ) ( backend . PluginContext , error )
}