PluginManager: Make remaining plugin state non-global (#32094)
* PluginDashboards: Use plugin manager interface Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * PluginManager: Make panels non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * PluginManager: Make apps non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * PluginManager: Make static routes non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * PluginManager: Make pluginTypes non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
@@ -17,10 +17,19 @@ type Manager interface {
|
||||
GetDataPlugin(id string) DataPlugin
|
||||
// GetPlugin gets a plugin with a certain ID.
|
||||
GetPlugin(id string) *PluginBase
|
||||
// GetApp gets an app plugin with a certain ID.
|
||||
GetApp(id string) *AppPlugin
|
||||
// DataSourceCount gets the number of data sources.
|
||||
DataSourceCount() int
|
||||
// DataSources gets all data sources.
|
||||
DataSources() []*DataSourcePlugin
|
||||
// Apps gets all app plugins.
|
||||
Apps() []*AppPlugin
|
||||
// PanelCount gets the number of panels.
|
||||
PanelCount() int
|
||||
// AppCount gets the number of apps.
|
||||
AppCount() int
|
||||
// GetEnabledPlugins gets enabled plugins.
|
||||
// GetEnabledPlugins gets enabled plugins.
|
||||
GetEnabledPlugins(orgID int64) (*EnabledPlugins, error)
|
||||
// GrafanaLatestVersion gets the latest Grafana version.
|
||||
@@ -29,6 +38,8 @@ type Manager interface {
|
||||
GrafanaHasUpdate() bool
|
||||
// Plugins gets all plugins.
|
||||
Plugins() []*PluginBase
|
||||
// StaticRoutes gets all static routes.
|
||||
StaticRoutes() []*PluginStaticRoute
|
||||
// GetPluginSettings gets settings for a certain plugin.
|
||||
GetPluginSettings(orgID int64) (map[string]*models.PluginSettingInfoDTO, error)
|
||||
// GetPluginDashboards gets dashboards for a certain org/plugin.
|
||||
@@ -41,6 +52,10 @@ type Manager interface {
|
||||
requestHandler DataRequestHandler) (PluginDashboardInfoDTO, error)
|
||||
// ScanningErrors returns plugin scanning errors encountered.
|
||||
ScanningErrors() []PluginError
|
||||
// LoadPluginDashboard loads a plugin dashboard.
|
||||
LoadPluginDashboard(pluginID, path string) (*models.Dashboard, error)
|
||||
// IsAppInstalled returns whether an app is installed.
|
||||
IsAppInstalled(id string) bool
|
||||
}
|
||||
|
||||
type ImportDashboardInput struct {
|
||||
|
||||
@@ -78,16 +78,14 @@ func pluginScenario(t *testing.T, desc string, fn func(*testing.T, *PluginManage
|
||||
t.Helper()
|
||||
|
||||
t.Run("Given a plugin", func(t *testing.T) {
|
||||
pm := &PluginManager{
|
||||
Cfg: &setting.Cfg{
|
||||
FeatureToggles: map[string]bool{},
|
||||
PluginSettings: setting.PluginSettings{
|
||||
"test-app": map[string]string{
|
||||
"path": "testdata/test-app",
|
||||
},
|
||||
pm := newManager(&setting.Cfg{
|
||||
FeatureToggles: map[string]bool{},
|
||||
PluginSettings: setting.PluginSettings{
|
||||
"test-app": map[string]string{
|
||||
"path": "testdata/test-app",
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
err := pm.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
func (pm *PluginManager) GetPluginDashboards(orgId int64, pluginId string) ([]*plugins.PluginDashboardInfoDTO, error) {
|
||||
plugin, exists := pm.plugins[pluginId]
|
||||
func (pm *PluginManager) GetPluginDashboards(orgID int64, pluginID string) ([]*plugins.PluginDashboardInfoDTO, error) {
|
||||
plugin, exists := pm.plugins[pluginID]
|
||||
if !exists {
|
||||
return nil, plugins.PluginNotFoundError{PluginID: pluginId}
|
||||
return nil, plugins.PluginNotFoundError{PluginID: pluginID}
|
||||
}
|
||||
|
||||
result := make([]*plugins.PluginDashboardInfoDTO, 0)
|
||||
|
||||
// load current dashboards
|
||||
query := models.GetDashboardsByPluginIdQuery{OrgId: orgId, PluginId: pluginId}
|
||||
query := models.GetDashboardsByPluginIdQuery{OrgId: orgID, PluginId: pluginID}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,10 +70,10 @@ func (pm *PluginManager) GetPluginDashboards(orgId int64, pluginId string) ([]*p
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (pm *PluginManager) LoadPluginDashboard(pluginId, path string) (*models.Dashboard, error) {
|
||||
plugin, exists := pm.plugins[pluginId]
|
||||
func (pm *PluginManager) LoadPluginDashboard(pluginID, path string) (*models.Dashboard, error) {
|
||||
plugin, exists := pm.plugins[pluginID]
|
||||
if !exists {
|
||||
return nil, plugins.PluginNotFoundError{PluginID: pluginId}
|
||||
return nil, plugins.PluginNotFoundError{PluginID: pluginID}
|
||||
}
|
||||
|
||||
dashboardFilePath := filepath.Join(plugin.PluginDir, path)
|
||||
|
||||
@@ -7,61 +7,53 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPluginDashboards(t *testing.T) {
|
||||
Convey("When asking for plugin dashboard info", t, func() {
|
||||
pm := &PluginManager{
|
||||
Cfg: &setting.Cfg{
|
||||
FeatureToggles: map[string]bool{},
|
||||
PluginSettings: setting.PluginSettings{
|
||||
"test-app": map[string]string{
|
||||
"path": "testdata/test-app",
|
||||
},
|
||||
},
|
||||
func TestGetPluginDashboards(t *testing.T) {
|
||||
pm := newManager(&setting.Cfg{
|
||||
FeatureToggles: map[string]bool{},
|
||||
PluginSettings: setting.PluginSettings{
|
||||
"test-app": map[string]string{
|
||||
"path": "testdata/test-app",
|
||||
},
|
||||
}
|
||||
err := pm.Init()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
if query.Slug == "nginx-connections" {
|
||||
dash := models.NewDashboard("Nginx Connections")
|
||||
dash.Data.Set("revision", "1.1")
|
||||
query.Result = dash
|
||||
return nil
|
||||
}
|
||||
|
||||
return models.ErrDashboardNotFound
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardsByPluginIdQuery) error {
|
||||
var data = simplejson.New()
|
||||
data.Set("title", "Nginx Connections")
|
||||
data.Set("revision", 22)
|
||||
|
||||
query.Result = []*models.Dashboard{
|
||||
{Slug: "nginx-connections", Data: data},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
dashboards, err := pm.GetPluginDashboards(1, "test-app")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("should return 2 dashboards", func() {
|
||||
So(len(dashboards), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("should include installed version info", func() {
|
||||
So(dashboards[0].Title, ShouldEqual, "Nginx Connections")
|
||||
So(dashboards[0].Revision, ShouldEqual, 25)
|
||||
So(dashboards[0].ImportedRevision, ShouldEqual, 22)
|
||||
So(dashboards[0].ImportedUri, ShouldEqual, "db/nginx-connections")
|
||||
|
||||
So(dashboards[1].Revision, ShouldEqual, 2)
|
||||
So(dashboards[1].ImportedRevision, ShouldEqual, 0)
|
||||
})
|
||||
},
|
||||
})
|
||||
err := pm.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
if query.Slug == "nginx-connections" {
|
||||
dash := models.NewDashboard("Nginx Connections")
|
||||
dash.Data.Set("revision", "1.1")
|
||||
query.Result = dash
|
||||
return nil
|
||||
}
|
||||
|
||||
return models.ErrDashboardNotFound
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardsByPluginIdQuery) error {
|
||||
var data = simplejson.New()
|
||||
data.Set("title", "Nginx Connections")
|
||||
data.Set("revision", 22)
|
||||
|
||||
query.Result = []*models.Dashboard{
|
||||
{Slug: "nginx-connections", Data: data},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
dashboards, err := pm.GetPluginDashboards(1, "test-app")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, dashboards, 2)
|
||||
assert.Equal(t, "Nginx Connections", dashboards[0].Title)
|
||||
assert.Equal(t, int64(25), dashboards[0].Revision)
|
||||
assert.Equal(t, int64(22), dashboards[0].ImportedRevision)
|
||||
assert.Equal(t, "db/nginx-connections", dashboards[0].ImportedUri)
|
||||
|
||||
assert.Equal(t, int64(2), dashboards[1].Revision)
|
||||
assert.Equal(t, int64(0), dashboards[1].ImportedRevision)
|
||||
}
|
||||
|
||||
@@ -28,11 +28,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Panels map[string]*plugins.PanelPlugin
|
||||
StaticRoutes []*plugins.PluginStaticRoute
|
||||
Apps map[string]*plugins.AppPlugin
|
||||
PluginTypes map[string]interface{}
|
||||
|
||||
plog log.Logger
|
||||
)
|
||||
|
||||
@@ -63,31 +58,34 @@ type PluginManager struct {
|
||||
grafanaHasUpdate bool
|
||||
pluginScanningErrors map[string]plugins.PluginError
|
||||
|
||||
renderer *plugins.RendererPlugin
|
||||
dataSources map[string]*plugins.DataSourcePlugin
|
||||
plugins map[string]*plugins.PluginBase
|
||||
renderer *plugins.RendererPlugin
|
||||
dataSources map[string]*plugins.DataSourcePlugin
|
||||
plugins map[string]*plugins.PluginBase
|
||||
panels map[string]*plugins.PanelPlugin
|
||||
apps map[string]*plugins.AppPlugin
|
||||
staticRoutes []*plugins.PluginStaticRoute
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.RegisterService(&PluginManager{
|
||||
dataSources: map[string]*plugins.DataSourcePlugin{},
|
||||
registry.Register(®istry.Descriptor{
|
||||
Name: "PluginManager",
|
||||
Instance: newManager(nil),
|
||||
})
|
||||
}
|
||||
|
||||
func newManager(cfg *setting.Cfg) *PluginManager {
|
||||
return &PluginManager{
|
||||
Cfg: cfg,
|
||||
dataSources: map[string]*plugins.DataSourcePlugin{},
|
||||
plugins: map[string]*plugins.PluginBase{},
|
||||
panels: map[string]*plugins.PanelPlugin{},
|
||||
apps: map[string]*plugins.AppPlugin{},
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *PluginManager) Init() error {
|
||||
pm.log = log.New("plugins")
|
||||
plog = log.New("plugins")
|
||||
|
||||
StaticRoutes = []*plugins.PluginStaticRoute{}
|
||||
Panels = map[string]*plugins.PanelPlugin{}
|
||||
Apps = map[string]*plugins.AppPlugin{}
|
||||
pm.plugins = map[string]*plugins.PluginBase{}
|
||||
PluginTypes = map[string]interface{}{
|
||||
"panel": plugins.PanelPlugin{},
|
||||
"datasource": plugins.DataSourcePlugin{},
|
||||
"app": plugins.AppPlugin{},
|
||||
"renderer": plugins.RendererPlugin{},
|
||||
}
|
||||
pm.pluginScanningErrors = map[string]plugins.PluginError{}
|
||||
|
||||
pm.log.Info("Starting plugin search")
|
||||
@@ -133,24 +131,24 @@ func (pm *PluginManager) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, panel := range Panels {
|
||||
for _, panel := range pm.panels {
|
||||
staticRoutes := panel.InitFrontendPlugin(pm.Cfg)
|
||||
StaticRoutes = append(StaticRoutes, staticRoutes...)
|
||||
pm.staticRoutes = append(pm.staticRoutes, staticRoutes...)
|
||||
}
|
||||
|
||||
for _, ds := range pm.dataSources {
|
||||
staticRoutes := ds.InitFrontendPlugin(pm.Cfg)
|
||||
StaticRoutes = append(StaticRoutes, staticRoutes...)
|
||||
pm.staticRoutes = append(pm.staticRoutes, staticRoutes...)
|
||||
}
|
||||
|
||||
for _, app := range Apps {
|
||||
staticRoutes := app.InitApp(Panels, pm.dataSources, pm.Cfg)
|
||||
StaticRoutes = append(StaticRoutes, staticRoutes...)
|
||||
for _, app := range pm.apps {
|
||||
staticRoutes := app.InitApp(pm.panels, pm.dataSources, pm.Cfg)
|
||||
pm.staticRoutes = append(pm.staticRoutes, staticRoutes...)
|
||||
}
|
||||
|
||||
if pm.renderer != nil {
|
||||
staticRoutes := pm.renderer.InitFrontendPlugin(pm.Cfg)
|
||||
StaticRoutes = append(StaticRoutes, staticRoutes...)
|
||||
pm.staticRoutes = append(pm.staticRoutes, staticRoutes...)
|
||||
}
|
||||
|
||||
for _, p := range pm.plugins {
|
||||
@@ -203,6 +201,14 @@ func (pm *PluginManager) DataSourceCount() int {
|
||||
return len(pm.dataSources)
|
||||
}
|
||||
|
||||
func (pm *PluginManager) PanelCount() int {
|
||||
return len(pm.panels)
|
||||
}
|
||||
|
||||
func (pm *PluginManager) AppCount() int {
|
||||
return len(pm.apps)
|
||||
}
|
||||
|
||||
func (pm *PluginManager) Plugins() []*plugins.PluginBase {
|
||||
var rslt []*plugins.PluginBase
|
||||
for _, p := range pm.plugins {
|
||||
@@ -212,10 +218,23 @@ func (pm *PluginManager) Plugins() []*plugins.PluginBase {
|
||||
return rslt
|
||||
}
|
||||
|
||||
func (pm *PluginManager) Apps() []*plugins.AppPlugin {
|
||||
var rslt []*plugins.AppPlugin
|
||||
for _, p := range pm.apps {
|
||||
rslt = append(rslt, p)
|
||||
}
|
||||
|
||||
return rslt
|
||||
}
|
||||
|
||||
func (pm *PluginManager) GetPlugin(id string) *plugins.PluginBase {
|
||||
return pm.plugins[id]
|
||||
}
|
||||
|
||||
func (pm *PluginManager) GetApp(id string) *plugins.AppPlugin {
|
||||
return pm.apps[id]
|
||||
}
|
||||
|
||||
func (pm *PluginManager) GrafanaLatestVersion() string {
|
||||
return pm.grafanaLatestVersion
|
||||
}
|
||||
@@ -270,6 +289,13 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
|
||||
pm.log.Debug("Initial plugin loading done")
|
||||
|
||||
pluginTypes := map[string]interface{}{
|
||||
"panel": plugins.PanelPlugin{},
|
||||
"datasource": plugins.DataSourcePlugin{},
|
||||
"app": plugins.AppPlugin{},
|
||||
"renderer": plugins.RendererPlugin{},
|
||||
}
|
||||
|
||||
// 2nd pass: Validate and register plugins
|
||||
for dpath, plugin := range scanner.plugins {
|
||||
// Try to find any root plugin
|
||||
@@ -298,7 +324,7 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
|
||||
pm.log.Debug("Attempting to add plugin", "id", plugin.Id)
|
||||
|
||||
pluginGoType, exists := PluginTypes[plugin.Type]
|
||||
pluginGoType, exists := pluginTypes[plugin.Type]
|
||||
if !exists {
|
||||
return fmt.Errorf("unknown plugin type %q", plugin.Type)
|
||||
}
|
||||
@@ -364,13 +390,13 @@ func (pm *PluginManager) loadPlugin(jsonParser *json.Decoder, pluginBase *plugin
|
||||
pm.dataSources[p.Id] = p
|
||||
pb = &p.PluginBase
|
||||
case *plugins.PanelPlugin:
|
||||
Panels[p.Id] = p
|
||||
pm.panels[p.Id] = p
|
||||
pb = &p.PluginBase
|
||||
case *plugins.RendererPlugin:
|
||||
pm.renderer = p
|
||||
pb = &p.PluginBase
|
||||
case *plugins.AppPlugin:
|
||||
Apps[p.Id] = p
|
||||
pm.apps[p.Id] = p
|
||||
pb = &p.PluginBase
|
||||
default:
|
||||
panic(fmt.Sprintf("Unrecognized plugin type %T", plug))
|
||||
@@ -651,3 +677,7 @@ func (pm *PluginManager) GetDataPlugin(id string) plugins.DataPlugin {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *PluginManager) StaticRoutes() []*plugins.PluginStaticRoute {
|
||||
return pm.staticRoutes
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ func TestPluginManager_Init(t *testing.T) {
|
||||
|
||||
assert.Empty(t, pm.scanningErrors)
|
||||
assert.Greater(t, len(pm.dataSources), 1)
|
||||
assert.Greater(t, len(Panels), 1)
|
||||
assert.Greater(t, len(pm.panels), 1)
|
||||
assert.Equal(t, "app/plugins/datasource/graphite/module", pm.dataSources["graphite"].Module)
|
||||
assert.NotEmpty(t, Apps)
|
||||
assert.Equal(t, "public/plugins/test-app/img/logo_large.png", Apps["test-app"].Info.Logos.Large)
|
||||
assert.Equal(t, "public/plugins/test-app/img/screenshot2.png", Apps["test-app"].Info.Screenshots[1].Path)
|
||||
assert.NotEmpty(t, pm.apps)
|
||||
assert.Equal(t, "public/plugins/test-app/img/logo_large.png", pm.apps["test-app"].Info.Logos.Large)
|
||||
assert.Equal(t, "public/plugins/test-app/img/screenshot2.png", pm.apps["test-app"].Info.Screenshots[1].Path)
|
||||
})
|
||||
|
||||
t.Run("With external back-end plugin lacking signature", func(t *testing.T) {
|
||||
@@ -253,15 +253,12 @@ func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager {
|
||||
staticRootPath, err := filepath.Abs("../../../public/")
|
||||
require.NoError(t, err)
|
||||
|
||||
pm := &PluginManager{
|
||||
Cfg: &setting.Cfg{
|
||||
Raw: ini.Empty(),
|
||||
Env: setting.Prod,
|
||||
StaticRootPath: staticRootPath,
|
||||
},
|
||||
BackendPluginManager: &fakeBackendPluginManager{},
|
||||
dataSources: map[string]*plugins.DataSourcePlugin{},
|
||||
}
|
||||
pm := newManager(&setting.Cfg{
|
||||
Raw: ini.Empty(),
|
||||
Env: setting.Prod,
|
||||
StaticRootPath: staticRootPath,
|
||||
})
|
||||
pm.BackendPluginManager = &fakeBackendPluginManager{}
|
||||
for _, cb := range cbs {
|
||||
cb(pm)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (pm *PluginManager) GetPluginSettings(orgID int64) (map[string]*models.Plug
|
||||
}
|
||||
|
||||
// apps are disabled by default unless autoEnabled: true
|
||||
if app, exists := Apps[pluginDef.Id]; exists {
|
||||
if app, exists := pm.apps[pluginDef.Id]; exists {
|
||||
opt.Enabled = app.AutoEnabled
|
||||
opt.Pinned = app.AutoEnabled
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (pm *PluginManager) GetEnabledPlugins(orgID int64) (*plugins.EnabledPlugins
|
||||
return enabledPlugins, err
|
||||
}
|
||||
|
||||
for pluginID, app := range Apps {
|
||||
for pluginID, app := range pm.apps {
|
||||
if b, ok := pluginSettingMap[pluginID]; ok {
|
||||
app.Pinned = b.Pinned
|
||||
enabledPlugins.Apps = append(enabledPlugins.Apps, app)
|
||||
@@ -77,7 +77,7 @@ func (pm *PluginManager) GetEnabledPlugins(orgID int64) (*plugins.EnabledPlugins
|
||||
}
|
||||
}
|
||||
|
||||
for _, panel := range Panels {
|
||||
for _, panel := range pm.panels {
|
||||
if _, exists := pluginSettingMap[panel.Id]; exists {
|
||||
enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (pm *PluginManager) GetEnabledPlugins(orgID int64) (*plugins.EnabledPlugins
|
||||
}
|
||||
|
||||
// IsAppInstalled checks if an app plugin with provided plugin ID is installed.
|
||||
func IsAppInstalled(pluginID string) bool {
|
||||
_, exists := Apps[pluginID]
|
||||
func (pm *PluginManager) IsAppInstalled(pluginID string) bool {
|
||||
_, exists := pm.apps[pluginID]
|
||||
return exists
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"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/manager"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
@@ -21,9 +20,9 @@ func init() {
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
DataService *tsdb.Service `inject:""`
|
||||
PluginManager *manager.PluginManager `inject:""`
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
DataService *tsdb.Service `inject:""`
|
||||
PluginManager plugins.Manager `inject:""`
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user