mirror of https://github.com/grafana/grafana.git
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/fakes"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
"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/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/registry"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||||
|
@ -60,7 +61,7 @@ func TestCallResource(t *testing.T) {
|
||||||
reg := registry.ProvideService()
|
reg := registry.ProvideService()
|
||||||
cdn := pluginscdn.ProvideService(pCfg)
|
cdn := pluginscdn.ProvideService(pCfg)
|
||||||
l := loader.ProvideService(pCfg, fakes.NewFakeLicensingService(), signature.NewUnsignedAuthorizer(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)
|
srcs := sources.ProvideService(cfg, pCfg)
|
||||||
ps, err := store.ProvideService(reg, srcs, l)
|
ps, err := store.ProvideService(reg, srcs, l)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -24,9 +24,10 @@ type Installer interface {
|
||||||
Remove(ctx context.Context, pluginID string) error
|
Remove(ctx context.Context, pluginID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginSource struct {
|
type PluginSource interface {
|
||||||
Class Class
|
PluginClass(ctx context.Context) Class
|
||||||
Paths []string
|
PluginURIs(ctx context.Context) []string
|
||||||
|
DefaultSignature(ctx context.Context) (Signature, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompatOpts struct {
|
type CompatOpts struct {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -40,7 +41,14 @@ func (f LocalFS) Open(name string) (fs.File, error) {
|
||||||
if kv.f != nil {
|
if kv.f != nil {
|
||||||
return 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
|
return nil, ErrFileNotExist
|
||||||
}
|
}
|
||||||
|
@ -70,14 +78,24 @@ type LocalFile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LocalFile) Stat() (fs.FileInfo, error) {
|
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) {
|
func (p *LocalFile) Read(bytes []byte) (int, error) {
|
||||||
var err error
|
var err error
|
||||||
p.f, err = os.Open(p.path)
|
p.f, err = os.Open(p.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return 0, ErrFileNotExist
|
||||||
|
}
|
||||||
|
return 0, ErrPluginFileRead
|
||||||
}
|
}
|
||||||
return p.f.Read(bytes)
|
return p.f.Read(bytes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,13 @@ func (i *FakePluginInstaller) Remove(ctx context.Context, pluginID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeLoader struct {
|
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
|
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 {
|
if l.LoadFunc != nil {
|
||||||
return l.LoadFunc(ctx, class, paths)
|
return l.LoadFunc(ctx, src)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -381,13 +381,40 @@ func (f *FakePluginFiles) Files() []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeSources struct {
|
type FakeSourceRegistry struct {
|
||||||
ListFunc func(_ context.Context) []plugins.PluginSource
|
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 {
|
if s.ListFunc != nil {
|
||||||
return s.ListFunc(ctx)
|
return s.ListFunc(ctx)
|
||||||
}
|
}
|
||||||
return []plugins.PluginSource{}
|
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/log"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
"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/repo"
|
||||||
"github.com/grafana/grafana/pkg/plugins/storage"
|
"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)
|
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 {
|
if err != nil {
|
||||||
m.log.Error("Could not load plugins", "paths", pathsToScan, "err", err)
|
m.log.Error("Could not load plugins", "paths", pathsToScan, "err", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -34,9 +34,9 @@ func TestPluginManager_Add_Remove(t *testing.T) {
|
||||||
|
|
||||||
var loadedPaths []string
|
var loadedPaths []string
|
||||||
loader := &fakes.FakeLoader{
|
loader := &fakes.FakeLoader{
|
||||||
LoadFunc: func(_ context.Context, _ plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
LoadFunc: func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||||
loadedPaths = append(loadedPaths, paths...)
|
loadedPaths = append(loadedPaths, src.PluginURIs(ctx)...)
|
||||||
require.Equal(t, []string{zipNameV1}, paths)
|
require.Equal(t, []string{zipNameV1}, src.PluginURIs(ctx))
|
||||||
return []*plugins.Plugin{pluginV1}, nil
|
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{{
|
mockZipV2 := &zip.ReadCloser{Reader: zip.Reader{File: []*zip.File{{
|
||||||
FileHeader: zip.FileHeader{Name: zipNameV2},
|
FileHeader: zip.FileHeader{Name: zipNameV2},
|
||||||
}}}}
|
}}}}
|
||||||
loader.LoadFunc = func(_ context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
loader.LoadFunc = func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||||
require.Equal(t, plugins.External, class)
|
require.Equal(t, plugins.External, src.PluginClass(ctx))
|
||||||
require.Equal(t, []string{zipNameV2}, paths)
|
require.Equal(t, []string{zipNameV2}, src.PluginURIs(ctx))
|
||||||
return []*plugins.Plugin{pluginV2}, nil
|
return []*plugins.Plugin{pluginV2}, nil
|
||||||
}
|
}
|
||||||
pluginRepo.GetPluginDownloadOptionsFunc = func(_ context.Context, pluginID, version string, _ repo.CompatOpts) (*repo.PluginDownloadOptions, error) {
|
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 {
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/infra/fs"
|
"github.com/grafana/grafana/pkg/infra/fs"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/log"
|
"github.com/grafana/grafana/pkg/plugins/log"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,32 +23,34 @@ var (
|
||||||
ErrInvalidPluginJSONFilePath = errors.New("invalid plugin.json filepath was provided")
|
ErrInvalidPluginJSONFilePath = errors.New("invalid plugin.json filepath was provided")
|
||||||
)
|
)
|
||||||
|
|
||||||
type FS struct {
|
type Local struct {
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFS(logger log.Logger) *FS {
|
func NewLocalFinder() *Local {
|
||||||
return &FS{log: logger.New("fs")}
|
return &Local{
|
||||||
|
log: log.New("local.finder"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FS) Find(_ context.Context, pluginPaths ...string) ([]*plugins.FoundBundle, error) {
|
func (l *Local) Find(ctx context.Context, src plugins.PluginSource) ([]*plugins.FoundBundle, error) {
|
||||||
if len(pluginPaths) == 0 {
|
if len(src.PluginURIs(ctx)) == 0 {
|
||||||
return []*plugins.FoundBundle{}, nil
|
return []*plugins.FoundBundle{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginJSONPaths []string
|
var pluginJSONPaths []string
|
||||||
for _, path := range pluginPaths {
|
for _, path := range src.PluginURIs(ctx) {
|
||||||
exists, err := fs.Exists(path)
|
exists, err := fs.Exists(path)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if !exists {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
paths, err := f.getAbsPluginJSONPaths(path)
|
paths, err := l.getAbsPluginJSONPaths(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// load plugin.json files and map directory to JSON data
|
||||||
foundPlugins := make(map[string]plugins.JSONData)
|
foundPlugins := make(map[string]plugins.JSONData)
|
||||||
for _, pluginJSONPath := range pluginJSONPaths {
|
for _, pluginJSONPath := range pluginJSONPaths {
|
||||||
plugin, err := f.readPluginJSON(pluginJSONPath)
|
plugin, err := l.readPluginJSON(pluginJSONPath)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginJSONAbsPath, err := filepath.Abs(pluginJSONPath)
|
pluginJSONAbsPath, err := filepath.Abs(pluginJSONPath)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, dupe := foundPlugins[filepath.Dir(pluginJSONAbsPath)]; dupe {
|
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
|
continue
|
||||||
}
|
}
|
||||||
foundPlugins[filepath.Dir(pluginJSONAbsPath)] = plugin
|
foundPlugins[filepath.Dir(pluginJSONAbsPath)] = plugin
|
||||||
|
@ -121,7 +122,30 @@ func (f *FS) Find(_ context.Context, pluginPaths ...string) ([]*plugins.FoundBun
|
||||||
return result, nil
|
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 pluginJSONPaths []string
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -134,11 +158,11 @@ func (f *FS) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||||
func(currentPath string, fi os.FileInfo, err error) error {
|
func(currentPath string, fi os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
if errors.Is(err, os.ErrPermission) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,70 +190,6 @@ func (f *FS) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||||
return pluginJSONPaths, nil
|
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) {
|
func collectFilesWithin(dir string) (map[string]struct{}, error) {
|
||||||
files := map[string]struct{}{}
|
files := map[string]struct{}{}
|
||||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
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
|
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/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"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/services/org"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -278,8 +278,12 @@ func TestFinder_Find(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
f := newFS(log.NewTestLogger())
|
f := NewLocalFinder()
|
||||||
pluginBundles, err := f.Find(context.Background(), tc.pluginDirs...)
|
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) {
|
if (err != nil) && !errors.Is(err, tc.err) {
|
||||||
t.Errorf("Find() error = %v, expected error %v", err, tc.err)
|
t.Errorf("Find() error = %v, expected error %v", err, tc.err)
|
||||||
return
|
return
|
||||||
|
@ -307,7 +311,7 @@ func TestFinder_getAbsPluginJSONPaths(t *testing.T) {
|
||||||
walk = origWalk
|
walk = origWalk
|
||||||
})
|
})
|
||||||
|
|
||||||
finder := newFS(log.NewTestLogger())
|
finder := NewLocalFinder()
|
||||||
paths, err := finder.getAbsPluginJSONPaths("test")
|
paths, err := finder.getAbsPluginJSONPaths("test")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, paths)
|
require.Empty(t, paths)
|
||||||
|
@ -322,7 +326,7 @@ func TestFinder_getAbsPluginJSONPaths(t *testing.T) {
|
||||||
walk = origWalk
|
walk = origWalk
|
||||||
})
|
})
|
||||||
|
|
||||||
finder := newFS(log.NewTestLogger())
|
finder := NewLocalFinder()
|
||||||
paths, err := finder.getAbsPluginJSONPaths("test")
|
paths, err := finder.getAbsPluginJSONPaths("test")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, paths)
|
require.Empty(t, paths)
|
||||||
|
@ -337,7 +341,7 @@ func TestFinder_getAbsPluginJSONPaths(t *testing.T) {
|
||||||
walk = origWalk
|
walk = origWalk
|
||||||
})
|
})
|
||||||
|
|
||||||
finder := newFS(log.NewTestLogger())
|
finder := NewLocalFinder()
|
||||||
paths, err := finder.getAbsPluginJSONPaths("test")
|
paths, err := finder.getAbsPluginJSONPaths("test")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Empty(t, paths)
|
require.Empty(t, paths)
|
||||||
|
@ -396,7 +400,7 @@ func TestFinder_readPluginJSON(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
pluginPath string
|
pluginPath string
|
||||||
expected plugins.JSONData
|
expected plugins.JSONData
|
||||||
failed bool
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid plugin",
|
name: "Valid plugin",
|
||||||
|
@ -444,27 +448,23 @@ func TestFinder_readPluginJSON(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid plugin JSON",
|
name: "Invalid plugin JSON",
|
||||||
pluginPath: "../testdata/invalid-plugin-json/plugin.json",
|
pluginPath: "../../testdata/invalid-plugin-json/plugin.json",
|
||||||
failed: true,
|
err: ErrInvalidPluginJSON,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Non-existing JSON file",
|
|
||||||
pluginPath: "nonExistingFile.json",
|
|
||||||
failed: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f := newFS(log.NewTestLogger())
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := f.readPluginJSON(tt.pluginPath)
|
reader, err := os.Open(tt.pluginPath)
|
||||||
if (err != nil) && !tt.failed {
|
require.NoError(t, err)
|
||||||
t.Errorf("readPluginJSON() error = %v, failed %v", err, tt.failed)
|
got, err := ReadPluginJSON(reader)
|
||||||
return
|
if tt.err != nil {
|
||||||
|
require.ErrorIs(t, err, tt.err)
|
||||||
}
|
}
|
||||||
if !cmp.Equal(got, tt.expected) {
|
if !cmp.Equal(got, tt.expected) {
|
||||||
t.Errorf("Unexpected pluginJSONData: %v", cmp.Diff(got, tt.expected))
|
t.Errorf("Unexpected pluginJSONData: %v", cmp.Diff(got, tt.expected))
|
||||||
}
|
}
|
||||||
|
require.NoError(t, reader.Close())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.
|
// Service is responsible for loading plugins from the file system.
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// Load will return a list of plugins found in the provided file system paths.
|
// 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 will unload a specified plugin from the file system.
|
||||||
Unload(ctx context.Context, pluginID string) error
|
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,
|
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 {
|
roleRegistry plugins.RoleRegistry, pluginsCDNService *pluginscdn.Service, assetPath *assetpath.Service) *Loader {
|
||||||
return New(cfg, license, authorizer, pluginRegistry, backendProvider, process.NewManager(pluginRegistry),
|
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,
|
func New(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||||
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider,
|
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider,
|
||||||
processManager process.Service, pluginStorage storage.Manager, roleRegistry plugins.RoleRegistry,
|
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{
|
return &Loader{
|
||||||
pluginFinder: finder.NewService(),
|
pluginFinder: pluginFinder,
|
||||||
pluginRegistry: pluginRegistry,
|
pluginRegistry: pluginRegistry,
|
||||||
pluginInitializer: initializer.New(cfg, backendProvider, license),
|
pluginInitializer: initializer.New(cfg, backendProvider, license),
|
||||||
signatureValidator: signature.NewValidator(authorizer),
|
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) {
|
func (l *Loader) Load(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||||
found, err := l.pluginFinder.Find(ctx, paths...)
|
found, err := l.pluginFinder.Find(ctx, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
var loadedPlugins []*plugins.Plugin
|
||||||
for _, p := range found {
|
for _, p := range found {
|
||||||
if _, exists := l.pluginRegistry.Plugin(ctx, p.Primary.JSONData.ID); exists {
|
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}
|
sig = plugins.Signature{Status: plugins.SignatureValid}
|
||||||
} else {
|
} else {
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
l.log.Warn("Could not calculate plugin signature state", "pluginID", p.Primary.JSONData.ID, "err", err)
|
l.log.Warn("Could not calculate plugin signature state", "pluginID", p.Primary.JSONData.ID, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class := src.PluginClass(ctx)
|
||||||
plugin, err := l.createPluginBase(p.Primary.JSONData, class, p.Primary.FS)
|
plugin, err := l.createPluginBase(p.Primary.JSONData, class, p.Primary.FS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.log.Error("Could not create primary plugin base", "pluginID", p.Primary.JSONData.ID, "err", err)
|
l.log.Error("Could not create primary plugin base", "pluginID", p.Primary.JSONData.ID, "err", err)
|
||||||
|
|
|
@ -7,9 +7,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"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"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -18,8 +15,12 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/plugins/config"
|
"github.com/grafana/grafana/pkg/plugins/config"
|
||||||
"github.com/grafana/grafana/pkg/plugins/log"
|
"github.com/grafana/grafana/pkg/plugins/log"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
"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/loader/initializer"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
"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"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ var localFSComparer = cmp.Comparer(func(fs1 plugins.LocalFS, fs2 plugins.LocalFS
|
||||||
fs1Files := fs1.Files()
|
fs1Files := fs1.Files()
|
||||||
fs2Files := fs2.Files()
|
fs2Files := fs2.Files()
|
||||||
|
|
||||||
|
finder.NewLocalFinder()
|
||||||
sort.SliceStable(fs1Files, func(i, j int) bool {
|
sort.SliceStable(fs1Files, func(i, j int) bool {
|
||||||
return fs1Files[i] < fs1Files[j]
|
return fs1Files[i] < fs1Files[j]
|
||||||
})
|
})
|
||||||
|
@ -516,7 +518,7 @@ func TestLoader_Load(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(tt.name, func(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)
|
require.NoError(t, err)
|
||||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(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
|
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)
|
require.NoError(t, err)
|
||||||
sort.SliceStable(got, func(i, j int) bool {
|
sort.SliceStable(got, func(i, j int) bool {
|
||||||
return got[i].ID < got[j].ID
|
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())
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||||
|
@ -868,7 +884,14 @@ func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
||||||
l.processManager = procMgr
|
l.processManager = procMgr
|
||||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if !cmp.Equal(got, expected, compareOpts...) {
|
if !cmp.Equal(got, expected, compareOpts...) {
|
||||||
|
@ -947,7 +970,14 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||||
l.processManager = procMgr
|
l.processManager = procMgr
|
||||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if !cmp.Equal(got, expected, compareOpts...) {
|
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())
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// to ensure we can compare with expected
|
// 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)
|
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||||
|
|
||||||
t.Run("Load will exclude plugins that already exist", func(t *testing.T) {
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// to ensure we can compare with expected
|
// to ensure we can compare with expected
|
||||||
|
@ -1213,7 +1257,14 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||||
l.processManager = procMgr
|
l.processManager = procMgr
|
||||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// to ensure we can compare with expected
|
// 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)
|
cdn := pluginscdn.ProvideService(cfg)
|
||||||
l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(),
|
l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(),
|
||||||
fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakePluginStorage(),
|
fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakePluginStorage(),
|
||||||
fakes.NewFakeRoleRegistry(), cdn, assetpath.ProvideService(cdn))
|
fakes.NewFakeRoleRegistry(), cdn, assetpath.ProvideService(cdn), finder.NewLocalFinder())
|
||||||
|
|
||||||
for _, cb := range cbs {
|
for _, cb := range cbs {
|
||||||
cb(l)
|
cb(l)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
"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/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/registry"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
"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})
|
lic := plicensing.ProvideLicensing(cfg, &licensing.OSSLicensingService{Cfg: cfg})
|
||||||
l := loader.ProvideService(pCfg, lic, signature.NewUnsignedAuthorizer(pCfg),
|
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))
|
cdn, assetpath.ProvideService(cdn))
|
||||||
srcs := sources.ProvideService(cfg, pCfg)
|
srcs := sources.ProvideService(cfg, pCfg)
|
||||||
ps, err := store.ProvideService(reg, srcs, l)
|
ps, err := store.ProvideService(reg, srcs, l)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package signature
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -76,7 +77,7 @@ func (m *PluginManifest) isV2() bool {
|
||||||
return strings.HasPrefix(m.ManifestVersion, "2.")
|
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
|
// if any error occurs or the manifest is not valid, this will return an error
|
||||||
func ReadPluginManifest(body []byte) (*PluginManifest, error) {
|
func ReadPluginManifest(body []byte) (*PluginManifest, error) {
|
||||||
block, _ := clearsign.Decode(body)
|
block, _ := clearsign.Decode(body)
|
||||||
|
@ -98,11 +99,9 @@ func ReadPluginManifest(body []byte) (*PluginManifest, error) {
|
||||||
return &manifest, nil
|
return &manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Calculate(mlog log.Logger, class plugins.Class, plugin plugins.FoundPlugin) (plugins.Signature, error) {
|
func Calculate(ctx context.Context, mlog log.Logger, src plugins.PluginSource, plugin plugins.FoundPlugin) (plugins.Signature, error) {
|
||||||
if class == plugins.Core {
|
if defaultSignature, exists := src.DefaultSignature(ctx); exists {
|
||||||
return plugins.Signature{
|
return defaultSignature, nil
|
||||||
Status: plugins.SignatureInternal,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(plugin.FS.Files()) == 0 {
|
if len(plugin.FS.Files()) == 0 {
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package signature
|
package signature
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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) {
|
func TestReadPluginManifest(t *testing.T) {
|
||||||
|
@ -152,7 +155,11 @@ func TestCalculate(t *testing.T) {
|
||||||
setting.AppUrl = tc.appURL
|
setting.AppUrl = tc.appURL
|
||||||
|
|
||||||
basePath := filepath.Join(parentDir, "testdata/non-pvt-with-root-url/plugin")
|
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{
|
JSONData: plugins.JSONData{
|
||||||
ID: "test-datasource",
|
ID: "test-datasource",
|
||||||
Info: plugins.Info{
|
Info: plugins.Info{
|
||||||
|
@ -178,7 +185,11 @@ func TestCalculate(t *testing.T) {
|
||||||
basePath := "../testdata/renderer-added-file/plugin"
|
basePath := "../testdata/renderer-added-file/plugin"
|
||||||
|
|
||||||
runningWindows = true
|
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{
|
JSONData: plugins.JSONData{
|
||||||
ID: "test-renderer",
|
ID: "test-renderer",
|
||||||
Type: plugins.Renderer,
|
Type: plugins.Renderer,
|
||||||
|
|
|
@ -6,6 +6,6 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resolver interface {
|
type Registry interface {
|
||||||
List(context.Context) []plugins.PluginSource
|
List(context.Context) []plugins.PluginSource
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
func (s *Service) List(_ context.Context) []plugins.PluginSource {
|
||||||
return []plugins.PluginSource{
|
return []plugins.PluginSource{
|
||||||
{Class: plugins.Core, Paths: corePluginPaths(s.gCfg.StaticRootPath)},
|
NewLocalSource(plugins.Core, corePluginPaths(s.gCfg.StaticRootPath)),
|
||||||
{Class: plugins.Bundled, Paths: []string{s.gCfg.BundledPluginsPath}},
|
NewLocalSource(plugins.Bundled, []string{s.gCfg.BundledPluginsPath}),
|
||||||
{Class: plugins.External, Paths: append([]string{s.cfg.PluginsPath}, pluginFSPaths(s.cfg.PluginSettings)...)},
|
NewLocalSource(plugins.External, append([]string{s.cfg.PluginsPath}, pluginFSPaths(s.cfg.PluginSettings)...)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSources_List(t *testing.T) {
|
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{
|
cfg := &setting.Cfg{
|
||||||
BundledPluginsPath: "path1",
|
BundledPluginsPath: "path1",
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,28 @@ func TestSources_List(t *testing.T) {
|
||||||
s := ProvideService(cfg, pCfg)
|
s := ProvideService(cfg, pCfg)
|
||||||
srcs := s.List(context.Background())
|
srcs := s.List(context.Background())
|
||||||
|
|
||||||
expected := []plugins.PluginSource{
|
ctx := context.Background()
|
||||||
{Class: plugins.Core, Paths: []string{"app/plugins/datasource", "app/plugins/panel"}},
|
|
||||||
{Class: plugins.Bundled, Paths: []string{"path1"}},
|
require.Len(t, srcs, 3)
|
||||||
{Class: plugins.External, Paths: []string{"path2", "path3"}},
|
|
||||||
}
|
require.Equal(t, srcs[0].PluginClass(ctx), plugins.Core)
|
||||||
require.Equal(t, expected, srcs)
|
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
|
pluginRegistry registry.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(pluginRegistry registry.Service, pluginSources sources.Resolver,
|
func ProvideService(pluginRegistry registry.Service, pluginSources sources.Registry,
|
||||||
pluginLoader loader.Service) (*Service, error) {
|
pluginLoader loader.Service) (*Service, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for _, ps := range pluginSources.List(ctx) {
|
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
|
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) {
|
t.Run("Plugin sources are added in order", func(t *testing.T) {
|
||||||
var addedPaths []string
|
var addedPaths []string
|
||||||
l := &fakes.FakeLoader{
|
l := &fakes.FakeLoader{
|
||||||
LoadFunc: func(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
LoadFunc: func(ctx context.Context, src plugins.PluginSource) ([]*plugins.Plugin, error) {
|
||||||
addedPaths = append(addedPaths, paths...)
|
addedPaths = append(addedPaths, src.PluginURIs(ctx)...)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
srcs := &fakes.FakeSources{ListFunc: func(_ context.Context) []plugins.PluginSource {
|
srcs := &fakes.FakeSourceRegistry{ListFunc: func(_ context.Context) []plugins.PluginSource {
|
||||||
return []plugins.PluginSource{
|
return []plugins.PluginSource{
|
||||||
{
|
&fakes.FakePluginSource{
|
||||||
Class: plugins.Bundled,
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||||
Paths: []string{"path1"},
|
return plugins.Bundled
|
||||||
|
},
|
||||||
|
PluginURIsFunc: func(ctx context.Context) []string {
|
||||||
|
return []string{"path1"}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
&fakes.FakePluginSource{
|
||||||
Class: plugins.External,
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
||||||
Paths: []string{"path2", "path3"},
|
return plugins.External
|
||||||
|
},
|
||||||
|
PluginURIsFunc: func(ctx context.Context) []string {
|
||||||
|
return []string{"path2", "path3"}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -20,7 +20,10 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"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 {
|
type Plugin struct {
|
||||||
JSONData
|
JSONData
|
||||||
|
@ -427,6 +430,10 @@ const (
|
||||||
External Class = "external"
|
External Class = "external"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c Class) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
var PluginTypes = []Type{
|
var PluginTypes = []Type{
|
||||||
DataSource,
|
DataSource,
|
||||||
Panel,
|
Panel,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package pluginsintegration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
"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/client"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
"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/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/process"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||||
|
@ -53,7 +55,7 @@ var WireSet = wire.NewSet(
|
||||||
plugincontext.ProvideService,
|
plugincontext.ProvideService,
|
||||||
licensing.ProvideLicensing,
|
licensing.ProvideLicensing,
|
||||||
wire.Bind(new(plugins.Licensing), new(*licensing.Service)),
|
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,
|
sources.ProvideService,
|
||||||
pluginSettings.ProvideService,
|
pluginSettings.ProvideService,
|
||||||
wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)),
|
wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)),
|
||||||
|
@ -66,6 +68,8 @@ var WireExtensionSet = wire.NewSet(
|
||||||
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),
|
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),
|
||||||
signature.ProvideOSSAuthorizer,
|
signature.ProvideOSSAuthorizer,
|
||||||
wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)),
|
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,
|
func ProvideClientDecorator(cfg *setting.Cfg, pCfg *config.Cfg,
|
||||||
|
|
Loading…
Reference in New Issue