The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/registry/apis/provisioning/resources/folders.go

183 lines
5.1 KiB

package resources
import (
"context"
"errors"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
const MaxNumberOfFolders = 10000
type FolderManager struct {
repo repository.ReaderWriter
tree FolderTree
client dynamic.ResourceInterface
}
func NewFolderManager(repo repository.ReaderWriter, client dynamic.ResourceInterface, lookup FolderTree) *FolderManager {
return &FolderManager{
repo: repo,
tree: lookup,
client: client,
}
}
func (fm *FolderManager) Client() dynamic.ResourceInterface {
return fm.client
}
func (fm *FolderManager) Tree() FolderTree {
return fm.tree
}
func (fm *FolderManager) SetTree(tree FolderTree) {
fm.tree = tree
}
// EnsureFoldersExist creates the folder structure in the cluster.
func (fm *FolderManager) EnsureFolderPathExist(ctx context.Context, filePath string) (parent string, err error) {
cfg := fm.repo.Config()
parent = RootFolder(cfg)
dir := filePath
if !safepath.IsDir(filePath) {
dir = safepath.Dir(filePath)
}
if dir == "" {
return parent, nil
}
f := ParseFolder(dir, cfg.Name)
if fm.tree.In(f.ID) {
return f.ID, nil
}
err = safepath.Walk(ctx, f.Path, func(ctx context.Context, traverse string) error {
f := ParseFolder(traverse, cfg.GetName())
if fm.tree.In(f.ID) {
parent = f.ID
return nil
}
if err := fm.EnsureFolderExists(ctx, f, parent); err != nil {
return fmt.Errorf("ensure folder exists: %w", err)
}
fm.tree.Add(f, parent)
parent = f.ID
return nil
})
if err != nil {
return "", err
}
return f.ID, nil
}
// EnsureFolderExists creates the folder if it doesn't exist.
// If the folder already exists:
// - it will error if the folder is not owned by this repository
func (fm *FolderManager) EnsureFolderExists(ctx context.Context, folder Folder, parent string) error {
cfg := fm.repo.Config()
obj, err := fm.client.Get(ctx, folder.ID, metav1.GetOptions{})
if err == nil {
current, ok := obj.GetAnnotations()[utils.AnnoKeyManagerIdentity]
if !ok {
return fmt.Errorf("target folder is not managed by a repository")
}
if current != cfg.Name {
return fmt.Errorf("target folder is managed by a different repository (%s)", current)
}
return nil
} else if !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to check if folder exists: %w", err)
}
// Always use the provisioning identity when writing
ctx, _, err = identity.WithProvisioningIdentity(ctx, cfg.GetNamespace())
if err != nil {
return fmt.Errorf("unable to use provisioning identity %w", err)
}
obj = &unstructured.Unstructured{
Object: map[string]interface{}{
"spec": map[string]any{
"title": folder.Title,
},
},
}
obj.SetAPIVersion(folders.APIVERSION)
obj.SetKind(folders.FolderResourceInfo.GroupVersionKind().Kind)
obj.SetNamespace(cfg.GetNamespace())
obj.SetName(folder.ID)
meta, err := utils.MetaAccessor(obj)
if err != nil {
return fmt.Errorf("create meta accessor for the object: %w", err)
}
if parent != "" {
meta.SetFolder(parent)
}
meta.SetManagerProperties(utils.ManagerProperties{
Kind: utils.ManagerKindRepo,
Identity: cfg.GetName(),
})
meta.SetSourceProperties(utils.SourceProperties{
Path: folder.Path,
})
if _, err := fm.client.Create(ctx, obj, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("failed to create folder: %w", err)
}
return nil
}
func (fm *FolderManager) GetFolder(ctx context.Context, name string) (*unstructured.Unstructured, error) {
return fm.client.Get(ctx, name, metav1.GetOptions{})
}
// ReplicateTree replicates the folder tree to the repository.
// The function fn is called for each folder.
// If the folder already exists, the function is called with created set to false.
// If the folder is created, the function is called with created set to true.
func (fm *FolderManager) EnsureFolderTreeExists(ctx context.Context, ref, path string, tree FolderTree, fn func(folder Folder, created bool, err error) error) error {
return tree.Walk(ctx, func(ctx context.Context, folder Folder, parent string) error {
p := folder.Path
if path != "" {
p = safepath.Join(path, p)
}
if !safepath.IsDir(p) {
p = p + "/" // trailing slash indicates folder
}
_, err := fm.repo.Read(ctx, p, ref)
if err != nil && (!errors.Is(err, repository.ErrFileNotFound) && !apierrors.IsNotFound(err)) {
return fn(folder, false, fmt.Errorf("check if folder exists before writing: %w", err))
} else if err == nil {
return fn(folder, false, nil)
}
msg := fmt.Sprintf("Add folder %s", p)
if err := fm.repo.Create(ctx, p, ref, nil, msg); err != nil {
return fn(folder, true, fmt.Errorf("write folder in repo: %w", err))
}
// Add it to the existing tree
fm.tree.Add(folder, parent)
return fn(folder, true, nil)
})
}