mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 05:22:49 +08:00
143 lines
4.2 KiB
Go
143 lines
4.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/dynamic"
|
|
|
|
"github.com/grafana/grafana-app-sdk/logging"
|
|
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
|
"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"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
|
)
|
|
|
|
// RemoveOrphanResourcesFinalizer removes everything this repo created
|
|
const RemoveOrphanResourcesFinalizer = "remove-orphan-resources"
|
|
|
|
// ReleaseOrphanResourcesFinalizer removes the metadata for anything this repo created
|
|
const ReleaseOrphanResourcesFinalizer = "release-orphan-resources"
|
|
|
|
// CleanFinalizer calls the "OnDelete" function for resource
|
|
const CleanFinalizer = "cleanup"
|
|
|
|
type finalizer struct {
|
|
lister resources.ResourceLister
|
|
clientFactory resources.ClientFactory
|
|
}
|
|
|
|
func (f *finalizer) process(ctx context.Context,
|
|
repo repository.Repository,
|
|
finalizers []string,
|
|
) error {
|
|
logger := logging.FromContext(ctx)
|
|
|
|
for _, finalizer := range finalizers {
|
|
switch finalizer {
|
|
case CleanFinalizer:
|
|
// NOTE: the controller loop will never get run unless a finalizer is set
|
|
hooks, ok := repo.(repository.Hooks)
|
|
if ok {
|
|
if err := hooks.OnDelete(ctx); err != nil {
|
|
logger.Warn("Error running deletion hooks", "err", err)
|
|
}
|
|
}
|
|
|
|
case ReleaseOrphanResourcesFinalizer:
|
|
err := f.processExistingItems(ctx, repo.Config(),
|
|
func(client dynamic.ResourceInterface, item *provisioning.ResourceListItem) error {
|
|
_, err := client.Patch(ctx, item.Name, types.JSONPatchType, []byte(`[
|
|
{"op": "remove", "path": "/metadata/annotations/`+utils.AnnoKeyManagerKind+`" },
|
|
{"op": "remove", "path": "/metadata/annotations/`+utils.AnnoKeyManagerIdentity+`" },
|
|
{"op": "remove", "path": "/metadata/annotations/`+utils.AnnoKeySourcePath+`" },
|
|
{"op": "remove", "path": "/metadata/annotations/`+utils.AnnoKeySourceChecksum+`" }
|
|
]`), v1.PatchOptions{})
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case RemoveOrphanResourcesFinalizer:
|
|
err := f.processExistingItems(ctx, repo.Config(),
|
|
func(client dynamic.ResourceInterface, item *provisioning.ResourceListItem) error {
|
|
return client.Delete(ctx, item.Name, v1.DeleteOptions{})
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
logger.Warn("skipping unknown finalizer", "finalizer", finalizer)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// internal iterator to walk the existing items
|
|
func (f *finalizer) processExistingItems(
|
|
ctx context.Context,
|
|
repo *provisioning.Repository,
|
|
cb func(client dynamic.ResourceInterface, item *provisioning.ResourceListItem) error,
|
|
) error {
|
|
logger := logging.FromContext(ctx)
|
|
clients, err := f.clientFactory.Clients(ctx, repo.Namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
items, err := f.lister.List(ctx, repo.Namespace, repo.Name)
|
|
if err != nil {
|
|
logger.Warn("error listing resources", "error", err)
|
|
return err
|
|
}
|
|
|
|
// Safe deletion order
|
|
sortResourceListForDeletion(items)
|
|
count := 0
|
|
errors := 0
|
|
|
|
for _, item := range items.Items {
|
|
res, _, err := clients.ForResource(schema.GroupVersionResource{
|
|
Group: item.Group,
|
|
Resource: item.Resource,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = cb(res, &item)
|
|
if err != nil {
|
|
logger.Warn("error processing item", "name", item.Name, "error", err)
|
|
errors++
|
|
} else {
|
|
count++
|
|
}
|
|
}
|
|
logger.Info("processed orphan items", "items", count, "errors", errors)
|
|
return nil
|
|
}
|
|
|
|
func sortResourceListForDeletion(list *provisioning.ResourceList) {
|
|
// FIXME: this code should be simplified once unified storage folders support recursive deletion
|
|
// Sort by the following logic:
|
|
// - Put folders at the end so that we empty them first.
|
|
// - Sort folders by depth so that we remove the deepest first
|
|
sort.Slice(list.Items, func(i, j int) bool {
|
|
switch {
|
|
case list.Items[i].Group != folders.RESOURCE:
|
|
return true
|
|
case list.Items[j].Group != folders.RESOURCE:
|
|
return false
|
|
default:
|
|
return len(strings.Split(list.Items[i].Path, "/")) > len(strings.Split(list.Items[j].Path, "/"))
|
|
}
|
|
})
|
|
}
|