Storage: Add basic file upload management (#50638)
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
package store
|
||||
|
||||
type RootStorageConfig struct {
|
||||
Type string `json:"type"`
|
||||
Prefix string `json:"prefix"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Prefix string `json:"prefix"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
|
||||
// Depending on type, these will be configured
|
||||
Disk *StorageLocalDiskConfig `json:"disk,omitempty"`
|
||||
|
||||
@@ -2,12 +2,14 @@ package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
@@ -57,7 +59,8 @@ func (s *httpStorage) Upload(c *models.ReqContext) response.Response {
|
||||
}
|
||||
c.Req.Body = http.MaxBytesReader(c.Resp, c.Req.Body, MAX_UPLOAD_SIZE)
|
||||
if err := c.Req.ParseMultipartForm(MAX_UPLOAD_SIZE); err != nil {
|
||||
return response.Error(400, "Please limit file uploaded under 1MB", err)
|
||||
msg := fmt.Sprintf("Please limit file uploaded under %s", util.ByteCountSI(MAX_UPLOAD_SIZE))
|
||||
return response.Error(400, msg, err)
|
||||
}
|
||||
|
||||
files := c.Req.MultipartForm.File["file"]
|
||||
@@ -92,7 +95,7 @@ func (s *httpStorage) Upload(c *models.ReqContext) response.Response {
|
||||
return errFileTooBig
|
||||
}
|
||||
|
||||
path := RootUpload + "/" + fileHeader.Filename
|
||||
path := RootResources + "/" + fileHeader.Filename
|
||||
|
||||
mimeType := http.DetectContentType(data)
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ var ErrValidationFailed = errors.New("request validation failed")
|
||||
var ErrFileAlreadyExists = errors.New("file exists")
|
||||
|
||||
const RootPublicStatic = "public-static"
|
||||
const RootUpload = "upload"
|
||||
const RootResources = "resources"
|
||||
|
||||
const MAX_UPLOAD_SIZE = 1024 * 1024 // 1MB
|
||||
const MAX_UPLOAD_SIZE = 3 * 1024 * 1024 // 3MB
|
||||
|
||||
type StorageService interface {
|
||||
registry.BackgroundService
|
||||
@@ -60,21 +60,25 @@ func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles,
|
||||
Path: cfg.StaticRootPath,
|
||||
Roots: []string{
|
||||
"/testdata/",
|
||||
// "/img/icons/",
|
||||
// "/img/bg/",
|
||||
"/img/",
|
||||
"/gazetteer/",
|
||||
"/maps/",
|
||||
},
|
||||
}).setReadOnly(true).setBuiltin(true),
|
||||
}).setReadOnly(true).setBuiltin(true).
|
||||
setDescription("Access files from the static public files"),
|
||||
}
|
||||
|
||||
initializeOrgStorages := func(orgId int64) []storageRuntime {
|
||||
storages := make([]storageRuntime, 0)
|
||||
if features.IsEnabled(featuremgmt.FlagStorageLocalUpload) {
|
||||
config := &StorageSQLConfig{orgId: orgId}
|
||||
storages = append(storages, newSQLStorage(RootUpload, "Local file upload", config, sql).setBuiltin(true))
|
||||
storages = append(storages,
|
||||
newSQLStorage(RootResources,
|
||||
"Resources",
|
||||
&StorageSQLConfig{orgId: orgId}, sql).
|
||||
setBuiltin(true).
|
||||
setDescription("Upload custom resource files"))
|
||||
}
|
||||
|
||||
return storages
|
||||
}
|
||||
|
||||
@@ -133,16 +137,16 @@ type UploadRequest struct {
|
||||
}
|
||||
|
||||
func (s *standardStorageService) Upload(ctx context.Context, user *models.SignedInUser, req *UploadRequest) error {
|
||||
upload, _ := s.tree.getRoot(getOrgId(user), RootUpload)
|
||||
upload, _ := s.tree.getRoot(getOrgId(user), RootResources)
|
||||
if upload == nil {
|
||||
return ErrUploadFeatureDisabled
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.Path, RootUpload+"/") {
|
||||
if !strings.HasPrefix(req.Path, RootResources+"/") {
|
||||
return ErrUnsupportedStorage
|
||||
}
|
||||
|
||||
storagePath := strings.TrimPrefix(req.Path, RootUpload)
|
||||
storagePath := strings.TrimPrefix(req.Path, RootResources)
|
||||
validationResult := s.validateUploadRequest(ctx, user, req, storagePath)
|
||||
if !validationResult.ok {
|
||||
grafanaStorageLogger.Warn("file upload validation failed", "filetype", req.MimeType, "path", req.Path, "reason", validationResult.reason)
|
||||
@@ -178,7 +182,7 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed
|
||||
}
|
||||
|
||||
func (s *standardStorageService) Delete(ctx context.Context, user *models.SignedInUser, path string) error {
|
||||
upload, _ := s.tree.getRoot(getOrgId(user), RootUpload)
|
||||
upload, _ := s.tree.getRoot(getOrgId(user), RootResources)
|
||||
if upload == nil {
|
||||
return fmt.Errorf("upload feature is not enabled")
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestUpload(t *testing.T) {
|
||||
request := UploadRequest{
|
||||
EntityType: EntityTypeImage,
|
||||
Contents: make([]byte, 0),
|
||||
Path: "upload/myFile.jpg",
|
||||
Path: "resources/myFile.jpg",
|
||||
MimeType: "image/jpg",
|
||||
}
|
||||
err = s.Upload(context.Background(), dummyUser, &request)
|
||||
|
||||
+36
-11
@@ -89,31 +89,52 @@ func (t *nestedTree) ListFolder(ctx context.Context, orgId int64, path string) (
|
||||
if path == "" || path == "/" {
|
||||
t.assureOrgIsInitialized(orgId)
|
||||
|
||||
idx := 0
|
||||
count := len(t.rootsByOrgId[ac.GlobalOrgID])
|
||||
if orgId != ac.GlobalOrgID {
|
||||
count += len(t.rootsByOrgId[orgId])
|
||||
}
|
||||
|
||||
title := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
names := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
title := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
descr := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
types := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
readOnly := data.NewFieldFromFieldType(data.FieldTypeBool, count)
|
||||
builtIn := data.NewFieldFromFieldType(data.FieldTypeBool, count)
|
||||
mtype := data.NewFieldFromFieldType(data.FieldTypeString, count)
|
||||
title.Name = "title"
|
||||
names.Name = "name"
|
||||
descr.Name = "description"
|
||||
mtype.Name = "mediaType"
|
||||
for i, f := range t.rootsByOrgId[ac.GlobalOrgID] {
|
||||
names.Set(i, f.Meta().Config.Prefix)
|
||||
title.Set(i, f.Meta().Config.Name)
|
||||
mtype.Set(i, "directory")
|
||||
types.Name = "storageType"
|
||||
readOnly.Name = "readOnly"
|
||||
builtIn.Name = "builtIn"
|
||||
for _, f := range t.rootsByOrgId[ac.GlobalOrgID] {
|
||||
meta := f.Meta()
|
||||
names.Set(idx, meta.Config.Prefix)
|
||||
title.Set(idx, meta.Config.Name)
|
||||
descr.Set(idx, meta.Config.Description)
|
||||
mtype.Set(idx, "directory")
|
||||
types.Set(idx, meta.Config.Type)
|
||||
readOnly.Set(idx, meta.ReadOnly)
|
||||
builtIn.Set(idx, meta.Builtin)
|
||||
idx++
|
||||
}
|
||||
if orgId != ac.GlobalOrgID {
|
||||
for i, f := range t.rootsByOrgId[orgId] {
|
||||
names.Set(i, f.Meta().Config.Prefix)
|
||||
title.Set(i, f.Meta().Config.Name)
|
||||
mtype.Set(i, "directory")
|
||||
for _, f := range t.rootsByOrgId[orgId] {
|
||||
meta := f.Meta()
|
||||
names.Set(idx, meta.Config.Prefix)
|
||||
title.Set(idx, meta.Config.Name)
|
||||
descr.Set(idx, meta.Config.Description)
|
||||
mtype.Set(idx, "directory")
|
||||
types.Set(idx, meta.Config.Type)
|
||||
readOnly.Set(idx, meta.ReadOnly)
|
||||
builtIn.Set(idx, meta.Builtin)
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
frame := data.NewFrame("", names, title, mtype)
|
||||
frame := data.NewFrame("", names, title, descr, mtype, types, readOnly, builtIn)
|
||||
frame.SetMeta(&data.FrameMeta{
|
||||
Type: data.FrameTypeDirectoryListing,
|
||||
})
|
||||
@@ -125,7 +146,11 @@ func (t *nestedTree) ListFolder(ctx context.Context, orgId int64, path string) (
|
||||
return nil, nil // not found (or not ready)
|
||||
}
|
||||
|
||||
listResponse, err := root.List(ctx, path, nil, &filestorage.ListOptions{Recursive: false, WithFolders: true, WithFiles: true})
|
||||
listResponse, err := root.List(ctx, path, nil, &filestorage.ListOptions{
|
||||
Recursive: false,
|
||||
WithFolders: true,
|
||||
WithFiles: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -82,6 +82,11 @@ func (t *baseStorageRuntime) setBuiltin(val bool) *baseStorageRuntime {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *baseStorageRuntime) setDescription(v string) *baseStorageRuntime {
|
||||
t.meta.Config.Description = v
|
||||
return t
|
||||
}
|
||||
|
||||
type RootStorageMeta struct {
|
||||
ReadOnly bool `json:"editable,omitempty"`
|
||||
Builtin bool `json:"builtin,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user