mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Rendering: add capabilities check (#44470)
* #44449: add feature check to rendering service * #44449: formatting * #44449: rename feature -> capability (https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities, https://developer.mozilla.org/en-US/docs/Web/API/InputDeviceInfo/getCapabilities) * #44449: refactor * #44449: remove commented code * Update pkg/services/rendering/capabilities.go Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * #44449: review fixes Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
This commit is contained in:
parent
4e37a53a1c
commit
254c59725e
@ -345,6 +345,10 @@ type testRenderService struct {
|
|||||||
renderErrorImageProvider func(error error) (*rendering.RenderResult, error)
|
renderErrorImageProvider func(error error) (*rendering.RenderResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *testRenderService) HasCapability(feature rendering.CapabilityName) (rendering.CapabilitySupportRequestResult, error) {
|
||||||
|
return rendering.CapabilitySupportRequestResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *testRenderService) IsAvailable() bool {
|
func (s *testRenderService) IsAvailable() bool {
|
||||||
if s.isAvailableProvider != nil {
|
if s.isAvailableProvider != nil {
|
||||||
return s.isAvailableProvider()
|
return s.isAvailableProvider()
|
||||||
|
55
pkg/services/rendering/capabilities.go
Normal file
55
pkg/services/rendering/capabilities.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package rendering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Capability struct {
|
||||||
|
name CapabilityName
|
||||||
|
semverConstraint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CapabilityName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScalingDownImages CapabilityName = "ScalingDownImages"
|
||||||
|
FullHeightImages CapabilityName = "FullHeightImages"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrUnknownCapability = errors.New("unknown capability")
|
||||||
|
var ErrInvalidPluginVersion = errors.New("invalid plugin version")
|
||||||
|
|
||||||
|
func (rs *RenderingService) HasCapability(capability CapabilityName) (CapabilitySupportRequestResult, error) {
|
||||||
|
if !rs.IsAvailable() {
|
||||||
|
return CapabilitySupportRequestResult{IsSupported: false, SemverConstraint: ""}, ErrRenderUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
var semverConstraint string
|
||||||
|
for i := range rs.capabilities {
|
||||||
|
if rs.capabilities[i].name == capability {
|
||||||
|
semverConstraint = rs.capabilities[i].semverConstraint
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if semverConstraint == "" {
|
||||||
|
return CapabilitySupportRequestResult{}, ErrUnknownCapability
|
||||||
|
}
|
||||||
|
|
||||||
|
compiledSemverConstraint, err := semver.NewConstraint(semverConstraint)
|
||||||
|
if err != nil {
|
||||||
|
rs.log.Error("Failed to parse semver constraint", "constraint", semverConstraint, "capability", capability, "error", err.Error())
|
||||||
|
return CapabilitySupportRequestResult{IsSupported: false, SemverConstraint: semverConstraint}, ErrUnknownCapability
|
||||||
|
}
|
||||||
|
|
||||||
|
imageRendererVersion := rs.Version()
|
||||||
|
compiledImageRendererVersion, err := semver.NewVersion(imageRendererVersion)
|
||||||
|
if err != nil {
|
||||||
|
rs.log.Error("Failed to parse plugin version", "version", imageRendererVersion, "error", err.Error())
|
||||||
|
return CapabilitySupportRequestResult{IsSupported: false, SemverConstraint: semverConstraint}, ErrInvalidPluginVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
return CapabilitySupportRequestResult{IsSupported: compiledSemverConstraint.Check(compiledImageRendererVersion), SemverConstraint: semverConstraint}, nil
|
||||||
|
}
|
136
pkg/services/rendering/capabilities_test.go
Normal file
136
pkg/services/rendering/capabilities_test.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package rendering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dummyPluginManager struct{}
|
||||||
|
|
||||||
|
func (d *dummyPluginManager) Renderer() *plugins.Plugin {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dummyRendererUrl = "http://dummyurl.com"
|
||||||
|
var testCapabilitySemverConstraint = "> 1.0.0"
|
||||||
|
var testCapabilityName = CapabilityName("TestCap")
|
||||||
|
var testCapabilityNameInvalidSemver = CapabilityName("TestCapInvalidSemver")
|
||||||
|
|
||||||
|
func TestCapabilities(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
rs := &RenderingService{
|
||||||
|
Cfg: cfg,
|
||||||
|
RendererPluginManager: &dummyPluginManager{},
|
||||||
|
log: log.New("test-capabilities-rendering-service"),
|
||||||
|
capabilities: []Capability{
|
||||||
|
{name: testCapabilityName, semverConstraint: testCapabilitySemverConstraint},
|
||||||
|
{name: testCapabilityNameInvalidSemver, semverConstraint: "asfasf"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rendererUrl string
|
||||||
|
rendererVersion string
|
||||||
|
capabilityName CapabilityName
|
||||||
|
expectedError error
|
||||||
|
expectedResult CapabilitySupportRequestResult
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "when image-renderer plugin is not available",
|
||||||
|
rendererUrl: "",
|
||||||
|
rendererVersion: "",
|
||||||
|
capabilityName: testCapabilityName,
|
||||||
|
expectedError: ErrRenderUnavailable,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: false,
|
||||||
|
SemverConstraint: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when image-renderer plugin version is not populated",
|
||||||
|
rendererUrl: dummyRendererUrl,
|
||||||
|
rendererVersion: "",
|
||||||
|
capabilityName: testCapabilityName,
|
||||||
|
expectedError: ErrInvalidPluginVersion,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: false,
|
||||||
|
SemverConstraint: testCapabilitySemverConstraint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when image-renderer plugin version is not valid",
|
||||||
|
rendererUrl: dummyRendererUrl,
|
||||||
|
rendererVersion: "abcd",
|
||||||
|
capabilityName: testCapabilityName,
|
||||||
|
expectedError: ErrInvalidPluginVersion,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: false,
|
||||||
|
SemverConstraint: testCapabilitySemverConstraint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when image-renderer version does not match target constraint",
|
||||||
|
rendererUrl: dummyRendererUrl,
|
||||||
|
rendererVersion: "1.0.0",
|
||||||
|
capabilityName: testCapabilityName,
|
||||||
|
expectedError: nil,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: false,
|
||||||
|
SemverConstraint: testCapabilitySemverConstraint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when image-renderer version matches target constraint",
|
||||||
|
rendererUrl: dummyRendererUrl,
|
||||||
|
rendererVersion: "2.0.0",
|
||||||
|
capabilityName: testCapabilityName,
|
||||||
|
expectedError: nil,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: true,
|
||||||
|
SemverConstraint: testCapabilitySemverConstraint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when capability is unknown",
|
||||||
|
rendererUrl: dummyRendererUrl,
|
||||||
|
rendererVersion: "1.0.0",
|
||||||
|
capabilityName: CapabilityName("unknown"),
|
||||||
|
expectedError: ErrUnknownCapability,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: false,
|
||||||
|
SemverConstraint: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when capability has invalid semver constraint",
|
||||||
|
rendererUrl: dummyRendererUrl,
|
||||||
|
rendererVersion: "1.0.0",
|
||||||
|
capabilityName: testCapabilityNameInvalidSemver,
|
||||||
|
expectedError: ErrUnknownCapability,
|
||||||
|
expectedResult: CapabilitySupportRequestResult{
|
||||||
|
IsSupported: false,
|
||||||
|
SemverConstraint: "asfasf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
rs.Cfg.RendererUrl = tt.rendererUrl
|
||||||
|
rs.version = tt.rendererVersion
|
||||||
|
res, err := rs.HasCapability(tt.capabilityName)
|
||||||
|
|
||||||
|
if tt.expectedError == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.ErrorIs(t, err, tt.expectedError)
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.expectedResult, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,11 @@ type Session interface {
|
|||||||
Dispose(ctx context.Context)
|
Dispose(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CapabilitySupportRequestResult struct {
|
||||||
|
IsSupported bool
|
||||||
|
SemverConstraint string
|
||||||
|
}
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
IsAvailable() bool
|
IsAvailable() bool
|
||||||
Version() string
|
Version() string
|
||||||
@ -103,5 +108,6 @@ type Service interface {
|
|||||||
RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error)
|
RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error)
|
||||||
RenderErrorImage(theme Theme, error error) (*RenderResult, error)
|
RenderErrorImage(theme 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)
|
||||||
CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error)
|
CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error)
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ type RenderingService struct {
|
|||||||
inProgressCount int32
|
inProgressCount int32
|
||||||
version string
|
version string
|
||||||
versionMutex sync.RWMutex
|
versionMutex sync.RWMutex
|
||||||
|
capabilities []Capability
|
||||||
|
|
||||||
perRequestRenderKeyProvider renderKeyProvider
|
perRequestRenderKeyProvider renderKeyProvider
|
||||||
Cfg *setting.Cfg
|
Cfg *setting.Cfg
|
||||||
@ -81,6 +82,16 @@ func ProvideService(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache, rm p
|
|||||||
log: logger,
|
log: logger,
|
||||||
keyExpiry: 5 * time.Minute,
|
keyExpiry: 5 * time.Minute,
|
||||||
},
|
},
|
||||||
|
capabilities: []Capability{
|
||||||
|
{
|
||||||
|
name: FullHeightImages,
|
||||||
|
semverConstraint: ">= 3.4.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ScalingDownImages,
|
||||||
|
semverConstraint: ">= 3.4.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
RemoteCacheService: remoteCache,
|
RemoteCacheService: remoteCache,
|
||||||
RendererPluginManager: rm,
|
RendererPluginManager: rm,
|
||||||
|
Loading…
Reference in New Issue
Block a user