From 69f788ad55fd61ede3e6e360350fad74444e594a Mon Sep 17 00:00:00 2001 From: Will Browne Date: Fri, 10 Oct 2025 10:35:22 +0100 Subject: [PATCH] Plugins: Remove `pluginAssetProvider` feature toggle (#112046) * remove toggle * fix lint issue --- .../src/types/featureToggles.gen.ts | 5 - pkg/plugins/config/config.go | 1 - pkg/plugins/manager/fakes/fakes.go | 35 ++ .../manager/loader/assetpath/assetpath.go | 137 ------ .../loader/assetpath/assetpath_test.go | 430 ------------------ .../manager/pipeline/bootstrap/bootstrap.go | 4 +- .../manager/pipeline/bootstrap/factory.go | 54 ++- .../manager/pipeline/bootstrap/steps.go | 10 +- pkg/plugins/pluginassets/pluginassets.go | 2 +- pkg/server/wire_gen.go | 15 +- pkg/services/featuremgmt/registry.go | 10 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 - pkg/services/featuremgmt/toggles_gen.json | 3 +- .../pluginsintegration/loader/fakes.go | 42 -- .../pluginsintegration/loader/loader_test.go | 113 +---- .../pluginsintegration/pipeline/pipeline.go | 6 +- .../pluginsintegration/pluginconfig/config.go | 1 - .../pluginsintegration/pluginsintegration.go | 4 +- .../pluginsintegration/test_helper.go | 7 +- 20 files changed, 108 insertions(+), 776 deletions(-) delete mode 100644 pkg/plugins/manager/loader/assetpath/assetpath.go delete mode 100644 pkg/plugins/manager/loader/assetpath/assetpath_test.go delete mode 100644 pkg/services/pluginsintegration/loader/fakes.go diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 632a507db07..79257c3d6da 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1113,11 +1113,6 @@ export interface FeatureToggles { */ alertingNotificationHistory?: boolean; /** - * Allows decoupled core plugins to load from the Grafana CDN - * @default false - */ - pluginAssetProvider?: boolean; - /** * Enable dual reader for unified storage search */ unifiedStorageSearchDualReaderEnabled?: boolean; diff --git a/pkg/plugins/config/config.go b/pkg/plugins/config/config.go index 96ebd61b08c..e4edf65dd3d 100644 --- a/pkg/plugins/config/config.go +++ b/pkg/plugins/config/config.go @@ -34,7 +34,6 @@ type Features struct { // Needed only until Tempo Alerting / metrics TraceQL is stable // https://github.com/grafana/grafana/issues/106888 TempoAlertingEnabled bool - PluginAssetProvider bool } // NewPluginManagementCfg returns a new PluginManagementCfg. diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index 531acef3548..fffd3fb98b1 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -698,3 +698,38 @@ func (p *AssetProvider) AssetPath(plugin pluginassets.PluginInfo, assetPath ...s } return "", nil } + +type FakeErrorTracker struct { + RecordFunc func(ctx context.Context, err *plugins.Error) + ClearFunc func(ctx context.Context, pluginID string) + ErrorsFunc func(ctx context.Context) []*plugins.Error +} + +func NewFakeErrorTracker() *FakeErrorTracker { + return &FakeErrorTracker{} +} + +func (t *FakeErrorTracker) Record(ctx context.Context, err *plugins.Error) { + if t.RecordFunc != nil { + t.RecordFunc(ctx, err) + return + } +} + +func (t *FakeErrorTracker) Clear(ctx context.Context, pluginID string) { + if t.ClearFunc != nil { + t.ClearFunc(ctx, pluginID) + return + } +} + +func (t *FakeErrorTracker) Errors(ctx context.Context) []*plugins.Error { + if t.ErrorsFunc != nil { + return t.ErrorsFunc(ctx) + } + return nil +} + +func (t *FakeErrorTracker) Error(ctx context.Context, pluginID string) *plugins.Error { + return &plugins.Error{} +} diff --git a/pkg/plugins/manager/loader/assetpath/assetpath.go b/pkg/plugins/manager/loader/assetpath/assetpath.go deleted file mode 100644 index cf0d0729854..00000000000 --- a/pkg/plugins/manager/loader/assetpath/assetpath.go +++ /dev/null @@ -1,137 +0,0 @@ -package assetpath - -import ( - "fmt" - "net/url" - "path" - "path/filepath" - "strings" - - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/config" - "github.com/grafana/grafana/pkg/plugins/manager/fakes" - "github.com/grafana/grafana/pkg/plugins/pluginassets" - "github.com/grafana/grafana/pkg/plugins/pluginscdn" -) - -// Service provides methods for constructing asset paths for plugins. -// It supports core plugins, external plugins stored on the local filesystem, and external plugins stored -// on the plugins CDN, and it will switch to the correct implementation depending on the plugin and the config. -type Service struct { - cdn *pluginscdn.Service - cfg *config.PluginManagementCfg - assetProvider pluginassets.Provider -} - -func ProvideService(cfg *config.PluginManagementCfg, cdn *pluginscdn.Service, assetProvider pluginassets.Provider) *Service { - return &Service{cfg: cfg, cdn: cdn, assetProvider: assetProvider} -} - -func DefaultService(cfg *config.PluginManagementCfg) *Service { - return &Service{cfg: cfg, cdn: pluginscdn.ProvideService(cfg), assetProvider: fakes.NewFakeAssetProvider()} -} - -// Base returns the base path for the specified plugin. -func (s *Service) Base(n pluginassets.PluginInfo) (string, error) { - if s.cfg.Features.PluginAssetProvider { - return s.assetProvider.AssetPath(n) - } - - if n.Class == plugins.ClassCDN { - return n.FS.Base(), nil - } - if s.cdn.PluginSupported(n.JsonData.ID) { - return s.cdn.AssetURL(n.JsonData.ID, n.JsonData.Info.Version, "") - } - if n.Parent != nil { - relPath, err := n.Parent.FS.Rel(n.FS.Base()) - if err != nil { - return "", err - } - if s.cdn.PluginSupported(n.Parent.JsonData.ID) { - return s.cdn.AssetURL(n.Parent.JsonData.ID, n.Parent.JsonData.Info.Version, relPath) - } - } - - return path.Join("public/plugins", n.JsonData.ID), nil -} - -// Module returns the module.js path for the specified plugin. -func (s *Service) Module(n pluginassets.PluginInfo) (string, error) { - if s.cfg.Features.PluginAssetProvider { - return s.assetProvider.Module(n) - } - - if n.Class == plugins.ClassCore && filepath.Base(n.FS.Base()) != "dist" { - return path.Join("core:plugin", filepath.Base(n.FS.Base())), nil - } - - return s.RelativeURL(n, "module.js") -} - -// RelativeURL returns the relative URL for an arbitrary plugin asset. -func (s *Service) RelativeURL(n pluginassets.PluginInfo, pathStr string) (string, error) { - if s.cfg.Features.PluginAssetProvider { - return s.assetProvider.AssetPath(n, pathStr) - } - - if n.Class == plugins.ClassCDN { - return pluginscdn.JoinPath(n.FS.Base(), pathStr) - } - - if s.cdn.PluginSupported(n.JsonData.ID) { - return s.cdn.NewCDNURLConstructor(n.JsonData.ID, n.JsonData.Info.Version).StringPath(pathStr) - } - if n.Parent != nil { - if s.cdn.PluginSupported(n.Parent.JsonData.ID) { - relPath, err := n.Parent.FS.Rel(n.FS.Base()) - if err != nil { - return "", err - } - return s.cdn.AssetURL(n.Parent.JsonData.ID, n.Parent.JsonData.Info.Version, path.Join(relPath, pathStr)) - } - } - // Local - u, err := url.Parse(pathStr) - if err != nil { - return "", fmt.Errorf("url parse: %w", err) - } - if u.IsAbs() { - return pathStr, nil - } - - baseURL, err := s.Base(n) - if err != nil { - return "", err - } - - // has already been prefixed with base path - if strings.HasPrefix(pathStr, baseURL) { - return pathStr, nil - } - return path.Join(baseURL, pathStr), nil -} - -// DefaultLogoPath returns the default logo path for the specified plugin type. -func (s *Service) DefaultLogoPath(pluginType plugins.Type) string { - return path.Join("public/img", fmt.Sprintf("icn-%s.svg", string(pluginType))) -} - -func (s *Service) GetTranslations(n pluginassets.PluginInfo) (map[string]string, error) { - pathToTranslations, err := s.RelativeURL(n, "locales") - if err != nil { - return nil, fmt.Errorf("get locales: %w", err) - } - - // loop through all the languages specified in the plugin.json and add them to the list - translations := map[string]string{} - for _, language := range n.JsonData.Languages { - file := fmt.Sprintf("%s.json", n.JsonData.ID) - translations[language], err = url.JoinPath(pathToTranslations, language, file) - if err != nil { - return nil, fmt.Errorf("join path: %w", err) - } - } - - return translations, nil -} diff --git a/pkg/plugins/manager/loader/assetpath/assetpath_test.go b/pkg/plugins/manager/loader/assetpath/assetpath_test.go deleted file mode 100644 index 2593d3c5dab..00000000000 --- a/pkg/plugins/manager/loader/assetpath/assetpath_test.go +++ /dev/null @@ -1,430 +0,0 @@ -package assetpath - -import ( - "net/url" - "path" - "strings" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/config" - "github.com/grafana/grafana/pkg/plugins/manager/fakes" - "github.com/grafana/grafana/pkg/plugins/pluginassets" - "github.com/grafana/grafana/pkg/plugins/pluginscdn" -) - -func pluginFS(basePath string) *fakes.FakePluginFS { - return fakes.NewFakePluginFS(basePath) -} - -func TestService(t *testing.T) { - for _, tc := range []struct { - name string - cdnBaseURL string - }{ - { - name: "Simple", - cdnBaseURL: "https://cdn.example.com", - }, - { - name: "Not root", - cdnBaseURL: "https://cdn.example.com/plugins", - }, - { - name: "End slashes", - cdnBaseURL: "https://cdn.example.com/plugins////", - }, - } { - t.Run(tc.name, func(t *testing.T) { - cfg := &config.PluginManagementCfg{ - PluginsCDNURLTemplate: tc.cdnBaseURL, - PluginSettings: map[string]map[string]string{ - "one": {"cdn": "true"}, - "two": {}, - }, - } - svc := ProvideService(cfg, pluginscdn.ProvideService(cfg), fakes.NewFakeAssetProvider()) - - tableOldFS := fakes.NewFakePluginFS("/grafana/public/app/plugins/panel/table-old") - jsonData := map[string]plugins.JSONData{ - "table-old": {ID: "table-old", Info: plugins.Info{Version: "1.0.0"}}, - - "one": {ID: "one", Info: plugins.Info{Version: "1.0.0"}}, - "two": {ID: "two", Info: plugins.Info{Version: "2.0.0"}}, - } - - t.Run("CDN Base URL", func(t *testing.T) { - base, err := svc.cdn.BaseURL() - require.NoError(t, err) - require.Equal(t, tc.cdnBaseURL, base) - }) - - t.Run("Base", func(t *testing.T) { - base, err := svc.Base(pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil)) - require.NoError(t, err) - - oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") - require.NoError(t, err) - require.Equal(t, oneCDNURL, base) - - base, err = svc.Base(pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassCDN, pluginFS(oneCDNURL), nil)) - require.NoError(t, err) - require.Equal(t, oneCDNURL, base) - - base, err = svc.Base(pluginassets.NewPluginInfo(jsonData["two"], plugins.ClassExternal, pluginFS("two"), nil)) - require.NoError(t, err) - require.Equal(t, "public/plugins/two", base) - - base, err = svc.Base(pluginassets.NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS, nil)) - require.NoError(t, err) - require.Equal(t, "public/plugins/table-old", base) - - parentFS := pluginFS(oneCDNURL) - parentFS.RelFunc = func(_ string) (string, error) { - return "child-plugins/two", nil - } - parent := pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) - child := pluginassets.NewPluginInfo(jsonData["two"], plugins.ClassExternal, fakes.NewFakePluginFS(""), &parent) - base, err = svc.Base(child) - require.NoError(t, err) - - childBase, err := url.JoinPath(oneCDNURL, "child-plugins/two") - require.NoError(t, err) - require.Equal(t, childBase, base) - }) - - t.Run("Module", func(t *testing.T) { - module, err := svc.Module(pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil)) - require.NoError(t, err) - - oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") - require.NoError(t, err) - - oneCDNModuleURL, err := url.JoinPath(oneCDNURL, "module.js") - require.NoError(t, err) - require.Equal(t, oneCDNModuleURL, module) - - fs := pluginFS("one") - module, err = svc.Module(pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassCDN, fs, nil)) - require.NoError(t, err) - require.Equal(t, path.Join(fs.Base(), "module.js"), module) - - module, err = svc.Module(pluginassets.NewPluginInfo(jsonData["two"], plugins.ClassExternal, pluginFS("two"), nil)) - require.NoError(t, err) - require.Equal(t, "public/plugins/two/module.js", module) - - module, err = svc.Module(pluginassets.NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS, nil)) - require.NoError(t, err) - require.Equal(t, "core:plugin/table-old", module) - - parentFS := pluginFS(oneCDNURL) - parentFS.RelFunc = func(_ string) (string, error) { - return "child-plugins/two", nil - } - parent := pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) - child := pluginassets.NewPluginInfo(jsonData["two"], plugins.ClassExternal, fakes.NewFakePluginFS(""), &parent) - module, err = svc.Module(child) - require.NoError(t, err) - - childModule, err := url.JoinPath(oneCDNURL, "child-plugins/two/module.js") - require.NoError(t, err) - require.Equal(t, childModule, module) - }) - - t.Run("RelativeURL", func(t *testing.T) { - pluginsMap := map[string]*plugins.Plugin{ - "one": { - JSONData: plugins.JSONData{ID: "one", Info: plugins.Info{Version: "1.0.0"}}, - }, - "two": { - JSONData: plugins.JSONData{ID: "two", Info: plugins.Info{Version: "2.0.0"}}, - }, - } - - u, err := svc.RelativeURL(pluginassets.NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil), "") - require.NoError(t, err) - // given an empty path, base URL will be returned - baseURL, err := svc.Base(pluginassets.NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil)) - require.NoError(t, err) - require.Equal(t, baseURL, u) - - u, err = svc.RelativeURL(pluginassets.NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil), "path/to/file.txt") - require.NoError(t, err) - require.Equal(t, strings.TrimRight(tc.cdnBaseURL, "/")+"/one/1.0.0/public/plugins/one/path/to/file.txt", u) - - u, err = svc.RelativeURL(pluginassets.NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, pluginFS("two"), nil), "path/to/file.txt") - require.NoError(t, err) - require.Equal(t, "public/plugins/two/path/to/file.txt", u) - - u, err = svc.RelativeURL(pluginassets.NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, pluginFS("two"), nil), "default") - require.NoError(t, err) - require.Equal(t, "public/plugins/two/default", u) - - oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") - require.NoError(t, err) - - u, err = svc.RelativeURL(pluginassets.NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassCDN, pluginFS(oneCDNURL), nil), "path/to/file.txt") - require.NoError(t, err) - - oneCDNRelativeURL, err := url.JoinPath(oneCDNURL, "path/to/file.txt") - require.NoError(t, err) - require.Equal(t, oneCDNRelativeURL, u) - - parentFS := pluginFS(oneCDNURL) - parentFS.RelFunc = func(_ string) (string, error) { - return "child-plugins/two", nil - } - parent := pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) - child := pluginassets.NewPluginInfo(jsonData["two"], plugins.ClassExternal, fakes.NewFakePluginFS(""), &parent) - u, err = svc.RelativeURL(child, "path/to/file.txt") - require.NoError(t, err) - - oneCDNRelativeURL, err = url.JoinPath(oneCDNURL, "child-plugins/two/path/to/file.txt") - require.NoError(t, err) - require.Equal(t, oneCDNRelativeURL, u) - - u, err = svc.RelativeURL(child, "../path/to/file.txt") - require.NoError(t, err) - oneCDNRelativeURL, err = url.JoinPath(oneCDNURL, "child-plugins/path/to/file.txt") - require.NoError(t, err) - require.Equal(t, oneCDNRelativeURL, u) - }) - - t.Run("GetTranslations", func(t *testing.T) { - pluginInfo := pluginassets.NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil) - pluginInfo.JsonData.Languages = []string{"en-US", "pt-BR"} - translations, err := svc.GetTranslations(pluginInfo) - require.NoError(t, err) - oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "one", "1.0.0", "public", "plugins", "one") - require.NoError(t, err) - enURL, err := url.JoinPath(oneCDNURL, "locales", "en-US", "one.json") - require.NoError(t, err) - ptBRURL, err := url.JoinPath(oneCDNURL, "locales", "pt-BR", "one.json") - require.NoError(t, err) - - require.Equal(t, map[string]string{"en-US": enURL, "pt-BR": ptBRURL}, translations) - }) - }) - } -} - -func TestService_ChildPlugins(t *testing.T) { - type expected struct { - module string - baseURL string - relURL string - } - - tcs := []struct { - name string - cfg *config.PluginManagementCfg - pluginInfo func() pluginassets.PluginInfo - expected expected - }{ - { - name: "Local FS external plugin", - cfg: &config.PluginManagementCfg{}, - pluginInfo: func() pluginassets.PluginInfo { - return pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil) - }, - expected: expected{ - module: "public/plugins/parent/module.js", - baseURL: "public/plugins/parent", - relURL: "public/plugins/parent/path/to/file.txt", - }, - }, - { - name: "Local FS external plugin with child", - cfg: &config.PluginManagementCfg{}, - pluginInfo: func() pluginassets.PluginInfo { - parentInfo := pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil) - childInfo := pluginassets.NewPluginInfo(plugins.JSONData{ID: "child", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent/child"), &parentInfo) - return childInfo - }, - expected: expected{ - module: "public/plugins/child/module.js", - baseURL: "public/plugins/child", - relURL: "public/plugins/child/path/to/file.txt", - }, - }, - { - name: "Local FS core plugin", - cfg: &config.PluginManagementCfg{}, - pluginInfo: func() pluginassets.PluginInfo { - return pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCore, plugins.NewLocalFS("/plugins/parent"), nil) - }, - expected: expected{ - module: "core:plugin/parent", - baseURL: "public/plugins/parent", - relURL: "public/plugins/parent/path/to/file.txt", - }, - }, - { - name: "Externally-built Local FS core plugin", - cfg: &config.PluginManagementCfg{}, - pluginInfo: func() pluginassets.PluginInfo { - return pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCore, plugins.NewLocalFS("/plugins/parent/dist"), nil) - }, - expected: expected{ - module: "public/plugins/parent/module.js", - baseURL: "public/plugins/parent", - relURL: "public/plugins/parent/path/to/file.txt", - }, - }, - { - name: "CDN Class plugin", - cfg: &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.example.com", - }, - pluginInfo: func() pluginassets.PluginInfo { - return pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCDN, pluginFS("https://cdn.example.com/plugins/parent"), nil) - }, - expected: expected{ - module: "https://cdn.example.com/plugins/parent/module.js", - baseURL: "https://cdn.example.com/plugins/parent", - relURL: "https://cdn.example.com/plugins/parent/path/to/file.txt", - }, - }, - { - name: "CDN Class plugin with child", - cfg: &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.example.com", - }, - pluginInfo: func() pluginassets.PluginInfo { - // Note: fake plugin FS is the most convenient way to mock the plugin FS for CDN plugins - parentInfo := pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCDN, pluginFS("https://cdn.example.com/parent"), nil) - childInfo := pluginassets.NewPluginInfo(plugins.JSONData{ID: "child", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCDN, pluginFS("https://cdn.example.com/parent/some/other/dir/child"), &parentInfo) - return childInfo - }, - expected: expected{ - module: "https://cdn.example.com/parent/some/other/dir/child/module.js", - baseURL: "https://cdn.example.com/parent/some/other/dir/child", - relURL: "https://cdn.example.com/parent/some/other/dir/child/path/to/file.txt", - }, - }, - { - name: "CDN supported plugin", - cfg: &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.example.com", - PluginSettings: map[string]map[string]string{ - "parent": {"cdn": "true"}, - }, - }, - pluginInfo: func() pluginassets.PluginInfo { - return pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil) - }, - expected: expected{ - module: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/module.js", - baseURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent", - relURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/path/to/file.txt", - }, - }, - { - name: "CDN supported plugin with child", - cfg: &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.example.com", - PluginSettings: map[string]map[string]string{ - "parent": {"cdn": "true"}, - }, - }, - pluginInfo: func() pluginassets.PluginInfo { - parentInfo := pluginassets.NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil) - childInfo := pluginassets.NewPluginInfo(plugins.JSONData{ID: "child", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent/child"), &parentInfo) - return childInfo - }, - expected: expected{ - module: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/child/module.js", - baseURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/child", - relURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/child/path/to/file.txt", - }, - }, - } - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - svc := ProvideService(tc.cfg, pluginscdn.ProvideService(tc.cfg), fakes.NewFakeAssetProvider()) - - module, err := svc.Module(tc.pluginInfo()) - require.NoError(t, err) - require.Equal(t, tc.expected.module, module) - - baseURL, err := svc.Base(tc.pluginInfo()) - require.NoError(t, err) - require.Equal(t, tc.expected.baseURL, baseURL) - - relURL, err := svc.RelativeURL(tc.pluginInfo(), "path/to/file.txt") - require.NoError(t, err) - require.Equal(t, tc.expected.relURL, relURL) - }) - } -} - -func TestService_AssetProviderPrecedence(t *testing.T) { - cfg := &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.example.com", - PluginSettings: map[string]map[string]string{ - "test-plugin": {"cdn": "true"}, - }, - Features: config.Features{PluginAssetProvider: true}, - } - cdn := pluginscdn.ProvideService(cfg) - - pluginInfo := pluginassets.NewPluginInfo( - plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, - plugins.ClassExternal, - pluginFS("/plugins/test-plugin"), - nil, - ) - - t.Run("Asset provider enabled takes precedence when feature enabled", func(t *testing.T) { - assetProvider := fakes.NewFakeAssetProvider() - assetProvider.AssetPathFunc = func(n pluginassets.PluginInfo, pathElems ...string) (string, error) { - return "from-asset-provider", nil - } - assetProvider.ModuleFunc = func(n pluginassets.PluginInfo) (string, error) { - return "module-from-asset-provider", nil - } - - svc := ProvideService(cfg, cdn, assetProvider) - - base, err := svc.Base(pluginInfo) - require.NoError(t, err) - require.Equal(t, "from-asset-provider", base) - - module, err := svc.Module(pluginInfo) - require.NoError(t, err) - require.Equal(t, "module-from-asset-provider", module) - - relURL, err := svc.RelativeURL(pluginInfo, "path/to/file.txt") - require.NoError(t, err) - require.Equal(t, "from-asset-provider", relURL) - }) - - t.Run("GetTranslations uses asset provider when feature enabled", func(t *testing.T) { - assetProvider := fakes.NewFakeAssetProvider() - assetProvider.AssetPathFunc = func(n pluginassets.PluginInfo, pathElems ...string) (string, error) { - return path.Join("translation-path", path.Join(pathElems...)), nil - } - - pluginWithLangs := pluginassets.NewPluginInfo( - plugins.JSONData{ - ID: "multilang-plugin", - Info: plugins.Info{Version: "1.0.0"}, - Languages: []string{"en-US", "es-ES", "fr-FR"}, - }, - plugins.ClassExternal, - pluginFS("/plugins/multilang-plugin"), - nil, - ) - - svc := ProvideService(cfg, cdn, assetProvider) - translations, err := svc.GetTranslations(pluginWithLangs) - require.NoError(t, err) - require.Len(t, translations, 3) - // All translation paths should come from the asset provider - require.Equal(t, "translation-path/locales/en-US/multilang-plugin.json", translations["en-US"]) - require.Equal(t, "translation-path/locales/es-ES/multilang-plugin.json", translations["es-ES"]) - require.Equal(t, "translation-path/locales/fr-FR/multilang-plugin.json", translations["fr-FR"]) - }) -} diff --git a/pkg/plugins/manager/pipeline/bootstrap/bootstrap.go b/pkg/plugins/manager/pipeline/bootstrap/bootstrap.go index f69a0314f52..0fb1217ec70 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/bootstrap.go +++ b/pkg/plugins/manager/pipeline/bootstrap/bootstrap.go @@ -6,8 +6,8 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/log" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/manager/signature" + "github.com/grafana/grafana/pkg/plugins/pluginassets" ) // Bootstrapper is responsible for the Bootstrap stage of the plugin loader pipeline. @@ -44,7 +44,7 @@ type Opts struct { // New returns a new Bootstrap stage. func New(cfg *config.PluginManagementCfg, opts Opts) *Bootstrap { if opts.ConstructFunc == nil { - opts.ConstructFunc = DefaultConstructFunc(cfg, signature.DefaultCalculator(cfg), assetpath.DefaultService(cfg)) + opts.ConstructFunc = DefaultConstructFunc(cfg, signature.DefaultCalculator(cfg), pluginassets.NewLocalProvider()) } if opts.DecorateFuncs == nil { diff --git a/pkg/plugins/manager/pipeline/bootstrap/factory.go b/pkg/plugins/manager/pipeline/bootstrap/factory.go index de617075fc0..17dc43e22cc 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/factory.go +++ b/pkg/plugins/manager/pipeline/bootstrap/factory.go @@ -2,11 +2,12 @@ package bootstrap import ( "fmt" + "net/url" + "path" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/log" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/pluginassets" ) @@ -17,13 +18,13 @@ type pluginFactoryFunc func(p *plugins.FoundBundle, pluginClass plugins.Class, s // It creates the plugin using plugin information found during the Discovery stage and makes use of the assetPath // service to set the plugin's BaseURL, Module, Logos and Screenshots fields. type DefaultPluginFactory struct { - assetPath *assetpath.Service - features *config.Features + assetProvider pluginassets.Provider + features *config.Features } // NewDefaultPluginFactory returns a new DefaultPluginFactory. -func NewDefaultPluginFactory(features *config.Features, assetPath *assetpath.Service) *DefaultPluginFactory { - return &DefaultPluginFactory{assetPath: assetPath, features: features} +func NewDefaultPluginFactory(features *config.Features, assetProvider pluginassets.Provider) *DefaultPluginFactory { + return &DefaultPluginFactory{assetProvider: assetProvider, features: features} } func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class plugins.Class, @@ -54,11 +55,11 @@ func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class p func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Class, sig plugins.Signature, info pluginassets.PluginInfo) (*plugins.Plugin, error) { - baseURL, err := f.assetPath.Base(info) + baseURL, err := f.assetProvider.AssetPath(info) if err != nil { return nil, fmt.Errorf("base url: %w", err) } - moduleURL, err := f.assetPath.Module(info) + moduleURL, err := f.assetProvider.Module(info) if err != nil { return nil, fmt.Errorf("module url: %w", err) } @@ -74,33 +75,33 @@ func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Cl } plugin.SetLogger(log.New(fmt.Sprintf("plugin.%s", plugin.ID))) - if err = setImages(plugin, f.assetPath, info); err != nil { + if err = setImages(plugin, f.assetProvider, info); err != nil { return nil, err } - if err := setTranslations(plugin, f.assetPath, info); err != nil { + if err := setTranslations(plugin, f.assetProvider, info); err != nil { return nil, err } return plugin, nil } -func setImages(p *plugins.Plugin, assetPath *assetpath.Service, info pluginassets.PluginInfo) error { +func setImages(p *plugins.Plugin, assetProvider pluginassets.Provider, info pluginassets.PluginInfo) error { var err error for _, dst := range []*string{&p.Info.Logos.Small, &p.Info.Logos.Large} { if len(*dst) == 0 { - *dst = assetPath.DefaultLogoPath(p.Type) + *dst = defaultLogoPath(p.Type) continue } - *dst, err = assetPath.RelativeURL(info, *dst) + *dst, err = assetProvider.AssetPath(info, *dst) if err != nil { return fmt.Errorf("logo: %w", err) } } for i := 0; i < len(p.Info.Screenshots); i++ { screenshot := &p.Info.Screenshots[i] - screenshot.Path, err = assetPath.RelativeURL(info, screenshot.Path) + screenshot.Path, err = assetProvider.AssetPath(info, screenshot.Path) if err != nil { return fmt.Errorf("screenshot %d relative url: %w", i, err) } @@ -108,8 +109,8 @@ func setImages(p *plugins.Plugin, assetPath *assetpath.Service, info pluginasset return nil } -func setTranslations(p *plugins.Plugin, assetPath *assetpath.Service, info pluginassets.PluginInfo) error { - translations, err := assetPath.GetTranslations(info) +func setTranslations(p *plugins.Plugin, assetProvider pluginassets.Provider, info pluginassets.PluginInfo) error { + translations, err := getTranslations(assetProvider, info) if err != nil { return fmt.Errorf("set translations: %w", err) } @@ -117,3 +118,26 @@ func setTranslations(p *plugins.Plugin, assetPath *assetpath.Service, info plugi p.Translations = translations return nil } + +func defaultLogoPath(pluginType plugins.Type) string { + return path.Join("public/img", fmt.Sprintf("icn-%s.svg", string(pluginType))) +} + +func getTranslations(assetProvider pluginassets.Provider, n pluginassets.PluginInfo) (map[string]string, error) { + pathToTranslations, err := assetProvider.AssetPath(n, "locales") + if err != nil { + return nil, fmt.Errorf("get locales: %w", err) + } + + // loop through all the languages specified in the plugin.json and add them to the list + translations := map[string]string{} + for _, language := range n.JsonData.Languages { + file := fmt.Sprintf("%s.json", n.JsonData.ID) + translations[language], err = url.JoinPath(pathToTranslations, language, file) + if err != nil { + return nil, fmt.Errorf("join path: %w", err) + } + } + + return translations, nil +} diff --git a/pkg/plugins/manager/pipeline/bootstrap/steps.go b/pkg/plugins/manager/pipeline/bootstrap/steps.go index 042a83e35e2..7dcc452807b 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/steps.go +++ b/pkg/plugins/manager/pipeline/bootstrap/steps.go @@ -9,7 +9,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/log" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" + "github.com/grafana/grafana/pkg/plugins/pluginassets" ) // DefaultConstructor implements the default ConstructFunc used for the Construct step of the Bootstrap stage. @@ -22,8 +22,8 @@ type DefaultConstructor struct { } // DefaultConstructFunc is the default ConstructFunc used for the Construct step of the Bootstrap stage. -func DefaultConstructFunc(cfg *config.PluginManagementCfg, signatureCalculator plugins.SignatureCalculator, assetPath *assetpath.Service) ConstructFunc { - return NewDefaultConstructor(cfg, signatureCalculator, assetPath).Construct +func DefaultConstructFunc(cfg *config.PluginManagementCfg, signatureCalculator plugins.SignatureCalculator, assetProvider pluginassets.Provider) ConstructFunc { + return NewDefaultConstructor(cfg, signatureCalculator, assetProvider).Construct } // DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage. @@ -37,9 +37,9 @@ func DefaultDecorateFuncs(cfg *config.PluginManagementCfg) []DecorateFunc { } // NewDefaultConstructor returns a new DefaultConstructor. -func NewDefaultConstructor(cfg *config.PluginManagementCfg, signatureCalculator plugins.SignatureCalculator, assetPath *assetpath.Service) *DefaultConstructor { +func NewDefaultConstructor(cfg *config.PluginManagementCfg, signatureCalculator plugins.SignatureCalculator, assetProvider pluginassets.Provider) *DefaultConstructor { return &DefaultConstructor{ - pluginFactoryFunc: NewDefaultPluginFactory(&cfg.Features, assetPath).createPlugin, + pluginFactoryFunc: NewDefaultPluginFactory(&cfg.Features, assetProvider).createPlugin, signatureCalculator: signatureCalculator, log: log.New("plugins.construct"), } diff --git a/pkg/plugins/pluginassets/pluginassets.go b/pkg/plugins/pluginassets/pluginassets.go index 363ddd9ecca..715c0ec11c5 100644 --- a/pkg/plugins/pluginassets/pluginassets.go +++ b/pkg/plugins/pluginassets/pluginassets.go @@ -11,7 +11,7 @@ var _ Provider = (*LocalProvider)(nil) type LocalProvider struct{} -func ProvideService() *LocalProvider { +func NewLocalProvider() *LocalProvider { return &LocalProvider{} } diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 9d08ac59c32..7b965f38de3 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -40,7 +40,6 @@ import ( provider2 "github.com/grafana/grafana/pkg/plugins/backendplugin/provider" manager4 "github.com/grafana/grafana/pkg/plugins/manager" "github.com/grafana/grafana/pkg/plugins/manager/filestore" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/signature" @@ -361,10 +360,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api keyRetriever := dynamic.ProvideService(cfg, keystoreService) keyretrieverService := keyretriever.ProvideService(keyRetriever) signatureSignature := signature.ProvideService(pluginManagementCfg, keyretrieverService) - pluginscdnService := pluginscdn.ProvideService(pluginManagementCfg) - localProvider := pluginassets.ProvideService() - assetpathService := assetpath.ProvideService(pluginManagementCfg, pluginscdnService, localProvider) - bootstrap := pipeline.ProvideBootstrapStage(pluginManagementCfg, signatureSignature, assetpathService) + localProvider := pluginassets.NewLocalProvider() + bootstrap := pipeline.ProvideBootstrapStage(pluginManagementCfg, signatureSignature, localProvider) unsignedPluginAuthorizer := signature.ProvideOSSAuthorizer(pluginManagementCfg) validation := signature.ProvideValidatorService(unsignedPluginAuthorizer) angularpatternsstoreService := angularpatternsstore.ProvideService(kvStore) @@ -677,6 +674,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } + pluginscdnService := pluginscdn.ProvideService(pluginManagementCfg) pluginassetsService := pluginassets2.ProvideService(pluginManagementCfg, pluginscdnService, signatureSignature, pluginstoreService) avatarCacheServer := avatar.ProvideAvatarCacheServer(cfg) prefService := prefimpl.ProvideService(sqlStore, cfg) @@ -963,10 +961,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac keyRetriever := dynamic.ProvideService(cfg, keystoreService) keyretrieverService := keyretriever.ProvideService(keyRetriever) signatureSignature := signature.ProvideService(pluginManagementCfg, keyretrieverService) - pluginscdnService := pluginscdn.ProvideService(pluginManagementCfg) - localProvider := pluginassets.ProvideService() - assetpathService := assetpath.ProvideService(pluginManagementCfg, pluginscdnService, localProvider) - bootstrap := pipeline.ProvideBootstrapStage(pluginManagementCfg, signatureSignature, assetpathService) + localProvider := pluginassets.NewLocalProvider() + bootstrap := pipeline.ProvideBootstrapStage(pluginManagementCfg, signatureSignature, localProvider) unsignedPluginAuthorizer := signature.ProvideOSSAuthorizer(pluginManagementCfg) validation := signature.ProvideValidatorService(unsignedPluginAuthorizer) angularpatternsstoreService := angularpatternsstore.ProvideService(kvStore) @@ -1281,6 +1277,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } + pluginscdnService := pluginscdn.ProvideService(pluginManagementCfg) pluginassetsService := pluginassets2.ProvideService(pluginManagementCfg, pluginscdnService, signatureSignature, pluginstoreService) avatarCacheServer := avatar.ProvideAvatarCacheServer(cfg) prefService := prefimpl.ProvideService(sqlStore, cfg) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 0e8ea6e2fa9..567c6d566e7 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1929,16 +1929,6 @@ var ( HideFromDocs: true, Expression: "false", }, - { - Name: "pluginAssetProvider", - Description: "Allows decoupled core plugins to load from the Grafana CDN", - Stage: FeatureStageExperimental, - Owner: grafanaPluginsPlatformSquad, - HideFromAdminPage: true, - HideFromDocs: true, - Expression: "false", - RequiresRestart: true, - }, { Name: "unifiedStorageSearchDualReaderEnabled", Description: "Enable dual reader for unified storage search", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 865f8a157c7..0118448325c 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -248,7 +248,6 @@ foldersAppPlatformAPI,experimental,@grafana/grafana-search-navigate-organise,fal enablePluginImporter,experimental,@grafana/plugins-platform-backend,false,false,true otelLogsFormatting,experimental,@grafana/observability-logs,false,false,true alertingNotificationHistory,experimental,@grafana/alerting-squad,false,false,false -pluginAssetProvider,experimental,@grafana/plugins-platform-backend,false,true,false unifiedStorageSearchDualReaderEnabled,experimental,@grafana/search-and-storage,false,false,false dashboardDsAdHocFiltering,GA,@grafana/datapro,false,false,true dashboardLevelTimeMacros,experimental,@grafana/dashboards-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index e345b809a70..fbc93fc3fc9 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -1002,10 +1002,6 @@ const ( // Enables the notification history feature FlagAlertingNotificationHistory = "alertingNotificationHistory" - // FlagPluginAssetProvider - // Allows decoupled core plugins to load from the Grafana CDN - FlagPluginAssetProvider = "pluginAssetProvider" - // FlagUnifiedStorageSearchDualReaderEnabled // Enable dual reader for unified storage search FlagUnifiedStorageSearchDualReaderEnabled = "unifiedStorageSearchDualReaderEnabled" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 548eb4b7006..d60e4938bf0 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2814,7 +2814,8 @@ "metadata": { "name": "pluginAssetProvider", "resourceVersion": "1753448760331", - "creationTimestamp": "2025-07-17T15:20:35Z" + "creationTimestamp": "2025-07-17T15:20:35Z", + "deletionTimestamp": "2025-10-03T16:13:14Z" }, "spec": { "description": "Allows decoupled core plugins to load from the Grafana CDN", diff --git a/pkg/services/pluginsintegration/loader/fakes.go b/pkg/services/pluginsintegration/loader/fakes.go deleted file mode 100644 index 2a6809deb3c..00000000000 --- a/pkg/services/pluginsintegration/loader/fakes.go +++ /dev/null @@ -1,42 +0,0 @@ -package loader - -import ( - "context" - - "github.com/grafana/grafana/pkg/plugins" -) - -type fakeErrorTracker struct { - RecordFunc func(ctx context.Context, err *plugins.Error) - ClearFunc func(ctx context.Context, pluginID string) - ErrorsFunc func(ctx context.Context) []*plugins.Error -} - -func newFakeErrorTracker() *fakeErrorTracker { - return &fakeErrorTracker{} -} - -func (t *fakeErrorTracker) Record(ctx context.Context, err *plugins.Error) { - if t.RecordFunc != nil { - t.RecordFunc(ctx, err) - return - } -} - -func (t *fakeErrorTracker) Clear(ctx context.Context, pluginID string) { - if t.ClearFunc != nil { - t.ClearFunc(ctx, pluginID) - return - } -} - -func (t *fakeErrorTracker) Errors(ctx context.Context) []*plugins.Error { - if t.ErrorsFunc != nil { - return t.ErrorsFunc(ctx) - } - return nil -} - -func (t *fakeErrorTracker) Error(ctx context.Context, pluginID string) *plugins.Error { - return &plugins.Error{} -} diff --git a/pkg/services/pluginsintegration/loader/loader_test.go b/pkg/services/pluginsintegration/loader/loader_test.go index 05d5a1b5847..b584df21bab 100644 --- a/pkg/services/pluginsintegration/loader/loader_test.go +++ b/pkg/services/pluginsintegration/loader/loader_test.go @@ -20,18 +20,15 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/plugins/manager/loader/angular/angularinspector" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/signature" "github.com/grafana/grafana/pkg/plugins/manager/sources" "github.com/grafana/grafana/pkg/plugins/pluginassets" - "github.com/grafana/grafana/pkg/plugins/pluginscdn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins" - "github.com/grafana/grafana/pkg/setting" ) var compareOpts = []cmp.Option{cmpopts.IgnoreFields(plugins.Plugin{}, "client", "log", "mu"), fsComparer} @@ -468,7 +465,8 @@ func TestLoader_Load(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() procMgr := fakes.NewFakeProcessManager() errTracker := pluginerrs.ProvideErrorTracker() - l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, errTracker) + + l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, errTracker, pluginassets.NewLocalProvider()) t.Run(tt.name, func(t *testing.T) { got, err := l.Load(context.Background(), sources.NewLocalSource(tt.class, tt.pluginPaths)) @@ -586,91 +584,6 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) { }) } -func TestLoader_Load_CustomSource(t *testing.T) { - t.Run("Load a plugin", func(t *testing.T) { - cfg := &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.example.com", - PluginSettings: setting.PluginSettings{ - "grafana-worldmap-panel": {"cdn": "true"}, - }, - } - - pluginPaths := []string{filepath.Join(testDataDir(t), "cdn")} - expected := []*plugins.Plugin{{ - JSONData: plugins.JSONData{ - ID: "grafana-worldmap-panel", - Type: plugins.TypePanel, - Name: "Worldmap Panel", - Info: plugins.Info{ - Version: "0.3.3", - Links: []plugins.InfoLink{ - {Name: "Project site", URL: "https://github.com/grafana/worldmap-panel"}, - {Name: "MIT License", URL: "https://github.com/grafana/worldmap-panel/blob/master/LICENSE"}, - }, - Logos: plugins.Logos{ - // Path substitution - Small: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap_logo.svg", - Large: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap_logo.svg", - }, - Screenshots: []plugins.Screenshots{ - { - Name: "World", - Path: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap-world.png", - }, - { - Name: "USA", - Path: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap-usa.png", - }, - { - Name: "Light Theme", - Path: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap-light-theme.png", - }, - }, - }, - Dependencies: plugins.Dependencies{ - GrafanaVersion: "3.x.x", - Plugins: []plugins.Dependency{}, - Extensions: plugins.ExtensionsDependencies{ - ExposedComponents: []string{}, - }, - }, - Extensions: plugins.Extensions{ - AddedLinks: []plugins.AddedLink{}, - AddedComponents: []plugins.AddedComponent{}, - AddedFunctions: []plugins.AddedFunction{}, - - ExposedComponents: []plugins.ExposedComponent{}, - ExtensionPoints: []plugins.ExtensionPoint{}, - }, - }, - FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "cdn/plugin")), - Class: plugins.ClassExternal, - Signature: plugins.SignatureStatusValid, - BaseURL: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel", - Module: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/module.js", - Translations: map[string]string{}, - }} - - l := newLoader(t, cfg, fakes.NewFakePluginRegistry(), fakes.NewFakeProcessManager(), fakes.NewFakeBackendProcessProvider(), newFakeErrorTracker()) - got, err := l.Load(context.Background(), &fakes.FakePluginSource{ - PluginClassFunc: func(ctx context.Context) plugins.Class { - return plugins.ClassExternal - }, - DiscoverFunc: sources.NewLocalSource(plugins.ClassExternal, pluginPaths).Discover, - DefaultSignatureFunc: func(ctx context.Context) (plugins.Signature, bool) { - return plugins.Signature{ - Status: plugins.SignatureStatusValid, - }, true - }, - }) - - require.NoError(t, err) - if !cmp.Equal(got, expected, compareOpts...) { - t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...)) - } - }) -} - func TestLoader_Load_MultiplePlugins(t *testing.T) { t.Run("Load multiple", func(t *testing.T) { tests := []struct { @@ -753,7 +666,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) { procMgr := fakes.NewFakeProcessManager() errTracker := pluginerrs.ProvideErrorTracker() - l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, errTracker) + l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, errTracker, pluginassets.NewLocalProvider()) t.Run(tt.name, func(t *testing.T) { got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { @@ -864,7 +777,7 @@ func TestLoader_Load_RBACReady(t *testing.T) { reg := fakes.NewFakePluginRegistry() procPrvdr := fakes.NewFakeBackendProcessProvider() procMgr := fakes.NewFakeProcessManager() - l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, newFakeErrorTracker()) + l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, fakes.NewFakeErrorTracker(), pluginassets.NewLocalProvider()) got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { @@ -931,7 +844,7 @@ func TestLoader_Load_Signature_RootURL(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() procMgr := fakes.NewFakeProcessManager() cfg := &config.PluginManagementCfg{GrafanaAppURL: defaultAppURL} - l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeErrorTracker()) + l := newLoader(t, cfg, reg, procMgr, procPrvdr, fakes.NewFakeErrorTracker(), pluginassets.NewLocalProvider()) got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { return plugins.ClassExternal @@ -1018,7 +931,7 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() procMgr := fakes.NewFakeProcessManager() cfg := &config.PluginManagementCfg{} - l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeErrorTracker()) + l := newLoader(t, cfg, reg, procMgr, procPrvdr, fakes.NewFakeErrorTracker(), pluginassets.NewLocalProvider()) got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { return plugins.ClassExternal @@ -1118,7 +1031,7 @@ func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) { } procMgr := fakes.NewFakeProcessManager() cfg := &config.PluginManagementCfg{} - l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeErrorTracker()) + l := newLoader(t, cfg, reg, procMgr, procPrvdr, fakes.NewFakeErrorTracker(), pluginassets.NewLocalProvider()) got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { return plugins.ClassExternal @@ -1318,7 +1231,7 @@ func TestLoader_Load_NestedPlugins(t *testing.T) { procMgr := fakes.NewFakeProcessManager() reg := fakes.NewFakePluginRegistry() cfg := &config.PluginManagementCfg{} - l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeErrorTracker()) + l := newLoader(t, cfg, reg, procMgr, procPrvdr, fakes.NewFakeErrorTracker(), pluginassets.NewLocalProvider()) got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { @@ -1519,7 +1432,7 @@ func TestLoader_Load_NestedPlugins(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() procMgr := fakes.NewFakeProcessManager() cfg := &config.PluginManagementCfg{} - l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeErrorTracker()) + l := newLoader(t, cfg, reg, procMgr, procPrvdr, fakes.NewFakeErrorTracker(), pluginassets.NewLocalProvider()) got, err := l.Load(context.Background(), &fakes.FakePluginSource{ PluginClassFunc: func(ctx context.Context) plugins.Class { return plugins.ClassExternal @@ -1548,23 +1461,21 @@ type loaderDepOpts struct { } func newLoader(t *testing.T, cfg *config.PluginManagementCfg, reg registry.Service, proc process.Manager, - backendFactory plugins.BackendFactoryProvider, errTracker pluginerrs.ErrorTracker, + backendFactory plugins.BackendFactoryProvider, errTracker pluginerrs.ErrorTracker, pluginAssetsProvider pluginassets.Provider, ) *Loader { - assets := assetpath.ProvideService(cfg, pluginscdn.ProvideService(cfg), pluginassets.ProvideService()) angularInspector := angularinspector.NewStaticInspector() terminate, err := pipeline.ProvideTerminationStage(cfg, reg, proc) require.NoError(t, err) return ProvideService(cfg, pipeline.ProvideDiscoveryStage(cfg, reg), - pipeline.ProvideBootstrapStage(cfg, signature.DefaultCalculator(cfg), assets), + pipeline.ProvideBootstrapStage(cfg, signature.DefaultCalculator(cfg), pluginAssetsProvider), pipeline.ProvideValidationStage(cfg, signature.NewValidator(signature.NewUnsignedAuthorizer(cfg)), angularInspector), pipeline.ProvideInitializationStage(cfg, reg, backendFactory, proc, &fakes.FakeAuthService{}, fakes.NewFakeRoleRegistry(), fakes.NewFakeActionSetRegistry(), fakes.NewFakePluginEnvProvider(), tracing.InitializeTracerForTest(), provisionedplugins.NewNoop()), terminate, errTracker) } func newLoaderWithOpts(t *testing.T, cfg *config.PluginManagementCfg, opts loaderDepOpts) *Loader { - assets := assetpath.ProvideService(cfg, pluginscdn.ProvideService(cfg), pluginassets.ProvideService()) reg := fakes.NewFakePluginRegistry() proc := fakes.NewFakeProcessManager() @@ -1588,7 +1499,7 @@ func newLoaderWithOpts(t *testing.T, cfg *config.PluginManagementCfg, opts loade } return ProvideService(cfg, pipeline.ProvideDiscoveryStage(cfg, reg), - pipeline.ProvideBootstrapStage(cfg, signature.DefaultCalculator(cfg), assets), + pipeline.ProvideBootstrapStage(cfg, signature.DefaultCalculator(cfg), pluginassets.NewLocalProvider()), pipeline.ProvideValidationStage(cfg, signature.NewValidator(signature.NewUnsignedAuthorizer(cfg)), angularInspector), pipeline.ProvideInitializationStage(cfg, reg, backendFactoryProvider, proc, authServiceRegistry, fakes.NewFakeRoleRegistry(), fakes.NewFakeActionSetRegistry(), fakes.NewFakePluginEnvProvider(), tracing.InitializeTracerForTest(), provisionedplugins.NewNoop()), terminate, errTracker) diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index ecc0c16c2b3..f805377cdde 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/envvars" "github.com/grafana/grafana/pkg/plugins/manager/loader/angular/angularinspector" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/bootstrap" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/discovery" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/initialization" @@ -19,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/signature" + "github.com/grafana/grafana/pkg/plugins/pluginassets" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins" ) @@ -42,7 +42,7 @@ func ProvideDiscoveryStage(cfg *config.PluginManagementCfg, pr registry.Service) }) } -func ProvideBootstrapStage(cfg *config.PluginManagementCfg, sc plugins.SignatureCalculator, a *assetpath.Service) *bootstrap.Bootstrap { +func ProvideBootstrapStage(cfg *config.PluginManagementCfg, sc plugins.SignatureCalculator, ap pluginassets.Provider) *bootstrap.Bootstrap { disableAlertingForTempoDecorateFunc := func(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { if p.ID == coreplugin.Tempo && !cfg.Features.TempoAlertingEnabled { p.Alerting = false @@ -51,7 +51,7 @@ func ProvideBootstrapStage(cfg *config.PluginManagementCfg, sc plugins.Signature } return bootstrap.New(cfg, bootstrap.Opts{ - ConstructFunc: bootstrap.DefaultConstructFunc(cfg, sc, a), + ConstructFunc: bootstrap.DefaultConstructFunc(cfg, sc, ap), DecorateFuncs: append(bootstrap.DefaultDecorateFuncs(cfg), disableAlertingForTempoDecorateFunc), }) } diff --git a/pkg/services/pluginsintegration/pluginconfig/config.go b/pkg/services/pluginsintegration/pluginconfig/config.go index 97cfadbff5f..ba9e8333db0 100644 --- a/pkg/services/pluginsintegration/pluginconfig/config.go +++ b/pkg/services/pluginsintegration/pluginconfig/config.go @@ -32,7 +32,6 @@ func ProvidePluginManagementConfig(cfg *setting.Cfg, settingProvider setting.Pro SkipHostEnvVarsEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSkipHostEnvVars), SriChecksEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSriChecks), TempoAlertingEnabled: features.IsEnabledGlobally(featuremgmt.FlagTempoAlerting), - PluginAssetProvider: features.IsEnabledGlobally(featuremgmt.FlagPluginAssetProvider), }, cfg.GrafanaComAPIURL, cfg.DisablePlugins, diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index 73d39b06321..b6d016662be 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/filestore" pluginLoader "github.com/grafana/grafana/pkg/plugins/manager/loader" pAngularInspector "github.com/grafana/grafana/pkg/plugins/manager/loader/angular/angularinspector" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/bootstrap" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/discovery" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/initialization" @@ -78,7 +77,6 @@ var WireSet = wire.NewSet( wire.Bind(new(process.Manager), new(*process.Service)), coreplugin.ProvideCoreRegistry, pluginscdn.ProvideService, - assetpath.ProvideService, pipeline.ProvideDiscoveryStage, wire.Bind(new(discovery.Discoverer), new(*discovery.Discovery)), @@ -155,7 +153,7 @@ var WireExtensionSet = wire.NewSet( wire.Bind(new(sources.Registry), new(*sources.Service)), checkregistry.ProvideService, wire.Bind(new(checkregistry.CheckService), new(*checkregistry.Service)), - pluginassets2.ProvideService, + pluginassets2.NewLocalProvider, wire.Bind(new(pluginassets2.Provider), new(*pluginassets2.LocalProvider)), ) diff --git a/pkg/services/pluginsintegration/test_helper.go b/pkg/services/pluginsintegration/test_helper.go index 6689430c43b..ed807e9b6bd 100644 --- a/pkg/services/pluginsintegration/test_helper.go +++ b/pkg/services/pluginsintegration/test_helper.go @@ -15,7 +15,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/plugins/manager/loader" "github.com/grafana/grafana/pkg/plugins/manager/loader/angular/angularinspector" - "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/bootstrap" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/discovery" "github.com/grafana/grafana/pkg/plugins/manager/pipeline/initialization" @@ -27,7 +26,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/signature/statickey" "github.com/grafana/grafana/pkg/plugins/manager/sources" "github.com/grafana/grafana/pkg/plugins/pluginassets" - "github.com/grafana/grafana/pkg/plugins/pluginscdn" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" @@ -47,13 +45,12 @@ func CreateIntegrationTestCtx(t *testing.T, cfg *setting.Cfg, coreRegistry *core pCfg, err := pluginconfig.ProvidePluginManagementConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - cdn := pluginscdn.ProvideService(pCfg) reg := registry.ProvideService() angularInspector := angularinspector.NewStaticInspector() proc := process.ProvideService() disc := pipeline.ProvideDiscoveryStage(pCfg, reg) - boot := pipeline.ProvideBootstrapStage(pCfg, signature.ProvideService(pCfg, statickey.New()), assetpath.ProvideService(pCfg, cdn, pluginassets.ProvideService())) + boot := pipeline.ProvideBootstrapStage(pCfg, signature.ProvideService(pCfg, statickey.New()), pluginassets.NewLocalProvider()) valid := pipeline.ProvideValidationStage(pCfg, signature.NewValidator(signature.NewUnsignedAuthorizer(pCfg)), angularInspector) init := pipeline.ProvideInitializationStage(pCfg, reg, provider.ProvideService(coreRegistry), proc, &fakes.FakeAuthService{}, fakes.NewFakeRoleRegistry(), fakes.NewFakeActionSetRegistry(), nil, tracing.InitializeTracerForTest(), provisionedplugins.NewNoop()) term, err := pipeline.ProvideTerminationStage(pCfg, reg, proc) @@ -91,7 +88,7 @@ func CreateTestLoader(t *testing.T, cfg *pluginsCfg.PluginManagementCfg, opts Lo } if opts.Bootstrapper == nil { - opts.Bootstrapper = pipeline.ProvideBootstrapStage(cfg, signature.ProvideService(cfg, statickey.New()), assetpath.ProvideService(cfg, pluginscdn.ProvideService(cfg), pluginassets.ProvideService())) + opts.Bootstrapper = pipeline.ProvideBootstrapStage(cfg, signature.ProvideService(cfg, statickey.New()), pluginassets.NewLocalProvider()) } if opts.Validator == nil {