diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index b8f5d20ed1e..5606d4b6259 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -69,6 +69,7 @@ type FS interface { Base() string Files() ([]string, error) + Rel(string) (string, error) } type FSRemover interface { diff --git a/pkg/plugins/localfiles.go b/pkg/plugins/localfiles.go index 5981d13256e..0385f69dc94 100644 --- a/pkg/plugins/localfiles.go +++ b/pkg/plugins/localfiles.go @@ -77,6 +77,10 @@ func (f LocalFS) fileIsAllowed(basePath string, absolutePath string, info os.Fil return true, nil } +func (f LocalFS) Rel(p string) (string, error) { + return filepath.Rel(f.basePath, p) +} + // walkFunc returns a filepath.WalkFunc that accumulates absolute file paths into acc by walking over f.Base(). // f.fileIsAllowed is used as WalkFunc, see its documentation for more information on which files are collected. func (f LocalFS) walkFunc(basePath string, acc map[string]struct{}) filepath.WalkFunc { diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index b648e94c579..8abcb907b32 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -403,35 +403,43 @@ func (f *FakeActionSetRegistry) RegisterActionSets(_ context.Context, _ string, return f.ExpectedErr } -type FakePluginFiles struct { +type FakePluginFS struct { OpenFunc func(name string) (fs.File, error) RemoveFunc func() error + RelFunc func(string) (string, error) base string } -func NewFakePluginFiles(base string) *FakePluginFiles { - return &FakePluginFiles{ +func NewFakePluginFS(base string) *FakePluginFS { + return &FakePluginFS{ base: base, } } -func (f *FakePluginFiles) Open(name string) (fs.File, error) { +func (f *FakePluginFS) Open(name string) (fs.File, error) { if f.OpenFunc != nil { return f.OpenFunc(name) } return nil, nil } -func (f *FakePluginFiles) Base() string { +func (f *FakePluginFS) Rel(_ string) (string, error) { + if f.RelFunc != nil { + return f.RelFunc(f.base) + } + return "", nil +} + +func (f *FakePluginFS) Base() string { return f.base } -func (f *FakePluginFiles) Files() ([]string, error) { +func (f *FakePluginFS) Files() ([]string, error) { return []string{}, nil } -func (f *FakePluginFiles) Remove() error { +func (f *FakePluginFS) Remove() error { if f.RemoveFunc != nil { return f.RemoveFunc() } diff --git a/pkg/plugins/manager/loader/assetpath/assetpath.go b/pkg/plugins/manager/loader/assetpath/assetpath.go index eb24b0434a8..e26ce3bae1d 100644 --- a/pkg/plugins/manager/loader/assetpath/assetpath.go +++ b/pkg/plugins/manager/loader/assetpath/assetpath.go @@ -27,14 +27,16 @@ func ProvideService(cfg *config.PluginManagementCfg, cdn *pluginscdn.Service) *S type PluginInfo struct { pluginJSON plugins.JSONData class plugins.Class - dir string + fs plugins.FS + parent *PluginInfo } -func NewPluginInfo(pluginJSON plugins.JSONData, class plugins.Class, fs plugins.FS) PluginInfo { +func NewPluginInfo(pluginJSON plugins.JSONData, class plugins.Class, fs plugins.FS, parent *PluginInfo) PluginInfo { return PluginInfo{ pluginJSON: pluginJSON, class: class, - dir: fs.Base(), + fs: fs, + parent: parent, } } @@ -45,33 +47,77 @@ func DefaultService(cfg *config.PluginManagementCfg) *Service { // Base returns the base path for the specified plugin. func (s *Service) Base(n PluginInfo) (string, error) { if n.class == plugins.ClassCore { - baseDir := getBaseDir(n.dir) + baseDir := getBaseDir(n.fs.Base()) return path.Join("public/app/plugins", string(n.pluginJSON.Type), baseDir), nil } + if n.class == plugins.ClassCDN { + return n.fs.Base(), nil + } if s.cdn.PluginSupported(n.pluginJSON.ID) { return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "") } + if n.parent != nil { + if s.cdn.PluginSupported(n.parent.pluginJSON.ID) { + relPath, err := n.parent.fs.Rel(n.fs.Base()) + if err != nil { + return "", err + } + return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, relPath) + } + } + return path.Join("public/plugins", n.pluginJSON.ID), nil } // Module returns the module.js path for the specified plugin. func (s *Service) Module(n PluginInfo) (string, error) { if n.class == plugins.ClassCore { - if filepath.Base(n.dir) == "dist" { + if filepath.Base(n.fs.Base()) == "dist" { // The core plugin has been built externally, use the module from the dist folder } else { - baseDir := getBaseDir(n.dir) + baseDir := getBaseDir(n.fs.Base()) return path.Join("core:plugin", baseDir), nil } } + if n.class == plugins.ClassCDN { + return pluginscdn.JoinPath(n.fs.Base(), "module.js") + } + if s.cdn.PluginSupported(n.pluginJSON.ID) { return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "module.js") } + if n.parent != nil { + if s.cdn.PluginSupported(n.parent.pluginJSON.ID) { + relPath, err := n.parent.fs.Rel(n.fs.Base()) + if err != nil { + return "", err + } + return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, "module.js")) + } + } + return path.Join("public/plugins", n.pluginJSON.ID, "module.js"), nil } // RelativeURL returns the relative URL for an arbitrary plugin asset. func (s *Service) RelativeURL(n PluginInfo, pathStr string) (string, error) { + if n.class == plugins.ClassCDN { + return pluginscdn.JoinPath(n.fs.Base(), pathStr) + } + + if s.cdn.PluginSupported(n.pluginJSON.ID) { + return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr) + } + if n.parent != nil { + if s.cdn.PluginSupported(n.parent.pluginJSON.ID) { + relPath, err := n.parent.fs.Rel(n.fs.Base()) + if err != nil { + return "", err + } + return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, pathStr)) + } + } + if s.cdn.PluginSupported(n.pluginJSON.ID) { return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr) } diff --git a/pkg/plugins/manager/loader/assetpath/assetpath_test.go b/pkg/plugins/manager/loader/assetpath/assetpath_test.go index 9802dcfe00b..be8ce730b95 100644 --- a/pkg/plugins/manager/loader/assetpath/assetpath_test.go +++ b/pkg/plugins/manager/loader/assetpath/assetpath_test.go @@ -2,6 +2,7 @@ package assetpath import ( "net/url" + "path" "strings" "testing" @@ -13,8 +14,8 @@ import ( "github.com/grafana/grafana/pkg/plugins/pluginscdn" ) -func extPath(pluginID string) *fakes.FakePluginFiles { - return fakes.NewFakePluginFiles(pluginID) +func pluginFS(basePath string) *fakes.FakePluginFS { + return fakes.NewFakePluginFS(basePath) } func TestService(t *testing.T) { @@ -45,7 +46,7 @@ func TestService(t *testing.T) { } svc := ProvideService(cfg, pluginscdn.ProvideService(cfg)) - tableOldFS := fakes.NewFakePluginFiles("/grafana/public/app/plugins/panel/table-old") + 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"}}, @@ -60,37 +61,75 @@ func TestService(t *testing.T) { }) t.Run("Base", func(t *testing.T) { - base, err := svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassExternal, extPath("one"))) + base, err := svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil)) require.NoError(t, err) - u, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") + oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") require.NoError(t, err) - require.Equal(t, u, base) + require.Equal(t, oneCDNURL, base) - base, err = svc.Base(NewPluginInfo(jsonData["two"], plugins.ClassExternal, extPath("two"))) + base, err = svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassCDN, pluginFS(oneCDNURL), nil)) + require.NoError(t, err) + require.Equal(t, oneCDNURL, base) + + base, err = svc.Base(NewPluginInfo(jsonData["two"], plugins.ClassExternal, pluginFS("two"), nil)) require.NoError(t, err) require.Equal(t, "public/plugins/two", base) - base, err = svc.Base(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS)) + base, err = svc.Base(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS, nil)) require.NoError(t, err) require.Equal(t, "public/app/plugins/table-old", base) + + parentFS := pluginFS(oneCDNURL) + parentFS.RelFunc = func(_ string) (string, error) { + return "child-plugins/two", nil + } + parent := NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) + child := 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(NewPluginInfo(jsonData["one"], plugins.ClassExternal, extPath("one"))) + module, err := svc.Module(NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil)) require.NoError(t, err) - u, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one/module.js") + oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") require.NoError(t, err) - require.Equal(t, u, module) - module, err = svc.Module(NewPluginInfo(jsonData["two"], plugins.ClassExternal, extPath("two"))) + oneCDNModuleURL, err := url.JoinPath(oneCDNURL, "module.js") + require.NoError(t, err) + require.Equal(t, oneCDNModuleURL, module) + + fs := pluginFS("one") + module, err = svc.Module(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(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(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS)) + module, err = svc.Module(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 := NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) + child := 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) { @@ -103,24 +142,47 @@ func TestService(t *testing.T) { }, } - u, err := svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")), "") + u, err := svc.RelativeURL(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(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one"))) + baseURL, err := svc.Base(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil)) require.NoError(t, err) require.Equal(t, baseURL, u) - u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")), "path/to/file.txt") + u, err = svc.RelativeURL(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(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, extPath("two")), "path/to/file.txt") + u, err = svc.RelativeURL(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(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, extPath("two")), "default") + u, err = svc.RelativeURL(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(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 := NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) + child := 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) }) }) } diff --git a/pkg/plugins/manager/pipeline/bootstrap/factory.go b/pkg/plugins/manager/pipeline/bootstrap/factory.go index c5f23865a67..9b6b5fd718b 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/factory.go +++ b/pkg/plugins/manager/pipeline/bootstrap/factory.go @@ -25,7 +25,8 @@ func NewDefaultPluginFactory(assetPath *assetpath.Service) *DefaultPluginFactory func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class plugins.Class, sig plugins.Signature) (*plugins.Plugin, error) { - plugin, err := f.newPlugin(bundle.Primary, class, sig) + parentInfo := assetpath.NewPluginInfo(bundle.Primary.JSONData, class, bundle.Primary.FS, nil) + plugin, err := f.newPlugin(bundle.Primary, class, sig, parentInfo) if err != nil { return nil, err } @@ -36,7 +37,8 @@ func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class p plugin.Children = make([]*plugins.Plugin, 0, len(bundle.Children)) for _, child := range bundle.Children { - cp, err := f.newPlugin(*child, class, sig) + childInfo := assetpath.NewPluginInfo(child.JSONData, class, child.FS, &parentInfo) + cp, err := f.newPlugin(*child, class, sig, childInfo) if err != nil { return nil, err } @@ -47,8 +49,8 @@ func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class p return plugin, nil } -func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Class, sig plugins.Signature) (*plugins.Plugin, error) { - info := assetpath.NewPluginInfo(p.JSONData, class, p.FS) +func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Class, sig plugins.Signature, + info assetpath.PluginInfo) (*plugins.Plugin, error) { baseURL, err := f.assetPath.Base(info) if err != nil { return nil, fmt.Errorf("base url: %w", err) @@ -69,14 +71,13 @@ 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); err != nil { + if err = setImages(plugin, f.assetPath, info); err != nil { return nil, err } return plugin, nil } -func setImages(p *plugins.Plugin, assetPath *assetpath.Service) error { - info := assetpath.NewPluginInfo(p.JSONData, p.Class, p.FS) +func setImages(p *plugins.Plugin, assetPath *assetpath.Service, info assetpath.PluginInfo) error { var err error for _, dst := range []*string{&p.Info.Logos.Small, &p.Info.Logos.Large} { if len(*dst) == 0 { diff --git a/pkg/plugins/manager/pipeline/bootstrap/steps_test.go b/pkg/plugins/manager/pipeline/bootstrap/steps_test.go index 31eedd6ddc2..3bb8ca78e02 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/steps_test.go +++ b/pkg/plugins/manager/pipeline/bootstrap/steps_test.go @@ -98,7 +98,7 @@ func TestTemplateDecorateFunc(t *testing.T) { func Test_configureAppChildPlugin(t *testing.T) { t.Run("Child plugin will inherit parent version information when version is empty", func(t *testing.T) { child := &plugins.Plugin{ - FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"), + FS: fakes.NewFakePluginFS("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"), } parent := &plugins.Plugin{ JSONData: plugins.JSONData{ @@ -107,7 +107,7 @@ func Test_configureAppChildPlugin(t *testing.T) { Info: plugins.Info{Version: "1.0.0"}, }, Class: plugins.ClassCore, - FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"), + FS: fakes.NewFakePluginFS("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"), BaseURL: "public/app/plugins/app/testdata-app", } @@ -119,7 +119,7 @@ func Test_configureAppChildPlugin(t *testing.T) { t.Run("Child plugin will not inherit parent version information when version is non-empty", func(t *testing.T) { child := &plugins.Plugin{ - FS: fakes.NewFakePluginFiles("/plugins/parent-app/child-panel"), + FS: fakes.NewFakePluginFS("/plugins/parent-app/child-panel"), JSONData: plugins.JSONData{ Info: plugins.Info{Version: "2.0.2"}, }, @@ -131,7 +131,7 @@ func Test_configureAppChildPlugin(t *testing.T) { Info: plugins.Info{Version: "2.0.0"}, }, Class: plugins.ClassExternal, - FS: fakes.NewFakePluginFiles("/plugins/parent-app"), + FS: fakes.NewFakePluginFS("/plugins/parent-app"), BaseURL: "plugins/parent-app", } diff --git a/pkg/plugins/manager/signature/manifest_test.go b/pkg/plugins/manager/signature/manifest_test.go index 1414f4d61b2..cb7364ed93a 100644 --- a/pkg/plugins/manager/signature/manifest_test.go +++ b/pkg/plugins/manager/signature/manifest_test.go @@ -351,6 +351,10 @@ func (f fsPathSeparatorFiles) Files() ([]string, error) { return files, nil } +func (f fsPathSeparatorFiles) Rel(base string) (string, error) { + return filepath.Rel(f.Base(), strings.ReplaceAll(base, f.separator, string(filepath.Separator))) +} + func (f fsPathSeparatorFiles) Open(name string) (fs.File, error) { return f.FS.Open(strings.ReplaceAll(name, f.separator, string(filepath.Separator))) } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 1f2cc1aa513..b52585c46ab 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -505,6 +505,7 @@ const ( ClassCore Class = "core" ClassBundled Class = "bundled" ClassExternal Class = "external" + ClassCDN Class = "cdn" ) func (c Class) String() string { diff --git a/pkg/plugins/pluginscdn/pluginscdn.go b/pkg/plugins/pluginscdn/pluginscdn.go index 44445d449fd..4161954b981 100644 --- a/pkg/plugins/pluginscdn/pluginscdn.go +++ b/pkg/plugins/pluginscdn/pluginscdn.go @@ -2,6 +2,7 @@ package pluginscdn import ( "errors" + "net/url" "strings" "github.com/grafana/grafana/pkg/plugins/config" @@ -62,3 +63,7 @@ func (s *Service) AssetURL(pluginID, pluginVersion, assetPath string) (string, e } return s.NewCDNURLConstructor(pluginID, pluginVersion).StringPath(assetPath) } + +func JoinPath(base string, assetPath ...string) (string, error) { + return url.JoinPath(base, assetPath...) +} diff --git a/pkg/plugins/test_utils.go b/pkg/plugins/test_utils.go index 97f5f3d3d9a..bdf731855ec 100644 --- a/pkg/plugins/test_utils.go +++ b/pkg/plugins/test_utils.go @@ -36,6 +36,10 @@ func (f inMemoryFS) Files() ([]string, error) { return fps, nil } +func (f inMemoryFS) Rel(_ string) (string, error) { + return "", nil +} + func (f inMemoryFS) Open(fn string) (fs.File, error) { if _, ok := f.files[fn]; !ok { return nil, ErrFileNotExist diff --git a/pkg/services/pluginsintegration/pluginstore/store_test.go b/pkg/services/pluginsintegration/pluginstore/store_test.go index f6817b13d00..a4ac6434ff3 100644 --- a/pkg/services/pluginsintegration/pluginstore/store_test.go +++ b/pkg/services/pluginsintegration/pluginstore/store_test.go @@ -118,11 +118,11 @@ func TestStore_Plugins(t *testing.T) { func TestStore_Routes(t *testing.T) { t.Run("Routes returns all static routes for non-decommissioned plugins", func(t *testing.T) { - p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.TypeRenderer}, FS: fakes.NewFakePluginFiles("/some/dir")} - p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "b-test-panel", Type: plugins.TypePanel}, FS: fakes.NewFakePluginFiles("/grafana/")} - p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "c-test-secrets", Type: plugins.TypeSecretsManager}, FS: fakes.NewFakePluginFiles("./secrets"), Class: plugins.ClassCore} - p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "d-test-datasource", Type: plugins.TypeDataSource}, FS: fakes.NewFakePluginFiles("../test")} - p5 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "e-test-app", Type: plugins.TypeApp}, FS: fakes.NewFakePluginFiles("any/path")} + p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.TypeRenderer}, FS: fakes.NewFakePluginFS("/some/dir")} + p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "b-test-panel", Type: plugins.TypePanel}, FS: fakes.NewFakePluginFS("/grafana/")} + p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "c-test-secrets", Type: plugins.TypeSecretsManager}, FS: fakes.NewFakePluginFS("./secrets"), Class: plugins.ClassCore} + p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "d-test-datasource", Type: plugins.TypeDataSource}, FS: fakes.NewFakePluginFS("../test")} + p5 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "e-test-app", Type: plugins.TypeApp}, FS: fakes.NewFakePluginFS("any/path")} p6 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "f-test-app", Type: plugins.TypeApp}} p6.RegisterClient(&DecommissionedPlugin{})