mirror of
https://github.com/grafana/grafana.git
synced 2025-09-25 14:43:46 +08:00
[WIP] Plugins: Refactoring backend initialization flow (#42247)
* refactoring store interface and init flow * fix import * fix linter * refactor resource calling * load with class * re-order args * fix tests * fix linter * remove old creator * add custom config struct * fix some tests * cleanup * fix linter * add connect failure error * remove unused err * convert test over
This commit is contained in:
@ -1,19 +1,21 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"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"
|
||||
@ -35,16 +37,18 @@ func TestLoader_Load(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *setting.Cfg
|
||||
class plugins.Class
|
||||
cfg *plugins.Cfg
|
||||
pluginPaths []string
|
||||
existingPlugins map[string]struct{}
|
||||
want []*plugins.Plugin
|
||||
pluginErrors map[string]*plugins.Error
|
||||
}{
|
||||
{
|
||||
name: "Load a Core plugin",
|
||||
cfg: &setting.Cfg{
|
||||
StaticRootPath: corePluginDir,
|
||||
name: "Load a Core plugin",
|
||||
class: plugins.Core,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: corePluginDir,
|
||||
},
|
||||
pluginPaths: []string{filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")},
|
||||
want: []*plugins.Plugin{
|
||||
@ -86,15 +90,16 @@ func TestLoader_Load(t *testing.T) {
|
||||
Module: "app/plugins/datasource/cloudwatch/module",
|
||||
BaseURL: "public/app/plugins/datasource/cloudwatch",
|
||||
PluginDir: filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch"),
|
||||
Signature: "internal",
|
||||
Class: "core",
|
||||
Signature: plugins.SignatureInternal,
|
||||
Class: plugins.Core,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load a Bundled plugin",
|
||||
cfg: &setting.Cfg{
|
||||
BundledPluginsPath: filepath.Join(parentDir, "testdata"),
|
||||
name: "Load a Bundled plugin",
|
||||
class: plugins.Bundled,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir, "testdata"),
|
||||
},
|
||||
pluginPaths: []string{"../testdata/valid-v2-signature"},
|
||||
want: []*plugins.Plugin{
|
||||
@ -129,12 +134,13 @@ func TestLoader_Load(t *testing.T) {
|
||||
Signature: "valid",
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
Class: "bundled",
|
||||
Class: plugins.Bundled,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Load an External plugin",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load plugin with symbolic links",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
},
|
||||
pluginPaths: []string{"../testdata/symbolic-plugin-dirs"},
|
||||
@ -210,10 +216,11 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Load an unsigned plugin (development)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin (development)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
DevMode: true,
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "development",
|
||||
},
|
||||
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
||||
want: []*plugins.Plugin{
|
||||
@ -248,10 +255,10 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Load an unsigned plugin (production)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin (production)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
},
|
||||
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
||||
want: []*plugins.Plugin{},
|
||||
@ -263,10 +270,10 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin using PluginsAllowUnsigned config (production)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin using PluginsAllowUnsigned config (production)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
PluginsAllowUnsigned: []string{"test"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
||||
@ -298,15 +305,15 @@ func TestLoader_Load(t *testing.T) {
|
||||
Module: "plugins/test/module",
|
||||
BaseURL: "public/plugins/test",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/unsigned-datasource/plugin"),
|
||||
Signature: "unsigned",
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin with modified signature (production)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin with modified signature (production)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
},
|
||||
pluginPaths: []string{"../testdata/lacking-files"},
|
||||
want: []*plugins.Plugin{},
|
||||
@ -318,10 +325,10 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin with modified signature using PluginsAllowUnsigned config (production) still includes a signing error",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin with modified signature using PluginsAllowUnsigned config (production) still includes a signing error",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
PluginsAllowUnsigned: []string{"test"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/lacking-files"},
|
||||
@ -333,11 +340,61 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an app with includes",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
PluginsAllowUnsigned: []string{"test-app"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/test-app-with-includes"},
|
||||
want: []*plugins.Plugin{
|
||||
{JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Type: "app",
|
||||
Name: "Test App",
|
||||
Info: plugins.Info{
|
||||
Author: plugins.InfoLink{
|
||||
Name: "Test Inc.",
|
||||
URL: "http://test.com",
|
||||
},
|
||||
Description: "Official Grafana Test App & Dashboard bundle",
|
||||
Version: "1.0.0",
|
||||
Links: []plugins.InfoLink{
|
||||
{Name: "Project site", URL: "http://project.com"},
|
||||
{Name: "License & Terms", URL: "http://license.com"},
|
||||
},
|
||||
Logos: plugins.Logos{
|
||||
Small: "public/img/icn-app.svg",
|
||||
Large: "public/img/icn-app.svg",
|
||||
},
|
||||
Updated: "2015-02-10",
|
||||
},
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaDependency: ">=8.0.0",
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-memory", DefaultNav: true},
|
||||
{Name: "Root Page (react)", Type: "page", Role: "Viewer", Path: "/a/my-simple-app", DefaultNav: true, AddToNav: true, Slug: "root-page-react"},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/test-app-with-includes"),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
l := newLoader(tt.cfg)
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := l.Load(tt.pluginPaths, tt.existingPlugins)
|
||||
got, err := l.Load(context.Background(), tt.class, tt.pluginPaths, tt.existingPlugins)
|
||||
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))
|
||||
@ -362,7 +419,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
t.Run("Load multiple", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *setting.Cfg
|
||||
cfg *plugins.Cfg
|
||||
pluginPaths []string
|
||||
appURL string
|
||||
existingPlugins map[string]struct{}
|
||||
@ -371,8 +428,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Load multiple plugins (broken, valid, unsigned)",
|
||||
cfg: &setting.Cfg{
|
||||
Env: "production",
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
},
|
||||
appURL: "http://localhost:3000",
|
||||
@ -434,7 +490,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
})
|
||||
setting.AppUrl = tt.appURL
|
||||
|
||||
got, err := l.Load(tt.pluginPaths, tt.existingPlugins)
|
||||
got, err := l.Load(context.Background(), plugins.External, tt.pluginPaths, tt.existingPlugins)
|
||||
require.NoError(t, err)
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
@ -488,17 +544,17 @@ func TestLoader_Signature_RootURL(t *testing.T) {
|
||||
Executable: "test",
|
||||
},
|
||||
PluginDir: filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin"),
|
||||
Class: "external",
|
||||
Signature: "valid",
|
||||
SignatureType: "private",
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.PrivateSignature,
|
||||
SignatureOrg: "Will Browne",
|
||||
Module: "plugins/test/module",
|
||||
BaseURL: "public/plugins/test",
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(&setting.Cfg{PluginsPath: filepath.Join(parentDir)})
|
||||
got, err := l.Load(paths, map[string]struct{}{})
|
||||
l := newLoader(&plugins.Cfg{PluginsPath: filepath.Join(parentDir)})
|
||||
got, err := l.Load(context.Background(), plugins.External, paths, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
@ -566,11 +622,11 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(&setting.Cfg{
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: filepath.Dir(pluginDir),
|
||||
})
|
||||
|
||||
got, err := l.Load([]string{pluginDir, pluginDir}, map[string]struct{}{})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{pluginDir, pluginDir}, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
@ -612,10 +668,10 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
Module: "plugins/test-ds/module",
|
||||
BaseURL: "public/plugins/test-ds",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/nested-plugins/parent"),
|
||||
Signature: "valid",
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
Class: "external",
|
||||
Class: plugins.External,
|
||||
}
|
||||
|
||||
child := &plugins.Plugin{
|
||||
@ -644,10 +700,10 @@ 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"),
|
||||
Signature: "valid",
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
Class: "external",
|
||||
Class: plugins.External,
|
||||
}
|
||||
|
||||
parent.Children = []*plugins.Plugin{child}
|
||||
@ -655,11 +711,11 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
|
||||
t.Run("Load nested External plugins", func(t *testing.T) {
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
l := newLoader(&setting.Cfg{
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
})
|
||||
|
||||
got, err := l.Load([]string{"../testdata/nested-plugins"}, map[string]struct{}{})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
@ -677,11 +733,11 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
parent.Children = nil
|
||||
expected := []*plugins.Plugin{parent}
|
||||
|
||||
l := newLoader(&setting.Cfg{
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
})
|
||||
|
||||
got, err := l.Load([]string{"../testdata/nested-plugins"}, map[string]struct{}{
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{
|
||||
"test-panel": {},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
@ -740,10 +796,10 @@ func TestLoader_readPluginJSON(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard"},
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard"},
|
||||
{Name: "Nginx Panel", Type: "panel"},
|
||||
{Name: "Nginx Datasource", Type: "datasource"},
|
||||
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard", Role: models.ROLE_VIEWER},
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: models.ROLE_VIEWER},
|
||||
{Name: "Nginx Panel", Type: "panel", Role: models.ROLE_VIEWER},
|
||||
{Name: "Nginx Datasource", Type: "datasource", Role: models.ROLE_VIEWER},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
@ -822,71 +878,33 @@ func Test_validatePluginJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_pluginClass(t *testing.T) {
|
||||
type args struct {
|
||||
pluginDir string
|
||||
cfg *setting.Cfg
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expected plugins.Class
|
||||
}{
|
||||
{
|
||||
name: "Core plugin class",
|
||||
args: args{
|
||||
pluginDir: "/root/app/plugins/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
StaticRootPath: "/root",
|
||||
},
|
||||
func Test_setPathsBasedOnApp(t *testing.T) {
|
||||
t.Run("When setting paths based on core plugin on Windows", func(t *testing.T) {
|
||||
child := &plugins.Plugin{
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata\\datasources\\datasource",
|
||||
}
|
||||
parent := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "testdata",
|
||||
},
|
||||
expected: plugins.Core,
|
||||
},
|
||||
{
|
||||
name: "Bundled plugin class",
|
||||
args: args{
|
||||
pluginDir: "/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
BundledPluginsPath: "/test-app",
|
||||
},
|
||||
},
|
||||
expected: plugins.Bundled,
|
||||
},
|
||||
{
|
||||
name: "External plugin class",
|
||||
args: args{
|
||||
pluginDir: "/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
PluginsPath: "/test-app",
|
||||
},
|
||||
},
|
||||
expected: plugins.External,
|
||||
},
|
||||
{
|
||||
name: "External plugin class",
|
||||
args: args{
|
||||
pluginDir: "/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
PluginsPath: "/root",
|
||||
},
|
||||
},
|
||||
expected: plugins.External,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := newLoader(tt.args.cfg)
|
||||
got := l.pluginClass(tt.args.pluginDir)
|
||||
assert.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
Class: plugins.Core,
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
|
||||
BaseURL: "public/app/plugins/app/testdata",
|
||||
}
|
||||
|
||||
setChildModule(parent, child)
|
||||
|
||||
assert.Equal(t, "app/plugins/app/testdata/datasources/datasource/module", child.Module)
|
||||
assert.Equal(t, "testdata", child.IncludedInAppID)
|
||||
assert.Equal(t, "public/app/plugins/app/testdata", child.BaseURL)
|
||||
})
|
||||
}
|
||||
|
||||
func newLoader(cfg *setting.Cfg) *Loader {
|
||||
func newLoader(cfg *plugins.Cfg) *Loader {
|
||||
return &Loader{
|
||||
cfg: cfg,
|
||||
pluginFinder: finder.New(cfg),
|
||||
pluginInitializer: initializer.New(cfg, &fakeLicensingService{}),
|
||||
pluginFinder: finder.New(),
|
||||
pluginInitializer: initializer.New(cfg, &provider.Service{}, &fakeLicensingService{}),
|
||||
signatureValidator: signature.NewValidator(&signature.UnsignedPluginAuthorizer{Cfg: cfg}),
|
||||
errs: make(map[string]*plugins.SignatureError),
|
||||
log: &fakeLogger{},
|
||||
@ -934,6 +952,10 @@ type fakeLogger struct {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (fl fakeLogger) New(_ ...interface{}) log.MultiLoggers {
|
||||
return log.MultiLoggers{}
|
||||
}
|
||||
|
||||
func (fl fakeLogger) Info(_ string, _ ...interface{}) {
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user