From 2e507d50422b00a2a73e4446445cdec5a0fee6b6 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Wed, 5 Nov 2025 10:33:45 +0100 Subject: [PATCH] Advisor: Add mock checks to standalone setup (#113406) --- apps/advisor/Makefile | 17 +++ apps/advisor/README.md | 14 +++ .../checkregistry/mockchecks/checkregistry.go | 51 +++++++- .../mockchecks/mocksvcs/datasourcesvc.go | 44 +++++++ .../mockchecks/mocksvcs/pluginclient.go | 19 +++ .../mocksvcs/plugincontextprovider.go | 53 ++++++++ .../mocksvcs/pluginerrorresolver.go | 19 +++ .../mockchecks/mocksvcs/pluginrepo.go | 26 ++++ .../mockchecks/mocksvcs/pluginstore.go | 114 ++++++++++++++++++ .../mockchecks/mocksvcs/updatechecker.go | 18 +++ .../pkg/app/checks/datasourcecheck/check.go | 6 +- .../datasourcecheck/health_check_step.go | 2 +- apps/advisor/pkg/standalone/server.go | 2 +- 13 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/datasourcesvc.go create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginclient.go create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/plugincontextprovider.go create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginerrorresolver.go create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginrepo.go create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginstore.go create mode 100644 apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/updatechecker.go diff --git a/apps/advisor/Makefile b/apps/advisor/Makefile index 010b283b65a..7ea492797fe 100644 --- a/apps/advisor/Makefile +++ b/apps/advisor/Makefile @@ -15,3 +15,20 @@ generate: install-app-sdk update-app-sdk .PHONY: run run: @go run ./pkg/standalone/server.go --etcd-servers=http://127.0.0.1:22379 --secure-port 7445 + +.PHONY: create-checks +create-checks: + @echo "Creating plugin check..." + @curl -k -X POST https://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks \ + -H "Content-Type: application/json" \ + -d '{"kind":"Check","apiVersion":"advisor.grafana.app/v0alpha1","spec":{"data":{}},"metadata":{"generateName":"check-","labels":{"advisor.grafana.app/type":"plugin"},"namespace":"stacks-1"},"status":{"report":{"count":0,"failures":[]}}}' \ + && echo "Plugin check created successfully" + @echo "Creating datasource check..." + @curl -k -X POST https://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks \ + -H "Content-Type: application/json" \ + -d '{"kind":"Check","apiVersion":"advisor.grafana.app/v0alpha1","spec":{"data":{}},"metadata":{"generateName":"check-","labels":{"advisor.grafana.app/type":"datasource"},"namespace":"stacks-1"},"status":{"report":{"count":0,"failures":[]}}}' \ + && echo "Datasource check created successfully" + +delete-checks: + @curl -k -X DELETE https://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks \ + && echo "All checks deleted successfully" diff --git a/apps/advisor/README.md b/apps/advisor/README.md index aa48ef8e4e4..410a54f89e3 100644 --- a/apps/advisor/README.md +++ b/apps/advisor/README.md @@ -163,3 +163,17 @@ make run # Start the advisor app in standalone mode ``` This will start the advisor app on port 7445. You can then access the advisor app at `http://localhost:7445`. + +To see some sample checks, you can run the following command: + +```bash +make create-checks +``` + +Then you can see list in the URL: `http://localhost:7445/apis/advisor.grafana.app/v0alpha1/namespaces/stacks-1/checks` + +To delete all checks, you can run the following command: + +```bash +make delete-checks +``` diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go b/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go index b751b606cd8..e1b9cf88bf4 100644 --- a/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/checkregistry.go @@ -1,12 +1,59 @@ package mockchecks -import "github.com/grafana/grafana/apps/advisor/pkg/app/checks" +import ( + "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks/datasourcecheck" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks/plugincheck" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/plugins/repo" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" +) // mockchecks.CheckRegistry is a mock implementation of the checkregistry.CheckService interface // TODO: Add mocked checks here type CheckRegistry struct { + datasourceSvc datasources.DataSourceService + pluginStore pluginstore.Store + pluginClient plugins.Client + pluginRepo repo.Service + GrafanaVersion string + pluginContextProvider datasourcecheck.PluginContextProvider + updateChecker pluginchecker.PluginUpdateChecker + pluginErrorResolver plugins.ErrorResolver } func (m *CheckRegistry) Checks() []checks.Check { - return []checks.Check{} + return []checks.Check{ + datasourcecheck.New( + m.datasourceSvc, + m.pluginStore, + m.pluginContextProvider, + m.pluginClient, + m.pluginRepo, + m.GrafanaVersion, + ), + plugincheck.New( + m.pluginStore, + m.pluginRepo, + m.updateChecker, + m.pluginErrorResolver, + m.GrafanaVersion, + ), + } +} + +func New() *CheckRegistry { + return &CheckRegistry{ + datasourceSvc: &mocksvcs.DatasourceSvc{}, + pluginStore: &mocksvcs.PluginStore{}, + pluginClient: &mocksvcs.PluginClient{}, + pluginRepo: &mocksvcs.PluginRepo{}, + pluginContextProvider: &mocksvcs.PluginContextProvider{}, + updateChecker: &mocksvcs.UpdateChecker{}, + pluginErrorResolver: &mocksvcs.PluginErrorResolver{}, + GrafanaVersion: "1.0.0", + } } diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/datasourcesvc.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/datasourcesvc.go new file mode 100644 index 00000000000..73122e53adb --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/datasourcesvc.go @@ -0,0 +1,44 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/datasources" +) + +var dss = map[string]*datasources.DataSource{ + "prometheus-uid": { + ID: 1, + UID: "prometheus-uid", + Name: "Prometheus", + Type: "prometheus", + }, + "mysql-uid": { + ID: 2, + UID: "mysql-uid", + Name: "MySQL", + Type: "mysql", + }, + "unknown-uid": { + ID: 3, + UID: "unknown-uid", + Name: "Unknown", + Type: "unknown", + }, +} + +type DatasourceSvc struct { + datasources.DataSourceService +} + +func (m *DatasourceSvc) GetDataSources(ctx context.Context, query *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) { + sources := make([]*datasources.DataSource, 0, len(dss)) + for _, ds := range dss { + sources = append(sources, ds) + } + return sources, nil +} + +func (m *DatasourceSvc) GetDataSource(ctx context.Context, query *datasources.GetDataSourceQuery) (*datasources.DataSource, error) { + return dss[query.UID], nil +} diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginclient.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginclient.go new file mode 100644 index 00000000000..a8ce175a687 --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginclient.go @@ -0,0 +1,19 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/plugins" +) + +type PluginClient struct { + plugins.Client +} + +func (m *PluginClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + return &backend.CheckHealthResult{ + Status: backend.HealthStatusOk, + Message: "Plugin is healthy", + }, nil +} diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/plugincontextprovider.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/plugincontextprovider.go new file mode 100644 index 00000000000..b5bb6f88e8e --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/plugincontextprovider.go @@ -0,0 +1,53 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/datasources" +) + +type PluginContextProvider struct { +} + +// ACTUALLY USED by datasourcecheck +func (m *PluginContextProvider) GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) { + // Create a plugin context with sample data based on the datasource + pluginContext := backend.PluginContext{ + PluginID: pluginID, + PluginVersion: "1.0.0", + OrgID: 1, + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + ID: ds.ID, + UID: ds.UID, + Name: ds.Name, + URL: ds.URL, + JSONData: []byte(`{ + "httpMethod": "GET", + "timeout": "30s", + "keepCookies": [] + }`), + DecryptedSecureJSONData: map[string]string{ + "password": "sample-password", + "apiKey": "sample-api-key", + }, + }, + GrafanaConfig: backend.NewGrafanaCfg(map[string]string{ + "app_url": "http://localhost:3000", + "default_timezone": "UTC", + }), + } + + // Add user context if provided + if user != nil && !user.IsNil() { + pluginContext.User = &backend.User{ + Login: user.GetLogin(), + Name: user.GetName(), + Email: user.GetEmail(), + Role: string(user.GetOrgRole()), + } + } + + return pluginContext, nil +} diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginerrorresolver.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginerrorresolver.go new file mode 100644 index 00000000000..db545827991 --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginerrorresolver.go @@ -0,0 +1,19 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana/pkg/plugins" +) + +type PluginErrorResolver struct { +} + +// Assume no plugin with errors +func (m *PluginErrorResolver) PluginErrors(ctx context.Context) []*plugins.Error { + return nil +} + +func (m *PluginErrorResolver) PluginError(ctx context.Context, pluginID string) *plugins.Error { + return nil +} diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginrepo.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginrepo.go new file mode 100644 index 00000000000..0ab8d225314 --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginrepo.go @@ -0,0 +1,26 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana/pkg/plugins/repo" +) + +type PluginRepo struct { + repo.Service +} + +func (m *PluginRepo) GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error) { + return []repo.PluginInfo{ + { + ID: 1, + Slug: "grafana-piechart-panel", + Version: "1.6.0", + }, + { + ID: 2, + Slug: "prometheus", + Version: "10.0.0", + }, + }, nil +} diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginstore.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginstore.go new file mode 100644 index 00000000000..782d93024f3 --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/pluginstore.go @@ -0,0 +1,114 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" +) + +type PluginStore struct { +} + +var ps = map[string]pluginstore.Plugin{ + "prometheus": { + JSONData: plugins.JSONData{ + ID: "prometheus", + Type: plugins.TypeDataSource, + Name: "Prometheus", + Info: plugins.Info{ + Author: plugins.InfoLink{ + Name: "Grafana Labs", + }, + Version: "10.0.0", + }, + Category: "Time series databases", + State: plugins.ReleaseStateAlpha, + Backend: true, + Metrics: true, + Logs: true, + Alerting: true, + Explore: true, + }, + Class: plugins.ClassCore, + Signature: plugins.SignatureStatusInternal, + SignatureType: plugins.SignatureTypeGrafana, + SignatureOrg: "grafana.com", + }, + "test-datasource": { + JSONData: plugins.JSONData{ + ID: "grafana-piechart-panel", + Type: plugins.TypePanel, + Name: "Pie Chart", + Info: plugins.Info{ + Author: plugins.InfoLink{ + Name: "Grafana Labs", + }, + Version: "1.6.0", + }, + Category: "Visualization", + State: plugins.ReleaseStateAlpha, + }, + Class: plugins.ClassCore, + Signature: plugins.SignatureStatusInternal, + SignatureType: plugins.SignatureTypeGrafana, + SignatureOrg: "grafana.com", + }, + "grafana-piechart-panel": { + JSONData: plugins.JSONData{ + ID: "prometheus", + Type: plugins.TypeDataSource, + Name: "Prometheus", + Info: plugins.Info{ + Author: plugins.InfoLink{ + Name: "Grafana Labs", + }, + Version: "10.0.0", + }, + Category: "Time series databases", + State: plugins.ReleaseStateAlpha, + Backend: true, + Metrics: true, + Logs: true, + Alerting: true, + Explore: true, + }, + Class: plugins.ClassCore, + Signature: plugins.SignatureStatusInternal, + SignatureType: plugins.SignatureTypeGrafana, + SignatureOrg: "grafana.com", + }, + "test-app": { + JSONData: plugins.JSONData{ + ID: "test-app", + Type: plugins.TypeApp, + Name: "Test App", + Info: plugins.Info{ + Author: plugins.InfoLink{ + Name: "Test Author", + }, + Version: "2.0.0", + }, + Category: "Application", + State: plugins.ReleaseStateAlpha, + AutoEnabled: true, + }, + Class: plugins.ClassExternal, + Signature: plugins.SignatureStatusValid, + SignatureType: plugins.SignatureTypeCommercial, + SignatureOrg: "test.com", + }, +} + +func (s *PluginStore) Plugin(ctx context.Context, pluginID string) (pluginstore.Plugin, bool) { + p, ok := ps[pluginID] + return p, ok +} + +func (s *PluginStore) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []pluginstore.Plugin { + plugins := make([]pluginstore.Plugin, 0, len(ps)) + for _, p := range ps { + plugins = append(plugins, p) + } + return plugins +} diff --git a/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/updatechecker.go b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/updatechecker.go new file mode 100644 index 00000000000..efbeb217e74 --- /dev/null +++ b/apps/advisor/pkg/app/checkregistry/mockchecks/mocksvcs/updatechecker.go @@ -0,0 +1,18 @@ +package mocksvcs + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" +) + +type UpdateChecker struct { +} + +func (m *UpdateChecker) IsUpdatable(ctx context.Context, plugin pluginstore.Plugin) bool { + return true +} + +func (m *UpdateChecker) CanUpdate(pluginId string, currentVersion string, targetVersion string, onlyMinor bool) bool { + return true +} diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index 524dae04268..cae29e181fd 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -26,7 +26,7 @@ const ( type check struct { DatasourceSvc datasources.DataSourceService PluginStore pluginstore.Store - PluginContextProvider pluginContextProvider + PluginContextProvider PluginContextProvider PluginClient plugins.Client PluginRepo repo.Service GrafanaVersion string @@ -37,7 +37,7 @@ type check struct { func New( datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store, - pluginContextProvider pluginContextProvider, + pluginContextProvider PluginContextProvider, pluginClient plugins.Client, pluginRepo repo.Service, grafanaVersion string, @@ -168,6 +168,6 @@ func (c *check) canBeInstalled(ctx context.Context, pluginType string) (bool, er return isAvailableInRepo, nil } -type pluginContextProvider interface { +type PluginContextProvider interface { GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) } diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go b/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go index c020d0ba82a..7e88e872e05 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/health_check_step.go @@ -15,7 +15,7 @@ import ( ) type healthCheckStep struct { - PluginContextProvider pluginContextProvider + PluginContextProvider PluginContextProvider PluginClient plugins.Client } diff --git a/apps/advisor/pkg/standalone/server.go b/apps/advisor/pkg/standalone/server.go index 6e2ad32cc8b..80dd82418ef 100644 --- a/apps/advisor/pkg/standalone/server.go +++ b/apps/advisor/pkg/standalone/server.go @@ -29,7 +29,7 @@ func main() { KubeConfig: rest.Config{}, // this will be replaced by the apiserver loopback config ManifestData: *apis.LocalManifest().ManifestData, SpecificConfig: checkregistry.AdvisorAppConfig{ - CheckRegistry: &mockchecks.CheckRegistry{}, + CheckRegistry: mockchecks.New(), PluginConfig: map[string]string{}, StackID: "1", // Numeric stack ID for standalone mode OrgService: nil, // Not needed when StackID is set