| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | package datasourcecheck | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2025-02-25 18:41:44 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	"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-02-25 18:41:44 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/repo" | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-10 16:51:00 +08:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	CheckID             = "datasource" | 
					
						
							|  |  |  | 	HealthCheckStepID   = "health-check" | 
					
						
							|  |  |  | 	UIDValidationStepID = "uid-validation" | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 	MissingPluginStepID = "missing-plugin" | 
					
						
							| 
									
										
										
										
											2025-04-10 16:51:00 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | type check struct { | 
					
						
							|  |  |  | 	DatasourceSvc         datasources.DataSourceService | 
					
						
							|  |  |  | 	PluginStore           pluginstore.Store | 
					
						
							|  |  |  | 	PluginContextProvider pluginContextProvider | 
					
						
							|  |  |  | 	PluginClient          plugins.Client | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 	PluginRepo            repo.Service | 
					
						
							| 
									
										
										
										
											2025-02-25 18:41:44 +08:00
										 |  |  | 	log                   log.Logger | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 	pluginRepo repo.Service, | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | ) checks.Check { | 
					
						
							|  |  |  | 	return &check{ | 
					
						
							|  |  |  | 		DatasourceSvc:         datasourceSvc, | 
					
						
							|  |  |  | 		PluginStore:           pluginStore, | 
					
						
							|  |  |  | 		PluginContextProvider: pluginContextProvider, | 
					
						
							|  |  |  | 		PluginClient:          pluginClient, | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 		PluginRepo:            pluginRepo, | 
					
						
							| 
									
										
										
										
											2025-02-25 18:41:44 +08:00
										 |  |  | 		log:                   log.New("advisor.datasourcecheck"), | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-04-10 16:51:00 +08:00
										 |  |  | 	return CheckID | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-02-25 18:41:44 +08:00
										 |  |  | 			log:                   c.log, | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 		&missingPluginStep{ | 
					
						
							|  |  |  | 			PluginStore: c.PluginStore, | 
					
						
							|  |  |  | 			PluginRepo:  c.PluginRepo, | 
					
						
							|  |  |  | 			log:         c.log, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2025-04-10 16:51:00 +08:00
										 |  |  | 	return UIDValidationStepID | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *uidValidationStep) Title() string { | 
					
						
							|  |  |  | 	return "UID validation" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *uidValidationStep) Description() string { | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | 	return "Checks if the UID of a data source is valid." | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *uidValidationStep) Resolution() string { | 
					
						
							|  |  |  | 	return "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 or delete the data source and create a new one." | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 			s.ID(), | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | 			fmt.Sprintf("%s (%s)", ds.Name, ds.UID), | 
					
						
							|  |  |  | 			[]advisor.CheckErrorLink{}, | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 		), nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil, nil | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type healthCheckStep struct { | 
					
						
							|  |  |  | 	PluginContextProvider pluginContextProvider | 
					
						
							|  |  |  | 	PluginClient          plugins.Client | 
					
						
							| 
									
										
										
										
											2025-02-25 18:41:44 +08:00
										 |  |  | 	log                   log.Logger | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *healthCheckStep) Title() string { | 
					
						
							|  |  |  | 	return "Health check" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *healthCheckStep) Description() string { | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | 	return "Checks if a data sources is healthy." | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *healthCheckStep) Resolution() string { | 
					
						
							|  |  |  | 	return "Go to the data source configuration page and address the issues reported." | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *healthCheckStep) ID() string { | 
					
						
							| 
									
										
										
										
											2025-04-10 16:51:00 +08:00
										 |  |  | 	return HealthCheckStepID | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2025-04-02 17:21:37 +08:00
										 |  |  | 		if errors.Is(err, plugins.ErrPluginNotRegistered) { | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | 			// The plugin is not installed, handle this in the missing plugin step
 | 
					
						
							|  |  |  | 			return nil, nil | 
					
						
							| 
									
										
										
										
											2025-04-02 17:21:37 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | 		// Unable to check health check
 | 
					
						
							| 
									
										
										
										
											2025-02-25 18:41:44 +08:00
										 |  |  | 		s.log.Error("Failed to get plugin context", "datasource_uid", ds.UID, "error", err) | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | 		return nil, nil | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	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-25 18:41:44 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			s.log.Debug("Failed to check health", "datasource_uid", ds.UID, "error", err) | 
					
						
							|  |  |  | 			if errors.Is(err, plugins.ErrMethodNotImplemented) || errors.Is(err, plugins.ErrPluginUnavailable) { | 
					
						
							|  |  |  | 				// The plugin does not support backend health checks
 | 
					
						
							|  |  |  | 				return nil, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			s.log.Debug("Failed to check health", "datasource_uid", ds.UID, "status", resp.Status, "message", resp.Message) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-02-07 22:48:18 +08:00
										 |  |  | 		return checks.NewCheckReportFailure( | 
					
						
							|  |  |  | 			advisor.CheckReportFailureSeverityHigh, | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 			s.ID(), | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | 			ds.Name, | 
					
						
							|  |  |  | 			[]advisor.CheckErrorLink{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					Message: "Fix me", | 
					
						
							|  |  |  | 					Url:     fmt.Sprintf("/connections/datasources/edit/%s", ds.UID), | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 		), 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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-11 18:11:05 +08:00
										 |  |  | type missingPluginStep struct { | 
					
						
							|  |  |  | 	PluginStore pluginstore.Store | 
					
						
							|  |  |  | 	PluginRepo  repo.Service | 
					
						
							|  |  |  | 	log         log.Logger | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *missingPluginStep) Title() string { | 
					
						
							|  |  |  | 	return "Missing plugin check" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *missingPluginStep) Description() string { | 
					
						
							|  |  |  | 	return "Checks if the plugin associated with the data source is installed." | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *missingPluginStep) Resolution() string { | 
					
						
							|  |  |  | 	return "Delete the datasource or install the plugin." | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *missingPluginStep) ID() string { | 
					
						
							|  |  |  | 	return MissingPluginStepID | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *missingPluginStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any) (*advisor.CheckReportFailure, error) { | 
					
						
							|  |  |  | 	ds, ok := i.(*datasources.DataSource) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("invalid item type %T", i) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, exists := s.PluginStore.Plugin(ctx, ds.Type) | 
					
						
							|  |  |  | 	if !exists { | 
					
						
							|  |  |  | 		links := []advisor.CheckErrorLink{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Message: "Delete data source", | 
					
						
							|  |  |  | 				Url:     fmt.Sprintf("/connections/datasources/edit/%s", ds.UID), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		_, err := s.PluginRepo.PluginInfo(ctx, ds.Type) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			// Plugin is available in the repo
 | 
					
						
							|  |  |  | 			links = append(links, advisor.CheckErrorLink{ | 
					
						
							|  |  |  | 				Message: "Install plugin", | 
					
						
							|  |  |  | 				Url:     fmt.Sprintf("/plugins/%s", ds.Type), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// The plugin is not installed
 | 
					
						
							|  |  |  | 		return checks.NewCheckReportFailure( | 
					
						
							|  |  |  | 			advisor.CheckReportFailureSeverityHigh, | 
					
						
							|  |  |  | 			s.ID(), | 
					
						
							|  |  |  | 			ds.Name, | 
					
						
							|  |  |  | 			links, | 
					
						
							|  |  |  | 		), nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | } |