mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: remove the entity kind registry (#79178)
This commit is contained in:
parent
2a2a132c61
commit
9849c954a3
@ -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 := ®istry{
|
||||
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…
Reference in New Issue
Block a user