mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
Feature: Allow to load a core plugin as external (#75157)
This commit is contained in:
parent
e0659c05da
commit
61cdfba87a
@ -2195,6 +2195,12 @@ Available in Grafana v9.5.0 or later, and [OpenTelemetry must be configured as w
|
||||
|
||||
If `true`, propagate the tracing context to the plugin backend and enable tracing (if the backend supports it).
|
||||
|
||||
## as_external
|
||||
|
||||
Load an external version of a core plugin if it has been installed.
|
||||
|
||||
Experimental. Requires the feature toggle `externalCorePlugins` to be enabled.
|
||||
|
||||
<hr>
|
||||
|
||||
## [plugin.grafana-image-renderer]
|
||||
|
@ -136,6 +136,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `requestInstrumentationStatusSource` | Include a status source label for request metrics and logs |
|
||||
| `wargamesTesting` | Placeholder feature flag for internal testing |
|
||||
| `alertingInsights` | Show the new alerting insights landing page |
|
||||
| `externalCorePlugins` | Allow core plugins to be loaded as external |
|
||||
| `pluginsAPIMetrics` | Sends metrics of public grafana packages usage by plugins |
|
||||
|
||||
## Development feature toggles
|
||||
|
@ -127,5 +127,6 @@ export interface FeatureToggles {
|
||||
lokiRunQueriesInParallel?: boolean;
|
||||
wargamesTesting?: boolean;
|
||||
alertingInsights?: boolean;
|
||||
externalCorePlugins?: boolean;
|
||||
pluginsAPIMetrics?: boolean;
|
||||
}
|
||||
|
@ -759,6 +759,12 @@ var (
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
},
|
||||
{
|
||||
Name: "externalCorePlugins",
|
||||
Description: "Allow core plugins to be loaded as external",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "pluginsAPIMetrics",
|
||||
Description: "Sends metrics of public grafana packages usage by plugins",
|
||||
|
@ -108,4 +108,5 @@ requestInstrumentationStatusSource,experimental,@grafana/plugins-platform-backen
|
||||
lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false,false
|
||||
wargamesTesting,experimental,@grafana/hosted-grafana-team,false,false,false,false
|
||||
alertingInsights,experimental,@grafana/alerting-squad,false,false,false,true
|
||||
externalCorePlugins,experimental,@grafana/plugins-platform-backend,false,false,false,false
|
||||
pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,false,false,false,true
|
||||
|
|
@ -443,6 +443,10 @@ const (
|
||||
// Show the new alerting insights landing page
|
||||
FlagAlertingInsights = "alertingInsights"
|
||||
|
||||
// FlagExternalCorePlugins
|
||||
// Allow core plugins to be loaded as external
|
||||
FlagExternalCorePlugins = "externalCorePlugins"
|
||||
|
||||
// FlagPluginsAPIMetrics
|
||||
// Sends metrics of public grafana packages usage by plugins
|
||||
FlagPluginsAPIMetrics = "pluginsAPIMetrics"
|
||||
|
@ -33,6 +33,9 @@ func ProvideDiscoveryStage(cfg *config.Cfg, pf finder.Finder, pr registry.Servic
|
||||
func(_ context.Context, _ plugins.Class, b []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
||||
return NewDisablePluginsStep(cfg).Filter(b)
|
||||
},
|
||||
func(_ context.Context, c plugins.Class, b []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
||||
return NewAsExternalStep(cfg).Filter(c, b)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -157,3 +157,61 @@ func (c *DisablePlugins) Filter(bundles []*plugins.FoundBundle) ([]*plugins.Foun
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// AsExternal is a filter step that will skip loading a core plugin to use an external one.
|
||||
type AsExternal struct {
|
||||
log log.Logger
|
||||
cfg *config.Cfg
|
||||
}
|
||||
|
||||
// NewDisablePluginsStep returns a new DisablePlugins.
|
||||
func NewAsExternalStep(cfg *config.Cfg) *AsExternal {
|
||||
return &AsExternal{
|
||||
cfg: cfg,
|
||||
log: log.New("plugins.asExternal"),
|
||||
}
|
||||
}
|
||||
|
||||
// Filter will filter out any plugins that are marked to be disabled.
|
||||
func (c *AsExternal) Filter(cl plugins.Class, bundles []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
||||
if c.cfg.Features == nil || !c.cfg.Features.IsEnabled(featuremgmt.FlagExternalCorePlugins) {
|
||||
return bundles, nil
|
||||
}
|
||||
|
||||
if cl == plugins.ClassCore {
|
||||
res := []*plugins.FoundBundle{}
|
||||
for _, bundle := range bundles {
|
||||
pluginCfg := c.cfg.PluginSettings[bundle.Primary.JSONData.ID]
|
||||
// Skip core plugins if the feature flag is enabled and the plugin is in the skip list.
|
||||
// It could be loaded later as an external plugin.
|
||||
if pluginCfg["as_external"] == "true" {
|
||||
c.log.Debug("Skipping the core plugin load", "pluginID", bundle.Primary.JSONData.ID)
|
||||
} else {
|
||||
res = append(res, bundle)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if cl == plugins.ClassExternal {
|
||||
// Warn if the plugin is not found in the external plugins directory.
|
||||
asExternal := map[string]bool{}
|
||||
for pluginID, pluginCfg := range c.cfg.PluginSettings {
|
||||
if pluginCfg["as_external"] == "true" {
|
||||
asExternal[pluginID] = true
|
||||
}
|
||||
}
|
||||
for _, bundle := range bundles {
|
||||
if asExternal[bundle.Primary.JSONData.ID] {
|
||||
delete(asExternal, bundle.Primary.JSONData.ID)
|
||||
}
|
||||
}
|
||||
if len(asExternal) > 0 {
|
||||
for p := range asExternal {
|
||||
c.log.Error("Core plugin expected to be loaded as external, but it is missing", "pluginID", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bundles, nil
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -43,3 +46,59 @@ func TestSkipPlugins(t *testing.T) {
|
||||
require.Len(t, filtered, 1)
|
||||
require.Equal(t, filtered[0].Primary.JSONData.ID, "plugin3")
|
||||
}
|
||||
|
||||
func TestAsExternal(t *testing.T) {
|
||||
bundles := []*plugins.FoundBundle{
|
||||
{
|
||||
Primary: plugins.FoundPlugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "plugin1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Primary: plugins.FoundPlugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "plugin2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should skip a core plugin", func(t *testing.T) {
|
||||
cfg := &config.Cfg{
|
||||
Features: featuremgmt.WithFeatures(featuremgmt.FlagExternalCorePlugins),
|
||||
PluginSettings: setting.PluginSettings{
|
||||
"plugin1": map[string]string{
|
||||
"as_external": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s := NewAsExternalStep(cfg)
|
||||
filtered, err := s.Filter(plugins.ClassCore, bundles)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, filtered, 1)
|
||||
require.Equal(t, filtered[0].Primary.JSONData.ID, "plugin2")
|
||||
})
|
||||
|
||||
t.Run("should log an error if an external plugin is not available", func(t *testing.T) {
|
||||
cfg := &config.Cfg{
|
||||
Features: featuremgmt.WithFeatures(featuremgmt.FlagExternalCorePlugins),
|
||||
PluginSettings: setting.PluginSettings{
|
||||
"plugin3": map[string]string{
|
||||
"as_external": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fakeLogger := log.NewTestLogger()
|
||||
s := NewAsExternalStep(cfg)
|
||||
s.log = fakeLogger
|
||||
|
||||
filtered, err := s.Filter(plugins.ClassExternal, bundles)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, filtered, 2)
|
||||
require.Equal(t, fakeLogger.ErrorLogs.Calls, 1)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user