Chore: remove the entity kind registry (#79178)

pull/79195/head
Ryan McKinley 2 years ago committed by GitHub
parent 2a2a132c61
commit 9849c954a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      pkg/server/wire.go
  2. 48
      pkg/services/store/entity/models.go
  3. 8
      pkg/services/store/kind/dashboard/summary.go
  4. 52
      pkg/services/store/kind/dataframe/summary.go
  5. 42
      pkg/services/store/kind/dataframe/summary_test.go
  6. 4
      pkg/services/store/kind/dummy/doc.go
  7. 61
      pkg/services/store/kind/dummy/summary.go
  8. 45
      pkg/services/store/kind/folder/summary.go
  9. 59
      pkg/services/store/kind/geojson/summary.go
  10. 54
      pkg/services/store/kind/geojson/summary_test.go
  11. 37
      pkg/services/store/kind/jsonobj/summary.go
  12. 38
      pkg/services/store/kind/jsonobj/summary_test.go
  13. 44
      pkg/services/store/kind/png/summary.go
  14. 33
      pkg/services/store/kind/png/summary_test.go
  15. 50
      pkg/services/store/kind/preferences/summary.go
  16. 182
      pkg/services/store/kind/registry.go
  17. 43
      pkg/services/store/kind/registry_test.go
  18. 62
      pkg/services/store/kind/snapshot/summary.go
  19. 66
      pkg/services/store/kind/svg/summary.go
  20. 4
      pkg/services/store/validate.go
  21. 2
      pkg/util/svg.go

@ -136,7 +136,6 @@ import (
"github.com/grafana/grafana/pkg/services/store"
entityDB "github.com/grafana/grafana/pkg/services/store/entity/db"
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash"
"github.com/grafana/grafana/pkg/services/store/kind"
"github.com/grafana/grafana/pkg/services/store/resolver"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
"github.com/grafana/grafana/pkg/services/supportbundles"
@ -342,7 +341,6 @@ var wireBasicSet = wire.NewSet(
grpcserver.ProvideHealthService,
grpcserver.ProvideReflectionService,
interceptors.ProvideAuthenticator,
kind.ProvideService, // The registry of known kinds
entityDB.ProvideEntityDB,
wire.Bind(new(sqlstash.EntityDB), new(*entityDB.EntityDB)),
sqlstash.ProvideSQLEntityServer,

@ -1,19 +1,15 @@
package entity
import context "context"
//-----------------------------------------------------------------------------------------------------
// NOTE: the object store is in heavy development, and the locations will likely continue to move
//-----------------------------------------------------------------------------------------------------
import (
"context"
)
const (
StandardKindDashboard = "dashboard"
StandardKindPlaylist = "playlist"
StandardKindSnapshot = "snapshot"
StandardKindFolder = "folder"
StandardKindPreferences = "preferences"
StandardKindDashboard = "dashboard"
StandardKindPlaylist = "playlist"
StandardKindFolder = "folder"
// StandardKindDataSource: not a real kind yet, but used to define references from dashboards
// Types: influx, prometheus, testdata, ...
@ -23,18 +19,6 @@ const (
// Standalone panel is not an object kind yet -- library panel, or nested in dashboard
StandardKindPanel = "panel"
// entity.StandardKindSVG SVG file support
StandardKindSVG = "svg"
// StandardKindPNG PNG file support
StandardKindPNG = "png"
// StandardKindGeoJSON represents spatial data
StandardKindGeoJSON = "geojson"
// StandardKindDataFrame data frame
StandardKindDataFrame = "frame"
// StandardKindJSONObj generic json object
StandardKindJSONObj = "jsonobj"
@ -64,28 +48,6 @@ const (
ExternalEntityReferenceRuntime_Transformer = "transformer"
)
// EntityKindInfo describes information needed from the object store
// All non-raw types will have a schema that can be used to validate
type EntityKindInfo struct {
// Unique short id for this kind
ID string `json:"id,omitempty"`
// Display name (may be equal to the ID)
Name string `json:"name,omitempty"`
// Kind description
Description string `json:"description,omitempty"`
// The format is not controlled by a schema
IsRaw bool `json:"isRaw,omitempty"`
// The preferred save extension (svg, png, parquet, etc) if one exists
FileExtension string `json:"fileExtension,omitempty"`
// The correct mime-type to return for raw objects
MimeType string `json:"mimeType,omitempty"`
}
// EntitySummaryBuilder will read an object, validate it, and return a summary, sanitized payload, or an error
// This should not include values that depend on system state, only the raw object
type EntitySummaryBuilder = func(ctx context.Context, uid string, body []byte) (*EntitySummary, []byte, error)

@ -11,14 +11,6 @@ import (
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindDashboard,
Name: "Dashboard",
Description: "Define a grafana dashboard layout",
}
}
// This summary does not resolve old name as UID
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
builder := NewStaticDashboardSummaryBuilder(&directLookup{}, true)

@ -1,52 +0,0 @@
package dataframe
import (
"context"
"encoding/json"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindDataFrame,
Name: "Data frame",
Description: "Data frame",
}
}
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
df := &data.Frame{}
err := json.Unmarshal(body, df)
if err != nil {
return nil, nil, err
}
rows, err := df.RowLen()
if err != nil {
return nil, nil, err
}
out, err := data.FrameToJSON(df, data.IncludeAll)
if err != nil {
return nil, nil, err
}
summary := &entity.EntitySummary{
Kind: entity.StandardKindDataFrame,
Name: df.Name,
UID: uid,
Fields: map[string]string{
"rows": fmt.Sprint(rows),
"cols": fmt.Sprint(len(df.Fields)),
},
}
if summary.Name == "" {
summary.Name = store.GuessNameFromUID(uid)
}
return summary, out, err
}
}

@ -1,42 +0,0 @@
package dataframe
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
)
func TestDataFrameSummary(t *testing.T) {
df := data.NewFrame("http_requests_total",
data.NewField("timestamp", nil, []time.Time{time.Now(), time.Now(), time.Now()}).SetConfig(&data.FieldConfig{
DisplayName: "A time Column.",
}),
data.NewField("value", data.Labels{"service": "auth"}, []float64{1.0, 2.0, 3.0}),
data.NewField("category", data.Labels{"service": "auth"}, []string{"foo", "bar", "test"}),
data.NewField("valid", data.Labels{"service": "auth"}, []bool{true, false, true}),
)
in, err := data.FrameToJSON(df, data.IncludeAll)
require.NoError(t, err)
summary, out, err := GetEntitySummaryBuilder()(context.Background(), "somthing", in)
require.NoError(t, err)
require.Equal(t, in, out) // same json
asjson, err := json.MarshalIndent(summary, "", " ")
// fmt.Printf(string(asjson))
require.NoError(t, err)
require.JSONEq(t, `{
"UID": "somthing",
"kind": "frame",
"name": "http_requests_total",
"fields": {
"cols": "4",
"rows": "3"
}
}`, string(asjson))
}

@ -1,4 +0,0 @@
// Package dummy provides a dummy kind useful for testing
//
// The dummy kind returns a complicated summary that can exercise most of the storage options
package dummy

@ -1,61 +0,0 @@
package dummy
import (
"context"
"fmt"
"time"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo(kind string) entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: kind,
Name: kind,
Description: "Dummy kind used for testing.",
IsRaw: true,
}
}
func GetEntitySummaryBuilder(kind string) entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
summary := &entity.EntitySummary{
Name: fmt.Sprintf("Dummy: %s", kind),
Kind: kind,
Description: fmt.Sprintf("Wrote at %s", time.Now().Local().String()),
Labels: map[string]string{
"hello": "world",
"tag1": "",
"tag2": "",
},
Fields: map[string]string{
"field1": "a string",
"field2": "1.224",
"field4": "true",
},
Error: nil, // ignore for now
Nested: nil, // ignore for now
References: []*entity.EntityExternalReference{
{
Family: "ds",
Type: "influx",
Identifier: "xyz",
},
{
Family: entity.StandardKindPanel,
Type: "heatmap",
},
{
Family: entity.StandardKindPanel,
Type: "timeseries",
},
},
}
if summary.UID != "" && uid != summary.UID {
return summary, nil, fmt.Errorf("internal UID mismatch")
}
return summary, body, nil
}
}

@ -1,45 +0,0 @@
package folder
import (
"context"
"encoding/json"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity"
)
type Model struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
}
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindFolder,
Name: "Folder",
}
}
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
obj := &Model{}
err := json.Unmarshal(body, obj)
if err != nil {
return nil, nil, err // unable to read object
}
if obj.Name == "" {
obj.Name = store.GuessNameFromUID(uid)
}
summary := &entity.EntitySummary{
Kind: entity.StandardKindFolder,
Name: obj.Name,
Description: obj.Description,
UID: uid,
}
out, err := json.MarshalIndent(obj, "", " ")
return summary, out, err
}
}

@ -1,59 +0,0 @@
package geojson
import (
"context"
"encoding/json"
"fmt"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindGeoJSON,
Name: "GeoJSON",
Description: "JSON formatted spatial data",
FileExtension: ".geojson",
MimeType: "application/json",
}
}
// Very basic geojson validator
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
var geojson map[string]any
err := json.Unmarshal(body, &geojson)
if err != nil {
return nil, nil, err
}
ftype, ok := geojson["type"].(string)
if !ok {
return nil, nil, fmt.Errorf("missing type")
}
body, err = json.Marshal(geojson)
if err != nil {
return nil, nil, err
}
summary := &entity.EntitySummary{
Kind: entity.StandardKindGeoJSON,
Name: store.GuessNameFromUID(uid),
UID: uid,
Fields: map[string]string{
"type": ftype,
},
}
if ftype == "FeatureCollection" {
features, ok := geojson["features"].([]any)
if ok {
summary.Fields["count"] = fmt.Sprint(len(features))
}
}
return summary, body, nil
}
}

@ -1,54 +0,0 @@
package geojson
import (
"context"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestGeoJSONSummary(t *testing.T) {
builder := GetEntitySummaryBuilder()
geo := []byte(`{"type":"FeatureCo`) // invalid
_, _, err := builder(context.Background(), "hello", geo)
require.Error(t, err)
geo = []byte(`{"type":"FeatureCollection","features":[]}`)
summary, out, err := builder(context.Background(), "hello", geo)
require.NoError(t, err)
require.NotEqual(t, geo, out) // wrote json
asjson, err := json.MarshalIndent(summary, "", " ")
//fmt.Printf(string(asjson))
require.NoError(t, err)
require.JSONEq(t, `{
"UID": "hello",
"kind": "geojson",
"name": "hello",
"fields": {
"type": "FeatureCollection",
"count": "0"
}
}`, string(asjson))
// Ignore gosec warning G304 since it's a test
// nolint:gosec
airports, err := os.ReadFile("../../../../../public/gazetteer/airports.geojson")
require.NoError(t, err)
summary, _, err = builder(context.Background(), "gaz/airports.geojson", airports)
require.NoError(t, err)
asjson, err = json.MarshalIndent(summary, "", " ")
//fmt.Printf(string(asjson))
require.NoError(t, err)
require.JSONEq(t, `{
"UID": "gaz/airports.geojson",
"kind": "geojson",
"name": "airports",
"fields": {
"type": "FeatureCollection",
"count": "888"
}
}`, string(asjson))
}

@ -1,37 +0,0 @@
package jsonobj
import (
"context"
"encoding/json"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindJSONObj,
Name: "JSON Object",
Description: "JSON Object",
}
}
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
v := make(map[string]any)
err := json.Unmarshal(body, &v)
if err != nil {
return nil, nil, err
}
out, err := json.MarshalIndent(v, "", " ")
if err != nil {
return nil, nil, err
}
return &entity.EntitySummary{
Kind: entity.StandardKindJSONObj,
Name: store.GuessNameFromUID(uid),
UID: uid,
}, out, err
}
}

@ -1,38 +0,0 @@
package jsonobj
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
)
func TestDataFrameSummary(t *testing.T) {
// Just creating a JSON blob
df := data.NewFrame("http_requests_total",
data.NewField("timestamp", nil, []time.Time{time.Now(), time.Now(), time.Now()}).SetConfig(&data.FieldConfig{
DisplayName: "A time Column.",
}),
data.NewField("value", data.Labels{"service": "auth"}, []float64{1.0, 2.0, 3.0}),
data.NewField("category", data.Labels{"service": "auth"}, []string{"foo", "bar", "test"}),
data.NewField("valid", data.Labels{"service": "auth"}, []bool{true, false, true}),
)
in, err := data.FrameToJSON(df, data.IncludeAll)
require.NoError(t, err)
summary, out, err := GetEntitySummaryBuilder()(context.Background(), "path/to/item", in)
require.NoError(t, err)
require.JSONEq(t, string(in), string(out)) // same json
asjson, err := json.MarshalIndent(summary, "", " ")
// fmt.Printf(string(asjson))
require.NoError(t, err)
require.JSONEq(t, `{
"name": "item",
"UID": "path/to/item",
"kind": "jsonobj"
}`, string(asjson))
}

@ -1,44 +0,0 @@
package png
import (
"bytes"
"context"
"fmt"
"image/png"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindPNG,
Name: "PNG",
Description: "PNG Image file",
IsRaw: true,
FileExtension: "png",
MimeType: "image/png",
}
}
// SVG sanitizer based on the rendering service
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
img, err := png.Decode(bytes.NewReader(body))
if err != nil {
return nil, nil, err
}
size := img.Bounds().Size()
summary := &entity.EntitySummary{
Kind: entity.StandardKindSVG,
Name: store.GuessNameFromUID(uid),
UID: uid,
Fields: map[string]string{
"width": fmt.Sprint(size.X),
"height": fmt.Sprint(size.Y),
},
}
return summary, body, nil
}
}

@ -1,33 +0,0 @@
package png
import (
"context"
"encoding/base64"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestPNGSummary(t *testing.T) {
const gopher = `iVBORw0KGgoAAAANSUhEUgAAAEsAAAA8CAAAAAALAhhPAAAFfUlEQVRYw62XeWwUVRzHf2+OPbo9d7tsWyiyaZti6eWGAhISoIGKECEKCAiJJkYTiUgTMYSIosYYBBIUIxoSPIINEBDi2VhwkQrVsj1ESgu9doHWdrul7ba73WNm3vOPtsseM9MdwvvrzTs+8/t95ze/33sI5BqiabU6m9En8oNjduLnAEDLUsQXFF8tQ5oxK3vmnNmDSMtrncks9Hhtt/qeWZapHb1ha3UqYSWVl2ZmpWgaXMXGohQAvmeop3bjTRtv6SgaK/Pb9/bFzUrYslbFAmHPp+3WhAYdr+7GN/YnpN46Opv55VDsJkoEpMrY/vO2BIYQ6LLvm0ThY3MzDzzeSJeeWNyTkgnIE5ePKsvKlcg/0T9QMzXalwXMlj54z4c0rh/mzEfr+FgWEz2w6uk8dkzFAgcARAgNp1ZYef8bH2AgvuStbc2/i6CiWGj98y2tw2l4FAXKkQBIf+exyRnteY83LfEwDQAYCoK+P6bxkZm/0966LxcAAILHB56kgD95PPxltuYcMtFTWw/FKkY/6Opf3GGd9ZF+Qp6mzJxzuRSractOmJrH1u8XTvWFHINNkLQLMR+XHXvfPPHw967raE1xxwtA36IMRfkAAG29/7mLuQcb2WOnsJReZGfpiHsSBX81cvMKywYZHhX5hFPtOqPGWZCXnhWGAu6lX91ElKXSalcLXu3UaOXVay57ZSe5f6Gpx7J2MXAsi7EqSp09b/MirKSyJfnfEEgeDjl8FgDAfvewP03zZ+AJ0m9aFRM8eEHBDRKjfcreDXnZdQuAxXpT2NRJ7xl3UkLBhuVGU16gZiGOgZmrSbRdqkILuL/yYoSXHHkl9KXgqNu3PB8oRg0geC5vFmLjad6mUyTKLmF3OtraWDIfACyXqmephaDABawfpi6tqqBZytfQMqOz6S09iWXhktrRaB8Xz4Yi/8gyABDm5NVe6qq/3VzPrcjELWrebVuyY2T7ar4zQyybUCtsQ5Es1FGaZVrRVQwAgHGW2ZCRZshI5bGQi7HesyE972pOSeMM0dSktlzxRdrlqb3Osa6CCS8IJoQQQgBAbTAa5l5epO34rJszibJI8rxLfGzcp1dRosutGeb2VDNgqYrwTiPNsLxXiPi3dz7LiS1WBRBDBOnqEjyy3aQb+/bLiJzz9dIkscVBBLxMfSEac7kO4Fpkngi0ruNBeSOal+u8jgOuqPz12nryMLCniEjtOOOmpt+KEIqsEdocJjYXwrh9OZqWJQyPCTo67LNS/TdxLAv6R5ZNK9npEjbYdT33gRo4o5oTqR34R+OmaSzDBWsAIPhuRcgyoteNi9gF0KzNYWVItPf2TLoXEg+7isNC7uJkgo1iQWOfRSP9NR11RtbZZ3OMG/VhL6jvx+J1m87+RCfJChAtEBQkSBX2PnSiihc/Twh3j0h7qdYQAoRVsRGmq7HU2QRbaxVGa1D6nIOqaIWRjyRZpHMQKWKpZM5feA+lzC4ZFultV8S6T0mzQGhQohi5I8iw+CsqBSxhFMuwyLgSwbghGb0AiIKkSDmGZVmJSiKihsiyOAUs70UkywooYP0bii9GdH4sfr1UNysd3fUyLLMQN+rsmo3grHl9VNJHbbwxoa47Vw5gupIqrZcjPh9R4Nye3nRDk199V+aetmvVtDRE8/+cbgAAgMIWGb3UA0MGLE9SCbWX670TDy1y98c3D27eppUjsZ6fql3jcd5rUe7+ZIlLNQny3Rd+E5Tct3WVhTM5RBCEdiEK0b6B+/ca2gYU393nFj/n1AygRQxPIUA043M42u85+z2SnssKrPl8Mx76NL3E6eXc3be7OD+H4WHbJkKI8AU8irbITQjZ+0hQcPEgId/Fn/pl9crKH02+5o2b9T/eMx7pKoskYgAAAABJRU5ErkJggg==`
img, err := base64.StdEncoding.DecodeString(gopher)
require.NoError(t, err)
summary, out, err := GetEntitySummaryBuilder()(context.Background(), "hello.png", img)
require.NoError(t, err)
require.Equal(t, img, out) // same image
asjson, err := json.MarshalIndent(summary, "", " ")
//fmt.Printf(string(asjson))
require.NoError(t, err)
require.JSONEq(t, `{
"UID": "hello.png",
"kind": "svg",
"name": "hello",
"fields": {
"height": "60",
"width": "75"
}
}`, string(asjson))
}

@ -1,50 +0,0 @@
package preferences
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/kinds/preferences"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindPreferences,
Name: "Preferences",
}
}
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
if uid != "default" {
if !(strings.HasPrefix(uid, "user-") || strings.HasPrefix(uid, "team-")) {
return nil, nil, fmt.Errorf("expecting UID: default, user-{#}, or team-{#}")
}
}
obj := &preferences.Spec{}
err := json.Unmarshal(body, obj)
if err != nil {
return nil, nil, err // unable to read object
}
summary := &entity.EntitySummary{
Kind: entity.StandardKindPreferences,
Name: uid, // team-${id} | user-${id}
UID: uid,
}
if obj.HomeDashboardUID != nil && *obj.HomeDashboardUID != "" {
summary.References = append(summary.References, &entity.EntityExternalReference{
Family: entity.StandardKindDashboard,
Identifier: *obj.HomeDashboardUID,
})
}
out, err := json.MarshalIndent(obj, "", " ")
return summary, out, err
}
}

@ -1,182 +0,0 @@
package kind
import (
"fmt"
"sort"
"sync"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/services/store/kind/dataframe"
"github.com/grafana/grafana/pkg/services/store/kind/folder"
"github.com/grafana/grafana/pkg/services/store/kind/geojson"
"github.com/grafana/grafana/pkg/services/store/kind/jsonobj"
"github.com/grafana/grafana/pkg/services/store/kind/png"
"github.com/grafana/grafana/pkg/services/store/kind/preferences"
"github.com/grafana/grafana/pkg/services/store/kind/snapshot"
"github.com/grafana/grafana/pkg/services/store/kind/svg"
"github.com/grafana/grafana/pkg/setting"
)
type KindRegistry interface {
Register(info entity.EntityKindInfo, builder entity.EntitySummaryBuilder) error
GetSummaryBuilder(kind string) entity.EntitySummaryBuilder
GetInfo(kind string) (entity.EntityKindInfo, error)
GetFromExtension(suffix string) (entity.EntityKindInfo, error)
GetKinds() []entity.EntityKindInfo
}
func NewKindRegistry() KindRegistry {
kinds := make(map[string]*kindValues)
kinds[entity.StandardKindDashboard] = &kindValues{
info: dashboard.GetEntityKindInfo(),
builder: dashboard.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindSnapshot] = &kindValues{
info: snapshot.GetEntityKindInfo(),
builder: snapshot.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindFolder] = &kindValues{
info: folder.GetEntityKindInfo(),
builder: folder.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindPNG] = &kindValues{
info: png.GetEntityKindInfo(),
builder: png.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindGeoJSON] = &kindValues{
info: geojson.GetEntityKindInfo(),
builder: geojson.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindDataFrame] = &kindValues{
info: dataframe.GetEntityKindInfo(),
builder: dataframe.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindJSONObj] = &kindValues{
info: jsonobj.GetEntityKindInfo(),
builder: jsonobj.GetEntitySummaryBuilder(),
}
kinds[entity.StandardKindPreferences] = &kindValues{
info: preferences.GetEntityKindInfo(),
builder: preferences.GetEntitySummaryBuilder(),
}
// create a registry
reg := &registry{
mutex: sync.RWMutex{},
kinds: kinds,
}
reg.updateInfoArray()
return reg
}
// TODO? This could be a zero dependency service that others are responsible for configuring
func ProvideService(cfg *setting.Cfg, renderer rendering.Service) KindRegistry {
reg := NewKindRegistry()
// Register SVG support
//-----------------------
info := svg.GetEntityKindInfo()
allowUnsanitizedSvgUpload := cfg != nil && cfg.Storage.AllowUnsanitizedSvgUpload
support := svg.GetEntitySummaryBuilder(allowUnsanitizedSvgUpload, renderer)
_ = reg.Register(info, support)
return reg
}
type kindValues struct {
info entity.EntityKindInfo
builder entity.EntitySummaryBuilder
}
type registry struct {
mutex sync.RWMutex
kinds map[string]*kindValues
info []entity.EntityKindInfo
suffix map[string]entity.EntityKindInfo
}
func (r *registry) updateInfoArray() {
suffix := make(map[string]entity.EntityKindInfo)
info := make([]entity.EntityKindInfo, 0, len(r.kinds))
for _, v := range r.kinds {
info = append(info, v.info)
if v.info.FileExtension != "" {
suffix[v.info.FileExtension] = v.info
}
}
sort.Slice(info, func(i, j int) bool {
return info[i].ID < info[j].ID
})
r.info = info
r.suffix = suffix
}
func (r *registry) Register(info entity.EntityKindInfo, builder entity.EntitySummaryBuilder) error {
if info.ID == "" || builder == nil {
return fmt.Errorf("invalid kind")
}
r.mutex.Lock()
defer r.mutex.Unlock()
if r.kinds[info.ID] != nil {
return fmt.Errorf("already exits")
}
r.kinds[info.ID] = &kindValues{
info: info,
builder: builder,
}
r.updateInfoArray()
return nil
}
// GetSummaryBuilder returns a builder or nil if not found
func (r *registry) GetSummaryBuilder(kind string) entity.EntitySummaryBuilder {
r.mutex.RLock()
defer r.mutex.RUnlock()
v, ok := r.kinds[kind]
if !ok {
// fallback to default
v, ok = r.kinds[entity.StandardKindJSONObj]
}
if ok {
return v.builder
}
return nil
}
// GetInfo returns the registered info
func (r *registry) GetInfo(kind string) (entity.EntityKindInfo, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
v, ok := r.kinds[kind]
if ok {
return v.info, nil
}
return entity.EntityKindInfo{}, fmt.Errorf("not found")
}
// GetInfo returns the registered info
func (r *registry) GetFromExtension(suffix string) (entity.EntityKindInfo, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
v, ok := r.suffix[suffix]
if ok {
return v, nil
}
return entity.EntityKindInfo{}, fmt.Errorf("not found")
}
// GetSummaryBuilder returns a builder or nil if not found
func (r *registry) GetKinds() []entity.EntityKindInfo {
r.mutex.RLock()
defer r.mutex.RUnlock()
return r.info // returns a copy of the array
}

@ -1,43 +0,0 @@
package kind
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/store/kind/dummy"
)
func TestKindRegistry(t *testing.T) {
registry := NewKindRegistry()
err := registry.Register(dummy.GetEntityKindInfo("test"), dummy.GetEntitySummaryBuilder("test"))
require.NoError(t, err)
ids := []string{}
for _, k := range registry.GetKinds() {
ids = append(ids, k.ID)
}
require.Equal(t, []string{
"dashboard",
"folder",
"frame",
"geojson",
"jsonobj",
"png",
"preferences",
"snapshot",
"test",
}, ids)
// Check that we registered a test item
info, err := registry.GetInfo("test")
require.NoError(t, err)
require.Equal(t, "test", info.Name)
require.True(t, info.IsRaw)
// Get by suffix
info, err = registry.GetFromExtension("png")
require.NoError(t, err)
require.Equal(t, "PNG", info.Name)
require.True(t, info.IsRaw)
}

@ -1,62 +0,0 @@
package snapshot
import (
"context"
"encoding/json"
"fmt"
"github.com/grafana/grafana/pkg/services/store/entity"
)
// A snapshot is a dashboard with no external queries and a few additional properties
type Model struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
DeleteKey string `json:"deleteKey"`
ExternalURL string `json:"externalURL"`
Expires int64 `json:"expires,omitempty"` // time that this expires
DashboardUID string `json:"dashboard,omitempty"`
Snapshot json.RawMessage `json:"snapshot,omitempty"`
}
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindSnapshot,
Name: "Snapshot",
}
}
func GetEntitySummaryBuilder() entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
obj := &Model{}
err := json.Unmarshal(body, obj)
if err != nil {
return nil, nil, err // unable to read object
}
if obj.Name == "" {
return nil, nil, fmt.Errorf("expected snapshot name")
}
if obj.DeleteKey == "" {
return nil, nil, fmt.Errorf("expected delete key")
}
summary := &entity.EntitySummary{
Kind: entity.StandardKindFolder,
Name: obj.Name,
Description: obj.Description,
UID: uid,
Fields: map[string]string{
"deleteKey": obj.DeleteKey,
"externalURL": obj.ExternalURL,
"expires": fmt.Sprint(obj.Expires),
},
References: []*entity.EntityExternalReference{
{Family: entity.StandardKindDashboard, Identifier: obj.DashboardUID},
},
}
// Keep the original body
return summary, body, err
}
}

@ -1,66 +0,0 @@
package svg
import (
"context"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/store/entity"
)
func GetEntityKindInfo() entity.EntityKindInfo {
return entity.EntityKindInfo{
ID: entity.StandardKindSVG,
Name: "SVG",
Description: "Scalable Vector Graphics",
IsRaw: true,
FileExtension: "svg",
MimeType: "image/svg+xml",
}
}
// SVG sanitizer based on the rendering service
func GetEntitySummaryBuilder(allowUnsanitizedSvgUpload bool, renderer rendering.Service) entity.EntitySummaryBuilder {
return func(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) {
if !IsSVG(body) {
return nil, nil, fmt.Errorf("invalid svg")
}
// When a renderer exists, we can return a sanitized version
var sanitized []byte
if renderer != nil {
rsp, err := renderer.SanitizeSVG(ctx, &rendering.SanitizeSVGRequest{
Content: body,
})
if err != nil && !allowUnsanitizedSvgUpload {
return nil, nil, err
}
sanitized = rsp.Sanitized
}
if sanitized == nil {
if !allowUnsanitizedSvgUpload {
return nil, nil, fmt.Errorf("unable to sanitize svg")
}
sanitized = body
}
return &entity.EntitySummary{
Kind: entity.StandardKindSVG,
Name: guessNameFromUID(uid),
UID: uid,
}, sanitized, nil
}
}
func guessNameFromUID(uid string) string {
sidx := strings.LastIndex(uid, "/") + 1
didx := strings.LastIndex(uid, ".")
if didx > sidx && didx != sidx {
return uid[sidx:didx]
}
if sidx > 0 {
return uid[sidx:]
}
return uid
}

@ -9,8 +9,8 @@ import (
"strings"
"github.com/grafana/grafana/pkg/infra/filestorage"
"github.com/grafana/grafana/pkg/services/store/kind/svg"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
)
var (
@ -52,7 +52,7 @@ func fail(reason string) validationResult {
func (s *standardStorageService) detectMimeType(ctx context.Context, user *user.SignedInUser, uploadRequest *UploadRequest) string {
if strings.HasSuffix(uploadRequest.Path, ".svg") {
if svg.IsSVG(uploadRequest.Contents) {
if util.IsSVG(uploadRequest.Contents) {
return "image/svg+xml"
}
}

@ -23,7 +23,7 @@
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//OTHER DEALINGS IN THE SOFTWARE.
package svg
package util
import (
"regexp"
Loading…
Cancel
Save