diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index fa7db79a687..07fcf057b45 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -476,6 +476,7 @@ func (b *ProvisioningAPIBuilder) GetPostStartHooks() (map[string]genericapiserve c.ProvisioningV0alpha1(), repoInformer, b, // repoGetter + b.parsers, b.identities, b.tester, b.jobs, diff --git a/pkg/registry/apis/provisioning/repository_controller.go b/pkg/registry/apis/provisioning/repository_controller.go index 095c6d57a83..c0424df15f6 100644 --- a/pkg/registry/apis/provisioning/repository_controller.go +++ b/pkg/registry/apis/provisioning/repository_controller.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/auth" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) const loggerName = "provisioning-repository-controller" @@ -48,6 +49,7 @@ type RepositoryController struct { client client.ProvisioningV0alpha1Interface repoLister listers.RepositoryLister repoSynced cache.InformerSynced + parsers *resources.ParserFactory jobs jobs.JobQueue @@ -69,6 +71,7 @@ func NewRepositoryController( provisioningClient client.ProvisioningV0alpha1Interface, repoInformer informer.RepositoryInformer, repoGetter RepoGetter, + parsers *resources.ParserFactory, identities auth.BackgroundIdentityService, tester *RepositoryTester, jobs jobs.JobQueue, @@ -84,6 +87,7 @@ func NewRepositoryController( }, ), repoGetter: repoGetter, + parsers: parsers, identities: identities, tester: tester, jobs: jobs, @@ -230,10 +234,27 @@ func (rc *RepositoryController) process(item *queueItem) error { return fmt.Errorf("unable to create repository from object: %w", err) } - hooks, ok := repo.(repository.RepositoryHooks) - if ok { - return hooks.OnDelete(ctx) + if hooks, ok := repo.(repository.RepositoryHooks); !ok { + err := hooks.OnDelete(ctx) + if err != nil { + return fmt.Errorf("handle repository delete: %w", err) + } } + + parser, err := rc.parsers.GetParser(ctx, repo) + if err != nil { + return fmt.Errorf("failed to get parser for %s: %w", repo.Config().Name, err) + } + + replicator, err := resources.NewReplicator(repo, parser) + if err != nil { + return fmt.Errorf("error creating replicator") + } + + if err := replicator.Unsync(ctx, resources.UnsyncOptions{}); err != nil { + return fmt.Errorf("unsync repository: %w", err) + } + return nil } diff --git a/pkg/registry/apis/provisioning/resources/replicator.go b/pkg/registry/apis/provisioning/resources/replicator.go index 92902808941..a5d579ce899 100644 --- a/pkg/registry/apis/provisioning/resources/replicator.go +++ b/pkg/registry/apis/provisioning/resources/replicator.go @@ -18,11 +18,16 @@ import ( "k8s.io/client-go/dynamic" "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/apimachinery/utils" apiutils "github.com/grafana/grafana/pkg/apimachinery/utils" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" ) +type UnsyncOptions struct { + KeepDashboards bool +} + type Replicator struct { client *DynamicClient parser *Parser @@ -99,6 +104,39 @@ func (r *Replicator) Sync(ctx context.Context) (string, error) { return latest, nil } +func (r *Replicator) Unsync(ctx context.Context, opts UnsyncOptions) error { + cfg := r.repository.Config() + + logger := logging.FromContext(ctx) + logger = logger.With("repository", cfg.Name, "namespace", cfg.GetNamespace(), "folder", cfg.Spec.Folder) + logger.Info("start repository unsync") + defer logger.Info("end repository unsync") + + if r.repository.Config().Spec.Folder != "" { + obj, err := r.folders.Get(ctx, r.repository.Config().Spec.Folder, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("get folder to unsync: %w", err) + } + + meta, err := apiutils.MetaAccessor(obj) + if err != nil { + return fmt.Errorf("create meta accessor from folder object: %w", err) + } + + meta.SetRepositoryInfo(nil) + obj, err = r.folders.Update(ctx, obj, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("remove repo info from folder: %w", err) + } + + logger.Info("removed repo info from folder", "object", obj) + } else { + logger.Info("skip repo info removal as it's root folder") + } + + return nil +} + // replicateTree replicates all files in the repository. func (r *Replicator) replicateTree(ctx context.Context, ref string) error { tree, err := r.repository.ReadTree(ctx, ref) @@ -211,12 +249,16 @@ func (r *Replicator) createFolderPath(ctx context.Context, filePath string) (str } logger := logging.FromContext(ctx).With("file", filePath) + + var currentPath string for _, folder := range strings.Split(dir, "/") { if folder == "" { // Trailing / leading slash? continue } + currentPath = path.Join(currentPath, folder) + logger := logger.With("folder", folder) obj, err := r.folders.Get(ctx, folder, metav1.GetOptions{}) // FIXME: Check for IsNotFound properly @@ -226,21 +268,31 @@ func (r *Replicator) createFolderPath(ctx context.Context, filePath string) (str continue } - _, err = r.folders.Create(ctx, &unstructured.Unstructured{ + obj = &unstructured.Unstructured{ Object: map[string]interface{}{ - "metadata": map[string]any{ - "name": folder, - "namespace": r.client.GetNamespace(), - "annotations": map[string]any{ - apiutils.AnnoKeyFolder: parent, - }, - }, "spec": map[string]any{ "title": folder, // TODO: how do we want to get this? "description": "Repository-managed folder.", }, }, - }, metav1.CreateOptions{}) + } + + meta, err := apiutils.MetaAccessor(obj) + if err != nil { + return "", fmt.Errorf("create meta accessor for the object: %w", err) + } + + obj.SetNamespace(r.client.GetNamespace()) + obj.SetName(folder) + meta.SetFolder(parent) + meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{ + Name: r.repository.Config().Name, + Path: currentPath, + Hash: "", // FIXME: which hash? + Timestamp: nil, // ???&info.Modified.Time, + }) + + _, err = r.folders.Create(ctx, obj, metav1.CreateOptions{}) if err != nil { return parent, fmt.Errorf("failed to create folder '%s': %w", folder, err) } @@ -494,8 +546,24 @@ func (r *Replicator) ensureRepositoryFolderExists(ctx context.Context) error { return nil } - _, err := r.folders.Get(ctx, r.repository.Config().Spec.Folder, metav1.GetOptions{}) + obj, err := r.folders.Get(ctx, r.repository.Config().Spec.Folder, metav1.GetOptions{}) if err == nil { + meta, err := utils.MetaAccessor(obj) + if err != nil { + return fmt.Errorf("create meta accessor for the object: %w", err) + } + + meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{ + Name: r.repository.Config().Name, + Path: "", + Hash: "", // FIXME: which hash? + Timestamp: nil, // ???&info.Modified.Time, + }) + + if _, err := r.folders.Update(ctx, obj, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("failed to add repo info to configured folder: %w", err) + } + return nil } else if !apierrors.IsNotFound(err) { return fmt.Errorf("failed to check if folder exists: %w", err) @@ -507,18 +575,29 @@ func (r *Replicator) ensureRepositoryFolderExists(ctx context.Context) error { title = cfg.Spec.Folder } - if _, err := r.folders.Create(ctx, &unstructured.Unstructured{ - Object: map[string]any{ - "metadata": map[string]any{ - "name": cfg.Spec.Folder, - "namespace": cfg.GetNamespace(), - }, + obj = &unstructured.Unstructured{ + Object: map[string]interface{}{ "spec": map[string]any{ - "title": title, - "description": "Repository-managed folder", + "title": title, // TODO: how do we want to get this? }, }, - }, metav1.CreateOptions{}); err != nil { + } + + meta, err := utils.MetaAccessor(obj) + if err != nil { + return fmt.Errorf("create meta accessor for the object: %w", err) + } + + obj.SetNamespace(cfg.GetNamespace()) + obj.SetName(cfg.Spec.Folder) + meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{ + Name: r.repository.Config().Name, + Path: "", + Hash: "", // FIXME: which hash? + Timestamp: nil, // ???&info.Modified.Time, + }) + + if _, err := r.folders.Create(ctx, obj, metav1.CreateOptions{}); err != nil { return fmt.Errorf("failed to create folder: %w", err) }