mirror of
https://github.com/grafana/grafana.git
synced 2025-01-24 15:27:01 -06:00
make plugin loading for nested plugins more deterministic (#44330)
This commit is contained in:
parent
e9375ecd4c
commit
124bf413a2
@ -19,20 +19,20 @@ func New() Finder {
|
||||
return Finder{log: log.New("plugin.finder")}
|
||||
}
|
||||
|
||||
func (f *Finder) Find(pluginDirs []string) ([]string, error) {
|
||||
func (f *Finder) Find(pluginPaths []string) ([]string, error) {
|
||||
var pluginJSONPaths []string
|
||||
|
||||
for _, dir := range pluginDirs {
|
||||
exists, err := fs.Exists(dir)
|
||||
for _, path := range pluginPaths {
|
||||
exists, err := fs.Exists(path)
|
||||
if err != nil {
|
||||
f.log.Warn("Error occurred when checking if plugin directory exists", "dir", dir, "err", err)
|
||||
f.log.Warn("Error occurred when checking if plugin directory exists", "path", path, "err", err)
|
||||
}
|
||||
if !exists {
|
||||
f.log.Warn("Skipping finding plugins as directory does not exist", "dir", dir)
|
||||
f.log.Warn("Skipping finding plugins as directory does not exist", "path", path)
|
||||
continue
|
||||
}
|
||||
|
||||
paths, err := f.getPluginJSONPaths(dir)
|
||||
paths, err := f.getAbsPluginJSONPaths(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -42,16 +42,16 @@ func (f *Finder) Find(pluginDirs []string) ([]string, error) {
|
||||
return pluginJSONPaths, nil
|
||||
}
|
||||
|
||||
func (f *Finder) getPluginJSONPaths(dir string) ([]string, error) {
|
||||
func (f *Finder) getAbsPluginJSONPaths(path string) ([]string, error) {
|
||||
var pluginJSONPaths []string
|
||||
|
||||
var err error
|
||||
dir, err = filepath.Abs(dir)
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if err := util.Walk(dir, true, true,
|
||||
if err := util.Walk(path, true, true,
|
||||
func(currentPath string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("filepath.Walk reported an error for %q: %w", currentPath, err)
|
||||
@ -73,11 +73,11 @@ func (f *Finder) getPluginJSONPaths(dir string) ([]string, error) {
|
||||
return nil
|
||||
}); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
f.log.Debug("Couldn't scan directory since it doesn't exist", "pluginDir", dir, "err", err)
|
||||
f.log.Debug("Couldn't scan directory since it doesn't exist", "pluginDir", path, "err", err)
|
||||
return []string{}, nil
|
||||
}
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
f.log.Debug("Couldn't scan directory due to lack of permissions", "pluginDir", dir, "err", err)
|
||||
f.log.Debug("Couldn't scan directory due to lack of permissions", "pluginDir", path, "err", err)
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
|
@ -97,12 +97,7 @@ func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSO
|
||||
// calculate initial signature state
|
||||
loadedPlugins := make(map[string]*plugins.Plugin)
|
||||
for pluginDir, pluginJSON := range foundPlugins {
|
||||
plugin := &plugins.Plugin{
|
||||
JSONData: pluginJSON,
|
||||
PluginDir: pluginDir,
|
||||
Class: class,
|
||||
}
|
||||
plugin.SetLogger(l.log.New("pluginID", plugin.ID))
|
||||
plugin := createPluginBase(pluginJSON, class, pluginDir, l.log)
|
||||
|
||||
sig, err := signature.Calculate(l.log, plugin)
|
||||
if err != nil {
|
||||
@ -165,15 +160,23 @@ func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSO
|
||||
}
|
||||
}
|
||||
|
||||
if plugin.IsApp() {
|
||||
setDefaultNavURL(plugin, l.cfg.AppSubURL)
|
||||
}
|
||||
|
||||
if plugin.Parent != nil && plugin.Parent.IsApp() {
|
||||
configureAppChildOPlugin(plugin.Parent, plugin)
|
||||
}
|
||||
|
||||
verifiedPlugins = append(verifiedPlugins, plugin)
|
||||
}
|
||||
|
||||
for _, p := range verifiedPlugins {
|
||||
l.setDefaults(p)
|
||||
err := l.pluginInitializer.Initialize(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
|
||||
}
|
||||
|
||||
return verifiedPlugins, nil
|
||||
@ -228,56 +231,49 @@ func (l *Loader) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error)
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func (l *Loader) setDefaults(p *plugins.Plugin) {
|
||||
setModule(p)
|
||||
func createPluginBase(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string, logger log.Logger) *plugins.Plugin {
|
||||
plugin := &plugins.Plugin{
|
||||
JSONData: pluginJSON,
|
||||
PluginDir: pluginDir,
|
||||
BaseURL: baseURL(pluginJSON, class, pluginDir),
|
||||
Module: module(pluginJSON, class, pluginDir),
|
||||
Class: class,
|
||||
}
|
||||
|
||||
plugin.SetLogger(logger.New("pluginID", plugin.ID))
|
||||
setImages(plugin)
|
||||
|
||||
return plugin
|
||||
}
|
||||
|
||||
func setImages(p *plugins.Plugin) {
|
||||
p.Info.Logos.Small = pluginLogoURL(p.Type, p.Info.Logos.Small, p.BaseURL)
|
||||
p.Info.Logos.Large = pluginLogoURL(p.Type, p.Info.Logos.Large, p.BaseURL)
|
||||
|
||||
for i := 0; i < len(p.Info.Screenshots); i++ {
|
||||
p.Info.Screenshots[i].Path = evalRelativePluginURLPath(p.Info.Screenshots[i].Path, p.BaseURL, p.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if p.IsApp() {
|
||||
for _, child := range p.Children {
|
||||
setPathsBasedOnApp(p, child)
|
||||
func setDefaultNavURL(p *plugins.Plugin, appSubURL string) {
|
||||
// slugify pages
|
||||
for _, include := range p.Includes {
|
||||
if include.Slug == "" {
|
||||
include.Slug = slug.Make(include.Name)
|
||||
}
|
||||
|
||||
// slugify pages
|
||||
for _, include := range p.Includes {
|
||||
if include.Slug == "" {
|
||||
include.Slug = slug.Make(include.Name)
|
||||
}
|
||||
if include.Type == "page" && include.DefaultNav {
|
||||
p.DefaultNavURL = l.cfg.AppSubURL + "/plugins/" + p.ID + "/page/" + include.Slug
|
||||
}
|
||||
if include.Type == "dashboard" && include.DefaultNav {
|
||||
p.DefaultNavURL = l.cfg.AppSubURL + "/dashboard/db/" + include.Slug
|
||||
}
|
||||
if include.Type == "page" && include.DefaultNav {
|
||||
p.DefaultNavURL = appSubURL + "/plugins/" + p.ID + "/page/" + include.Slug
|
||||
}
|
||||
if include.Type == "dashboard" && include.DefaultNav {
|
||||
p.DefaultNavURL = appSubURL + "/dashboard/db/" + include.Slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setModule(p *plugins.Plugin) {
|
||||
if p.IsCorePlugin() {
|
||||
// Previously there was an assumption that the Core plugins directory
|
||||
// should be public/app/plugins/<plugin type>/<plugin id>
|
||||
// However this can be an issue if the Core plugins directory is renamed
|
||||
baseDir := filepath.Base(p.PluginDir)
|
||||
|
||||
// use path package for the following statements because these are not file paths
|
||||
p.Module = path.Join("app/plugins", string(p.Type), baseDir, "module")
|
||||
p.BaseURL = path.Join("public/app/plugins", string(p.Type), baseDir)
|
||||
func configureAppChildOPlugin(parent *plugins.Plugin, child *plugins.Plugin) {
|
||||
if !parent.IsApp() {
|
||||
return
|
||||
}
|
||||
|
||||
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
|
||||
|
||||
p.Module = path.Join("plugins", p.ID, "module")
|
||||
p.BaseURL = path.Join("public/plugins", p.ID)
|
||||
}
|
||||
|
||||
func setPathsBasedOnApp(parent *plugins.Plugin, child *plugins.Plugin) {
|
||||
appSubPath := strings.ReplaceAll(strings.Replace(child.PluginDir, parent.PluginDir, "", 1), "\\", "/")
|
||||
child.IncludedInAppID = parent.ID
|
||||
child.BaseURL = parent.BaseURL
|
||||
@ -331,6 +327,22 @@ func (l *Loader) PluginErrors() []*plugins.Error {
|
||||
return errs
|
||||
}
|
||||
|
||||
func baseURL(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string) string {
|
||||
if class == plugins.Core {
|
||||
return path.Join("public/app/plugins", string(pluginJSON.Type), filepath.Base(pluginDir))
|
||||
}
|
||||
|
||||
return path.Join("public/plugins", pluginJSON.ID)
|
||||
}
|
||||
|
||||
func module(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string) string {
|
||||
if class == plugins.Core {
|
||||
return path.Join("app/plugins", string(pluginJSON.Type), filepath.Base(pluginDir), "module")
|
||||
}
|
||||
|
||||
return path.Join("plugins", pluginJSON.ID, "module")
|
||||
}
|
||||
|
||||
func validatePluginJSON(data plugins.JSONData) error {
|
||||
if data.ID == "" || !data.Type.IsValid() {
|
||||
return ErrInvalidPluginJSON
|
||||
|
@ -638,8 +638,7 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
t.Skip()
|
||||
parentDir, err := filepath.Abs("../")
|
||||
rootDir, err := filepath.Abs("../")
|
||||
if err != nil {
|
||||
t.Errorf("could not construct absolute path of root dir")
|
||||
return
|
||||
@ -670,7 +669,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
},
|
||||
Module: "plugins/test-ds/module",
|
||||
BaseURL: "public/plugins/test-ds",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/nested-plugins/parent"),
|
||||
PluginDir: filepath.Join(rootDir, "testdata/nested-plugins/parent"),
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
@ -702,7 +701,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
},
|
||||
Module: "plugins/test-panel/module",
|
||||
BaseURL: "public/plugins/test-panel",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/nested-plugins/parent/nested"),
|
||||
PluginDir: filepath.Join(rootDir, "testdata/nested-plugins/parent/nested"),
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
@ -715,7 +714,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
t.Run("Load nested External plugins", func(t *testing.T) {
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
PluginsPath: rootDir,
|
||||
})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{})
|
||||
@ -737,7 +736,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
expected := []*plugins.Plugin{parent}
|
||||
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
PluginsPath: rootDir,
|
||||
})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{
|
||||
@ -828,7 +827,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
},
|
||||
Module: "plugins/myorgid-simple-app/module",
|
||||
BaseURL: "public/plugins/myorgid-simple-app",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/app-with-child/dist"),
|
||||
PluginDir: filepath.Join(rootDir, "testdata/app-with-child/dist"),
|
||||
DefaultNavURL: "/plugins/myorgid-simple-app/page/root-page-react",
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
@ -866,7 +865,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
},
|
||||
Module: "plugins/myorgid-simple-app/child/module",
|
||||
BaseURL: "public/plugins/myorgid-simple-app",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/app-with-child/dist/child"),
|
||||
PluginDir: filepath.Join(rootDir, "testdata/app-with-child/dist/child"),
|
||||
IncludedInAppID: parent.ID,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
@ -879,7 +878,7 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
PluginsPath: rootDir,
|
||||
})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/app-with-child"}, map[string]struct{}{})
|
||||
@ -893,6 +892,39 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
t.Run("order of loaded parent and child plugins gives same output", func(t *testing.T) {
|
||||
parentPluginJSON := filepath.Join(rootDir, "testdata/app-with-child/dist/plugin.json")
|
||||
childPluginJSON := filepath.Join(rootDir, "testdata/app-with-child/dist/child/plugin.json")
|
||||
|
||||
got, err := l.loadPlugins(context.Background(), plugins.External, []string{
|
||||
parentPluginJSON, childPluginJSON},
|
||||
map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
})
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
got, err = l.loadPlugins(context.Background(), plugins.External, []string{
|
||||
childPluginJSON, parentPluginJSON},
|
||||
map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
})
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1028,14 +1060,15 @@ func Test_setPathsBasedOnApp(t *testing.T) {
|
||||
}
|
||||
parent := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "testdata",
|
||||
Type: plugins.App,
|
||||
ID: "testdata",
|
||||
},
|
||||
Class: plugins.Core,
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
|
||||
BaseURL: "public/app/plugins/app/testdata",
|
||||
}
|
||||
|
||||
setPathsBasedOnApp(parent, child)
|
||||
configureAppChildOPlugin(parent, child)
|
||||
|
||||
assert.Equal(t, "app/plugins/app/testdata/datasources/datasource/module", child.Module)
|
||||
assert.Equal(t, "testdata", child.IncludedInAppID)
|
||||
|
Loading…
Reference in New Issue
Block a user