diff --git a/changelogs/unreleased/9966-adam-jian-zhang b/changelogs/unreleased/9966-adam-jian-zhang new file mode 100644 index 000000000..c95540c80 --- /dev/null +++ b/changelogs/unreleased/9966-adam-jian-zhang @@ -0,0 +1 @@ +Fix issue #9937, add CLI support for restore filters via resource policy diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index 580bb36b9..3f59b6a6b 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -32,6 +32,7 @@ import ( kbclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/velero/internal/resourcemodifiers" + "github.com/vmware-tanzu/velero/internal/resourcepolicies" api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" @@ -61,7 +62,13 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command { velero restore create --from-schedule schedule-1 --allow-partially-failed # Create a restore for only persistentvolumeclaims and persistentvolumes within a backup. - velero restore create --from-backup backup-2 --include-resources persistentvolumeclaims,persistentvolumes`, + velero restore create --from-backup backup-2 --include-resources persistentvolumeclaims,persistentvolumes + +Notes: +- Global filters (--include-resources, --selector, etc.) apply to all included namespaces +- Namespace-scoped filters defined in --resource-policies-configmap refine global filters for matching namespaces (globally excluded kinds cannot be re-included) +- Fine-grained global filter policies defined in --resource-policies-configmap refine global filters for cluster-scoped resources +- Use 'velero restore describe' to view the referenced resource policies ConfigMap after restore creation`, Args: cobra.MaximumNArgs(1), Run: func(c *cobra.Command, args []string) { cmd.CheckError(o.Complete(args, f)) @@ -100,6 +107,7 @@ type CreateOptions struct { AllowPartiallyFailed flag.OptionalBool ItemOperationTimeout time.Duration ResourceModifierConfigMap string + ResourcePoliciesConfigMap string WriteSparseFiles flag.OptionalBool ParallelFilesDownload int client kbclient.WithWatch @@ -154,6 +162,8 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) { flags.StringVar(&o.ResourceModifierConfigMap, "resource-modifier-configmap", "", "Reference to the resource modifier configmap that restore will use") + flags.StringVar(&o.ResourcePoliciesConfigMap, "resource-policies-configmap", "", "Reference to the ConfigMap containing restore resource filter policies") + f = flags.VarPF(&o.WriteSparseFiles, "write-sparse-files", "", "Whether to write sparse files during restoring volumes") f.NoOptDefVal = cmd.TRUE @@ -310,6 +320,15 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { } } + var resPolicies *corev1api.TypedLocalObjectReference + + if o.ResourcePoliciesConfigMap != "" { + resPolicies = &corev1api.TypedLocalObjectReference{ + Kind: resourcepolicies.ConfigmapRefType, + Name: o.ResourcePoliciesConfigMap, + } + } + restore := &api.Restore{ ObjectMeta: metav1.ObjectMeta{ Namespace: f.Namespace(), @@ -332,6 +351,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { PreserveNodePorts: o.PreserveNodePorts.Value, IncludeClusterResources: o.IncludeClusterResources.Value, ResourceModifier: resModifiers, + ResourcePolicy: resPolicies, ItemOperationTimeout: metav1.Duration{ Duration: o.ItemOperationTimeout, }, diff --git a/pkg/cmd/cli/restore/create_test.go b/pkg/cmd/cli/restore/create_test.go index 8cc369dea..9a6a92608 100644 --- a/pkg/cmd/cli/restore/create_test.go +++ b/pkg/cmd/cli/restore/create_test.go @@ -77,6 +77,8 @@ func TestCreateCommand(t *testing.T) { includeClusterResources := "true" allowPartiallyFailed := "true" itemOperationTimeout := "10m0s" + resourceModifierConfigMap := "modifier-cm" + ResourcePoliciesConfigMap := "policies-cm" writeSparseFiles := "true" parallel := 2 flags := new(pflag.FlagSet) @@ -101,6 +103,8 @@ func TestCreateCommand(t *testing.T) { flags.Parse([]string{"--include-cluster-resources", includeClusterResources}) flags.Parse([]string{"--allow-partially-failed", allowPartiallyFailed}) flags.Parse([]string{"--item-operation-timeout", itemOperationTimeout}) + flags.Parse([]string{"--resource-modifier-configmap", resourceModifierConfigMap}) + flags.Parse([]string{"--resource-policies-configmap", ResourcePoliciesConfigMap}) flags.Parse([]string{"--write-sparse-files", writeSparseFiles}) flags.Parse([]string{"--parallel-files-download", "2"}) client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) @@ -139,6 +143,8 @@ func TestCreateCommand(t *testing.T) { require.Equal(t, includeClusterResources, o.IncludeClusterResources.String()) require.Equal(t, allowPartiallyFailed, o.AllowPartiallyFailed.String()) require.Equal(t, itemOperationTimeout, o.ItemOperationTimeout.String()) + require.Equal(t, resourceModifierConfigMap, o.ResourceModifierConfigMap) + require.Equal(t, ResourcePoliciesConfigMap, o.ResourcePoliciesConfigMap) require.Equal(t, writeSparseFiles, o.WriteSparseFiles.String()) require.Equal(t, parallel, o.ParallelFilesDownload) }) @@ -189,4 +195,37 @@ func TestCreateCommand(t *testing.T) { err := o.Validate(c, []string{}, f) require.Equal(t, "backups.velero.io \"not-exist\" not found", err.Error()) }) + + t.Run("create a restore with resource policies configmap", func(t *testing.T) { + f := &factorymocks.Factory{} + c := NewCreateCommand(f, "") + require.Equal(t, "Create a restore", c.Short) + flags := new(pflag.FlagSet) + o := NewCreateOptions() + o.BindFlags(flags) + + backupName := "backup-with-policies" + ResourcePoliciesConfigMap := "test-policies-cm" + flags.Parse([]string{"--from-backup", backupName}) + flags.Parse([]string{"--resource-policies-configmap", ResourcePoliciesConfigMap}) + + kbclient := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) + backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).Phase(velerov1api.BackupPhaseCompleted).Result() + require.NoError(t, kbclient.Create(t.Context(), backup, &controllerclient.CreateOptions{})) + + f.On("Namespace").Return(cmdtest.VeleroNameSpace) + f.On("KubebuilderWatchClient").Return(kbclient, nil) + + require.NoError(t, o.Complete(args, f)) + require.NoError(t, o.Validate(c, []string{}, f)) + require.NoError(t, o.Run(c, f)) + + // Verify the created restore object + createdRestore := &velerov1api.Restore{} + err := kbclient.Get(t.Context(), controllerclient.ObjectKey{Namespace: cmdtest.VeleroNameSpace, Name: name}, createdRestore) + require.NoError(t, err) + require.NotNil(t, createdRestore.Spec.ResourcePolicy) + require.Equal(t, "configmap", createdRestore.Spec.ResourcePolicy.Kind) + require.Equal(t, ResourcePoliciesConfigMap, createdRestore.Spec.ResourcePolicy.Name) + }) } diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index a89943e74..c33da9f69 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -219,6 +219,11 @@ func DescribeRestore( DescribeResourceModifier(d, restore.Spec.ResourceModifier) } + if restore.Spec.ResourcePolicy != nil { + d.Println() + DescribeResourcePolicies(d, restore.Spec.ResourcePolicy) + } + describeUploaderConfigForRestore(d, restore.Spec) d.Println()