mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 19:56:06 +00:00
Merge pull request #10098 from shubham-pampattiwar/impl/default-resource-modifier
Run the E2E test on kind / setup-test-matrix (push) Successful in 6s
e2e-test-kind.yaml / extract (push) Failing after 12s
Run the E2E test on kind / get-go-version (push) Failing after 13s
Run the E2E test on kind / build (push) Skipped
Run the E2E test on kind / run-e2e-test (push) Skipped
push.yml / extract (push) Successful in 12s
Main CI / get-go-version (push) Successful in 13s
Main CI / Build (push) Failing after 23s
Run the E2E test on kind / setup-test-matrix (push) Successful in 6s
e2e-test-kind.yaml / extract (push) Failing after 12s
Run the E2E test on kind / get-go-version (push) Failing after 13s
Run the E2E test on kind / build (push) Skipped
Run the E2E test on kind / run-e2e-test (push) Skipped
push.yml / extract (push) Successful in 12s
Main CI / get-go-version (push) Successful in 13s
Main CI / Build (push) Failing after 23s
Implement server default restore resource modifier
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Implement server default restore resource modifier
|
||||
@@ -467,6 +467,14 @@ spec:
|
||||
from. If specified, and BackupName is empty, Velero will restore
|
||||
from the most recent successful backup created from this schedule.
|
||||
type: string
|
||||
skipDefaultResourceModifier:
|
||||
description: |-
|
||||
SkipDefaultResourceModifier controls whether the server-configured default
|
||||
resource modifier is applied to this restore.
|
||||
When true, the default modifier is skipped even if configured on the server.
|
||||
Has no effect when a per-restore ResourceModifier is specified.
|
||||
nullable: true
|
||||
type: boolean
|
||||
uploaderConfig:
|
||||
description: UploaderConfig specifies the configuration for the restore.
|
||||
nullable: true
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: default-restore-resource-modifiers
|
||||
namespace: velero
|
||||
data:
|
||||
resource-modifiers.yaml: |
|
||||
version: v1
|
||||
resourceModifierRules:
|
||||
- conditions:
|
||||
groupResource: pods
|
||||
mergePatches:
|
||||
- patchData: |
|
||||
metadata:
|
||||
annotations:
|
||||
k8s.ovn.org/pod-networks: null
|
||||
k8s.v1.cni.cncf.io/network-status: null
|
||||
k8s.v1.cni.cncf.io/networks-status: null
|
||||
@@ -135,6 +135,14 @@ type RestoreSpec struct {
|
||||
// +nullable
|
||||
ResourcePolicy *corev1api.TypedLocalObjectReference `json:"resourcePolicy,omitempty"`
|
||||
|
||||
// SkipDefaultResourceModifier controls whether the server-configured default
|
||||
// resource modifier is applied to this restore.
|
||||
// When true, the default modifier is skipped even if configured on the server.
|
||||
// Has no effect when a per-restore ResourceModifier is specified.
|
||||
// +optional
|
||||
// +nullable
|
||||
SkipDefaultResourceModifier *bool `json:"skipDefaultResourceModifier,omitempty"`
|
||||
|
||||
// UploaderConfig specifies the configuration for the restore.
|
||||
// +optional
|
||||
// +nullable
|
||||
|
||||
@@ -1427,6 +1427,11 @@ func (in *RestoreSpec) DeepCopyInto(out *RestoreSpec) {
|
||||
*out = new(corev1.TypedLocalObjectReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.SkipDefaultResourceModifier != nil {
|
||||
in, out := &in.SkipDefaultResourceModifier, &out.SkipDefaultResourceModifier
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.UploaderConfig != nil {
|
||||
in, out := &in.UploaderConfig, &out.UploaderConfig
|
||||
*out = new(UploaderConfigForRestore)
|
||||
|
||||
@@ -181,3 +181,9 @@ func (b *RestoreBuilder) ResourcePoliciesConfigmap(name string) *RestoreBuilder
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// SkipDefaultResourceModifier sets whether to skip the server default resource modifier.
|
||||
func (b *RestoreBuilder) SkipDefaultResourceModifier(val bool) *RestoreBuilder {
|
||||
b.object.Spec.SkipDefaultResourceModifier = &val
|
||||
return b
|
||||
}
|
||||
|
||||
+105
-97
@@ -42,60 +42,61 @@ import (
|
||||
|
||||
// Options collects all the options for installing Velero into a Kubernetes cluster.
|
||||
type Options struct {
|
||||
Namespace string
|
||||
Image string
|
||||
BucketName string
|
||||
Prefix string
|
||||
ProviderName string
|
||||
PodAnnotations flag.Map
|
||||
PodLabels flag.Map
|
||||
ServiceAccountAnnotations flag.Map
|
||||
ServiceAccountName string
|
||||
VeleroPodCPURequest string
|
||||
VeleroPodMemRequest string
|
||||
VeleroPodCPULimit string
|
||||
VeleroPodMemLimit string
|
||||
NodeAgentPodCPURequest string
|
||||
NodeAgentPodMemRequest string
|
||||
NodeAgentPodCPULimit string
|
||||
NodeAgentPodMemLimit string
|
||||
RestoreOnly bool
|
||||
SecretFile string
|
||||
NoSecret bool
|
||||
DryRun bool
|
||||
BackupStorageConfig flag.Map
|
||||
VolumeSnapshotConfig flag.Map
|
||||
UseNodeAgent bool
|
||||
UseNodeAgentWindows bool
|
||||
PrivilegedNodeAgent bool
|
||||
Wait bool
|
||||
UseVolumeSnapshots bool
|
||||
DefaultRepoMaintenanceFrequency time.Duration
|
||||
GarbageCollectionFrequency time.Duration
|
||||
PodVolumeOperationTimeout time.Duration
|
||||
Plugins flag.StringArray
|
||||
NoDefaultBackupLocation bool
|
||||
CRDsOnly bool
|
||||
CACertFile string
|
||||
Features string
|
||||
DefaultVolumesToFsBackup bool
|
||||
UploaderType string
|
||||
DefaultSnapshotMoveData bool
|
||||
CSISnapshotEarlyFrequentPolling bool
|
||||
DisableInformerCache bool
|
||||
ScheduleSkipImmediately bool
|
||||
PodResources kubeutil.PodResources
|
||||
KeepLatestMaintenanceJobs int
|
||||
BackupRepoConfigMap string
|
||||
RepoMaintenanceJobConfigMap string
|
||||
NodeAgentConfigMap string
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
NodeAgentDisableHostPath bool
|
||||
kubeletRootDir string
|
||||
Apply bool
|
||||
ServerPriorityClassName string
|
||||
NodeAgentPriorityClassName string
|
||||
Namespace string
|
||||
Image string
|
||||
BucketName string
|
||||
Prefix string
|
||||
ProviderName string
|
||||
PodAnnotations flag.Map
|
||||
PodLabels flag.Map
|
||||
ServiceAccountAnnotations flag.Map
|
||||
ServiceAccountName string
|
||||
VeleroPodCPURequest string
|
||||
VeleroPodMemRequest string
|
||||
VeleroPodCPULimit string
|
||||
VeleroPodMemLimit string
|
||||
NodeAgentPodCPURequest string
|
||||
NodeAgentPodMemRequest string
|
||||
NodeAgentPodCPULimit string
|
||||
NodeAgentPodMemLimit string
|
||||
RestoreOnly bool
|
||||
SecretFile string
|
||||
NoSecret bool
|
||||
DryRun bool
|
||||
BackupStorageConfig flag.Map
|
||||
VolumeSnapshotConfig flag.Map
|
||||
UseNodeAgent bool
|
||||
UseNodeAgentWindows bool
|
||||
PrivilegedNodeAgent bool
|
||||
Wait bool
|
||||
UseVolumeSnapshots bool
|
||||
DefaultRepoMaintenanceFrequency time.Duration
|
||||
GarbageCollectionFrequency time.Duration
|
||||
PodVolumeOperationTimeout time.Duration
|
||||
Plugins flag.StringArray
|
||||
NoDefaultBackupLocation bool
|
||||
CRDsOnly bool
|
||||
CACertFile string
|
||||
Features string
|
||||
DefaultVolumesToFsBackup bool
|
||||
UploaderType string
|
||||
DefaultSnapshotMoveData bool
|
||||
CSISnapshotEarlyFrequentPolling bool
|
||||
DisableInformerCache bool
|
||||
ScheduleSkipImmediately bool
|
||||
PodResources kubeutil.PodResources
|
||||
KeepLatestMaintenanceJobs int
|
||||
BackupRepoConfigMap string
|
||||
RepoMaintenanceJobConfigMap string
|
||||
DefaultResourceModifierConfigMap string
|
||||
NodeAgentConfigMap string
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
NodeAgentDisableHostPath bool
|
||||
kubeletRootDir string
|
||||
Apply bool
|
||||
ServerPriorityClassName string
|
||||
NodeAgentPriorityClassName string
|
||||
}
|
||||
|
||||
// BindFlags adds command line values to the options struct.
|
||||
@@ -189,6 +190,12 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) {
|
||||
o.RepoMaintenanceJobConfigMap,
|
||||
"The name of ConfigMap containing repository maintenance Job configurations.",
|
||||
)
|
||||
flags.StringVar(
|
||||
&o.DefaultResourceModifierConfigMap,
|
||||
"default-resource-modifier-configmap",
|
||||
o.DefaultResourceModifierConfigMap,
|
||||
"The name of a ConfigMap in the Velero namespace containing default resource modifier rules applied to all restores.",
|
||||
)
|
||||
flags.StringVar(
|
||||
&o.NodeAgentConfigMap,
|
||||
"node-agent-configmap",
|
||||
@@ -298,49 +305,50 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) {
|
||||
}
|
||||
|
||||
return &install.VeleroOptions{
|
||||
Namespace: o.Namespace,
|
||||
Image: o.Image,
|
||||
ProviderName: o.ProviderName,
|
||||
Bucket: o.BucketName,
|
||||
Prefix: o.Prefix,
|
||||
PodAnnotations: o.PodAnnotations.Data(),
|
||||
PodLabels: o.PodLabels.Data(),
|
||||
ServiceAccountAnnotations: o.ServiceAccountAnnotations.Data(),
|
||||
ServiceAccountName: o.ServiceAccountName,
|
||||
VeleroPodResources: veleroPodResources,
|
||||
NodeAgentPodResources: nodeAgentPodResources,
|
||||
SecretData: secretData,
|
||||
RestoreOnly: o.RestoreOnly,
|
||||
UseNodeAgent: o.UseNodeAgent,
|
||||
UseNodeAgentWindows: o.UseNodeAgentWindows,
|
||||
PrivilegedNodeAgent: o.PrivilegedNodeAgent,
|
||||
UseVolumeSnapshots: o.UseVolumeSnapshots,
|
||||
BSLConfig: o.BackupStorageConfig.Data(),
|
||||
VSLConfig: o.VolumeSnapshotConfig.Data(),
|
||||
DefaultRepoMaintenanceFrequency: o.DefaultRepoMaintenanceFrequency,
|
||||
GarbageCollectionFrequency: o.GarbageCollectionFrequency,
|
||||
PodVolumeOperationTimeout: o.PodVolumeOperationTimeout,
|
||||
Plugins: o.Plugins,
|
||||
NoDefaultBackupLocation: o.NoDefaultBackupLocation,
|
||||
CACertData: caCertData,
|
||||
Features: strings.Split(o.Features, ","),
|
||||
DefaultVolumesToFsBackup: o.DefaultVolumesToFsBackup,
|
||||
UploaderType: o.UploaderType,
|
||||
DefaultSnapshotMoveData: o.DefaultSnapshotMoveData,
|
||||
CSISnapshotEarlyFrequentPolling: o.CSISnapshotEarlyFrequentPolling,
|
||||
DisableInformerCache: o.DisableInformerCache,
|
||||
ScheduleSkipImmediately: o.ScheduleSkipImmediately,
|
||||
PodResources: o.PodResources,
|
||||
KeepLatestMaintenanceJobs: o.KeepLatestMaintenanceJobs,
|
||||
BackupRepoConfigMap: o.BackupRepoConfigMap,
|
||||
RepoMaintenanceJobConfigMap: o.RepoMaintenanceJobConfigMap,
|
||||
NodeAgentConfigMap: o.NodeAgentConfigMap,
|
||||
ItemBlockWorkerCount: o.ItemBlockWorkerCount,
|
||||
ConcurrentBackups: o.ConcurrentBackups,
|
||||
KubeletRootDir: o.kubeletRootDir,
|
||||
NodeAgentDisableHostPath: o.NodeAgentDisableHostPath,
|
||||
ServerPriorityClassName: o.ServerPriorityClassName,
|
||||
NodeAgentPriorityClassName: o.NodeAgentPriorityClassName,
|
||||
Namespace: o.Namespace,
|
||||
Image: o.Image,
|
||||
ProviderName: o.ProviderName,
|
||||
Bucket: o.BucketName,
|
||||
Prefix: o.Prefix,
|
||||
PodAnnotations: o.PodAnnotations.Data(),
|
||||
PodLabels: o.PodLabels.Data(),
|
||||
ServiceAccountAnnotations: o.ServiceAccountAnnotations.Data(),
|
||||
ServiceAccountName: o.ServiceAccountName,
|
||||
VeleroPodResources: veleroPodResources,
|
||||
NodeAgentPodResources: nodeAgentPodResources,
|
||||
SecretData: secretData,
|
||||
RestoreOnly: o.RestoreOnly,
|
||||
UseNodeAgent: o.UseNodeAgent,
|
||||
UseNodeAgentWindows: o.UseNodeAgentWindows,
|
||||
PrivilegedNodeAgent: o.PrivilegedNodeAgent,
|
||||
UseVolumeSnapshots: o.UseVolumeSnapshots,
|
||||
BSLConfig: o.BackupStorageConfig.Data(),
|
||||
VSLConfig: o.VolumeSnapshotConfig.Data(),
|
||||
DefaultRepoMaintenanceFrequency: o.DefaultRepoMaintenanceFrequency,
|
||||
GarbageCollectionFrequency: o.GarbageCollectionFrequency,
|
||||
PodVolumeOperationTimeout: o.PodVolumeOperationTimeout,
|
||||
Plugins: o.Plugins,
|
||||
NoDefaultBackupLocation: o.NoDefaultBackupLocation,
|
||||
CACertData: caCertData,
|
||||
Features: strings.Split(o.Features, ","),
|
||||
DefaultVolumesToFsBackup: o.DefaultVolumesToFsBackup,
|
||||
UploaderType: o.UploaderType,
|
||||
DefaultSnapshotMoveData: o.DefaultSnapshotMoveData,
|
||||
CSISnapshotEarlyFrequentPolling: o.CSISnapshotEarlyFrequentPolling,
|
||||
DisableInformerCache: o.DisableInformerCache,
|
||||
ScheduleSkipImmediately: o.ScheduleSkipImmediately,
|
||||
PodResources: o.PodResources,
|
||||
KeepLatestMaintenanceJobs: o.KeepLatestMaintenanceJobs,
|
||||
BackupRepoConfigMap: o.BackupRepoConfigMap,
|
||||
RepoMaintenanceJobConfigMap: o.RepoMaintenanceJobConfigMap,
|
||||
DefaultResourceModifierConfigMap: o.DefaultResourceModifierConfigMap,
|
||||
NodeAgentConfigMap: o.NodeAgentConfigMap,
|
||||
ItemBlockWorkerCount: o.ItemBlockWorkerCount,
|
||||
ConcurrentBackups: o.ConcurrentBackups,
|
||||
KubeletRootDir: o.kubeletRootDir,
|
||||
NodeAgentDisableHostPath: o.NodeAgentDisableHostPath,
|
||||
ServerPriorityClassName: o.ServerPriorityClassName,
|
||||
NodeAgentPriorityClassName: o.NodeAgentPriorityClassName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -85,32 +85,33 @@ Notes:
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
BackupName string
|
||||
ScheduleName string
|
||||
RestoreName string
|
||||
RestoreVolumes flag.OptionalBool
|
||||
PreserveNodePorts flag.OptionalBool
|
||||
Labels flag.Map
|
||||
Annotations flag.Map
|
||||
IncludeNamespaces flag.StringArray
|
||||
ExcludeNamespaces flag.StringArray
|
||||
ExistingResourcePolicy string
|
||||
IncludeResources flag.StringArray
|
||||
ExcludeResources flag.StringArray
|
||||
StatusIncludeResources flag.StringArray
|
||||
StatusExcludeResources flag.StringArray
|
||||
NamespaceMappings flag.Map
|
||||
Selector flag.LabelSelector
|
||||
OrSelector flag.OrLabelSelector
|
||||
IncludeClusterResources flag.OptionalBool
|
||||
Wait bool
|
||||
AllowPartiallyFailed flag.OptionalBool
|
||||
ItemOperationTimeout time.Duration
|
||||
ResourceModifierConfigMap string
|
||||
ResourcePoliciesConfigMap string
|
||||
WriteSparseFiles flag.OptionalBool
|
||||
ParallelFilesDownload int
|
||||
client kbclient.WithWatch
|
||||
BackupName string
|
||||
ScheduleName string
|
||||
RestoreName string
|
||||
RestoreVolumes flag.OptionalBool
|
||||
PreserveNodePorts flag.OptionalBool
|
||||
Labels flag.Map
|
||||
Annotations flag.Map
|
||||
IncludeNamespaces flag.StringArray
|
||||
ExcludeNamespaces flag.StringArray
|
||||
ExistingResourcePolicy string
|
||||
IncludeResources flag.StringArray
|
||||
ExcludeResources flag.StringArray
|
||||
StatusIncludeResources flag.StringArray
|
||||
StatusExcludeResources flag.StringArray
|
||||
NamespaceMappings flag.Map
|
||||
Selector flag.LabelSelector
|
||||
OrSelector flag.OrLabelSelector
|
||||
IncludeClusterResources flag.OptionalBool
|
||||
Wait bool
|
||||
AllowPartiallyFailed flag.OptionalBool
|
||||
ItemOperationTimeout time.Duration
|
||||
ResourceModifierConfigMap string
|
||||
ResourcePoliciesConfigMap string
|
||||
SkipDefaultResourceModifier bool
|
||||
WriteSparseFiles flag.OptionalBool
|
||||
ParallelFilesDownload int
|
||||
client kbclient.WithWatch
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
@@ -164,6 +165,8 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
|
||||
flags.StringVar(&o.ResourcePoliciesConfigMap, "resource-policies-configmap", "", "Reference to the ConfigMap containing restore resource filter policies")
|
||||
|
||||
flags.BoolVar(&o.SkipDefaultResourceModifier, "skip-default-resource-modifier", false, "Skip applying the server-configured default resource modifier for this restore")
|
||||
|
||||
f = flags.VarPF(&o.WriteSparseFiles, "write-sparse-files", "", "Whether to write sparse files during restoring volumes")
|
||||
f.NoOptDefVal = cmd.TRUE
|
||||
|
||||
@@ -362,6 +365,10 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
},
|
||||
}
|
||||
|
||||
if o.SkipDefaultResourceModifier {
|
||||
restore.Spec.SkipDefaultResourceModifier = boolptr.True()
|
||||
}
|
||||
|
||||
if len([]string(o.StatusIncludeResources)) > 0 {
|
||||
restore.Spec.RestoreStatus = &api.RestoreStatusSpec{
|
||||
IncludedResources: o.StatusIncludeResources,
|
||||
|
||||
@@ -105,6 +105,7 @@ func TestCreateCommand(t *testing.T) {
|
||||
flags.Parse([]string{"--item-operation-timeout", itemOperationTimeout})
|
||||
flags.Parse([]string{"--resource-modifier-configmap", resourceModifierConfigMap})
|
||||
flags.Parse([]string{"--resource-policies-configmap", ResourcePoliciesConfigMap})
|
||||
flags.Parse([]string{"--skip-default-resource-modifier"})
|
||||
flags.Parse([]string{"--write-sparse-files", writeSparseFiles})
|
||||
flags.Parse([]string{"--parallel-files-download", "2"})
|
||||
client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch)
|
||||
@@ -145,6 +146,7 @@ func TestCreateCommand(t *testing.T) {
|
||||
require.Equal(t, itemOperationTimeout, o.ItemOperationTimeout.String())
|
||||
require.Equal(t, resourceModifierConfigMap, o.ResourceModifierConfigMap)
|
||||
require.Equal(t, ResourcePoliciesConfigMap, o.ResourcePoliciesConfigMap)
|
||||
require.True(t, o.SkipDefaultResourceModifier)
|
||||
require.Equal(t, writeSparseFiles, o.WriteSparseFiles.String())
|
||||
require.Equal(t, parallel, o.ParallelFilesDownload)
|
||||
})
|
||||
|
||||
@@ -182,6 +182,7 @@ type Config struct {
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
GlobalBackupVolumePoliciesConfigMap string
|
||||
DefaultResourceModifierConfigMap string
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *Config {
|
||||
@@ -282,4 +283,10 @@ func (c *Config) BindFlags(flags *pflag.FlagSet) {
|
||||
c.GlobalBackupVolumePoliciesConfigMap,
|
||||
"The name of a ConfigMap in the Velero install namespace holding global backup volume policies that are merged into every backup. Optional.",
|
||||
)
|
||||
flags.StringVar(
|
||||
&c.DefaultResourceModifierConfigMap,
|
||||
"default-resource-modifier-configmap",
|
||||
c.DefaultResourceModifierConfigMap,
|
||||
"The name of a ConfigMap in the Velero namespace containing default resource modifier rules applied to all restores. Ignored when a per-restore resource modifier is specified.",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -881,6 +881,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
s.config.DisableInformerCache,
|
||||
s.crClient,
|
||||
s.config.ResourceTimeout,
|
||||
s.config.DefaultResourceModifierConfigMap,
|
||||
)
|
||||
|
||||
if err = r.SetupWithManager(s.mgr); err != nil {
|
||||
|
||||
@@ -219,6 +219,10 @@ func DescribeRestore(
|
||||
DescribeResourceModifier(d, restore.Spec.ResourceModifier)
|
||||
}
|
||||
|
||||
if boolptr.IsSetToTrue(restore.Spec.SkipDefaultResourceModifier) {
|
||||
d.Printf("Skip Default Resource Modifier:\ttrue\n")
|
||||
}
|
||||
|
||||
if restore.Spec.ResourcePolicy != nil {
|
||||
d.Println()
|
||||
DescribeResourcePolicies(d, restore.Spec.ResourcePolicy)
|
||||
|
||||
@@ -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.
|
||||
@@ -431,27 +435,72 @@ 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))
|
||||
return backupInfo{}, nil, nil
|
||||
if restore.Spec.ResourceModifier != nil {
|
||||
if strings.EqualFold(restore.Spec.ResourceModifier.Kind, resourcemodifiers.ConfigmapRefType) {
|
||||
resourceModifiers = r.loadResourceModifierConfigMap(ctx, restore, restore.Spec.ResourceModifier.Name, false)
|
||||
if resourceModifiers == nil && len(restore.Status.ValidationErrors) > 0 {
|
||||
return backupInfo{}, nil, nil
|
||||
}
|
||||
} else {
|
||||
r.logger.Warnf("Unsupported resource modifier kind %q, only %q is supported", restore.Spec.ResourceModifier.Kind, resourcemodifiers.ConfigmapRefType)
|
||||
}
|
||||
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
|
||||
} else if r.defaultResourceModifierConfigMap != "" {
|
||||
if boolptr.IsSetToTrue(restore.Spec.SkipDefaultResourceModifier) {
|
||||
r.logger.Infof("Skipping default resource modifier configmap %s/%s as SkipDefaultResourceModifier is set", restore.Namespace, r.defaultResourceModifierConfigMap)
|
||||
} else {
|
||||
resourceModifiers = r.loadResourceModifierConfigMap(ctx, restore, r.defaultResourceModifierConfigMap, true)
|
||||
}
|
||||
r.logger.Infof("Retrieved Resource modifiers provided in configmap %s/%s", restore.Namespace, restore.Spec.ResourceModifier.Name)
|
||||
}
|
||||
|
||||
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: %v", restore.Namespace, cmName, err))
|
||||
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 {
|
||||
|
||||
@@ -116,6 +116,7 @@ func TestFetchBackupInfo(t *testing.T) {
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
"",
|
||||
)
|
||||
|
||||
if test.backupStoreError == nil {
|
||||
@@ -197,6 +198,7 @@ func TestProcessQueueItemSkips(t *testing.T) {
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
"",
|
||||
)
|
||||
|
||||
_, err := r.Reconcile(t.Context(), ctrl.Request{NamespacedName: types.NamespacedName{
|
||||
@@ -579,6 +581,7 @@ func TestRestoreReconcile(t *testing.T) {
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
"",
|
||||
)
|
||||
|
||||
r.clock = clocktesting.NewFakeClock(now)
|
||||
@@ -767,6 +770,7 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
"",
|
||||
)
|
||||
|
||||
restore := &velerov1api.Restore{
|
||||
@@ -863,6 +867,7 @@ func TestValidateAndCompleteWithResourcePolicySpecified(t *testing.T) {
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
"",
|
||||
)
|
||||
|
||||
restore := &velerov1api.Restore{
|
||||
@@ -992,6 +997,7 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
"",
|
||||
)
|
||||
|
||||
restore := &velerov1api.Restore{
|
||||
@@ -1110,6 +1116,184 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
|
||||
assert.Contains(t, restore3.Status.ValidationErrors[0], "Validation error in resource modifiers provided in configmap")
|
||||
}
|
||||
|
||||
func TestValidateAndCompleteWithDefaultResourceModifier(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
|
||||
validCMData := map[string]string{
|
||||
"modifiers.yaml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: pods\n mergePatches:\n - patchData: |\n metadata:\n annotations:\n k8s.ovn.org/pod-networks: null\n",
|
||||
}
|
||||
|
||||
setupReconciler := func(t *testing.T, defaultCM string) *restoreReconciler {
|
||||
t.Helper()
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
fakeGlobalClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
pluginManager := &pluginmocks.Manager{}
|
||||
backupStore := &persistencemocks.BackupStore{}
|
||||
|
||||
r := NewRestoreReconciler(
|
||||
t.Context(),
|
||||
velerov1api.DefaultNamespace,
|
||||
nil,
|
||||
fakeClient,
|
||||
velerotest.NewLogger(),
|
||||
logrus.DebugLevel,
|
||||
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
|
||||
NewFakeSingleObjectBackupStoreGetter(backupStore),
|
||||
metrics.NewServerMetrics(),
|
||||
formatFlag,
|
||||
60*time.Minute,
|
||||
false,
|
||||
fakeGlobalClient,
|
||||
10*time.Minute,
|
||||
defaultCM,
|
||||
)
|
||||
|
||||
location := builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), location))
|
||||
require.NoError(t, r.kbClient.Create(t.Context(),
|
||||
defaultBackup().ObjectMeta(builder.WithName("backup-1")).StorageLocation("default").Phase(velerov1api.BackupPhaseCompleted).Result(),
|
||||
))
|
||||
return r
|
||||
}
|
||||
|
||||
newRestore := func(perRestoreCM string, skip *bool) *velerov1api.Restore {
|
||||
restore := &velerov1api.Restore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: velerov1api.DefaultNamespace,
|
||||
Name: "restore-1",
|
||||
},
|
||||
Spec: velerov1api.RestoreSpec{
|
||||
BackupName: "backup-1",
|
||||
SkipDefaultResourceModifier: skip,
|
||||
},
|
||||
}
|
||||
if perRestoreCM != "" {
|
||||
restore.Spec.ResourceModifier = &corev1api.TypedLocalObjectReference{
|
||||
Kind: resourcemodifiers.ConfigmapRefType,
|
||||
Name: perRestoreCM,
|
||||
}
|
||||
}
|
||||
return restore
|
||||
}
|
||||
|
||||
t.Run("default modifier applied when no per-restore modifier", func(t *testing.T) {
|
||||
r := setupReconciler(t, "default-rm")
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "default-rm", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: validCMData,
|
||||
}))
|
||||
|
||||
restore := newRestore("", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.NotNil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("per-restore modifier takes exclusive precedence over default", func(t *testing.T) {
|
||||
// Default ConfigMap does NOT exist, but per-restore does.
|
||||
// If default were applied, it would fail. Per-restore should succeed.
|
||||
r := setupReconciler(t, "nonexistent-default")
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "per-restore-rm", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: validCMData,
|
||||
}))
|
||||
|
||||
restore := newRestore("per-restore-rm", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.NotNil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("skip default modifier when SkipDefaultResourceModifier is true", func(t *testing.T) {
|
||||
r := setupReconciler(t, "default-rm")
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "default-rm", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: validCMData,
|
||||
}))
|
||||
|
||||
skipTrue := true
|
||||
restore := newRestore("", &skipTrue)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("default modifier with invalid data is non-fatal", func(t *testing.T) {
|
||||
r := setupReconciler(t, "invalid-default")
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "invalid-default", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{
|
||||
"modifiers.yaml": "not-valid-yaml: [",
|
||||
},
|
||||
}))
|
||||
|
||||
restore := newRestore("", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("default modifier missing is non-fatal", func(t *testing.T) {
|
||||
r := setupReconciler(t, "nonexistent-cm")
|
||||
|
||||
restore := newRestore("", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("per-restore modifier missing is fatal", func(t *testing.T) {
|
||||
r := setupReconciler(t, "")
|
||||
|
||||
restore := newRestore("nonexistent-cm", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.NotEmpty(t, restore.Status.ValidationErrors)
|
||||
assert.Contains(t, restore.Status.ValidationErrors[0], "failed to get resource modifiers configmap")
|
||||
})
|
||||
|
||||
t.Run("no default configured and no per-restore modifier", func(t *testing.T) {
|
||||
r := setupReconciler(t, "")
|
||||
|
||||
restore := newRestore("", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("unsupported resource modifier kind does not apply default", func(t *testing.T) {
|
||||
r := setupReconciler(t, "default-rm")
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "default-rm", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: validCMData,
|
||||
}))
|
||||
|
||||
restore := newRestore("", nil)
|
||||
restore.Spec.ResourceModifier = &corev1api.TypedLocalObjectReference{
|
||||
Kind: "Secret",
|
||||
Name: "some-secret",
|
||||
}
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
|
||||
t.Run("default modifier validation failure is non-fatal", func(t *testing.T) {
|
||||
r := setupReconciler(t, "invalid-validation")
|
||||
require.NoError(t, r.kbClient.Create(t.Context(), &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "invalid-validation", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{
|
||||
"modifiers.yaml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: pods\n patches:\n - operation: invalid\n path: \"/spec\"\n value: \"test\"\n",
|
||||
},
|
||||
}))
|
||||
|
||||
restore := newRestore("", nil)
|
||||
_, rm, _ := r.validateAndComplete(t.Context(), restore)
|
||||
assert.Nil(t, rm)
|
||||
assert.Empty(t, restore.Status.ValidationErrors)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackupXorScheduleProvided(t *testing.T) {
|
||||
r := &velerov1api.Restore{}
|
||||
assert.False(t, backupXorScheduleProvided(r))
|
||||
|
||||
+42
-31
@@ -34,37 +34,38 @@ import (
|
||||
type podTemplateOption func(*podTemplateConfig)
|
||||
|
||||
type podTemplateConfig struct {
|
||||
image string
|
||||
envVars []corev1api.EnvVar
|
||||
restoreOnly bool
|
||||
annotations map[string]string
|
||||
labels map[string]string
|
||||
resources corev1api.ResourceRequirements
|
||||
withSecret bool
|
||||
defaultRepoMaintenanceFrequency time.Duration
|
||||
garbageCollectionFrequency time.Duration
|
||||
podVolumeOperationTimeout time.Duration
|
||||
plugins []string
|
||||
features []string
|
||||
defaultVolumesToFsBackup bool
|
||||
serviceAccountName string
|
||||
uploaderType string
|
||||
defaultSnapshotMoveData bool
|
||||
csiSnapshotEarlyFrequentPolling bool
|
||||
privilegedNodeAgent bool
|
||||
disableInformerCache bool
|
||||
scheduleSkipImmediately bool
|
||||
podResources kube.PodResources
|
||||
keepLatestMaintenanceJobs int
|
||||
backupRepoConfigMap string
|
||||
repoMaintenanceJobConfigMap string
|
||||
nodeAgentConfigMap string
|
||||
itemBlockWorkerCount int
|
||||
concurrentBackups int
|
||||
forWindows bool
|
||||
kubeletRootDir string
|
||||
nodeAgentDisableHostPath bool
|
||||
priorityClassName string
|
||||
image string
|
||||
envVars []corev1api.EnvVar
|
||||
restoreOnly bool
|
||||
annotations map[string]string
|
||||
labels map[string]string
|
||||
resources corev1api.ResourceRequirements
|
||||
withSecret bool
|
||||
defaultRepoMaintenanceFrequency time.Duration
|
||||
garbageCollectionFrequency time.Duration
|
||||
podVolumeOperationTimeout time.Duration
|
||||
plugins []string
|
||||
features []string
|
||||
defaultVolumesToFsBackup bool
|
||||
serviceAccountName string
|
||||
uploaderType string
|
||||
defaultSnapshotMoveData bool
|
||||
csiSnapshotEarlyFrequentPolling bool
|
||||
privilegedNodeAgent bool
|
||||
disableInformerCache bool
|
||||
scheduleSkipImmediately bool
|
||||
podResources kube.PodResources
|
||||
keepLatestMaintenanceJobs int
|
||||
backupRepoConfigMap string
|
||||
repoMaintenanceJobConfigMap string
|
||||
defaultResourceModifierConfigMap string
|
||||
nodeAgentConfigMap string
|
||||
itemBlockWorkerCount int
|
||||
concurrentBackups int
|
||||
forWindows bool
|
||||
kubeletRootDir string
|
||||
nodeAgentDisableHostPath bool
|
||||
priorityClassName string
|
||||
}
|
||||
|
||||
func WithImage(image string) podTemplateOption {
|
||||
@@ -229,6 +230,12 @@ func WithRepoMaintenanceJobConfigMap(repoMaintenanceJobConfigMap string) podTemp
|
||||
}
|
||||
}
|
||||
|
||||
func WithDefaultResourceModifierConfigMap(name string) podTemplateOption {
|
||||
return func(c *podTemplateConfig) {
|
||||
c.defaultResourceModifierConfigMap = name
|
||||
}
|
||||
}
|
||||
|
||||
func WithItemBlockWorkerCount(itemBlockWorkerCount int) podTemplateOption {
|
||||
return func(c *podTemplateConfig) {
|
||||
c.itemBlockWorkerCount = itemBlockWorkerCount
|
||||
@@ -350,6 +357,10 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1api.Deployme
|
||||
args = append(args, fmt.Sprintf("--repo-maintenance-job-configmap=%s", c.repoMaintenanceJobConfigMap))
|
||||
}
|
||||
|
||||
if len(c.defaultResourceModifierConfigMap) > 0 {
|
||||
args = append(args, fmt.Sprintf("--default-resource-modifier-configmap=%s", c.defaultResourceModifierConfigMap))
|
||||
}
|
||||
|
||||
if c.itemBlockWorkerCount > 0 {
|
||||
args = append(args, fmt.Sprintf("--item-block-worker-count=%d", c.itemBlockWorkerCount))
|
||||
}
|
||||
|
||||
@@ -109,6 +109,10 @@ func TestDeployment(t *testing.T) {
|
||||
assert.Len(t, deploy.Spec.Template.Spec.Containers[0].Args, 2)
|
||||
assert.Equal(t, "--repo-maintenance-job-configmap=test-repo-maintenance-config", deploy.Spec.Template.Spec.Containers[0].Args[1])
|
||||
|
||||
deploy = Deployment("velero", WithDefaultResourceModifierConfigMap("default-restore-modifiers"))
|
||||
assert.Len(t, deploy.Spec.Template.Spec.Containers[0].Args, 2)
|
||||
assert.Equal(t, "--default-resource-modifier-configmap=default-restore-modifiers", deploy.Spec.Template.Spec.Containers[0].Args[1])
|
||||
|
||||
assert.Equal(t, &corev1api.Affinity{
|
||||
NodeAffinity: &corev1api.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{
|
||||
|
||||
+48
-43
@@ -234,49 +234,50 @@ func appendUnstructured(list *unstructured.UnstructuredList, obj runtime.Object)
|
||||
}
|
||||
|
||||
type VeleroOptions struct {
|
||||
Namespace string
|
||||
Image string
|
||||
ProviderName string
|
||||
Bucket string
|
||||
Prefix string
|
||||
PodAnnotations map[string]string
|
||||
PodLabels map[string]string
|
||||
ServiceAccountAnnotations map[string]string
|
||||
ServiceAccountName string
|
||||
VeleroPodResources corev1api.ResourceRequirements
|
||||
NodeAgentPodResources corev1api.ResourceRequirements
|
||||
SecretData []byte
|
||||
RestoreOnly bool
|
||||
UseNodeAgent bool
|
||||
UseNodeAgentWindows bool
|
||||
PrivilegedNodeAgent bool
|
||||
UseVolumeSnapshots bool
|
||||
BSLConfig map[string]string
|
||||
VSLConfig map[string]string
|
||||
DefaultRepoMaintenanceFrequency time.Duration
|
||||
GarbageCollectionFrequency time.Duration
|
||||
PodVolumeOperationTimeout time.Duration
|
||||
Plugins []string
|
||||
NoDefaultBackupLocation bool
|
||||
CACertData []byte
|
||||
Features []string
|
||||
DefaultVolumesToFsBackup bool
|
||||
UploaderType string
|
||||
DefaultSnapshotMoveData bool
|
||||
CSISnapshotEarlyFrequentPolling bool
|
||||
DisableInformerCache bool
|
||||
ScheduleSkipImmediately bool
|
||||
PodResources kube.PodResources
|
||||
KeepLatestMaintenanceJobs int
|
||||
BackupRepoConfigMap string
|
||||
RepoMaintenanceJobConfigMap string
|
||||
NodeAgentConfigMap string
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
KubeletRootDir string
|
||||
NodeAgentDisableHostPath bool
|
||||
ServerPriorityClassName string
|
||||
NodeAgentPriorityClassName string
|
||||
Namespace string
|
||||
Image string
|
||||
ProviderName string
|
||||
Bucket string
|
||||
Prefix string
|
||||
PodAnnotations map[string]string
|
||||
PodLabels map[string]string
|
||||
ServiceAccountAnnotations map[string]string
|
||||
ServiceAccountName string
|
||||
VeleroPodResources corev1api.ResourceRequirements
|
||||
NodeAgentPodResources corev1api.ResourceRequirements
|
||||
SecretData []byte
|
||||
RestoreOnly bool
|
||||
UseNodeAgent bool
|
||||
UseNodeAgentWindows bool
|
||||
PrivilegedNodeAgent bool
|
||||
UseVolumeSnapshots bool
|
||||
BSLConfig map[string]string
|
||||
VSLConfig map[string]string
|
||||
DefaultRepoMaintenanceFrequency time.Duration
|
||||
GarbageCollectionFrequency time.Duration
|
||||
PodVolumeOperationTimeout time.Duration
|
||||
Plugins []string
|
||||
NoDefaultBackupLocation bool
|
||||
CACertData []byte
|
||||
Features []string
|
||||
DefaultVolumesToFsBackup bool
|
||||
UploaderType string
|
||||
DefaultSnapshotMoveData bool
|
||||
CSISnapshotEarlyFrequentPolling bool
|
||||
DisableInformerCache bool
|
||||
ScheduleSkipImmediately bool
|
||||
PodResources kube.PodResources
|
||||
KeepLatestMaintenanceJobs int
|
||||
BackupRepoConfigMap string
|
||||
RepoMaintenanceJobConfigMap string
|
||||
DefaultResourceModifierConfigMap string
|
||||
NodeAgentConfigMap string
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
KubeletRootDir string
|
||||
NodeAgentDisableHostPath bool
|
||||
ServerPriorityClassName string
|
||||
NodeAgentPriorityClassName string
|
||||
}
|
||||
|
||||
func AllCRDs() *unstructured.UnstructuredList {
|
||||
@@ -407,6 +408,10 @@ func AllResources(o *VeleroOptions) *unstructured.UnstructuredList {
|
||||
deployOpts = append(deployOpts, WithRepoMaintenanceJobConfigMap(o.RepoMaintenanceJobConfigMap))
|
||||
}
|
||||
|
||||
if len(o.DefaultResourceModifierConfigMap) > 0 {
|
||||
deployOpts = append(deployOpts, WithDefaultResourceModifierConfigMap(o.DefaultResourceModifierConfigMap))
|
||||
}
|
||||
|
||||
deploy := Deployment(o.Namespace, deployOpts...)
|
||||
|
||||
if err := appendUnstructured(resources, deploy); err != nil {
|
||||
|
||||
@@ -118,6 +118,32 @@ func TestAllResources(t *testing.T) {
|
||||
assert.Len(t, ds, 2)
|
||||
}
|
||||
|
||||
func TestAllResourcesWithDefaultResourceModifierConfigMap(t *testing.T) {
|
||||
option := &VeleroOptions{
|
||||
Namespace: "velero",
|
||||
SecretData: []byte{'a'},
|
||||
DefaultResourceModifierConfigMap: "default-rm",
|
||||
}
|
||||
list := AllResources(option)
|
||||
|
||||
for _, item := range list.Items {
|
||||
if item.GetKind() == "Deployment" && item.GetName() == "velero" {
|
||||
containers, _, _ := unstructured.NestedSlice(item.Object, "spec", "template", "spec", "containers")
|
||||
args, _, _ := unstructured.NestedStringSlice(containers[0].(map[string]any), "args")
|
||||
found := false
|
||||
for _, arg := range args {
|
||||
if arg == "--default-resource-modifier-configmap=default-rm" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "expected --default-resource-modifier-configmap=default-rm in deployment args")
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatal("velero deployment not found in AllResources output")
|
||||
}
|
||||
|
||||
func TestAllResourcesWithPriorityClassName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@@ -501,6 +501,7 @@ By far, `velero install` supports the following parameters to specify the extern
|
||||
* --backup-repository-configmap: [backup repository configuration document][15]
|
||||
* --node-agent-configmap: [node-agent concurrency configuration document][16], and there are some other documents specify other parts of node-agent-config.
|
||||
* --repo-maintenance-job-configmap: [repository maintenance configuration document][17]
|
||||
* --default-resource-modifier-configmap: [default restore resource modifier document][18]. When set, the referenced ConfigMap's resource modifier rules apply automatically to all restores that don't specify a per-restore modifier.
|
||||
|
||||
From v1.17, Velero adds verification for the ConfigMaps in CLI and server side, which means `velero install` CLI will fail and velero server and node-agent pod will exit if the specified ConfigMaps don't exist or are invalid.
|
||||
|
||||
@@ -539,3 +540,4 @@ The new workflow is:
|
||||
[15]: backup-repository-configuration.md
|
||||
[16]: node-agent-concurrency.md
|
||||
[17]: repository-maintenance.md
|
||||
[18]: restore-resource-modifiers.md#default-resource-modifiers
|
||||
|
||||
@@ -184,4 +184,47 @@ resourceModifierRules:
|
||||
|
||||
### Wildcard Support for GroupResource
|
||||
The user can specify a wildcard for groupResource in the conditions' struct. This will allow the user to apply the patches for all the resources of a particular group or all resources in all groups. For example, `*.apps` will apply to all the resources in the `apps` group, `*` will apply to all the resources in core group, `*.*` will apply to all the resources in all groups.
|
||||
- If both `*.groupName` and `namespaces` are specified, the patches will be applied to all the namespaced resources in this group in the specified namespaces and all the cluster resources in this group.
|
||||
- If both `*.groupName` and `namespaces` are specified, the patches will be applied to all the namespaced resources in this group in the specified namespaces and all the cluster resources in this group.
|
||||
|
||||
## Default Resource Modifiers
|
||||
|
||||
Velero supports a server-level default resource modifier that applies automatically to all restores without requiring per-restore configuration.
|
||||
This is useful for common transformations like stripping stale CNI annotations that can break workloads after restore.
|
||||
|
||||
### Configuration
|
||||
|
||||
1. Create a ConfigMap in the Velero namespace with your default resource modifier rules:
|
||||
|
||||
```bash
|
||||
kubectl apply -f examples/default-resource-modifier-cni.yaml
|
||||
```
|
||||
|
||||
2. Configure the Velero server to use it, either during install:
|
||||
|
||||
```bash
|
||||
velero install --default-resource-modifier-configmap=default-restore-resource-modifiers ...
|
||||
```
|
||||
|
||||
Or by editing an existing deployment:
|
||||
|
||||
```bash
|
||||
kubectl -n velero edit deploy velero
|
||||
# Add to the server args: --default-resource-modifier-configmap=default-restore-resource-modifiers
|
||||
```
|
||||
|
||||
### Precedence
|
||||
|
||||
When a per-restore modifier is specified via `--resource-modifier-configmap`, it takes exclusive precedence and the default is not applied.
|
||||
|
||||
### Opt-out
|
||||
|
||||
To skip the default modifier for a specific restore without specifying a per-restore modifier:
|
||||
|
||||
```bash
|
||||
velero restore create --from-backup my-backup --skip-default-resource-modifier
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
If the default ConfigMap is missing or contains invalid data, Velero logs a warning and proceeds with the restore.
|
||||
Per-restore modifier errors remain fatal and cause the restore to fail validation.
|
||||
Reference in New Issue
Block a user