diff --git a/pkg/plugins/backendplugin/client.go b/pkg/plugins/backendplugin/client.go index 31ae18a2233..91ef941a1e1 100644 --- a/pkg/plugins/backendplugin/client.go +++ b/pkg/plugins/backendplugin/client.go @@ -30,9 +30,12 @@ var handshake = goplugin.HandshakeConfig{ MagicCookieValue: grpcplugin.MagicCookieValue, } -func newClientConfig(executablePath string, logger log.Logger, versionedPlugins map[int]goplugin.PluginSet) *goplugin.ClientConfig { +func newClientConfig(executablePath string, env []string, logger log.Logger, versionedPlugins map[int]goplugin.PluginSet) *goplugin.ClientConfig { + cmd := exec.Command(executablePath) + cmd.Env = env + return &goplugin.ClientConfig{ - Cmd: exec.Command(executablePath), + Cmd: cmd, HandshakeConfig: handshake, VersionedPlugins: versionedPlugins, Logger: logWrapper{Logger: logger}, diff --git a/pkg/plugins/backendplugin/manager.go b/pkg/plugins/backendplugin/manager.go index 4123e9a519b..21a5af24504 100644 --- a/pkg/plugins/backendplugin/manager.go +++ b/pkg/plugins/backendplugin/manager.go @@ -3,11 +3,13 @@ package backendplugin import ( "context" "errors" + "fmt" "io" "sync" "time" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/proxyutil" @@ -49,14 +51,18 @@ type Manager interface { } type manager struct { - pluginsMu sync.RWMutex - plugins map[string]*BackendPlugin - logger log.Logger + Cfg *setting.Cfg `inject:""` + License models.Licensing `inject:""` + pluginsMu sync.RWMutex + plugins map[string]*BackendPlugin + logger log.Logger + pluginSettings map[string]pluginSettings } func (m *manager) Init() error { m.plugins = make(map[string]*BackendPlugin) m.logger = log.New("plugins.backend") + m.pluginSettings = extractPluginSettings(m.Cfg) return nil } @@ -78,13 +84,29 @@ func (m *manager) Register(descriptor PluginDescriptor) error { return errors.New("Backend plugin already registered") } + pluginSettings := pluginSettings{} + if ps, exists := m.pluginSettings[descriptor.pluginID]; exists { + pluginSettings = ps + } + + hostEnv := []string{ + fmt.Sprintf("GF_VERSION=%s", setting.BuildVersion), + fmt.Sprintf("GF_EDITION=%s", m.License.Edition()), + } + + if m.License.HasLicense() { + hostEnv = append(hostEnv, fmt.Sprintf("GF_ENTERPRISE_LICENSE_PATH=%s", m.Cfg.EnterpriseLicensePath)) + } + + env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv) + pluginLogger := m.logger.New("pluginId", descriptor.pluginID) plugin := &BackendPlugin{ id: descriptor.pluginID, executablePath: descriptor.executablePath, managed: descriptor.managed, clientFactory: func() *plugin.Client { - return plugin.NewClient(newClientConfig(descriptor.executablePath, pluginLogger, descriptor.versionedPlugins)) + return plugin.NewClient(newClientConfig(descriptor.executablePath, env, pluginLogger, descriptor.versionedPlugins)) }, startFns: descriptor.startFns, logger: pluginLogger, diff --git a/pkg/plugins/backendplugin/plugin_settings.go b/pkg/plugins/backendplugin/plugin_settings.go new file mode 100644 index 00000000000..0cc2353afb4 --- /dev/null +++ b/pkg/plugins/backendplugin/plugin_settings.go @@ -0,0 +1,39 @@ +package backendplugin + +import ( + "fmt" + "strings" + + "github.com/grafana/grafana/pkg/setting" +) + +type pluginSettings map[string]string + +func (ps pluginSettings) ToEnv(prefix string, hostEnv []string) []string { + env := []string{} + for k, v := range ps { + env = append(env, fmt.Sprintf("%s_%s=%s", prefix, strings.ToUpper(k), v)) + } + + env = append(env, hostEnv...) + + return env +} + +func extractPluginSettings(cfg *setting.Cfg) map[string]pluginSettings { + psMap := map[string]pluginSettings{} + for pluginID, settings := range cfg.PluginSettings { + ps := pluginSettings{} + for k, v := range settings { + if k == "path" || strings.ToLower(k) == "id" { + continue + } + + ps[k] = v + } + + psMap[pluginID] = ps + } + + return psMap +} diff --git a/pkg/plugins/backendplugin/plugin_settings_test.go b/pkg/plugins/backendplugin/plugin_settings_test.go new file mode 100644 index 00000000000..742c13585e3 --- /dev/null +++ b/pkg/plugins/backendplugin/plugin_settings_test.go @@ -0,0 +1,46 @@ +package backendplugin + +import ( + "sort" + "testing" + + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" +) + +func TestPluginSettings(t *testing.T) { + t.Run("Should only extract from sections beginning with 'plugin.' in config", func(t *testing.T) { + cfg := &setting.Cfg{ + PluginSettings: setting.PluginSettings{ + "plugin": map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + } + + ps := extractPluginSettings(cfg) + require.Len(t, ps, 1) + require.Len(t, ps["plugin"], 2) + + t.Run("Should skip path setting", func(t *testing.T) { + cfg.PluginSettings["plugin"]["path"] = "value" + ps := extractPluginSettings(cfg) + require.Len(t, ps["plugin"], 2) + }) + + t.Run("Should skip id setting", func(t *testing.T) { + cfg.PluginSettings["plugin"]["id"] = "value" + ps := extractPluginSettings(cfg) + require.Len(t, ps["plugin"], 2) + }) + + t.Run("Should return expected environment variables from plugin settings ", func(t *testing.T) { + ps := extractPluginSettings(cfg) + env := ps["plugin"].ToEnv("GF_PLUGIN", []string{"GF_VERSION=6.7.0"}) + sort.Strings(env) + require.Len(t, env, 3) + require.EqualValues(t, []string{"GF_PLUGIN_KEY1=value1", "GF_PLUGIN_KEY2=value2", "GF_VERSION=6.7.0"}, env) + }) + }) +} diff --git a/pkg/plugins/dashboard_importer_test.go b/pkg/plugins/dashboard_importer_test.go index 5f13c90767e..c78da3ec91d 100644 --- a/pkg/plugins/dashboard_importer_test.go +++ b/pkg/plugins/dashboard_importer_test.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" - "gopkg.in/ini.v1" ) func TestDashboardImport(t *testing.T) { @@ -87,17 +86,17 @@ func TestDashboardImport(t *testing.T) { func pluginScenario(desc string, t *testing.T, fn func()) { Convey("Given a plugin", t, func() { - setting.Raw = ini.Empty() - sec, _ := setting.Raw.NewSection("plugin.test-app") - _, err := sec.NewKey("path", "testdata/test-app") - So(err, ShouldBeNil) - pm := &PluginManager{ Cfg: &setting.Cfg{ FeatureToggles: map[string]bool{}, + PluginSettings: setting.PluginSettings{ + "test-app": map[string]string{ + "path": "testdata/test-app", + }, + }, }, } - err = pm.Init() + err := pm.Init() So(err, ShouldBeNil) Convey(desc, fn) diff --git a/pkg/plugins/dashboards_test.go b/pkg/plugins/dashboards_test.go index d264889fd88..b45400b8995 100644 --- a/pkg/plugins/dashboards_test.go +++ b/pkg/plugins/dashboards_test.go @@ -8,22 +8,21 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" - "gopkg.in/ini.v1" ) func TestPluginDashboards(t *testing.T) { Convey("When asking plugin dashboard info", t, func() { - setting.Raw = ini.Empty() - sec, _ := setting.Raw.NewSection("plugin.test-app") - _, err := sec.NewKey("path", "testdata/test-app") - So(err, ShouldBeNil) - pm := &PluginManager{ Cfg: &setting.Cfg{ FeatureToggles: map[string]bool{}, + PluginSettings: setting.PluginSettings{ + "test-app": map[string]string{ + "path": "testdata/test-app", + }, + }, }, } - err = pm.Init() + err := pm.Init() So(err, ShouldBeNil) bus.AddHandler("test", func(query *models.GetDashboardQuery) error { diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index ccf3ea8d3dc..e83786747f5 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -153,19 +153,14 @@ func (pm *PluginManager) Run(ctx context.Context) error { } func (pm *PluginManager) checkPluginPaths() error { - for _, section := range setting.Raw.Sections() { - if !strings.HasPrefix(section.Name(), "plugin.") { - continue - } - - path := section.Key("path").String() - if path == "" { + for pluginID, settings := range pm.Cfg.PluginSettings { + path, exists := settings["path"] + if !exists || path == "" { continue } if err := pm.scan(path); err != nil { - return errutil.Wrapf(err, "Failed to scan directory configured for plugin '%s': '%s'", - section.Name(), path) + return errutil.Wrapf(err, "Failed to scan directory configured for plugin '%s': '%s'", pluginID, path) } } diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index f1a36ee7bc5..8403ab8b609 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -32,18 +32,17 @@ func TestPluginScans(t *testing.T) { }) Convey("When reading app plugin definition", t, func() { - setting.Raw = ini.Empty() - sec, err := setting.Raw.NewSection("plugin.nginx-app") - So(err, ShouldBeNil) - _, err = sec.NewKey("path", "testdata/test-app") - So(err, ShouldBeNil) - pm := &PluginManager{ Cfg: &setting.Cfg{ FeatureToggles: map[string]bool{}, + PluginSettings: setting.PluginSettings{ + "nginx-app": map[string]string{ + "path": "testdata/test-app", + }, + }, }, } - err = pm.Init() + err := pm.Init() So(err, ShouldBeNil) So(len(Apps), ShouldBeGreaterThan, 0) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 55af02ecdba..93358ca2e27 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -259,6 +259,7 @@ type Cfg struct { MetricsEndpointDisableTotalStats bool PluginsEnableAlpha bool PluginsAppsSkipVerifyTLS bool + PluginSettings PluginSettings DisableSanitizeHtml bool EnterpriseLicensePath string diff --git a/pkg/setting/setting_plugins.go b/pkg/setting/setting_plugins.go new file mode 100644 index 00000000000..0430f630cd7 --- /dev/null +++ b/pkg/setting/setting_plugins.go @@ -0,0 +1,25 @@ +package setting + +import ( + "strings" + + "gopkg.in/ini.v1" +) + +// PluginSettings maps plugin id to map of key/value settings. +type PluginSettings map[string]map[string]string + +func extractPluginSettings(sections []*ini.Section) PluginSettings { + psMap := PluginSettings{} + for _, section := range sections { + sectionName := section.Name() + if !strings.HasPrefix(sectionName, "plugin.") { + continue + } + + pluginID := strings.Replace(sectionName, "plugin.", "", 1) + psMap[pluginID] = section.KeysHash() + } + + return psMap +} diff --git a/pkg/setting/setting_plugins_test.go b/pkg/setting/setting_plugins_test.go new file mode 100644 index 00000000000..7b666dea6f8 --- /dev/null +++ b/pkg/setting/setting_plugins_test.go @@ -0,0 +1,43 @@ +package setting + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPluginSettings(t *testing.T) { + cfg := NewCfg() + sec, err := cfg.Raw.NewSection("plugin") + require.NoError(t, err) + _, err = sec.NewKey("key", "value") + require.NoError(t, err) + + sec, err = cfg.Raw.NewSection("plugin.plugin") + require.NoError(t, err) + _, err = sec.NewKey("key1", "value1") + require.NoError(t, err) + _, err = sec.NewKey("key2", "value2") + require.NoError(t, err) + + sec, err = cfg.Raw.NewSection("plugin.plugin2") + require.NoError(t, err) + _, err = sec.NewKey("key3", "value3") + require.NoError(t, err) + _, err = sec.NewKey("key4", "value4") + require.NoError(t, err) + + sec, err = cfg.Raw.NewSection("other") + require.NoError(t, err) + _, err = sec.NewKey("keySomething", "whatever") + require.NoError(t, err) + + ps := extractPluginSettings(cfg.Raw.Sections()) + require.Len(t, ps, 2) + require.Len(t, ps["plugin"], 2) + require.Equal(t, ps["plugin"]["key1"], "value1") + require.Equal(t, ps["plugin"]["key2"], "value2") + require.Len(t, ps["plugin2"], 2) + require.Equal(t, ps["plugin2"]["key3"], "value3") + require.Equal(t, ps["plugin2"]["key4"], "value4") +}