mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
Plugins: Refactor loader + finder to support multiple sourcing methods (#64735)
* it's cdn time * tidy body closing * auto signed * fix close * update log name * remove comments
This commit is contained in:
parent
eba2c7b522
commit
ee2dd62a1f
@ -23,6 +23,7 @@ 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/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||
@ -60,7 +61,7 @@ func TestCallResource(t *testing.T) {
|
||||
reg := registry.ProvideService()
|
||||
cdn := pluginscdn.ProvideService(pCfg)
|
||||
l := loader.ProvideService(pCfg, fakes.NewFakeLicensingService(), signature.NewUnsignedAuthorizer(pCfg),
|
||||
reg, provider.ProvideService(coreRegistry), fakes.NewFakeRoleRegistry(), cdn, assetpath.ProvideService(cdn))
|
||||
reg, provider.ProvideService(coreRegistry), finder.NewLocalFinder(), fakes.NewFakeRoleRegistry(), cdn, assetpath.ProvideService(cdn))
|
||||
srcs := sources.ProvideService(cfg, pCfg)
|
||||
ps, err := store.ProvideService(reg, srcs, l)
|
||||
require.NoError(t, err)
|
||||
|
@ -24,9 +24,10 @@ type Installer interface {
|
||||
Remove(ctx context.Context, pluginID string) error
|
||||
}
|
||||
|
||||
type PluginSource struct {
|
||||
Class Class
|
||||
Paths []string
|
||||
type PluginSource interface {
|
||||
PluginClass(ctx context.Context) Class
|
||||
PluginURIs(ctx context.Context) []string
|
||||
DefaultSignature(ctx context.Context) (Signature, bool)
|
||||
}
|
||||
|
||||
type CompatOpts struct {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -40,7 +41,14 @@ func (f LocalFS) Open(name string) (fs.File, error) {
|
||||
if kv.f != nil {
|
||||
return kv.f, nil
|
||||
}
|
||||
return os.Open(kv.path)
|
||||
file, err := os.Open(kv.path)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, ErrFileNotExist
|
||||
}
|
||||
return nil, ErrPluginFileRead
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
return nil, ErrFileNotExist
|
||||
}
|
||||
@ -70,14 +78,24 @@ type LocalFile struct {
|
||||
}
|
||||
|
||||
func (p *LocalFile) Stat() (fs.FileInfo, error) {
|
||||
return os.Stat(p.path)
|
||||
fi, err := os.Stat(p.path)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, ErrFileNotExist
|
||||
}
|
||||
return nil, ErrPluginFileRead
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
func (p *LocalFile) Read(bytes []byte) (int, error) {
|
||||
var err error
|
||||
p.f, err = os.Open(p.path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return 0, ErrFileNotExist
|
||||
}
|
||||
return 0, ErrPluginFileRead
|
||||
}
|
||||
return p.f.Read(bytes)
|
||||
}
|
||||
|
@ -37,13 +37,13 @@ func (i *FakePluginInstaller) Remove(ctx context.Context, pluginID string) error
|
||||
}
|
||||
|
||||
type FakeLoader struct {
|
||||
LoadFunc func(_ context.Context, _ plugins.Class, paths []string) ([]*plugins.Plugin, error)
|
||||
LoadFunc func(_ context.Context, _ plugins.PluginSource) ([]*plugins.Plugin, error)
|
||||
UnloadFunc func(_ context.Context, _ string) error
|
||||
}
|
||||
|
||||
func (l *FakeLoader) Load(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
||||
func (l *FakeLoader) Load(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
if l.LoadFunc != nil {
|
||||
return l.LoadFunc(ctx, class, paths)
|
||||
return l.LoadFunc(ctx, src)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@ -381,13 +381,40 @@ func (f *FakePluginFiles) Files() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
type FakeSources struct {
|
||||
type FakeSourceRegistry struct {
|
||||
ListFunc func(_ context.Context) []plugins.PluginSource
|
||||
}
|
||||
|
||||
func (s *FakeSources) List(ctx context.Context) []plugins.PluginSource {
|
||||
func (s *FakeSourceRegistry) List(ctx context.Context) []plugins.PluginSource {
|
||||
if s.ListFunc != nil {
|
||||
return s.ListFunc(ctx)
|
||||
}
|
||||
return []plugins.PluginSource{}
|
||||
}
|
||||
|
||||
type FakePluginSource struct {
|
||||
PluginClassFunc func(ctx context.Context) plugins.Class
|
||||
PluginURIsFunc func(ctx context.Context) []string
|
||||
DefaultSignatureFunc func(ctx context.Context) (plugins.Signature, bool)
|
||||
}
|
||||
|
||||
func (s *FakePluginSource) PluginClass(ctx context.Context) plugins.Class {
|
||||
if s.PluginClassFunc != nil {
|
||||
return s.PluginClassFunc(ctx)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *FakePluginSource) PluginURIs(ctx context.Context) []string {
|
||||
if s.PluginURIsFunc != nil {
|
||||
return s.PluginURIsFunc(ctx)
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *FakePluginSource) DefaultSignature(ctx context.Context) (plugins.Signature, bool) {
|
||||
if s.DefaultSignatureFunc != nil {
|
||||
return s.DefaultSignatureFunc(ctx)
|
||||
}
|
||||
return plugins.Signature{}, false
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/plugins/storage"
|
||||
)
|
||||
@ -118,7 +119,7 @@ func (m *PluginInstaller) Add(ctx context.Context, pluginID, version string, opt
|
||||
pathsToScan = append(pathsToScan, depArchive.Path)
|
||||
}
|
||||
|
||||
_, err = m.pluginLoader.Load(ctx, plugins.External, pathsToScan)
|
||||
_, err = m.pluginLoader.Load(ctx, sources.NewLocalSource(plugins.External, pathsToScan))
|
||||
if err != nil {
|
||||
m.log.Error("Could not load plugins", "paths", pathsToScan, "err", err)
|
||||
return err
|
||||
|
@ -34,9 +34,9 @@ func TestPluginManager_Add_Remove(t *testing.T) {
|
||||
|
||||
var loadedPaths []string
|
||||
loader := &fakes.FakeLoader{
|
||||
LoadFunc: func(_ context.Context, _ plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
||||
loadedPaths = append(loadedPaths, paths...)
|
||||
require.Equal(t, []string{zipNameV1}, paths)
|
||||
LoadFunc: func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
loadedPaths = append(loadedPaths, src.PluginURIs(ctx)...)
|
||||
require.Equal(t, []string{zipNameV1}, src.PluginURIs(ctx))
|
||||
return []*plugins.Plugin{pluginV1}, nil
|
||||
},
|
||||
}
|
||||
@ -96,9 +96,9 @@ func TestPluginManager_Add_Remove(t *testing.T) {
|
||||
mockZipV2 := &zip.ReadCloser{Reader: zip.Reader{File: []*zip.File{{
|
||||
FileHeader: zip.FileHeader{Name: zipNameV2},
|
||||
}}}}
|
||||
loader.LoadFunc = func(_ context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
||||
require.Equal(t, plugins.External, class)
|
||||
require.Equal(t, []string{zipNameV2}, paths)
|
||||
loader.LoadFunc = func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
require.Equal(t, plugins.External, src.PluginClass(ctx))
|
||||
require.Equal(t, []string{zipNameV2}, src.PluginURIs(ctx))
|
||||
return []*plugins.Plugin{pluginV2}, nil
|
||||
}
|
||||
pluginRepo.GetPluginDownloadOptionsFunc = func(_ context.Context, pluginID, version string, _ repo.CompatOpts) (*repo.PluginDownloadOptions, error) {
|
||||
|
@ -1,44 +0,0 @@
|
||||
package finder
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
local *FS
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
logger := log.New("plugin.finder")
|
||||
return &Service{
|
||||
local: newFS(logger),
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Service) Find(ctx context.Context, pluginPaths ...string) ([]*plugins.FoundBundle, error) {
|
||||
if len(pluginPaths) == 0 {
|
||||
return []*plugins.FoundBundle{}, nil
|
||||
}
|
||||
|
||||
fbs := make(map[string][]*plugins.FoundBundle)
|
||||
for _, path := range pluginPaths {
|
||||
local, err := f.local.Find(ctx, path)
|
||||
if err != nil {
|
||||
f.log.Warn("Error occurred when trying to find plugin", "path", path)
|
||||
continue
|
||||
}
|
||||
fbs[path] = local
|
||||
}
|
||||
|
||||
var found []*plugins.FoundBundle
|
||||
for _, fb := range fbs {
|
||||
found = append(found, fb...)
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
@ -7,5 +7,5 @@ import (
|
||||
)
|
||||
|
||||
type Finder interface {
|
||||
Find(ctx context.Context, uris ...string) ([]*plugins.FoundBundle, error)
|
||||
Find(ctx context.Context, src plugins.PluginSource) ([]*plugins.FoundBundle, error)
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ package finder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -24,32 +23,34 @@ var (
|
||||
ErrInvalidPluginJSONFilePath = errors.New("invalid plugin.json filepath was provided")
|
||||
)
|
||||
|
||||
type FS struct {
|
||||
type Local struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func newFS(logger log.Logger) *FS {
|
||||
return &FS{log: logger.New("fs")}
|
||||
func NewLocalFinder() *Local {
|
||||
return &Local{
|
||||
log: log.New("local.finder"),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FS) Find(_ context.Context, pluginPaths ...string) ([]*plugins.FoundBundle, error) {
|
||||
if len(pluginPaths) == 0 {
|
||||
func (l *Local) Find(ctx context.Context, src plugins.PluginSource) ([]*plugins.FoundBundle, error) {
|
||||
if len(src.PluginURIs(ctx)) == 0 {
|
||||
return []*plugins.FoundBundle{}, nil
|
||||
}
|
||||
|
||||
var pluginJSONPaths []string
|
||||
for _, path := range pluginPaths {
|
||||
for _, path := range src.PluginURIs(ctx) {
|
||||
exists, err := fs.Exists(path)
|
||||
if err != nil {
|
||||
f.log.Warn("Skipping finding plugins as an error occurred", "path", path, "err", err)
|
||||
l.log.Warn("Skipping finding plugins as an error occurred", "path", path, "err", err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
f.log.Warn("Skipping finding plugins as directory does not exist", "path", path)
|
||||
l.log.Warn("Skipping finding plugins as directory does not exist", "path", path)
|
||||
continue
|
||||
}
|
||||
|
||||
paths, err := f.getAbsPluginJSONPaths(path)
|
||||
paths, err := l.getAbsPluginJSONPaths(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -59,20 +60,20 @@ func (f *FS) Find(_ context.Context, pluginPaths ...string) ([]*plugins.FoundBun
|
||||
// load plugin.json files and map directory to JSON data
|
||||
foundPlugins := make(map[string]plugins.JSONData)
|
||||
for _, pluginJSONPath := range pluginJSONPaths {
|
||||
plugin, err := f.readPluginJSON(pluginJSONPath)
|
||||
plugin, err := l.readPluginJSON(pluginJSONPath)
|
||||
if err != nil {
|
||||
f.log.Warn("Skipping plugin loading as its plugin.json could not be read", "path", pluginJSONPath, "err", err)
|
||||
l.log.Warn("Skipping plugin loading as its plugin.json could not be read", "path", pluginJSONPath, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
pluginJSONAbsPath, err := filepath.Abs(pluginJSONPath)
|
||||
if err != nil {
|
||||
f.log.Warn("Skipping plugin loading as absolute plugin.json path could not be calculated", "pluginID", plugin.ID, "err", err)
|
||||
l.log.Warn("Skipping plugin loading as absolute plugin.json path could not be calculated", "pluginID", plugin.ID, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, dupe := foundPlugins[filepath.Dir(pluginJSONAbsPath)]; dupe {
|
||||
f.log.Warn("Skipping plugin loading as it's a duplicate", "pluginID", plugin.ID)
|
||||
l.log.Warn("Skipping plugin loading as it's a duplicate", "pluginID", plugin.ID)
|
||||
continue
|
||||
}
|
||||
foundPlugins[filepath.Dir(pluginJSONAbsPath)] = plugin
|
||||
@ -121,7 +122,30 @@ func (f *FS) Find(_ context.Context, pluginPaths ...string) ([]*plugins.FoundBun
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *FS) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||
func (l *Local) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error) {
|
||||
reader, err := l.readFile(pluginJSONPath)
|
||||
defer func() {
|
||||
if reader == nil {
|
||||
return
|
||||
}
|
||||
if err = reader.Close(); err != nil {
|
||||
l.log.Warn("Failed to close plugin JSON file", "path", pluginJSONPath, "err", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
l.log.Warn("Skipping plugin loading as its plugin.json could not be read", "path", pluginJSONPath, "err", err)
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
plugin, err := ReadPluginJSON(reader)
|
||||
if err != nil {
|
||||
l.log.Warn("Skipping plugin loading as its plugin.json could not be read", "path", pluginJSONPath, "err", err)
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func (l *Local) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||
var pluginJSONPaths []string
|
||||
|
||||
var err error
|
||||
@ -134,11 +158,11 @@ func (f *FS) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||
func(currentPath string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
f.log.Error("Couldn't scan directory since it doesn't exist", "pluginDir", path, "err", err)
|
||||
l.log.Error("Couldn't scan directory since it doesn't exist", "pluginDir", path, "err", err)
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
f.log.Error("Couldn't scan directory due to lack of permissions", "pluginDir", path, "err", err)
|
||||
l.log.Error("Couldn't scan directory due to lack of permissions", "pluginDir", path, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -166,70 +190,6 @@ func (f *FS) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||
return pluginJSONPaths, nil
|
||||
}
|
||||
|
||||
func (f *FS) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error) {
|
||||
f.log.Debug("Loading plugin", "path", pluginJSONPath)
|
||||
|
||||
if !strings.EqualFold(filepath.Ext(pluginJSONPath), ".json") {
|
||||
return plugins.JSONData{}, ErrInvalidPluginJSONFilePath
|
||||
}
|
||||
|
||||
absPluginJSONPath, err := filepath.Abs(pluginJSONPath)
|
||||
if err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
// Wrapping in filepath.Clean to properly handle
|
||||
// gosec G304 Potential file inclusion via variable rule.
|
||||
reader, err := os.Open(filepath.Clean(absPluginJSONPath))
|
||||
if err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
defer func() {
|
||||
if reader == nil {
|
||||
return
|
||||
}
|
||||
if err = reader.Close(); err != nil {
|
||||
f.log.Warn("Failed to close JSON file", "path", pluginJSONPath, "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
plugin := plugins.JSONData{}
|
||||
if err = json.NewDecoder(reader).Decode(&plugin); err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
if err = validatePluginJSON(plugin); err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
if plugin.ID == "grafana-piechart-panel" {
|
||||
plugin.Name = "Pie Chart (old)"
|
||||
}
|
||||
|
||||
if len(plugin.Dependencies.Plugins) == 0 {
|
||||
plugin.Dependencies.Plugins = []plugins.Dependency{}
|
||||
}
|
||||
|
||||
if plugin.Dependencies.GrafanaVersion == "" {
|
||||
plugin.Dependencies.GrafanaVersion = "*"
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if include.Role == "" {
|
||||
include.Role = org.RoleViewer
|
||||
}
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func validatePluginJSON(data plugins.JSONData) error {
|
||||
if data.ID == "" || !data.Type.IsValid() {
|
||||
return ErrInvalidPluginJSON
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectFilesWithin(dir string) (map[string]struct{}, error) {
|
||||
files := map[string]struct{}{}
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
@ -284,3 +244,20 @@ func collectFilesWithin(dir string) (map[string]struct{}, error) {
|
||||
|
||||
return files, err
|
||||
}
|
||||
|
||||
func (l *Local) readFile(pluginJSONPath string) (io.ReadCloser, error) {
|
||||
l.log.Debug("Loading plugin", "path", pluginJSONPath)
|
||||
|
||||
if !strings.EqualFold(filepath.Ext(pluginJSONPath), ".json") {
|
||||
return nil, ErrInvalidPluginJSONFilePath
|
||||
}
|
||||
|
||||
absPluginJSONPath, err := filepath.Abs(pluginJSONPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrapping in filepath.Clean to properly handle
|
||||
// gosec G304 Potential file inclusion via variable rule.
|
||||
return os.Open(filepath.Clean(absPluginJSONPath))
|
||||
}
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -278,8 +278,12 @@ func TestFinder_Find(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := newFS(log.NewTestLogger())
|
||||
pluginBundles, err := f.Find(context.Background(), tc.pluginDirs...)
|
||||
f := NewLocalFinder()
|
||||
pluginBundles, err := f.Find(context.Background(), &fakes.FakePluginSource{
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return tc.pluginDirs
|
||||
},
|
||||
})
|
||||
if (err != nil) && !errors.Is(err, tc.err) {
|
||||
t.Errorf("Find() error = %v, expected error %v", err, tc.err)
|
||||
return
|
||||
@ -307,7 +311,7 @@ func TestFinder_getAbsPluginJSONPaths(t *testing.T) {
|
||||
walk = origWalk
|
||||
})
|
||||
|
||||
finder := newFS(log.NewTestLogger())
|
||||
finder := NewLocalFinder()
|
||||
paths, err := finder.getAbsPluginJSONPaths("test")
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, paths)
|
||||
@ -322,7 +326,7 @@ func TestFinder_getAbsPluginJSONPaths(t *testing.T) {
|
||||
walk = origWalk
|
||||
})
|
||||
|
||||
finder := newFS(log.NewTestLogger())
|
||||
finder := NewLocalFinder()
|
||||
paths, err := finder.getAbsPluginJSONPaths("test")
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, paths)
|
||||
@ -337,7 +341,7 @@ func TestFinder_getAbsPluginJSONPaths(t *testing.T) {
|
||||
walk = origWalk
|
||||
})
|
||||
|
||||
finder := newFS(log.NewTestLogger())
|
||||
finder := NewLocalFinder()
|
||||
paths, err := finder.getAbsPluginJSONPaths("test")
|
||||
require.Error(t, err)
|
||||
require.Empty(t, paths)
|
||||
@ -396,7 +400,7 @@ func TestFinder_readPluginJSON(t *testing.T) {
|
||||
name string
|
||||
pluginPath string
|
||||
expected plugins.JSONData
|
||||
failed bool
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid plugin",
|
||||
@ -444,27 +448,23 @@ func TestFinder_readPluginJSON(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Invalid plugin JSON",
|
||||
pluginPath: "../testdata/invalid-plugin-json/plugin.json",
|
||||
failed: true,
|
||||
},
|
||||
{
|
||||
name: "Non-existing JSON file",
|
||||
pluginPath: "nonExistingFile.json",
|
||||
failed: true,
|
||||
pluginPath: "../../testdata/invalid-plugin-json/plugin.json",
|
||||
err: ErrInvalidPluginJSON,
|
||||
},
|
||||
}
|
||||
|
||||
f := newFS(log.NewTestLogger())
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := f.readPluginJSON(tt.pluginPath)
|
||||
if (err != nil) && !tt.failed {
|
||||
t.Errorf("readPluginJSON() error = %v, failed %v", err, tt.failed)
|
||||
return
|
||||
reader, err := os.Open(tt.pluginPath)
|
||||
require.NoError(t, err)
|
||||
got, err := ReadPluginJSON(reader)
|
||||
if tt.err != nil {
|
||||
require.ErrorIs(t, err, tt.err)
|
||||
}
|
||||
if !cmp.Equal(got, tt.expected) {
|
||||
t.Errorf("Unexpected pluginJSONData: %v", cmp.Diff(got, tt.expected))
|
||||
}
|
||||
require.NoError(t, reader.Close())
|
||||
})
|
||||
}
|
||||
}
|
47
pkg/plugins/manager/loader/finder/util.go
Normal file
47
pkg/plugins/manager/loader/finder/util.go
Normal file
@ -0,0 +1,47 @@
|
||||
package finder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
)
|
||||
|
||||
func ReadPluginJSON(reader io.Reader) (plugins.JSONData, error) {
|
||||
plugin := plugins.JSONData{}
|
||||
if err := json.NewDecoder(reader).Decode(&plugin); err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
if err := validatePluginJSON(plugin); err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
if plugin.ID == "grafana-piechart-panel" {
|
||||
plugin.Name = "Pie Chart (old)"
|
||||
}
|
||||
|
||||
if len(plugin.Dependencies.Plugins) == 0 {
|
||||
plugin.Dependencies.Plugins = []plugins.Dependency{}
|
||||
}
|
||||
|
||||
if plugin.Dependencies.GrafanaVersion == "" {
|
||||
plugin.Dependencies.GrafanaVersion = "*"
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if include.Role == "" {
|
||||
include.Role = org.RoleViewer
|
||||
}
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func validatePluginJSON(data plugins.JSONData) error {
|
||||
if data.ID == "" || !data.Type.IsValid() {
|
||||
return ErrInvalidPluginJSON
|
||||
}
|
||||
return nil
|
||||
}
|
@ -9,7 +9,7 @@ import (
|
||||
// Service is responsible for loading plugins from the file system.
|
||||
type Service interface {
|
||||
// Load will return a list of plugins found in the provided file system paths.
|
||||
Load(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error)
|
||||
Load(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error)
|
||||
// Unload will unload a specified plugin from the file system.
|
||||
Unload(ctx context.Context, pluginID string) error
|
||||
}
|
||||
|
@ -42,18 +42,19 @@ type Loader struct {
|
||||
}
|
||||
|
||||
func ProvideService(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider,
|
||||
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider, pluginFinder finder.Finder,
|
||||
roleRegistry plugins.RoleRegistry, pluginsCDNService *pluginscdn.Service, assetPath *assetpath.Service) *Loader {
|
||||
return New(cfg, license, authorizer, pluginRegistry, backendProvider, process.NewManager(pluginRegistry),
|
||||
storage.FileSystem(log.NewPrettyLogger("loader.fs"), cfg.PluginsPath), roleRegistry, pluginsCDNService, assetPath)
|
||||
storage.FileSystem(log.NewPrettyLogger("loader.fs"), cfg.PluginsPath), roleRegistry, pluginsCDNService,
|
||||
assetPath, pluginFinder)
|
||||
}
|
||||
|
||||
func New(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider,
|
||||
processManager process.Service, pluginStorage storage.Manager, roleRegistry plugins.RoleRegistry,
|
||||
pluginsCDNService *pluginscdn.Service, assetPath *assetpath.Service) *Loader {
|
||||
pluginsCDNService *pluginscdn.Service, assetPath *assetpath.Service, pluginFinder finder.Finder) *Loader {
|
||||
return &Loader{
|
||||
pluginFinder: finder.NewService(),
|
||||
pluginFinder: pluginFinder,
|
||||
pluginRegistry: pluginRegistry,
|
||||
pluginInitializer: initializer.New(cfg, backendProvider, license),
|
||||
signatureValidator: signature.NewValidator(authorizer),
|
||||
@ -68,16 +69,16 @@ func New(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLo
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loader) Load(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
||||
found, err := l.pluginFinder.Find(ctx, paths...)
|
||||
func (l *Loader) Load(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
found, err := l.pluginFinder.Find(ctx, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.loadPlugins(ctx, class, found)
|
||||
return l.loadPlugins(ctx, src, found)
|
||||
}
|
||||
|
||||
func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, found []*plugins.FoundBundle) ([]*plugins.Plugin, error) {
|
||||
func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, found []*plugins.FoundBundle) ([]*plugins.Plugin, error) {
|
||||
var loadedPlugins []*plugins.Plugin
|
||||
for _, p := range found {
|
||||
if _, exists := l.pluginRegistry.Plugin(ctx, p.Primary.JSONData.ID); exists {
|
||||
@ -91,12 +92,13 @@ func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, found []*
|
||||
sig = plugins.Signature{Status: plugins.SignatureValid}
|
||||
} else {
|
||||
var err error
|
||||
sig, err = signature.Calculate(l.log, class, p.Primary)
|
||||
sig, err = signature.Calculate(ctx, l.log, src, p.Primary)
|
||||
if err != nil {
|
||||
l.log.Warn("Could not calculate plugin signature state", "pluginID", p.Primary.JSONData.ID, "err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
class := src.PluginClass(ctx)
|
||||
plugin, err := l.createPluginBase(p.Primary.JSONData, class, p.Primary.FS)
|
||||
if err != nil {
|
||||
l.log.Error("Could not create primary plugin base", "pluginID", p.Primary.JSONData.ID, "err", err)
|
||||
|
@ -7,9 +7,6 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -18,8 +15,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -29,6 +30,7 @@ var localFSComparer = cmp.Comparer(func(fs1 plugins.LocalFS, fs2 plugins.LocalFS
|
||||
fs1Files := fs1.Files()
|
||||
fs2Files := fs2.Files()
|
||||
|
||||
finder.NewLocalFinder()
|
||||
sort.SliceStable(fs1Files, func(i, j int) bool {
|
||||
return fs1Files[i] < fs1Files[j]
|
||||
})
|
||||
@ -516,7 +518,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), tt.class, tt.pluginPaths)
|
||||
got, err := l.Load(context.Background(), sources.NewLocalSource(tt.class, tt.pluginPaths))
|
||||
require.NoError(t, err)
|
||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
||||
@ -679,7 +681,14 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
})
|
||||
setting.AppUrl = tt.appURL
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, tt.pluginPaths)
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return tt.pluginPaths
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
@ -792,7 +801,14 @@ func TestLoader_Load_RBACReady(t *testing.T) {
|
||||
l.pluginInitializer = initializer.New(tt.cfg, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, tt.pluginPaths)
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return tt.pluginPaths
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||
@ -868,7 +884,14 @@ func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err := l.Load(context.Background(), plugins.External, paths)
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return paths
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts...) {
|
||||
@ -947,7 +970,14 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{pluginDir, pluginDir})
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return []string{pluginDir, pluginDir}
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts...) {
|
||||
@ -1046,7 +1076,14 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"})
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return []string{"../testdata/nested-plugins"}
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
@ -1062,7 +1099,14 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
|
||||
t.Run("Load will exclude plugins that already exist", func(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"})
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return []string{"../testdata/nested-plugins"}
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
@ -1213,7 +1257,14 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/app-with-child"})
|
||||
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return []string{"../testdata/app-with-child"}
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
@ -1256,7 +1307,7 @@ func newLoader(cfg *config.Cfg, cbs ...func(loader *Loader)) *Loader {
|
||||
cdn := pluginscdn.ProvideService(cfg)
|
||||
l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(),
|
||||
fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakePluginStorage(),
|
||||
fakes.NewFakeRoleRegistry(), cdn, assetpath.ProvideService(cdn))
|
||||
fakes.NewFakeRoleRegistry(), cdn, assetpath.ProvideService(cdn), finder.NewLocalFinder())
|
||||
|
||||
for _, cb := range cbs {
|
||||
cb(l)
|
||||
|
@ -24,6 +24,7 @@ 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/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||
@ -116,7 +117,7 @@ func TestIntegrationPluginManager(t *testing.T) {
|
||||
|
||||
lic := plicensing.ProvideLicensing(cfg, &licensing.OSSLicensingService{Cfg: cfg})
|
||||
l := loader.ProvideService(pCfg, lic, signature.NewUnsignedAuthorizer(pCfg),
|
||||
reg, provider.ProvideService(coreRegistry), fakes.NewFakeRoleRegistry(),
|
||||
reg, provider.ProvideService(coreRegistry), finder.NewLocalFinder(), fakes.NewFakeRoleRegistry(),
|
||||
cdn, assetpath.ProvideService(cdn))
|
||||
srcs := sources.ProvideService(cfg, pCfg)
|
||||
ps, err := store.ProvideService(reg, srcs, l)
|
||||
|
@ -2,6 +2,7 @@ package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@ -76,7 +77,7 @@ func (m *PluginManifest) isV2() bool {
|
||||
return strings.HasPrefix(m.ManifestVersion, "2.")
|
||||
}
|
||||
|
||||
// readPluginManifest attempts to read and verify the plugin manifest
|
||||
// ReadPluginManifest attempts to read and verify the plugin manifest
|
||||
// if any error occurs or the manifest is not valid, this will return an error
|
||||
func ReadPluginManifest(body []byte) (*PluginManifest, error) {
|
||||
block, _ := clearsign.Decode(body)
|
||||
@ -98,11 +99,9 @@ func ReadPluginManifest(body []byte) (*PluginManifest, error) {
|
||||
return &manifest, nil
|
||||
}
|
||||
|
||||
func Calculate(mlog log.Logger, class plugins.Class, plugin plugins.FoundPlugin) (plugins.Signature, error) {
|
||||
if class == plugins.Core {
|
||||
return plugins.Signature{
|
||||
Status: plugins.SignatureInternal,
|
||||
}, nil
|
||||
func Calculate(ctx context.Context, mlog log.Logger, src plugins.PluginSource, plugin plugins.FoundPlugin) (plugins.Signature, error) {
|
||||
if defaultSignature, exists := src.DefaultSignature(ctx); exists {
|
||||
return defaultSignature, nil
|
||||
}
|
||||
|
||||
if len(plugin.FS.Files()) == 0 {
|
||||
|
@ -1,16 +1,19 @@
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestReadPluginManifest(t *testing.T) {
|
||||
@ -152,7 +155,11 @@ func TestCalculate(t *testing.T) {
|
||||
setting.AppUrl = tc.appURL
|
||||
|
||||
basePath := filepath.Join(parentDir, "testdata/non-pvt-with-root-url/plugin")
|
||||
sig, err := Calculate(log.NewTestLogger(), plugins.External, plugins.FoundPlugin{
|
||||
sig, err := Calculate(context.Background(), log.NewTestLogger(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
}, plugins.FoundPlugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-datasource",
|
||||
Info: plugins.Info{
|
||||
@ -178,7 +185,11 @@ func TestCalculate(t *testing.T) {
|
||||
basePath := "../testdata/renderer-added-file/plugin"
|
||||
|
||||
runningWindows = true
|
||||
sig, err := Calculate(log.NewTestLogger(), plugins.External, plugins.FoundPlugin{
|
||||
sig, err := Calculate(context.Background(), log.NewTestLogger(), &fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
}, plugins.FoundPlugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-renderer",
|
||||
Type: plugins.Renderer,
|
||||
|
@ -6,6 +6,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
type Resolver interface {
|
||||
type Registry interface {
|
||||
List(context.Context) []plugins.PluginSource
|
||||
}
|
||||
|
38
pkg/plugins/manager/sources/source_local_disk.go
Normal file
38
pkg/plugins/manager/sources/source_local_disk.go
Normal file
@ -0,0 +1,38 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
type LocalSource struct {
|
||||
paths []string
|
||||
class plugins.Class
|
||||
}
|
||||
|
||||
func NewLocalSource(class plugins.Class, paths []string) *LocalSource {
|
||||
return &LocalSource{
|
||||
class: class,
|
||||
paths: paths,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalSource) PluginClass(_ context.Context) plugins.Class {
|
||||
return s.class
|
||||
}
|
||||
|
||||
func (s *LocalSource) PluginURIs(_ context.Context) []string {
|
||||
return s.paths
|
||||
}
|
||||
|
||||
func (s *LocalSource) DefaultSignature(_ context.Context) (plugins.Signature, bool) {
|
||||
switch s.class {
|
||||
case plugins.Core:
|
||||
return plugins.Signature{
|
||||
Status: plugins.SignatureInternal,
|
||||
}, true
|
||||
default:
|
||||
return plugins.Signature{}, false
|
||||
}
|
||||
}
|
@ -26,9 +26,9 @@ func ProvideService(gCfg *setting.Cfg, cfg *config.Cfg) *Service {
|
||||
|
||||
func (s *Service) List(_ context.Context) []plugins.PluginSource {
|
||||
return []plugins.PluginSource{
|
||||
{Class: plugins.Core, Paths: corePluginPaths(s.gCfg.StaticRootPath)},
|
||||
{Class: plugins.Bundled, Paths: []string{s.gCfg.BundledPluginsPath}},
|
||||
{Class: plugins.External, Paths: append([]string{s.cfg.PluginsPath}, pluginFSPaths(s.cfg.PluginSettings)...)},
|
||||
NewLocalSource(plugins.Core, corePluginPaths(s.gCfg.StaticRootPath)),
|
||||
NewLocalSource(plugins.Bundled, []string{s.gCfg.BundledPluginsPath}),
|
||||
NewLocalSource(plugins.External, append([]string{s.cfg.PluginsPath}, pluginFSPaths(s.cfg.PluginSettings)...)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSources_List(t *testing.T) {
|
||||
t.Run("Plugin sources are added in order", func(t *testing.T) {
|
||||
t.Run("Plugin sources are populated by default and listed in specific order", func(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
BundledPluginsPath: "path1",
|
||||
}
|
||||
@ -31,11 +31,28 @@ func TestSources_List(t *testing.T) {
|
||||
s := ProvideService(cfg, pCfg)
|
||||
srcs := s.List(context.Background())
|
||||
|
||||
expected := []plugins.PluginSource{
|
||||
{Class: plugins.Core, Paths: []string{"app/plugins/datasource", "app/plugins/panel"}},
|
||||
{Class: plugins.Bundled, Paths: []string{"path1"}},
|
||||
{Class: plugins.External, Paths: []string{"path2", "path3"}},
|
||||
}
|
||||
require.Equal(t, expected, srcs)
|
||||
ctx := context.Background()
|
||||
|
||||
require.Len(t, srcs, 3)
|
||||
|
||||
require.Equal(t, srcs[0].PluginClass(ctx), plugins.Core)
|
||||
require.Equal(t, srcs[0].PluginURIs(ctx), []string{"app/plugins/datasource", "app/plugins/panel"})
|
||||
sig, exists := srcs[0].DefaultSignature(ctx)
|
||||
require.True(t, exists)
|
||||
require.Equal(t, plugins.SignatureInternal, sig.Status)
|
||||
require.Equal(t, plugins.SignatureType(""), sig.Type)
|
||||
require.Equal(t, "", sig.SigningOrg)
|
||||
|
||||
require.Equal(t, srcs[1].PluginClass(ctx), plugins.Bundled)
|
||||
require.Equal(t, srcs[1].PluginURIs(ctx), []string{"path1"})
|
||||
sig, exists = srcs[1].DefaultSignature(ctx)
|
||||
require.False(t, exists)
|
||||
require.Equal(t, plugins.Signature{}, sig)
|
||||
|
||||
require.Equal(t, srcs[2].PluginClass(ctx), plugins.External)
|
||||
require.Equal(t, srcs[2].PluginURIs(ctx), []string{"path2", "path3"})
|
||||
sig, exists = srcs[2].DefaultSignature(ctx)
|
||||
require.False(t, exists)
|
||||
require.Equal(t, plugins.Signature{}, sig)
|
||||
})
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ type Service struct {
|
||||
pluginRegistry registry.Service
|
||||
}
|
||||
|
||||
func ProvideService(pluginRegistry registry.Service, pluginSources sources.Resolver,
|
||||
func ProvideService(pluginRegistry registry.Service, pluginSources sources.Registry,
|
||||
pluginLoader loader.Service) (*Service, error) {
|
||||
ctx := context.Background()
|
||||
for _, ps := range pluginSources.List(ctx) {
|
||||
if _, err := pluginLoader.Load(ctx, ps.Class, ps.Paths); err != nil {
|
||||
if _, err := pluginLoader.Load(ctx, ps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -15,21 +15,29 @@ func TestStore_ProvideService(t *testing.T) {
|
||||
t.Run("Plugin sources are added in order", func(t *testing.T) {
|
||||
var addedPaths []string
|
||||
l := &fakes.FakeLoader{
|
||||
LoadFunc: func(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
||||
addedPaths = append(addedPaths, paths...)
|
||||
LoadFunc: func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||
addedPaths = append(addedPaths, src.PluginURIs(ctx)...)
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
srcs := &fakes.FakeSources{ListFunc: func(_ context.Context) []plugins.PluginSource {
|
||||
srcs := &fakes.FakeSourceRegistry{ListFunc: func(_ context.Context) []plugins.PluginSource {
|
||||
return []plugins.PluginSource{
|
||||
{
|
||||
Class: plugins.Bundled,
|
||||
Paths: []string{"path1"},
|
||||
&fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.Bundled
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return []string{"path1"}
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: plugins.External,
|
||||
Paths: []string{"path2", "path3"},
|
||||
&fakes.FakePluginSource{
|
||||
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||
return plugins.External
|
||||
},
|
||||
PluginURIsFunc: func(ctx context.Context) []string {
|
||||
return []string{"path2", "path3"}
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
@ -20,7 +20,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var ErrFileNotExist = errors.New("file does not exist")
|
||||
var (
|
||||
ErrFileNotExist = errors.New("file does not exist")
|
||||
ErrPluginFileRead = errors.New("file could not be read")
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
JSONData
|
||||
@ -427,6 +430,10 @@ const (
|
||||
External Class = "external"
|
||||
)
|
||||
|
||||
func (c Class) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
var PluginTypes = []Type{
|
||||
DataSource,
|
||||
Panel,
|
||||
|
@ -2,6 +2,7 @@ package pluginsintegration
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/client"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/process"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
@ -53,7 +55,7 @@ var WireSet = wire.NewSet(
|
||||
plugincontext.ProvideService,
|
||||
licensing.ProvideLicensing,
|
||||
wire.Bind(new(plugins.Licensing), new(*licensing.Service)),
|
||||
wire.Bind(new(sources.Resolver), new(*sources.Service)),
|
||||
wire.Bind(new(sources.Registry), new(*sources.Service)),
|
||||
sources.ProvideService,
|
||||
pluginSettings.ProvideService,
|
||||
wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)),
|
||||
@ -66,6 +68,8 @@ var WireExtensionSet = wire.NewSet(
|
||||
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),
|
||||
signature.ProvideOSSAuthorizer,
|
||||
wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)),
|
||||
wire.Bind(new(finder.Finder), new(*finder.Local)),
|
||||
finder.NewLocalFinder,
|
||||
)
|
||||
|
||||
func ProvideClientDecorator(cfg *setting.Cfg, pCfg *config.Cfg,
|
||||
|
Loading…
Reference in New Issue
Block a user