Files
2025-04-21 16:20:39 +03:00

184 lines
5.1 KiB
Go

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)
})
}