* feat(provisioning): add generic version handling for dashboard export - Update export job to handle any dashboard version generically (v0, v1, v2, v3, etc.) - Dynamically construct GroupVersionResource for any stored version - Cache version-specific clients for efficiency - Add comprehensive table-driven unit tests for multiple versions - Add integration test to verify version handling end-to-end - Remove unnecessary version shim from clean operation (deletion works by name) * test: add unit test for v2 dashboard version (no suffix)
82 lines
2.6 KiB
Go
82 lines
2.6 KiB
Go
package migrate
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"github.com/grafana/grafana/apps/provisioning/pkg/repository"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
|
)
|
|
|
|
//go:generate mockery --name NamespaceCleaner --structname MockNamespaceCleaner --inpackage --filename mock_namespace_cleaner.go --with-expecter
|
|
type NamespaceCleaner interface {
|
|
Clean(ctx context.Context, namespace string, progress jobs.JobProgressRecorder) error
|
|
}
|
|
|
|
type namespaceCleaner struct {
|
|
clients resources.ClientFactory
|
|
}
|
|
|
|
func NewNamespaceCleaner(clients resources.ClientFactory) NamespaceCleaner {
|
|
return &namespaceCleaner{clients: clients}
|
|
}
|
|
|
|
func (c *namespaceCleaner) Clean(ctx context.Context, namespace string, progress jobs.JobProgressRecorder) error {
|
|
clients, err := c.clients.Clients(ctx, namespace)
|
|
if err != nil {
|
|
return fmt.Errorf("get clients: %w", err)
|
|
}
|
|
|
|
for _, kind := range resources.SupportedProvisioningResources {
|
|
progress.SetMessage(ctx, fmt.Sprintf("remove unprovisioned %s", kind.Resource))
|
|
client, _, err := clients.ForResource(ctx, kind)
|
|
if err != nil {
|
|
return fmt.Errorf("get resource client: %w", err)
|
|
}
|
|
|
|
if err = resources.ForEach(ctx, client, func(item *unstructured.Unstructured) error {
|
|
result := jobs.JobResourceResult{
|
|
Name: item.GetName(),
|
|
Kind: item.GetKind(),
|
|
Group: item.GroupVersionKind().Group,
|
|
Action: repository.FileActionDeleted,
|
|
}
|
|
|
|
// Skip provisioned resources - only delete unprovisioned (unmanaged) resources
|
|
meta, err := utils.MetaAccessor(item)
|
|
if err != nil {
|
|
result.Error = fmt.Errorf("extracting meta accessor for resource %s: %w", result.Name, err)
|
|
progress.Record(ctx, result)
|
|
return nil // Continue with next resource
|
|
}
|
|
|
|
manager, _ := meta.GetManagerProperties()
|
|
// Skip if resource is managed by any provisioning system
|
|
if manager.Identity != "" {
|
|
result.Action = repository.FileActionIgnored
|
|
progress.Record(ctx, result)
|
|
return nil // Skip this resource
|
|
}
|
|
|
|
// Deletion works by name, so we can use any client regardless of version
|
|
if err := client.Delete(ctx, item.GetName(), metav1.DeleteOptions{}); err != nil {
|
|
result.Error = fmt.Errorf("deleting resource %s/%s %s: %w", result.Group, result.Kind, result.Name, err)
|
|
progress.Record(ctx, result)
|
|
return fmt.Errorf("delete resource: %w", err)
|
|
}
|
|
|
|
progress.Record(ctx, result)
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|