mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 23:02:28 +08:00
manager exposes renderer + secrets manager (#54629)
This commit is contained in:
@ -54,7 +54,7 @@ func (hs *HTTPServer) AdminRollbackSecrets(c *models.ReqContext) response.Respon
|
|||||||
// To migrate to the plugin, it must be installed and configured
|
// To migrate to the plugin, it must be installed and configured
|
||||||
// so as not to lose access to migrated secrets
|
// so as not to lose access to migrated secrets
|
||||||
func (hs *HTTPServer) AdminMigrateSecretsToPlugin(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) AdminMigrateSecretsToPlugin(c *models.ReqContext) response.Response {
|
||||||
if skv.EvaluateRemoteSecretsPlugin(hs.secretsPluginManager, hs.Cfg) != nil {
|
if skv.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) != nil {
|
||||||
hs.log.Warn("Received secrets plugin migration request while plugin is not available")
|
hs.log.Warn("Received secrets plugin migration request while plugin is not available")
|
||||||
return response.Respond(http.StatusBadRequest, "Secrets plugin is not available")
|
return response.Respond(http.StatusBadRequest, "Secrets plugin is not available")
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ func (hs *HTTPServer) AdminMigrateSecretsToPlugin(c *models.ReqContext) response
|
|||||||
// To migrate from the plugin, it must be installed only
|
// To migrate from the plugin, it must be installed only
|
||||||
// as it is possible the user disabled it and then wants to migrate
|
// as it is possible the user disabled it and then wants to migrate
|
||||||
func (hs *HTTPServer) AdminMigrateSecretsFromPlugin(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) AdminMigrateSecretsFromPlugin(c *models.ReqContext) response.Response {
|
||||||
if hs.secretsPluginManager.SecretsManager() == nil {
|
if hs.secretsPluginManager.SecretsManager(c.Req.Context()) == nil {
|
||||||
hs.log.Warn("Received secrets plugin migration request while plugin is not installed")
|
hs.log.Warn("Received secrets plugin migration request while plugin is not installed")
|
||||||
return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed")
|
return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed")
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ func (hs *HTTPServer) AdminMigrateSecretsFromPlugin(c *models.ReqContext) respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) AdminDeleteAllSecretsManagerPluginSecrets(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) AdminDeleteAllSecretsManagerPluginSecrets(c *models.ReqContext) response.Response {
|
||||||
if hs.secretsPluginManager.SecretsManager() == nil {
|
if hs.secretsPluginManager.SecretsManager(c.Req.Context()) == nil {
|
||||||
hs.log.Warn("Received secrets plugin deletion request while plugin is not installed")
|
hs.log.Warn("Received secrets plugin deletion request while plugin is not installed")
|
||||||
return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed")
|
return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed")
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ type fakeRendererManager struct {
|
|||||||
plugins.RendererManager
|
plugins.RendererManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *fakeRendererManager) Renderer() *plugins.Plugin {
|
func (ps *fakeRendererManager) Renderer(_ context.Context) *plugins.Plugin {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasAccess := accesscontrol.HasAccess(hs.AccessControl, c)
|
hasAccess := accesscontrol.HasAccess(hs.AccessControl, c)
|
||||||
secretsManagerPluginEnabled := kvstore.EvaluateRemoteSecretsPlugin(hs.secretsPluginManager, hs.Cfg) == nil
|
secretsManagerPluginEnabled := kvstore.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) == nil
|
||||||
|
|
||||||
jsonObj := map[string]interface{}{
|
jsonObj := map[string]interface{}{
|
||||||
"defaultDatasource": defaultDS,
|
"defaultDatasource": defaultDS,
|
||||||
@ -154,7 +154,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
|||||||
"enabledFeatures": hs.License.EnabledFeatures(),
|
"enabledFeatures": hs.License.EnabledFeatures(),
|
||||||
},
|
},
|
||||||
"featureToggles": hs.Features.GetEnabled(c.Req.Context()),
|
"featureToggles": hs.Features.GetEnabled(c.Req.Context()),
|
||||||
"rendererAvailable": hs.RenderService.IsAvailable(),
|
"rendererAvailable": hs.RenderService.IsAvailable(c.Req.Context()),
|
||||||
"rendererVersion": hs.RenderService.Version(),
|
"rendererVersion": hs.RenderService.Version(),
|
||||||
"secretsManagerPluginEnabled": secretsManagerPluginEnabled,
|
"secretsManagerPluginEnabled": secretsManagerPluginEnabled,
|
||||||
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
|
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
|
||||||
|
@ -181,12 +181,12 @@ var wireSet = wire.NewSet(
|
|||||||
wire.Bind(new(repo.Service), new(*repo.Manager)),
|
wire.Bind(new(repo.Service), new(*repo.Manager)),
|
||||||
manager.ProvideService,
|
manager.ProvideService,
|
||||||
wire.Bind(new(plugins.Manager), new(*manager.PluginManager)),
|
wire.Bind(new(plugins.Manager), new(*manager.PluginManager)),
|
||||||
|
wire.Bind(new(plugins.RendererManager), new(*manager.PluginManager)),
|
||||||
|
wire.Bind(new(plugins.SecretsPluginManager), new(*manager.PluginManager)),
|
||||||
client.ProvideService,
|
client.ProvideService,
|
||||||
wire.Bind(new(plugins.Client), new(*client.Service)),
|
wire.Bind(new(plugins.Client), new(*client.Service)),
|
||||||
managerStore.ProvideService,
|
managerStore.ProvideService,
|
||||||
wire.Bind(new(plugins.Store), new(*managerStore.Service)),
|
wire.Bind(new(plugins.Store), new(*managerStore.Service)),
|
||||||
wire.Bind(new(plugins.RendererManager), new(*managerStore.Service)),
|
|
||||||
wire.Bind(new(plugins.SecretsPluginManager), new(*managerStore.Service)),
|
|
||||||
wire.Bind(new(plugins.StaticRouteResolver), new(*managerStore.Service)),
|
wire.Bind(new(plugins.StaticRouteResolver), new(*managerStore.Service)),
|
||||||
pluginDashboards.ProvideFileStoreManager,
|
pluginDashboards.ProvideFileStoreManager,
|
||||||
wire.Bind(new(pluginDashboards.FileStore), new(*pluginDashboards.FileStoreManager)),
|
wire.Bind(new(pluginDashboards.FileStore), new(*pluginDashboards.FileStoreManager)),
|
||||||
|
@ -54,12 +54,12 @@ type BackendFactoryProvider interface {
|
|||||||
|
|
||||||
type RendererManager interface {
|
type RendererManager interface {
|
||||||
// Renderer returns a renderer plugin.
|
// Renderer returns a renderer plugin.
|
||||||
Renderer() *Plugin
|
Renderer(ctx context.Context) *Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecretsPluginManager interface {
|
type SecretsPluginManager interface {
|
||||||
// SecretsManager returns a secretsmanager plugin
|
// SecretsManager returns a secretsmanager plugin
|
||||||
SecretsManager() *Plugin
|
SecretsManager(ctx context.Context) *Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaticRouteResolver interface {
|
type StaticRouteResolver interface {
|
||||||
|
@ -17,6 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ plugins.Manager = (*PluginManager)(nil)
|
var _ plugins.Manager = (*PluginManager)(nil)
|
||||||
|
var _ plugins.RendererManager = (*PluginManager)(nil)
|
||||||
|
var _ plugins.SecretsPluginManager = (*PluginManager)(nil)
|
||||||
|
|
||||||
type PluginManager struct {
|
type PluginManager struct {
|
||||||
cfg *plugins.Cfg
|
cfg *plugins.Cfg
|
||||||
@ -172,6 +174,24 @@ func (m *PluginManager) Remove(ctx context.Context, pluginID string) error {
|
|||||||
return m.pluginStorage.Remove(ctx, plugin.ID)
|
return m.pluginStorage.Remove(ctx, plugin.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *PluginManager) Renderer(ctx context.Context) *plugins.Plugin {
|
||||||
|
for _, p := range m.pluginRegistry.Plugins(ctx) {
|
||||||
|
if p.IsRenderer() && !p.IsDecommissioned() {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PluginManager) SecretsManager(ctx context.Context) *plugins.Plugin {
|
||||||
|
for _, p := range m.pluginRegistry.Plugins(ctx) {
|
||||||
|
if p.IsSecretsManager() && !p.IsDecommissioned() {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
|
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
|
||||||
func (m *PluginManager) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
|
func (m *PluginManager) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
|
||||||
p, exists := m.pluginRegistry.Plugin(ctx, pluginID)
|
p, exists := m.pluginRegistry.Plugin(ctx, pluginID)
|
||||||
|
@ -213,6 +213,52 @@ func TestPluginManager_Run(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestManager_Renderer(t *testing.T) {
|
||||||
|
t.Run("Renderer returns a single (non-decommissioned) renderer plugin", func(t *testing.T) {
|
||||||
|
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-renderer", Type: plugins.Renderer}}
|
||||||
|
p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-panel", Type: plugins.Panel}}
|
||||||
|
p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-app", Type: plugins.App}}
|
||||||
|
|
||||||
|
reg := &fakes.FakePluginRegistry{
|
||||||
|
Store: map[string]*plugins.Plugin{
|
||||||
|
p1.ID: p1,
|
||||||
|
p2.ID: p2,
|
||||||
|
p3.ID: p3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pm := New(&plugins.Cfg{}, reg, []plugins.PluginSource{}, &fakes.FakeLoader{}, &fakes.FakePluginRepo{},
|
||||||
|
&fakes.FakePluginStorage{}, &fakes.FakeProcessManager{})
|
||||||
|
|
||||||
|
r := pm.Renderer(context.Background())
|
||||||
|
require.Equal(t, p1, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_SecretsManager(t *testing.T) {
|
||||||
|
t.Run("Renderer returns a single (non-decommissioned) secrets manager plugin", func(t *testing.T) {
|
||||||
|
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-renderer", Type: plugins.Renderer}}
|
||||||
|
p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-panel", Type: plugins.Panel}}
|
||||||
|
p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-secrets", Type: plugins.SecretsManager}}
|
||||||
|
p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.DataSource}}
|
||||||
|
|
||||||
|
reg := &fakes.FakePluginRegistry{
|
||||||
|
Store: map[string]*plugins.Plugin{
|
||||||
|
p1.ID: p1,
|
||||||
|
p2.ID: p2,
|
||||||
|
p3.ID: p3,
|
||||||
|
p4.ID: p4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pm := New(&plugins.Cfg{}, reg, []plugins.PluginSource{}, &fakes.FakeLoader{}, &fakes.FakePluginRepo{},
|
||||||
|
&fakes.FakePluginStorage{}, &fakes.FakeProcessManager{})
|
||||||
|
|
||||||
|
r := pm.SecretsManager(context.Background())
|
||||||
|
require.Equal(t, p3, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func createPlugin(t *testing.T, pluginID string, class plugins.Class, managed, backend bool, cbs ...func(*plugins.Plugin)) *plugins.Plugin {
|
func createPlugin(t *testing.T, pluginID string, class plugins.Class, managed, backend bool, cbs ...func(*plugins.Plugin)) *plugins.Plugin {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ plugins.Store = (*Service)(nil)
|
var _ plugins.Store = (*Service)(nil)
|
||||||
var _ plugins.RendererManager = (*Service)(nil)
|
|
||||||
var _ plugins.SecretsPluginManager = (*Service)(nil)
|
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
pluginRegistry registry.Service
|
pluginRegistry registry.Service
|
||||||
@ -51,26 +49,6 @@ func (s *Service) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []pl
|
|||||||
return pluginsList
|
return pluginsList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Renderer() *plugins.Plugin {
|
|
||||||
for _, p := range s.availablePlugins(context.TODO()) {
|
|
||||||
if p.IsRenderer() {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) SecretsManager() *plugins.Plugin {
|
|
||||||
for _, p := range s.availablePlugins(context.TODO()) {
|
|
||||||
if p.IsSecretsManager() {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
|
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
|
||||||
func (s *Service) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
|
func (s *Service) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
|
||||||
p, exists := s.pluginRegistry.Plugin(ctx, pluginID)
|
p, exists := s.pluginRegistry.Plugin(ctx, pluginID)
|
||||||
|
@ -69,49 +69,6 @@ func TestStore_Plugins(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStore_Renderer(t *testing.T) {
|
|
||||||
t.Run("Renderer returns a single (non-decommissioned) renderer plugin", func(t *testing.T) {
|
|
||||||
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-renderer", Type: plugins.Renderer}}
|
|
||||||
p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-panel", Type: plugins.Panel}}
|
|
||||||
p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-app", Type: plugins.App}}
|
|
||||||
p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.DataSource}}
|
|
||||||
p4.RegisterClient(&DecommissionedPlugin{})
|
|
||||||
|
|
||||||
ps := ProvideService(
|
|
||||||
newFakePluginRegistry(map[string]*plugins.Plugin{
|
|
||||||
p1.ID: p1,
|
|
||||||
p2.ID: p2,
|
|
||||||
p3.ID: p3,
|
|
||||||
p4.ID: p4,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
r := ps.Renderer()
|
|
||||||
require.Equal(t, p1, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStore_SecretsManager(t *testing.T) {
|
|
||||||
t.Run("Renderer returns a single (non-decommissioned) secrets manager plugin", func(t *testing.T) {
|
|
||||||
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-renderer", Type: plugins.Renderer}}
|
|
||||||
p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-panel", Type: plugins.Panel}}
|
|
||||||
p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-secrets", Type: plugins.SecretsManager}}
|
|
||||||
p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.DataSource}}
|
|
||||||
|
|
||||||
ps := ProvideService(
|
|
||||||
newFakePluginRegistry(map[string]*plugins.Plugin{
|
|
||||||
p1.ID: p1,
|
|
||||||
p2.ID: p2,
|
|
||||||
p3.ID: p3,
|
|
||||||
p4.ID: p4,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
r := ps.SecretsManager()
|
|
||||||
require.Equal(t, p3, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStore_Routes(t *testing.T) {
|
func TestStore_Routes(t *testing.T) {
|
||||||
t.Run("Routes returns all static routes for non-decommissioned plugins", func(t *testing.T) {
|
t.Run("Routes returns all static routes for non-decommissioned plugins", func(t *testing.T) {
|
||||||
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.Renderer}, PluginDir: "/some/dir"}
|
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.Renderer}, PluginDir: "/some/dir"}
|
||||||
|
@ -176,12 +176,12 @@ var wireBasicSet = wire.NewSet(
|
|||||||
wire.Bind(new(repo.Service), new(*repo.Manager)),
|
wire.Bind(new(repo.Service), new(*repo.Manager)),
|
||||||
manager.ProvideService,
|
manager.ProvideService,
|
||||||
wire.Bind(new(plugins.Manager), new(*manager.PluginManager)),
|
wire.Bind(new(plugins.Manager), new(*manager.PluginManager)),
|
||||||
|
wire.Bind(new(plugins.RendererManager), new(*manager.PluginManager)),
|
||||||
|
wire.Bind(new(plugins.SecretsPluginManager), new(*manager.PluginManager)),
|
||||||
client.ProvideService,
|
client.ProvideService,
|
||||||
wire.Bind(new(plugins.Client), new(*client.Service)),
|
wire.Bind(new(plugins.Client), new(*client.Service)),
|
||||||
managerStore.ProvideService,
|
managerStore.ProvideService,
|
||||||
wire.Bind(new(plugins.Store), new(*managerStore.Service)),
|
wire.Bind(new(plugins.Store), new(*managerStore.Service)),
|
||||||
wire.Bind(new(plugins.RendererManager), new(*managerStore.Service)),
|
|
||||||
wire.Bind(new(plugins.SecretsPluginManager), new(*managerStore.Service)),
|
|
||||||
wire.Bind(new(plugins.StaticRouteResolver), new(*managerStore.Service)),
|
wire.Bind(new(plugins.StaticRouteResolver), new(*managerStore.Service)),
|
||||||
pluginDashboards.ProvideFileStoreManager,
|
pluginDashboards.ProvideFileStoreManager,
|
||||||
wire.Bind(new(pluginDashboards.FileStore), new(*pluginDashboards.FileStoreManager)),
|
wire.Bind(new(pluginDashboards.FileStore), new(*pluginDashboards.FileStoreManager)),
|
||||||
|
@ -243,7 +243,7 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
|
|||||||
scenarioCtx.rendererAvailable = true
|
scenarioCtx.rendererAvailable = true
|
||||||
|
|
||||||
renderService := &testRenderService{
|
renderService := &testRenderService{
|
||||||
isAvailableProvider: func() bool {
|
isAvailableProvider: func(ctx context.Context) bool {
|
||||||
return scenarioCtx.rendererAvailable
|
return scenarioCtx.rendererAvailable
|
||||||
},
|
},
|
||||||
renderProvider: func(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error) {
|
renderProvider: func(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error) {
|
||||||
@ -334,7 +334,7 @@ func (n *testNotifier) GetFrequency() time.Duration {
|
|||||||
var _ Notifier = &testNotifier{}
|
var _ Notifier = &testNotifier{}
|
||||||
|
|
||||||
type testRenderService struct {
|
type testRenderService struct {
|
||||||
isAvailableProvider func() bool
|
isAvailableProvider func(ctx context.Context) bool
|
||||||
renderProvider func(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error)
|
renderProvider func(ctx context.Context, opts rendering.Opts) (*rendering.RenderResult, error)
|
||||||
renderErrorImageProvider func(error error) (*rendering.RenderResult, error)
|
renderErrorImageProvider func(error error) (*rendering.RenderResult, error)
|
||||||
}
|
}
|
||||||
@ -343,13 +343,13 @@ func (s *testRenderService) SanitizeSVG(ctx context.Context, req *rendering.Sani
|
|||||||
return &rendering.SanitizeSVGResponse{Sanitized: req.Content}, nil
|
return &rendering.SanitizeSVGResponse{Sanitized: req.Content}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testRenderService) HasCapability(feature rendering.CapabilityName) (rendering.CapabilitySupportRequestResult, error) {
|
func (s *testRenderService) HasCapability(_ context.Context, feature rendering.CapabilityName) (rendering.CapabilitySupportRequestResult, error) {
|
||||||
return rendering.CapabilitySupportRequestResult{}, nil
|
return rendering.CapabilitySupportRequestResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testRenderService) IsAvailable() bool {
|
func (s *testRenderService) IsAvailable(ctx context.Context) bool {
|
||||||
if s.isAvailableProvider != nil {
|
if s.isAvailableProvider != nil {
|
||||||
return s.isAvailableProvider()
|
return s.isAvailableProvider(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package rendering
|
package rendering
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/Masterminds/semver"
|
"github.com/Masterminds/semver"
|
||||||
@ -22,8 +23,8 @@ const (
|
|||||||
var ErrUnknownCapability = errors.New("unknown capability")
|
var ErrUnknownCapability = errors.New("unknown capability")
|
||||||
var ErrInvalidPluginVersion = errors.New("invalid plugin version")
|
var ErrInvalidPluginVersion = errors.New("invalid plugin version")
|
||||||
|
|
||||||
func (rs *RenderingService) HasCapability(capability CapabilityName) (CapabilitySupportRequestResult, error) {
|
func (rs *RenderingService) HasCapability(ctx context.Context, capability CapabilityName) (CapabilitySupportRequestResult, error) {
|
||||||
if !rs.IsAvailable() {
|
if !rs.IsAvailable(ctx) {
|
||||||
return CapabilitySupportRequestResult{IsSupported: false, SemverConstraint: ""}, ErrRenderUnavailable
|
return CapabilitySupportRequestResult{IsSupported: false, SemverConstraint: ""}, ErrRenderUnavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package rendering
|
package rendering
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -12,7 +13,7 @@ import (
|
|||||||
|
|
||||||
type dummyPluginManager struct{}
|
type dummyPluginManager struct{}
|
||||||
|
|
||||||
func (d *dummyPluginManager) Renderer() *plugins.Plugin {
|
func (d *dummyPluginManager) Renderer(_ context.Context) *plugins.Plugin {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +124,7 @@ func TestCapabilities(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
rs.Cfg.RendererUrl = tt.rendererUrl
|
rs.Cfg.RendererUrl = tt.rendererUrl
|
||||||
rs.version = tt.rendererVersion
|
rs.version = tt.rendererVersion
|
||||||
res, err := rs.HasCapability(tt.capabilityName)
|
res, err := rs.HasCapability(context.Background(), tt.capabilityName)
|
||||||
|
|
||||||
if tt.expectedError == nil {
|
if tt.expectedError == nil {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -117,13 +117,13 @@ type CapabilitySupportRequestResult struct {
|
|||||||
|
|
||||||
//go:generate mockgen -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service
|
//go:generate mockgen -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service
|
||||||
type Service interface {
|
type Service interface {
|
||||||
IsAvailable() bool
|
IsAvailable(ctx context.Context) bool
|
||||||
Version() string
|
Version() string
|
||||||
Render(ctx context.Context, opts Opts, session Session) (*RenderResult, error)
|
Render(ctx context.Context, opts Opts, session Session) (*RenderResult, error)
|
||||||
RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error)
|
RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error)
|
||||||
RenderErrorImage(theme models.Theme, error error) (*RenderResult, error)
|
RenderErrorImage(theme models.Theme, error error) (*RenderResult, error)
|
||||||
GetRenderUser(ctx context.Context, key string) (*RenderUser, bool)
|
GetRenderUser(ctx context.Context, key string) (*RenderUser, bool)
|
||||||
HasCapability(capability CapabilityName) (CapabilitySupportRequestResult, error)
|
HasCapability(ctx context.Context, capability CapabilityName) (CapabilitySupportRequestResult, error)
|
||||||
CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error)
|
CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error)
|
||||||
SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error)
|
SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error)
|
||||||
}
|
}
|
||||||
|
@ -66,32 +66,32 @@ func (mr *MockServiceMockRecorder) GetRenderUser(arg0, arg1 interface{}) *gomock
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasCapability mocks base method.
|
// HasCapability mocks base method.
|
||||||
func (m *MockService) HasCapability(arg0 CapabilityName) (CapabilitySupportRequestResult, error) {
|
func (m *MockService) HasCapability(arg0 context.Context, arg1 CapabilityName) (CapabilitySupportRequestResult, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "HasCapability", arg0)
|
ret := m.ctrl.Call(m, "HasCapability", arg0, arg1)
|
||||||
ret0, _ := ret[0].(CapabilitySupportRequestResult)
|
ret0, _ := ret[0].(CapabilitySupportRequestResult)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasCapability indicates an expected call of HasCapability.
|
// HasCapability indicates an expected call of HasCapability.
|
||||||
func (mr *MockServiceMockRecorder) HasCapability(arg0 interface{}) *gomock.Call {
|
func (mr *MockServiceMockRecorder) HasCapability(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasCapability", reflect.TypeOf((*MockService)(nil).HasCapability), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasCapability", reflect.TypeOf((*MockService)(nil).HasCapability), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAvailable mocks base method.
|
// IsAvailable mocks base method.
|
||||||
func (m *MockService) IsAvailable() bool {
|
func (m *MockService) IsAvailable(arg0 context.Context) bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "IsAvailable")
|
ret := m.ctrl.Call(m, "IsAvailable", arg0)
|
||||||
ret0, _ := ret[0].(bool)
|
ret0, _ := ret[0].(bool)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAvailable indicates an expected call of IsAvailable.
|
// IsAvailable indicates an expected call of IsAvailable.
|
||||||
func (mr *MockServiceMockRecorder) IsAvailable() *gomock.Call {
|
func (mr *MockServiceMockRecorder) IsAvailable(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailable", reflect.TypeOf((*MockService)(nil).IsAvailable))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailable", reflect.TypeOf((*MockService)(nil).IsAvailable), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render mocks base method.
|
// Render mocks base method.
|
||||||
|
@ -156,9 +156,9 @@ func (rs *RenderingService) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs.pluginAvailable() {
|
if rs.pluginAvailable(ctx) {
|
||||||
rs.log = rs.log.New("renderer", "plugin")
|
rs.log = rs.log.New("renderer", "plugin")
|
||||||
rs.pluginInfo = rs.RendererPluginManager.Renderer()
|
rs.pluginInfo = rs.RendererPluginManager.Renderer(ctx)
|
||||||
|
|
||||||
if err := rs.startPlugin(ctx); err != nil {
|
if err := rs.startPlugin(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -191,16 +191,16 @@ func (rs *RenderingService) Run(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RenderingService) pluginAvailable() bool {
|
func (rs *RenderingService) pluginAvailable(ctx context.Context) bool {
|
||||||
return rs.RendererPluginManager.Renderer() != nil
|
return rs.RendererPluginManager.Renderer(ctx) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RenderingService) remoteAvailable() bool {
|
func (rs *RenderingService) remoteAvailable() bool {
|
||||||
return rs.Cfg.RendererUrl != ""
|
return rs.Cfg.RendererUrl != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RenderingService) IsAvailable() bool {
|
func (rs *RenderingService) IsAvailable(ctx context.Context) bool {
|
||||||
return rs.remoteAvailable() || rs.pluginAvailable()
|
return rs.remoteAvailable() || rs.pluginAvailable(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RenderingService) Version() string {
|
func (rs *RenderingService) Version() string {
|
||||||
@ -271,7 +271,7 @@ func (rs *RenderingService) render(ctx context.Context, opts Opts, renderKeyProv
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rs.IsAvailable() {
|
if !rs.IsAvailable(ctx) {
|
||||||
rs.log.Warn("Could not render image, no image renderer found/installed. " +
|
rs.log.Warn("Could not render image, no image renderer found/installed. " +
|
||||||
"For image rendering support please install the grafana-image-renderer plugin. " +
|
"For image rendering support please install the grafana-image-renderer plugin. " +
|
||||||
"Read more at https://grafana.com/docs/grafana/latest/administration/image_rendering/")
|
"Read more at https://grafana.com/docs/grafana/latest/administration/image_rendering/")
|
||||||
@ -316,7 +316,7 @@ func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts, session
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RenderingService) SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) {
|
func (rs *RenderingService) SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) {
|
||||||
capability, err := rs.HasCapability(SvgSanitization)
|
capability, err := rs.HasCapability(ctx, SvgSanitization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -338,7 +338,7 @@ func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts, renderK
|
|||||||
return nil, ErrConcurrentLimitReached
|
return nil, ErrConcurrentLimitReached
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rs.IsAvailable() {
|
if !rs.IsAvailable(ctx) {
|
||||||
return nil, ErrRenderUnavailable
|
return nil, ErrRenderUnavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ func TestRenderErrorImage(t *testing.T) {
|
|||||||
|
|
||||||
type unavailableRendererManager struct{}
|
type unavailableRendererManager struct{}
|
||||||
|
|
||||||
func (m unavailableRendererManager) Renderer() *plugins.Plugin { return nil }
|
func (m unavailableRendererManager) Renderer(_ context.Context) *plugins.Plugin { return nil }
|
||||||
|
|
||||||
func TestRenderUnavailableError(t *testing.T) {
|
func TestRenderUnavailableError(t *testing.T) {
|
||||||
rs := RenderingService{
|
rs := RenderingService{
|
||||||
|
@ -29,18 +29,19 @@ func ProvideService(
|
|||||||
) (SecretsKVStore, error) {
|
) (SecretsKVStore, error) {
|
||||||
var logger = log.New("secrets.kvstore")
|
var logger = log.New("secrets.kvstore")
|
||||||
var store SecretsKVStore
|
var store SecretsKVStore
|
||||||
|
ctx := context.Background()
|
||||||
store = NewSQLSecretsKVStore(sqlStore, secretsService, logger)
|
store = NewSQLSecretsKVStore(sqlStore, secretsService, logger)
|
||||||
err := EvaluateRemoteSecretsPlugin(pluginsManager, cfg)
|
err := EvaluateRemoteSecretsPlugin(ctx, pluginsManager, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("secrets manager evaluator returned false", "reason", err.Error())
|
logger.Debug("secrets manager evaluator returned false", "reason", err.Error())
|
||||||
} else {
|
} else {
|
||||||
// Attempt to start the plugin
|
// Attempt to start the plugin
|
||||||
var secretsPlugin secretsmanagerplugin.SecretsManagerPlugin
|
var secretsPlugin secretsmanagerplugin.SecretsManagerPlugin
|
||||||
secretsPlugin, err = StartAndReturnPlugin(pluginsManager, context.Background())
|
secretsPlugin, err = StartAndReturnPlugin(pluginsManager, ctx)
|
||||||
namespacedKVStore := GetNamespacedKVStore(kvstore)
|
namespacedKVStore := GetNamespacedKVStore(kvstore)
|
||||||
if err != nil || secretsPlugin == nil {
|
if err != nil || secretsPlugin == nil {
|
||||||
logger.Error("failed to start remote secrets management plugin", "msg", err.Error())
|
logger.Error("failed to start remote secrets management plugin", "msg", err.Error())
|
||||||
if isFatal, readErr := IsPluginStartupErrorFatal(context.Background(), namespacedKVStore); isFatal || readErr != nil {
|
if isFatal, readErr := IsPluginStartupErrorFatal(ctx, namespacedKVStore); isFatal || readErr != nil {
|
||||||
// plugin error was fatal or there was an error determining if the error was fatal
|
// plugin error was fatal or there was an error determining if the error was fatal
|
||||||
logger.Error("secrets management plugin is required to start -- exiting app")
|
logger.Error("secrets management plugin is required to start -- exiting app")
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
|
@ -96,7 +96,7 @@ func (s *MigrateFromPluginService) Migrate(ctx context.Context) error {
|
|||||||
logger.Debug("Shutting down secrets plugin now that migration is complete")
|
logger.Debug("Shutting down secrets plugin now that migration is complete")
|
||||||
// if `use_plugin` wasn't set, stop the plugin after migration
|
// if `use_plugin` wasn't set, stop the plugin after migration
|
||||||
if !s.cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool(false) {
|
if !s.cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool(false) {
|
||||||
err := s.manager.SecretsManager().Stop(ctx)
|
err := s.manager.SecretsManager(ctx).Stop(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log a warning but don't throw an error
|
// Log a warning but don't throw an error
|
||||||
logger.Error("Error stopping secrets plugin after migration", "error", err.Error())
|
logger.Error("Error stopping secrets plugin after migration", "error", err.Error())
|
||||||
|
@ -57,7 +57,7 @@ func setupTestMigrateFromPluginService(t *testing.T) (*MigrateFromPluginService,
|
|||||||
|
|
||||||
secretsSql := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsSql := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
|
|
||||||
return migratorService, manager.SecretsManager().SecretsManager, secretsSql
|
return migratorService, manager.SecretsManager(context.Background()).SecretsManager, secretsSql
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSecretToPluginStore(t *testing.T, plugin secretsmanagerplugin.SecretsManagerPlugin, ctx context.Context, orgId int64, namespace string, typ string, value string) {
|
func addSecretToPluginStore(t *testing.T, plugin secretsmanagerplugin.SecretsManagerPlugin, ctx context.Context, orgId int64, namespace string, typ string, value string) {
|
||||||
|
@ -43,7 +43,7 @@ func ProvideMigrateToPluginService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MigrateToPluginService) Migrate(ctx context.Context) error {
|
func (s *MigrateToPluginService) Migrate(ctx context.Context) error {
|
||||||
if err := secretskvs.EvaluateRemoteSecretsPlugin(s.manager, s.cfg); err == nil {
|
if err := secretskvs.EvaluateRemoteSecretsPlugin(ctx, s.manager, s.cfg); err == nil {
|
||||||
logger.Debug("starting migration of unified secrets to the plugin")
|
logger.Debug("starting migration of unified secrets to the plugin")
|
||||||
// we need to get the fallback store since in this scenario the secrets store would be the plugin.
|
// we need to get the fallback store since in this scenario the secrets store would be the plugin.
|
||||||
fallbackStore := s.secretsStore.Fallback()
|
fallbackStore := s.secretsStore.Fallback()
|
||||||
|
@ -225,12 +225,12 @@ func SetPluginStartupErrorFatal(ctx context.Context, kvstore *kvstore.Namespaced
|
|||||||
return kvstore.Set(ctx, QuitOnPluginStartupFailureKey, "true")
|
return kvstore.Set(ctx, QuitOnPluginStartupFailureKey, "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
func EvaluateRemoteSecretsPlugin(mg plugins.SecretsPluginManager, cfg *setting.Cfg) error {
|
func EvaluateRemoteSecretsPlugin(ctx context.Context, mg plugins.SecretsPluginManager, cfg *setting.Cfg) error {
|
||||||
usePlugin := cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool()
|
usePlugin := cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool()
|
||||||
if !usePlugin {
|
if !usePlugin {
|
||||||
return errPluginDisabledByConfig
|
return errPluginDisabledByConfig
|
||||||
}
|
}
|
||||||
pluginInstalled := mg.SecretsManager() != nil
|
pluginInstalled := mg.SecretsManager(ctx) != nil
|
||||||
if !pluginInstalled {
|
if !pluginInstalled {
|
||||||
return errPluginNotInstalled
|
return errPluginNotInstalled
|
||||||
}
|
}
|
||||||
@ -240,10 +240,10 @@ func EvaluateRemoteSecretsPlugin(mg plugins.SecretsPluginManager, cfg *setting.C
|
|||||||
func StartAndReturnPlugin(mg plugins.SecretsPluginManager, ctx context.Context) (smp.SecretsManagerPlugin, error) {
|
func StartAndReturnPlugin(mg plugins.SecretsPluginManager, ctx context.Context) (smp.SecretsManagerPlugin, error) {
|
||||||
var err error
|
var err error
|
||||||
startupOnce.Do(func() {
|
startupOnce.Do(func() {
|
||||||
err = mg.SecretsManager().Start(ctx)
|
err = mg.SecretsManager(ctx).Start(ctx)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return mg.SecretsManager().SecretsManager, nil
|
return mg.SecretsManager(ctx).SecretsManager, nil
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func TestFatalPluginErr_FatalFlagGetsUnSetWithBackwardsCompatEnabled(t *testing.
|
|||||||
require.NotNil(t, p.SecretsKVStore)
|
require.NotNil(t, p.SecretsKVStore)
|
||||||
|
|
||||||
// setup - store secret and manually bypassing the remote plugin impl
|
// setup - store secret and manually bypassing the remote plugin impl
|
||||||
_, err = p.PluginManager.SecretsManager().SecretsManager.SetSecret(context.Background(), &secretsmanagerplugin.SetSecretRequest{
|
_, err = p.PluginManager.SecretsManager(context.Background()).SecretsManager.SetSecret(context.Background(), &secretsmanagerplugin.SetSecretRequest{
|
||||||
KeyDescriptor: &secretsmanagerplugin.Key{
|
KeyDescriptor: &secretsmanagerplugin.Key{
|
||||||
OrgId: 0,
|
OrgId: 0,
|
||||||
Namespace: "postgres",
|
Namespace: "postgres",
|
||||||
|
@ -196,7 +196,7 @@ type fakePluginManager struct {
|
|||||||
plugin *plugins.Plugin
|
plugin *plugins.Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mg *fakePluginManager) SecretsManager() *plugins.Plugin {
|
func (mg *fakePluginManager) SecretsManager(_ context.Context) *plugins.Plugin {
|
||||||
if mg.plugin != nil {
|
if mg.plugin != nil {
|
||||||
return mg.plugin
|
return mg.plugin
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ func (d byOrgId) Less(i, j int) bool { return d[i].OrgId > d[j].OrgId }
|
|||||||
func (d byOrgId) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
func (d byOrgId) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||||
|
|
||||||
func (r *simpleCrawler) Run(ctx context.Context, auth CrawlerAuth, mode CrawlerMode, theme models.Theme, thumbnailKind models.ThumbnailKind) error {
|
func (r *simpleCrawler) Run(ctx context.Context, auth CrawlerAuth, mode CrawlerMode, theme models.Theme, thumbnailKind models.ThumbnailKind) error {
|
||||||
res, err := r.renderService.HasCapability(rendering.ScalingDownImages)
|
res, err := r.renderService.HasCapability(ctx, rendering.ScalingDownImages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -317,7 +317,7 @@ func (hs *thumbService) GetDashboardPreviewsSetupSettings(c *models.ReqContext)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *thumbService) getDashboardPreviewsSetupSettings(ctx context.Context) dashboardPreviewsSetupConfig {
|
func (hs *thumbService) getDashboardPreviewsSetupSettings(ctx context.Context) dashboardPreviewsSetupConfig {
|
||||||
systemRequirements := hs.getSystemRequirements()
|
systemRequirements := hs.getSystemRequirements(ctx)
|
||||||
thumbnailsExist, err := hs.thumbnailRepo.doThumbnailsExist(ctx)
|
thumbnailsExist, err := hs.thumbnailRepo.doThumbnailsExist(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -333,8 +333,8 @@ func (hs *thumbService) getDashboardPreviewsSetupSettings(ctx context.Context) d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *thumbService) getSystemRequirements() dashboardPreviewsSystemRequirements {
|
func (hs *thumbService) getSystemRequirements(ctx context.Context) dashboardPreviewsSystemRequirements {
|
||||||
res, err := hs.renderingService.HasCapability(rendering.ScalingDownImages)
|
res, err := hs.renderingService.HasCapability(ctx, rendering.ScalingDownImages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hs.log.Error("Error when verifying dashboard previews system requirements thumbnail", "err", err.Error())
|
hs.log.Error("Error when verifying dashboard previews system requirements thumbnail", "err", err.Error())
|
||||||
return dashboardPreviewsSystemRequirements{
|
return dashboardPreviewsSystemRequirements{
|
||||||
|
Reference in New Issue
Block a user