mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 05:42:31 +08:00
184 lines
5.1 KiB
Go
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)
|
|
})
|
|
}
|