Implement default resource modifier in restore controller

Thread DefaultResourceModifierConfigMap from server config through to
restoreReconciler. Refactor validateAndComplete to use a shared
loadResourceModifierConfigMap helper that handles both default and
per-restore ConfigMap loading.

Precedence: per-restore modifier takes exclusive precedence over the
default. Default ConfigMap errors are non-fatal (warn and proceed).
SkipDefaultResourceModifier opt-out is respected.

Includes unit tests covering: default-only, per-restore override,
skip flag, missing default (non-fatal), missing per-restore (fatal),
and no modifier configured.

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
This commit is contained in:
Shubham Pampattiwar
2026-08-03 13:19:47 -07:00
parent 70e70f14e2
commit 8ef8ab3b1d
3 changed files with 200 additions and 19 deletions
+60 -19
View File
@@ -55,6 +55,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
pkgrestore "github.com/vmware-tanzu/velero/pkg/restore"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/util/logging"
@@ -109,10 +110,11 @@ type restoreReconciler struct {
defaultItemOperationTimeout time.Duration
disableInformerCache bool
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
backupStoreGetter persistence.ObjectBackupStoreGetter
globalCrClient client.Client
resourceTimeout time.Duration
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
backupStoreGetter persistence.ObjectBackupStoreGetter
globalCrClient client.Client
resourceTimeout time.Duration
defaultResourceModifierConfigMap string
}
type backupInfo struct {
@@ -135,6 +137,7 @@ func NewRestoreReconciler(
disableInformerCache bool,
globalCrClient client.Client,
resourceTimeout time.Duration,
defaultResourceModifierConfigMap string,
) *restoreReconciler {
r := &restoreReconciler{
ctx: ctx,
@@ -154,8 +157,9 @@ func NewRestoreReconciler(
newPluginManager: newPluginManager,
backupStoreGetter: backupStoreGetter,
globalCrClient: globalCrClient,
resourceTimeout: resourceTimeout,
globalCrClient: globalCrClient,
resourceTimeout: resourceTimeout,
defaultResourceModifierConfigMap: defaultResourceModifierConfigMap,
}
// Move the periodical backup and restore metrics computing logic from controllers to here.
@@ -432,26 +436,63 @@ func (r *restoreReconciler) validateAndComplete(ctx context.Context, restore *ap
var resourceModifiers *resourcemodifiers.ResourceModifiers
if restore.Spec.ResourceModifier != nil && strings.EqualFold(restore.Spec.ResourceModifier.Kind, resourcemodifiers.ConfigmapRefType) {
ResourceModifierConfigMap := &corev1api.ConfigMap{}
err := r.kbClient.Get(ctx, client.ObjectKey{Namespace: restore.Namespace, Name: restore.Spec.ResourceModifier.Name}, ResourceModifierConfigMap)
if err != nil {
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, fmt.Sprintf("failed to get resource modifiers configmap %s/%s", restore.Namespace, restore.Spec.ResourceModifier.Name))
resourceModifiers = r.loadResourceModifierConfigMap(ctx, restore, restore.Spec.ResourceModifier.Name, false)
if resourceModifiers == nil && len(restore.Status.ValidationErrors) > 0 {
return backupInfo{}, nil, nil
}
resourceModifiers, err = resourcemodifiers.GetResourceModifiersFromConfig(ResourceModifierConfigMap)
if err != nil {
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, errors.Wrapf(err, "Error in parsing resource modifiers provided in configmap %s/%s", restore.Namespace, restore.Spec.ResourceModifier.Name).Error())
return backupInfo{}, nil, nil
} else if err = resourceModifiers.Validate(); err != nil {
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, errors.Wrapf(err, "Validation error in resource modifiers provided in configmap %s/%s", restore.Namespace, restore.Spec.ResourceModifier.Name).Error())
return backupInfo{}, nil, nil
}
r.logger.Infof("Retrieved Resource modifiers provided in configmap %s/%s", restore.Namespace, restore.Spec.ResourceModifier.Name)
} else if r.defaultResourceModifierConfigMap != "" && !boolptr.IsSetToTrue(restore.Spec.SkipDefaultResourceModifier) {
resourceModifiers = r.loadResourceModifierConfigMap(ctx, restore, r.defaultResourceModifierConfigMap, true)
}
return info, resourceModifiers, restoreResPolicies
}
// loadResourceModifierConfigMap loads and validates a resource modifier ConfigMap.
// When isDefault is true, errors are non-fatal (logged as warnings, returns nil).
// When isDefault is false, errors are added to restore.Status.ValidationErrors.
func (r *restoreReconciler) loadResourceModifierConfigMap(
ctx context.Context, restore *api.Restore, cmName string, isDefault bool,
) *resourcemodifiers.ResourceModifiers {
cm := &corev1api.ConfigMap{}
if err := r.kbClient.Get(ctx, client.ObjectKey{Namespace: restore.Namespace, Name: cmName}, cm); err != nil {
if isDefault {
r.logger.WithError(err).Warnf("Failed to retrieve default resource modifier configmap %s/%s, skipping", restore.Namespace, cmName)
return nil
}
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors,
fmt.Sprintf("failed to get resource modifiers configmap %s/%s", restore.Namespace, cmName))
return nil
}
modifiers, err := resourcemodifiers.GetResourceModifiersFromConfig(cm)
if err != nil {
if isDefault {
r.logger.WithError(err).Warnf("Error parsing default resource modifier configmap %s/%s, skipping", restore.Namespace, cmName)
return nil
}
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors,
errors.Wrapf(err, "Error in parsing resource modifiers provided in configmap %s/%s", restore.Namespace, cmName).Error())
return nil
}
if err = modifiers.Validate(); err != nil {
if isDefault {
r.logger.WithError(err).Warnf("Validation error in default resource modifier configmap %s/%s, skipping", restore.Namespace, cmName)
return nil
}
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors,
errors.Wrapf(err, "Validation error in resource modifiers provided in configmap %s/%s", restore.Namespace, cmName).Error())
return nil
}
source := "per-restore"
if isDefault {
source = "default"
}
r.logger.Infof("Retrieved %s resource modifiers from configmap %s/%s", source, restore.Namespace, cmName)
return modifiers
}
// backupXorScheduleProvided returns true if exactly one of BackupName and
// ScheduleName are non-empty for the restore, or false otherwise.
func backupXorScheduleProvided(restore *api.Restore) bool {