diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index fe45d5e7365..6e7b158b56c 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -32,7 +32,6 @@ type check struct { PluginClient plugins.Client PluginRepo repo.Service GrafanaVersion string - pluginIndex map[string]repo.PluginInfo } func New( @@ -81,15 +80,6 @@ func (c *check) ID() string { } func (c *check) Init(ctx context.Context) error { - compatOpts := repo.NewCompatOpts(c.GrafanaVersion, sysruntime.GOOS, sysruntime.GOARCH) - plugins, err := c.PluginRepo.GetPluginsInfo(ctx, compatOpts) - if err != nil { - return err - } - c.pluginIndex = make(map[string]repo.PluginInfo) - for _, p := range plugins { - c.pluginIndex[p.Slug] = p - } return nil } @@ -102,8 +92,8 @@ func (c *check) Steps() []checks.Step { }, &missingPluginStep{ PluginStore: c.PluginStore, + PluginRepo: c.PluginRepo, GrafanaVersion: c.GrafanaVersion, - pluginIndex: c.pluginIndex, }, } } @@ -221,8 +211,8 @@ func (s *healthCheckStep) Run(ctx context.Context, log logging.Logger, obj *advi type missingPluginStep struct { PluginStore pluginstore.Store + PluginRepo repo.Service GrafanaVersion string - pluginIndex map[string]repo.PluginInfo } func (s *missingPluginStep) Title() string { @@ -255,8 +245,14 @@ func (s *missingPluginStep) Run(ctx context.Context, log logging.Logger, obj *ad Url: fmt.Sprintf("/connections/datasources/edit/%s", ds.UID), }, } - _, ok := s.pluginIndex[ds.Type] - if ok { + plugins, err := s.PluginRepo.GetPluginsInfo(ctx, repo.GetPluginsInfoOptions{ + IncludeDeprecated: true, + Plugins: []string{ds.Type}, + }, repo.NewCompatOpts(s.GrafanaVersion, sysruntime.GOOS, sysruntime.GOARCH)) + if err != nil { + return nil, err + } + if len(plugins) > 0 { // Plugin is available in the repo links = append(links, advisor.CheckErrorLink{ Message: "Install plugin", diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go b/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go index f7f834a7b1b..74f78168853 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go @@ -276,6 +276,6 @@ type MockPluginRepo struct { plugins []repo.PluginInfo } -func (m *MockPluginRepo) GetPluginsInfo(context.Context, repo.CompatOpts) ([]repo.PluginInfo, error) { +func (m *MockPluginRepo) GetPluginsInfo(context.Context, repo.GetPluginsInfoOptions, repo.CompatOpts) ([]repo.PluginInfo, error) { return m.plugins, nil } diff --git a/apps/advisor/pkg/app/checks/plugincheck/check.go b/apps/advisor/pkg/app/checks/plugincheck/check.go index 68dc5a8eaf3..0bbf36fb4f5 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check.go @@ -64,7 +64,15 @@ func (c *check) Item(ctx context.Context, id string) (any, error) { func (c *check) Init(ctx context.Context) error { compatOpts := repo.NewCompatOpts(c.GrafanaVersion, sysruntime.GOOS, sysruntime.GOARCH) - plugins, err := c.PluginRepo.GetPluginsInfo(ctx, compatOpts) + ps := c.PluginStore.Plugins(ctx) + pluginIDs := make([]string, len(ps)) + for i, p := range ps { + pluginIDs[i] = p.ID + } + plugins, err := c.PluginRepo.GetPluginsInfo(ctx, repo.GetPluginsInfoOptions{ + IncludeDeprecated: true, + Plugins: pluginIDs, + }, compatOpts) if err != nil { return err } diff --git a/apps/advisor/pkg/app/checks/plugincheck/check_test.go b/apps/advisor/pkg/app/checks/plugincheck/check_test.go index 3bd0a97b62e..387708f8f7d 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check_test.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check_test.go @@ -168,7 +168,7 @@ type mockPluginRepo struct { pluginInfo []repo.PluginInfo } -func (m *mockPluginRepo) GetPluginsInfo(ctx context.Context, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error) { +func (m *mockPluginRepo) GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error) { return m.pluginInfo, nil } diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index eb9ecae8cfc..0c192b12817 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -276,7 +276,7 @@ func (r *FakePluginRepo) PluginInfo(ctx context.Context, pluginID string, compat return &repo.PluginInfo{}, nil } -func (r *FakePluginRepo) GetPluginsInfo(ctx context.Context, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error) { +func (r *FakePluginRepo) GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error) { return []repo.PluginInfo{}, nil } diff --git a/pkg/plugins/repo/ifaces.go b/pkg/plugins/repo/ifaces.go index 3ca6f6b100b..ad6a90fb208 100644 --- a/pkg/plugins/repo/ifaces.go +++ b/pkg/plugins/repo/ifaces.go @@ -17,7 +17,7 @@ type Service interface { // PluginVersion will return plugin version based on the requested information. PluginVersion(ctx context.Context, pluginID, version string, compatOpts CompatOpts) (VersionData, error) // GetPluginsInfo will return a list of plugins from grafana.com/api/plugins. - GetPluginsInfo(ctx context.Context, compatOpts CompatOpts) ([]PluginInfo, error) + GetPluginsInfo(ctx context.Context, options GetPluginsInfoOptions, compatOpts CompatOpts) ([]PluginInfo, error) } type CompatOpts struct { diff --git a/pkg/plugins/repo/service.go b/pkg/plugins/repo/service.go index 2bdd1d77c09..535439db184 100644 --- a/pkg/plugins/repo/service.go +++ b/pkg/plugins/repo/service.go @@ -13,6 +13,10 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" ) +const ( + getPluginsInfoMaxPlugins = 50 +) + type Manager struct { client *Client @@ -132,25 +136,55 @@ func (m *Manager) grafanaCompatiblePluginVersions(ctx context.Context, pluginID return v.Versions, nil } -func (m *Manager) GetPluginsInfo(ctx context.Context, compatOpts CompatOpts) ([]PluginInfo, error) { +type GetPluginsInfoOptions struct { + IncludeDeprecated bool + Plugins []string +} + +func (m *Manager) GetPluginsInfo(ctx context.Context, options GetPluginsInfoOptions, compatOpts CompatOpts) ([]PluginInfo, error) { u, err := url.Parse(m.client.grafanaComAPIURL) if err != nil { return nil, err } - body, err := m.client.SendReq(ctx, u, compatOpts) - if err != nil { - return nil, err + // If we are requesting more than 50 plugins, split the request into multiple requests to avoid + // the URL limit (local testing shows that the URL is <2000 characters for 100 plugins, so 50 is + // a safe limit). + plugins := [][]string{} + results := []PluginInfo{} + if len(options.Plugins) > getPluginsInfoMaxPlugins { + for i := 0; i < len(options.Plugins); i += getPluginsInfoMaxPlugins { + plugins = append(plugins, options.Plugins[i:min(i+getPluginsInfoMaxPlugins, len(options.Plugins))]) + } + } else { + plugins = [][]string{options.Plugins} + } + for _, p := range plugins { + q := u.Query() + if options.IncludeDeprecated { + q.Set("includeDeprecated", "true") + } + if len(p) > 0 { + q.Set("slugIn", strings.Join(p, ",")) + } + u.RawQuery = q.Encode() + + body, err := m.client.SendReq(ctx, u, compatOpts) + if err != nil { + return nil, err + } + + var v struct { + Items []PluginInfo `json:"items"` + } + err = json.Unmarshal(body, &v) + if err != nil { + m.log.Error("Failed to unmarshal plugin repo response", "error", err) + return nil, err + } + + results = append(results, v.Items...) } - var v struct { - Items []PluginInfo `json:"items"` - } - err = json.Unmarshal(body, &v) - if err != nil { - m.log.Error("Failed to unmarshal plugin repo response", "error", err) - return nil, err - } - - return v.Items, nil + return results, nil } diff --git a/pkg/plugins/repo/service_test.go b/pkg/plugins/repo/service_test.go index 4dae0b1ac0e..e4a35ed68ac 100644 --- a/pkg/plugins/repo/service_test.go +++ b/pkg/plugins/repo/service_test.go @@ -116,6 +116,7 @@ func TestPluginIndex(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "12.0.0", r.Header.Get("grafana-version")) require.Equal(t, "grafana 12.0.0", r.Header.Get("User-Agent")) + require.Equal(t, "includeDeprecated=true&slugIn=grafana-test-datasource", r.URL.RawQuery) w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") _, _ = fmt.Fprintf(w, `{"items": [{ "id": 1, "slug": "%s", "status": "active" }]}`, pluginID) @@ -127,7 +128,10 @@ func TestPluginIndex(t *testing.T) { BaseURL: srv.URL, Logger: log.NewTestPrettyLogger(), }) - pi, err := m.GetPluginsInfo(context.Background(), CompatOpts{ + pi, err := m.GetPluginsInfo(context.Background(), GetPluginsInfoOptions{ + IncludeDeprecated: true, + Plugins: []string{pluginID}, + }, CompatOpts{ grafanaVersion: "12.0.0", system: SystemCompatOpts{ os: "darwin",