From 17d84f56f74d7203039a1f3e17bda7c1ff998833 Mon Sep 17 00:00:00 2001 From: Michael Mandrus Date: Tue, 24 May 2022 11:48:47 -0400 Subject: [PATCH] wire up loading of secretsmanager plugin, using renderer plugin as a model --- .../backendplugin/grpcplugin/client.go | 39 ++++++++++++++----- .../backendplugin/grpcplugin/client_v2.go | 19 +++++++++ .../backendplugin/provider/provider.go | 29 +++++++++++++- pkg/plugins/ifaces.go | 5 +++ .../loader/initializer/initializer_test.go | 30 ++++++++++++++ pkg/plugins/manager/manager.go | 11 ++++++ pkg/plugins/plugins.go | 26 ++++++++----- 7 files changed, 138 insertions(+), 21 deletions(-) diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index 706c7e2504c..f0ccbb0d0e5 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" + "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" goplugin "github.com/hashicorp/go-plugin" ) @@ -41,23 +42,28 @@ func newClientConfig(executablePath string, env []string, logger log.Logger, // StartRendererFunc callback function called when a renderer plugin is started. type StartRendererFunc func(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error +// StartSecretsManagerFunc callback function called when a secrets manager plugin is started. +type StartSecretsManagerFunc func(pluginID string, secretsmanager secretsmanagerplugin.SecretsManagerPlugin, logger log.Logger) error + // PluginDescriptor is a descriptor used for registering backend plugins. type PluginDescriptor struct { - pluginID string - executablePath string - managed bool - versionedPlugins map[int]goplugin.PluginSet - startRendererFn StartRendererFunc + pluginID string + executablePath string + managed bool + versionedPlugins map[int]goplugin.PluginSet + startRendererFn StartRendererFunc + startSecretsmanagerFn StartSecretsManagerFunc } // getV2PluginSet returns list of plugins supported on v2. func getV2PluginSet() goplugin.PluginSet { return goplugin.PluginSet{ - "diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{}, - "resource": &grpcplugin.ResourceGRPCPlugin{}, - "data": &grpcplugin.DataGRPCPlugin{}, - "stream": &grpcplugin.StreamGRPCPlugin{}, - "renderer": &pluginextensionv2.RendererGRPCPlugin{}, + "diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{}, + "resource": &grpcplugin.ResourceGRPCPlugin{}, + "data": &grpcplugin.DataGRPCPlugin{}, + "stream": &grpcplugin.StreamGRPCPlugin{}, + "renderer": &pluginextensionv2.RendererGRPCPlugin{}, + "secretsmanager": &secretsmanagerplugin.SecretsManagerGRPCPlugin{}, } } @@ -85,3 +91,16 @@ func NewRendererPlugin(pluginID, executablePath string, startFn StartRendererFun startRendererFn: startFn, }) } + +// NewSecetsManagerPlugin creates a new secrets manager plugin factory used for registering a backend secrets manager plugin. +func NewSecretsManagerPlugin(pluginID, executablePath string, startFn StartSecretsManagerFunc) backendplugin.PluginFactoryFunc { + return newPlugin(PluginDescriptor{ + pluginID: pluginID, + executablePath: executablePath, + managed: false, + versionedPlugins: map[int]goplugin.PluginSet{ + grpcplugin.ProtocolVersion: getV2PluginSet(), + }, + startSecretsmanagerFn: startFn, + }) +} diff --git a/pkg/plugins/backendplugin/grpcplugin/client_v2.go b/pkg/plugins/backendplugin/grpcplugin/client_v2.go index 6d44516bda3..86efea54285 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_v2.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_v2.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" + "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/util/errutil" "github.com/hashicorp/go-plugin" "google.golang.org/grpc/codes" @@ -24,6 +25,7 @@ type ClientV2 struct { grpcplugin.DataClient grpcplugin.StreamClient pluginextensionv2.RendererPlugin + secretsmanagerplugin.SecretsManagerPlugin } func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugin.ClientProtocol) (pluginClient, error) { @@ -52,6 +54,11 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi return nil, err } + rawSecretsManager, err := rpcClient.Dispense("secretsmanager") + if err != nil { + return nil, err + } + c := ClientV2{} if rawDiagnostics != nil { if diagnosticsClient, ok := rawDiagnostics.(grpcplugin.DiagnosticsClient); ok { @@ -83,12 +90,24 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi } } + if rawSecretsManager != nil { + if secretsManagerPlugin, ok := rawSecretsManager.(secretsmanagerplugin.SecretsManagerPlugin); ok { + c.SecretsManagerPlugin = secretsManagerPlugin + } + } + if descriptor.startRendererFn != nil { if err := descriptor.startRendererFn(descriptor.pluginID, c.RendererPlugin, logger); err != nil { return nil, err } } + if descriptor.startSecretsmanagerFn != nil { + if err := descriptor.startSecretsmanagerFn(descriptor.pluginID, c.SecretsManagerPlugin, logger); err != nil { + return nil, err + } + } + return &c, nil } diff --git a/pkg/plugins/backendplugin/provider/provider.go b/pkg/plugins/backendplugin/provider/provider.go index 406f1f70639..218d89df5e1 100644 --- a/pkg/plugins/backendplugin/provider/provider.go +++ b/pkg/plugins/backendplugin/provider/provider.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" + "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" ) // PluginBackendProvider is a function type for initializing a Plugin backend. @@ -24,7 +25,7 @@ type Service struct { func New(providers ...PluginBackendProvider) *Service { if len(providers) == 0 { - return New(RendererProvider, DefaultProvider) + return New(RendererProvider, SecretsManagerProvider, DefaultProvider) } return &Service{ providerChain: providers, @@ -32,7 +33,7 @@ func New(providers ...PluginBackendProvider) *Service { } func ProvideService(coreRegistry *coreplugin.Registry) *Service { - return New(coreRegistry.BackendFactoryProvider(), RendererProvider, DefaultProvider) + return New(coreRegistry.BackendFactoryProvider(), RendererProvider, SecretsManagerProvider, DefaultProvider) } func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { @@ -56,6 +57,18 @@ var RendererProvider PluginBackendProvider = func(_ context.Context, p *plugins. ) } +var SecretsManagerProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { + if !p.IsSecretsManager() { + return nil + } + return grpcplugin.NewSecretsManagerPlugin(p.ID, filepath.Join(p.PluginDir, secretsManagerStartCmd()), + func(pluginID string, secretsmanager secretsmanagerplugin.SecretsManagerPlugin, logger log.Logger) error { + p.SecretsManager = secretsmanager + return nil + }, + ) +} + var DefaultProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { // TODO check for executable return grpcplugin.NewBackendPlugin(p.ID, filepath.Join(p.PluginDir, pluginStartCmd(p.Executable))) @@ -84,3 +97,15 @@ func rendererStartCmd() string { return fmt.Sprintf("%s_%s_%s%s", "plugin_start", os, strings.ToLower(arch), extension) } + +func secretsManagerStartCmd() string { + os := strings.ToLower(runtime.GOOS) + arch := runtime.GOARCH + extension := "" + + if os == "windows" { + extension = ".exe" + } + + return fmt.Sprintf("%s_%s_%s%s", "secrets_plugin_start", os, strings.ToLower(arch), extension) +} diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index c10283b0031..dae059f148e 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -60,6 +60,11 @@ type RendererManager interface { Renderer() *Plugin } +type SecretsManagerManager interface { + // SecretsManager returns a secretsmanager plugin + SecretsManager() *Plugin +} + type StaticRouteResolver interface { Routes() []*StaticRoute } diff --git a/pkg/plugins/manager/loader/initializer/initializer_test.go b/pkg/plugins/manager/loader/initializer/initializer_test.go index 75052de0753..34fe5c96d9e 100644 --- a/pkg/plugins/manager/loader/initializer/initializer_test.go +++ b/pkg/plugins/manager/loader/initializer/initializer_test.go @@ -79,6 +79,36 @@ func TestInitializer_Initialize(t *testing.T) { assert.NotNil(t, c) }) + t.Run("secretsmanager", func(t *testing.T) { + p := &plugins.Plugin{ + JSONData: plugins.JSONData{ + ID: "test", + Type: plugins.SecretsManager, + Dependencies: plugins.Dependencies{ + GrafanaVersion: ">=8.x", + }, + Backend: true, + }, + PluginDir: absCurPath, + Class: plugins.External, + } + + i := &Initializer{ + cfg: plugins.NewCfg(), + log: log.NewNopLogger(), + backendProvider: &fakeBackendProvider{ + plugin: p, + }, + } + + err := i.Initialize(context.Background(), p) + assert.NoError(t, err) + + c, exists := p.Client() + assert.True(t, exists) + assert.NotNil(t, c) + }) + t.Run("non backend plugin app", func(t *testing.T) { p := &plugins.Plugin{ JSONData: plugins.JSONData{ diff --git a/pkg/plugins/manager/manager.go b/pkg/plugins/manager/manager.go index 119fd500f30..821f76ca2a4 100644 --- a/pkg/plugins/manager/manager.go +++ b/pkg/plugins/manager/manager.go @@ -23,6 +23,7 @@ var _ plugins.Client = (*PluginManager)(nil) var _ plugins.Store = (*PluginManager)(nil) var _ plugins.StaticRouteResolver = (*PluginManager)(nil) var _ plugins.RendererManager = (*PluginManager)(nil) +var _ plugins.SecretsManagerManager = (*PluginManager)(nil) type PluginManager struct { cfg *plugins.Cfg @@ -116,6 +117,16 @@ func (m *PluginManager) Renderer() *plugins.Plugin { return nil } +func (m *PluginManager) SecretsManager() *plugins.Plugin { + for _, p := range m.plugins() { + if p.IsSecretsManager() { + return p + } + } + + return nil +} + func (m *PluginManager) Routes() []*plugins.StaticRoute { staticRoutes := make([]*plugins.StaticRoute, 0) diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 04cd731ddf4..dd17aae8501 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" + "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" ) type Plugin struct { @@ -36,9 +37,10 @@ type Plugin struct { Module string BaseURL string - Renderer pluginextensionv2.RendererPlugin - client backendplugin.Plugin - log log.Logger + Renderer pluginextensionv2.RendererPlugin + SecretsManager secretsmanagerplugin.SecretsManagerPlugin + client backendplugin.Plugin + log log.Logger } type PluginDTO struct { @@ -132,7 +134,7 @@ type JSONData struct { Streaming bool `json:"streaming"` SDK bool `json:"sdk,omitempty"` - // Backend (Datasource + Renderer) + // Backend (Datasource + Renderer + SecretsManager) Executable string `json:"executable,omitempty"` } @@ -347,6 +349,10 @@ func (p *Plugin) IsRenderer() bool { return p.Type == "renderer" } +func (p *Plugin) IsSecretsManager() bool { + return p.Type == "secretsmanager" +} + func (p *Plugin) IsDataSource() bool { return p.Type == "datasource" } @@ -384,20 +390,22 @@ var PluginTypes = []Type{ Panel, App, Renderer, + SecretsManager, } type Type string const ( - DataSource Type = "datasource" - Panel Type = "panel" - App Type = "app" - Renderer Type = "renderer" + DataSource Type = "datasource" + Panel Type = "panel" + App Type = "app" + Renderer Type = "renderer" + SecretsManager Type = "secretsmanager" ) func (pt Type) IsValid() bool { switch pt { - case DataSource, Panel, App, Renderer: + case DataSource, Panel, App, Renderer, SecretsManager: return true } return false