Plugins: Add inititialization stage to plugin loader pipeline (#72667)
* first pass * migrate tests * simplify * fix comments * fix linter * nil checks * remove comment
This commit is contained in:
@@ -47,7 +47,7 @@ func New(cfg *config.Cfg, opts Opts) *Bootstrap {
|
||||
opts.ConstructFunc = DefaultConstructFunc(signature.DefaultCalculator(cfg), assetpath.DefaultService(cfg))
|
||||
}
|
||||
|
||||
if len(opts.DecorateFuncs) == 0 {
|
||||
if opts.DecorateFuncs == nil {
|
||||
opts.DecorateFuncs = DefaultDecorateFuncs
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func New(cfg *config.Cfg, opts Opts) *Discovery {
|
||||
opts.FindFunc = DefaultFindFunc(cfg)
|
||||
}
|
||||
|
||||
if len(opts.FindFilterFuncs) == 0 {
|
||||
if opts.FindFilterFuncs == nil {
|
||||
opts.FindFilterFuncs = []FindFilterFunc{} // no filters by default
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Package initialization defines the fourth stage of the plugin loader pipeline.
|
||||
//
|
||||
// The Initialization stage must implement the Initializer interface.
|
||||
// - Initialize(ctx context.Context, ps []*plugins.Plugin) ([]*plugins.Plugin, error)
|
||||
|
||||
package initialization
|
||||
@@ -0,0 +1,67 @@
|
||||
package initialization
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
)
|
||||
|
||||
// Initializer is responsible for the Initialization stage of the plugin loader pipeline.
|
||||
type Initializer interface {
|
||||
Initialize(ctx context.Context, ps []*plugins.Plugin) ([]*plugins.Plugin, error)
|
||||
}
|
||||
|
||||
// InitializeFunc is the function used for the Initialize step of the Initialization stage.
|
||||
type InitializeFunc func(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error)
|
||||
|
||||
type Initialize struct {
|
||||
cfg *config.Cfg
|
||||
initializeSteps []InitializeFunc
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
type Opts struct {
|
||||
InitializeFuncs []InitializeFunc
|
||||
}
|
||||
|
||||
// New returns a new Initialization stage.
|
||||
func New(cfg *config.Cfg, opts Opts) *Initialize {
|
||||
if opts.InitializeFuncs == nil {
|
||||
opts.InitializeFuncs = []InitializeFunc{}
|
||||
}
|
||||
|
||||
return &Initialize{
|
||||
cfg: cfg,
|
||||
initializeSteps: opts.InitializeFuncs,
|
||||
log: log.New("plugins.initialization"),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize will execute the Initialize steps of the Initialization stage.
|
||||
func (i *Initialize) Initialize(ctx context.Context, ps []*plugins.Plugin) ([]*plugins.Plugin, error) {
|
||||
if len(i.initializeSteps) == 0 {
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
initializedPlugins := make([]*plugins.Plugin, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
var ip *plugins.Plugin
|
||||
stepFailed := false
|
||||
for _, init := range i.initializeSteps {
|
||||
ip, err = init(ctx, p)
|
||||
if err != nil {
|
||||
stepFailed = true
|
||||
i.log.Error("Could not initialize plugin", "pluginId", p.ID, "err", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !stepFailed {
|
||||
initializedPlugins = append(initializedPlugins, ip)
|
||||
}
|
||||
}
|
||||
|
||||
return initializedPlugins, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package initialization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/envvars"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
)
|
||||
|
||||
// BackendClientInit implements an InitializeFunc for initializing a backend plugin process.
|
||||
//
|
||||
// It uses the envvars.Provider to retrieve the environment variables required for the plugin and the plugins.BackendFactoryProvider
|
||||
// to get fetch backend factory, which is used to form a connection to the backend plugin process.
|
||||
//
|
||||
// Note: This step does not start the backend plugin process.
|
||||
type BackendClientInit struct {
|
||||
envVarProvider envvars.Provider
|
||||
backendProvider plugins.BackendFactoryProvider
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewBackendClientInitStep returns a new InitializeFunc for registering a backend plugin process.
|
||||
func NewBackendClientInitStep(envVarProvider envvars.Provider,
|
||||
backendProvider plugins.BackendFactoryProvider) InitializeFunc {
|
||||
return newBackendProcessRegistration(envVarProvider, backendProvider).Initialize
|
||||
}
|
||||
|
||||
func newBackendProcessRegistration(envVarProvider envvars.Provider,
|
||||
backendProvider plugins.BackendFactoryProvider) *BackendClientInit {
|
||||
return &BackendClientInit{
|
||||
backendProvider: backendProvider,
|
||||
envVarProvider: envVarProvider,
|
||||
log: log.New("plugins.backend.registration"),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize will initialize a backend plugin client, if the plugin is a backend plugin.
|
||||
func (b *BackendClientInit) Initialize(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
||||
if p.Backend {
|
||||
backendFactory := b.backendProvider.BackendFactory(ctx, p)
|
||||
if backendFactory == nil {
|
||||
return nil, errors.New("could not find backend factory for plugin")
|
||||
}
|
||||
|
||||
env, err := b.envVarProvider.Get(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if backendClient, err := backendFactory(p.ID, p.Logger(), env); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
p.RegisterClient(backendClient)
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// PluginRegistration implements an InitializeFunc for registering a plugin with the plugin registry.
|
||||
type PluginRegistration struct {
|
||||
pluginRegistry registry.Service
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewPluginRegistrationStep returns a new InitializeFunc for registering a plugin with the plugin registry.
|
||||
func NewPluginRegistrationStep(pluginRegistry registry.Service) InitializeFunc {
|
||||
return newPluginRegistration(pluginRegistry).Initialize
|
||||
}
|
||||
|
||||
func newPluginRegistration(pluginRegistry registry.Service) *PluginRegistration {
|
||||
return &PluginRegistration{
|
||||
pluginRegistry: pluginRegistry,
|
||||
log: log.New("plugins.registration"),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize registers the plugin with the plugin registry.
|
||||
func (r *PluginRegistration) Initialize(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
||||
if err := r.pluginRegistry.Add(ctx, p); err != nil {
|
||||
r.log.Error("Could not register plugin", "pluginID", p.ID, "err", err)
|
||||
return nil, errors.New("could not register plugin")
|
||||
}
|
||||
if !p.IsCorePlugin() {
|
||||
r.log.Info("Plugin registered", "pluginID", p.ID)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package initialization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
)
|
||||
|
||||
func TestInitializer_Initialize(t *testing.T) {
|
||||
t.Run("core backend datasource", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test",
|
||||
Type: plugins.TypeDataSource,
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Name: "Example dashboard",
|
||||
Type: plugins.TypeDashboard,
|
||||
},
|
||||
},
|
||||
Backend: true,
|
||||
},
|
||||
Class: plugins.ClassCore,
|
||||
}
|
||||
|
||||
stepFunc := NewBackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p})
|
||||
|
||||
var err error
|
||||
p, err = stepFunc(context.Background(), p)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, exists := p.Client()
|
||||
require.True(t, exists)
|
||||
require.NotNil(t, c)
|
||||
})
|
||||
|
||||
t.Run("renderer", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test",
|
||||
Type: plugins.TypeRenderer,
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: ">=8.x",
|
||||
},
|
||||
Backend: true,
|
||||
},
|
||||
Class: plugins.ClassExternal,
|
||||
}
|
||||
|
||||
stepFunc := NewBackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p})
|
||||
|
||||
var err error
|
||||
p, err = stepFunc(context.Background(), p)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, exists := p.Client()
|
||||
require.True(t, exists)
|
||||
require.NotNil(t, c)
|
||||
})
|
||||
|
||||
t.Run("secretsmanager", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test",
|
||||
Type: plugins.TypeSecretsManager,
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: ">=8.x",
|
||||
},
|
||||
Backend: true,
|
||||
},
|
||||
Class: plugins.ClassExternal,
|
||||
}
|
||||
|
||||
stepFunc := NewBackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p})
|
||||
|
||||
var err error
|
||||
p, err = stepFunc(context.Background(), p)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, exists := p.Client()
|
||||
require.True(t, exists)
|
||||
require.NotNil(t, c)
|
||||
})
|
||||
|
||||
t.Run("non backend plugin app", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
Backend: false,
|
||||
},
|
||||
}
|
||||
|
||||
i := NewBackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{
|
||||
plugin: p,
|
||||
})
|
||||
|
||||
var err error
|
||||
p, err = i(context.Background(), p)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, exists := p.Client()
|
||||
require.False(t, exists)
|
||||
require.Nil(t, c)
|
||||
})
|
||||
}
|
||||
|
||||
type fakeBackendProvider struct {
|
||||
plugins.BackendFactoryProvider
|
||||
|
||||
plugin *plugins.Plugin
|
||||
}
|
||||
|
||||
func (f *fakeBackendProvider) BackendFactory(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
return func(_ string, _ log.Logger, _ []string) (backendplugin.Plugin, error) {
|
||||
return f.plugin, nil
|
||||
}
|
||||
}
|
||||
|
||||
type fakeEnvVarsProvider struct {
|
||||
GetFunc func(ctx context.Context, p *plugins.Plugin) []string
|
||||
}
|
||||
|
||||
func (f *fakeEnvVarsProvider) Get(ctx context.Context, p *plugins.Plugin) ([]string, error) {
|
||||
if f.GetFunc != nil {
|
||||
return f.GetFunc(ctx, p), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user