|
|
|
|
@ -4,7 +4,8 @@ import ( |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"strings" |
|
|
|
|
"os" |
|
|
|
|
"path/filepath" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/infra/filestorage" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log" |
|
|
|
|
@ -25,9 +26,8 @@ var ErrValidationFailed = errors.New("request validation failed") |
|
|
|
|
var ErrFileAlreadyExists = errors.New("file exists") |
|
|
|
|
|
|
|
|
|
const RootPublicStatic = "public-static" |
|
|
|
|
const RootResources = "resources" |
|
|
|
|
|
|
|
|
|
const MAX_UPLOAD_SIZE = 3 * 1024 * 1024 // 3MB
|
|
|
|
|
const MAX_UPLOAD_SIZE = 1 * 1024 * 1024 // 3MB
|
|
|
|
|
|
|
|
|
|
type DeleteFolderCmd struct { |
|
|
|
|
Path string `json:"path"` |
|
|
|
|
@ -85,11 +85,26 @@ func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles, |
|
|
|
|
setDescription("Access files from the static public files"), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Development dashboards
|
|
|
|
|
if setting.Env != setting.Prod { |
|
|
|
|
devenv := filepath.Join(cfg.StaticRootPath, "..", "devenv") |
|
|
|
|
if _, err := os.Stat(devenv); !os.IsNotExist(err) { |
|
|
|
|
// path/to/whatever exists
|
|
|
|
|
s := newDiskStorage("devenv", "Development Environment", &StorageLocalDiskConfig{ |
|
|
|
|
Path: devenv, |
|
|
|
|
Roots: []string{ |
|
|
|
|
"/dev-dashboards/", |
|
|
|
|
}, |
|
|
|
|
}).setReadOnly(false).setDescription("Explore files within the developer environment directly") |
|
|
|
|
globalRoots = append(globalRoots, s) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
initializeOrgStorages := func(orgId int64) []storageRuntime { |
|
|
|
|
storages := make([]storageRuntime, 0) |
|
|
|
|
if features.IsEnabled(featuremgmt.FlagStorageLocalUpload) { |
|
|
|
|
storages = append(storages, |
|
|
|
|
newSQLStorage(RootResources, |
|
|
|
|
newSQLStorage("resources", |
|
|
|
|
"Resources", |
|
|
|
|
&StorageSQLConfig{orgId: orgId}, sql). |
|
|
|
|
setBuiltin(true). |
|
|
|
|
@ -155,22 +170,16 @@ type UploadRequest struct { |
|
|
|
|
OverwriteExistingFile bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func storageSupportsMutatingOperations(path string) bool { |
|
|
|
|
// TODO: this is temporary - make it rbac-driven
|
|
|
|
|
return strings.HasPrefix(path, RootResources+"/") || path == RootResources |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *standardStorageService) Upload(ctx context.Context, user *models.SignedInUser, req *UploadRequest) error { |
|
|
|
|
upload, _ := s.tree.getRoot(getOrgId(user), RootResources) |
|
|
|
|
upload, storagePath := s.tree.getRoot(getOrgId(user), req.Path) |
|
|
|
|
if upload == nil { |
|
|
|
|
return ErrUploadFeatureDisabled |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !storageSupportsMutatingOperations(req.Path) { |
|
|
|
|
if upload.Meta().ReadOnly { |
|
|
|
|
return ErrUnsupportedStorage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
@ -186,7 +195,7 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed |
|
|
|
|
grafanaStorageLogger.Info("uploading a file", "filetype", req.MimeType, "path", req.Path) |
|
|
|
|
|
|
|
|
|
if !req.OverwriteExistingFile { |
|
|
|
|
file, err := upload.Get(ctx, storagePath) |
|
|
|
|
file, err := upload.Store().Get(ctx, storagePath) |
|
|
|
|
if err != nil { |
|
|
|
|
grafanaStorageLogger.Error("failed while checking file existence", "err", err, "path", req.Path) |
|
|
|
|
return ErrUploadInternalError |
|
|
|
|
@ -197,7 +206,7 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := upload.Upsert(ctx, upsertCommand); err != nil { |
|
|
|
|
if err := upload.Store().Upsert(ctx, upsertCommand); err != nil { |
|
|
|
|
grafanaStorageLogger.Error("failed while uploading the file", "err", err, "path", req.Path) |
|
|
|
|
return ErrUploadInternalError |
|
|
|
|
} |
|
|
|
|
@ -206,34 +215,32 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *standardStorageService) DeleteFolder(ctx context.Context, user *models.SignedInUser, cmd *DeleteFolderCmd) error { |
|
|
|
|
resources, _ := s.tree.getRoot(getOrgId(user), RootResources) |
|
|
|
|
if resources == nil { |
|
|
|
|
root, storagePath := s.tree.getRoot(getOrgId(user), cmd.Path) |
|
|
|
|
if root == nil { |
|
|
|
|
return fmt.Errorf("resources storage is not enabled") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !storageSupportsMutatingOperations(cmd.Path) { |
|
|
|
|
if root.Meta().ReadOnly { |
|
|
|
|
return ErrUnsupportedStorage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
storagePath := strings.TrimPrefix(cmd.Path, RootResources) |
|
|
|
|
if storagePath == "" { |
|
|
|
|
storagePath = filestorage.Delimiter |
|
|
|
|
} |
|
|
|
|
return resources.DeleteFolder(ctx, storagePath, &filestorage.DeleteFolderOptions{Force: true}) |
|
|
|
|
return root.Store().DeleteFolder(ctx, storagePath, &filestorage.DeleteFolderOptions{Force: true}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *standardStorageService) CreateFolder(ctx context.Context, user *models.SignedInUser, cmd *CreateFolderCmd) error { |
|
|
|
|
if !storageSupportsMutatingOperations(cmd.Path) { |
|
|
|
|
return ErrUnsupportedStorage |
|
|
|
|
root, storagePath := s.tree.getRoot(getOrgId(user), cmd.Path) |
|
|
|
|
if root == nil { |
|
|
|
|
return fmt.Errorf("resources storage is not enabled") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
resources, _ := s.tree.getRoot(getOrgId(user), RootResources) |
|
|
|
|
if resources == nil { |
|
|
|
|
return fmt.Errorf("resources storage is not enabled") |
|
|
|
|
if root.Meta().ReadOnly { |
|
|
|
|
return ErrUnsupportedStorage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
storagePath := strings.TrimPrefix(cmd.Path, RootResources) |
|
|
|
|
err := resources.CreateFolder(ctx, storagePath) |
|
|
|
|
err := root.Store().CreateFolder(ctx, storagePath) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
@ -241,17 +248,16 @@ func (s *standardStorageService) CreateFolder(ctx context.Context, user *models. |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *standardStorageService) Delete(ctx context.Context, user *models.SignedInUser, path string) error { |
|
|
|
|
if !storageSupportsMutatingOperations(path) { |
|
|
|
|
return ErrUnsupportedStorage |
|
|
|
|
root, storagePath := s.tree.getRoot(getOrgId(user), path) |
|
|
|
|
if root == nil { |
|
|
|
|
return fmt.Errorf("resources storage is not enabled") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
resources, _ := s.tree.getRoot(getOrgId(user), RootResources) |
|
|
|
|
if resources == nil { |
|
|
|
|
return fmt.Errorf("resources storage is not enabled") |
|
|
|
|
if root.Meta().ReadOnly { |
|
|
|
|
return ErrUnsupportedStorage |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
storagePath := strings.TrimPrefix(path, RootResources) |
|
|
|
|
err := resources.Delete(ctx, storagePath) |
|
|
|
|
err := root.Store().Delete(ctx, storagePath) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|