package backupcontroller import ( "context" "errors" "fmt" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" ) // ErrNoMatchingStrategy is returned by ResolveBackupClass when the // referenced BackupClass exists but does not bind a strategy for the // requested Kind. Callers check for this with errors.Is so they can // classify the BackupJob/RestoreJob as terminally Failed rather than // requeueing forever — the misbinding is a documented configuration // error (e.g. tenant fires FoundationDB BackupJob against the platform // cozy-default BackupClass, which intentionally does not bind FDB). var ErrNoMatchingStrategy = errors.New("no matching strategy in BackupClass for application Kind") const ( // DefaultApplicationAPIGroup is the default API group for applications // when not specified in ApplicationRef or ApplicationSelector. // Deprecated: Use backupsv1alpha1.DefaultApplicationAPIGroup instead. DefaultApplicationAPIGroup = backupsv1alpha1.DefaultApplicationAPIGroup ) // NormalizeApplicationRef sets the default apiGroup to DefaultApplicationAPIGroup if it's not specified. // Deprecated: Use backupsv1alpha1.NormalizeApplicationRef instead. func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { return backupsv1alpha1.NormalizeApplicationRef(ref) } // ResolvedBackupConfig contains the resolved strategy and storage configuration // from a BackupClass. type ResolvedBackupConfig struct { StrategyRef corev1.TypedLocalObjectReference Parameters map[string]string } // ResolveBackupClass resolves a BackupClass and finds the matching strategy for the given application. // It normalizes the applicationRef's apiGroup (defaults to apps.cozystack.io if not specified) // and matches it against the strategies in the BackupClass. func ResolveBackupClass( ctx context.Context, c client.Client, backupClassName string, applicationRef corev1.TypedLocalObjectReference, ) (*ResolvedBackupConfig, error) { // Normalize applicationRef (default apiGroup if not specified) applicationRef = NormalizeApplicationRef(applicationRef) // Get BackupClass backupClass := &backupsv1alpha1.BackupClass{} if err := c.Get(ctx, client.ObjectKey{Name: backupClassName}, backupClass); err != nil { return nil, fmt.Errorf("failed to get BackupClass %s: %w", backupClassName, err) } // Determine application API group (already normalized, but extract for matching) appAPIGroup := backupsv1alpha1.DefaultApplicationAPIGroup if applicationRef.APIGroup != nil { appAPIGroup = *applicationRef.APIGroup } // Find matching strategy for _, strategy := range backupClass.Spec.Strategies { // Normalize strategy's application selector (default apiGroup if not specified) strategyAPIGroup := backupsv1alpha1.DefaultApplicationAPIGroup if strategy.Application.APIGroup != nil && *strategy.Application.APIGroup != "" { strategyAPIGroup = *strategy.Application.APIGroup } if strategyAPIGroup == appAPIGroup && strategy.Application.Kind == applicationRef.Kind { return &ResolvedBackupConfig{ StrategyRef: strategy.StrategyRef, Parameters: strategy.Parameters, }, nil } } return nil, fmt.Errorf("%w: BackupClass=%s application=%s/%s", ErrNoMatchingStrategy, backupClassName, appAPIGroup, applicationRef.Kind) }