Remove wildcard check from getNamespacesToList.

Expand wildcard in namespace filter only for backup scenario.
Restore doesn't need that now, because restore has logic to rely on
IncludeEverything function to check whether cluster-scoped resources
should be restored. Expand wildcard will break the logic.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
This commit is contained in:
Xun Jiang
2026-04-08 08:12:21 +08:00
committed by Xun Jiang/Bruce Jiang
parent bf9e1f8fd7
commit 8ac8f49b5c
14 changed files with 211 additions and 140 deletions
+1
View File
@@ -0,0 +1 @@
Remove wildcard check from getNamespacesToList.
+3 -3
View File
@@ -187,7 +187,7 @@ func getNamespaceIncludesExcludesAndArgoCDNamespaces(backup *velerov1api.Backup,
Excludes(backup.Spec.ExcludedNamespaces...)
// Expand wildcards if needed
if err := includesExcludes.ExpandIncludesExcludes(); err != nil {
if err := includesExcludes.ExpandIncludesExcludes(true); err != nil {
return nil, []string{}, err
}
@@ -286,7 +286,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
expandedExcludes := backupRequest.NamespaceIncludesExcludes.GetExcludes()
// Get the final namespace list after wildcard expansion
wildcardResult, err := backupRequest.NamespaceIncludesExcludes.ResolveNamespaceList()
wildcardResult, err := backupRequest.NamespaceIncludesExcludes.ResolveNamespaceList(true)
if err != nil {
log.WithError(err).Errorf("error resolving namespace list")
return err
@@ -410,7 +410,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
// Resolve namespaces for PVC-to-Pod cache building in volumehelper.
// See issue #9179 for details.
namespaces, err := backupRequest.NamespaceIncludesExcludes.ResolveNamespaceList()
namespaces, err := backupRequest.NamespaceIncludesExcludes.ResolveNamespaceList(true)
if err != nil {
log.WithError(err).Error("Failed to resolve namespace list for PVC-to-Pod cache")
return err
+10 -31
View File
@@ -36,6 +36,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -281,8 +282,8 @@ func TestBackupOldResourceFiltering(t *testing.T) {
Result(),
apiResources: []*test.APIResource{
test.Pods(
builder.ForPod("foo", "bar").Result(),
builder.ForPod("zoo", "raz").Result(),
builder.ForPod("foo", "bar").Phase(corev1api.PodRunning).Result(),
builder.ForPod("zoo", "raz").Phase(corev1api.PodRunning).Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
@@ -980,28 +981,6 @@ func TestCRDInclusion(t *testing.T) {
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
name: "include cluster resources=auto includes CRDs with CRs when backing up selected namespaces",
backup: defaultBackup().
IncludedNamespaces("foo").
Result(),
apiResources: []*test.APIResource{
test.CRDs(
builder.ForCustomResourceDefinitionV1Beta1("backups.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("volumesnapshotlocations.velero.io").Result(),
builder.ForCustomResourceDefinitionV1Beta1("test.velero.io").Result(),
),
test.VSLs(
builder.ForVolumeSnapshotLocation("foo", "vsl-1").Result(),
),
},
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
name: "include-cluster-resources=false excludes all CRDs when backing up selected namespaces",
backup: defaultBackup().
@@ -4296,6 +4275,12 @@ func (h *harness) addItems(t *testing.T, resource *test.APIResource) {
unstructuredObj := &unstructured.Unstructured{Object: obj}
if resource.Namespaced {
namespace := &corev1api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: item.GetNamespace()}}
err = h.backupper.kbClient.Create(t.Context(), namespace)
if err != nil && !apierrors.IsAlreadyExists(err) {
require.NoError(t, err)
}
_, err = h.DynamicClient.Resource(resource.GVR()).Namespace(item.GetNamespace()).Create(t.Context(), unstructuredObj, metav1.CreateOptions{})
} else {
_, err = h.DynamicClient.Resource(resource.GVR()).Create(t.Context(), unstructuredObj, metav1.CreateOptions{})
@@ -4346,7 +4331,7 @@ func newSnapshotLocation(ns, name, provider string) *velerov1.VolumeSnapshotLoca
}
func defaultBackup() *builder.BackupBuilder {
return builder.ForBackup(velerov1.DefaultNamespace, "backup-1").DefaultVolumesToFsBackup(false)
return builder.ForBackup(velerov1.DefaultNamespace, "backup-1").DefaultVolumesToFsBackup(false).IncludedNamespaces("*")
}
func toUnstructuredOrFail(t *testing.T, obj any) map[string]any {
@@ -5422,8 +5407,6 @@ func TestBackupNamespaces(t *testing.T) {
want: []string{
"resources/namespaces/cluster/ns-1.json",
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
"resources/namespaces/cluster/ns-3.json",
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
},
},
{
@@ -5457,10 +5440,6 @@ func TestBackupNamespaces(t *testing.T) {
want: []string{
"resources/namespaces/cluster/ns-1.json",
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
"resources/namespaces/cluster/ns-2.json",
"resources/namespaces/v1-preferredversion/cluster/ns-2.json",
"resources/namespaces/cluster/ns-3.json",
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
"resources/deployments.apps/namespaces/ns-1/deploy-1.json",
"resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json",
},
+7 -10
View File
@@ -633,22 +633,19 @@ func coreGroupResourcePriority(resource string) int {
}
// getNamespacesToList examines ie and resolves the includes and excludes to a full list of
// namespaces to list. If ie is nil or it includes *, the result is just "" (list across all
// namespaces). Otherwise, the result is a list of every included namespace minus all excluded ones.
// namespaces to list. If ie is nil, the result is just "" (list across all namespaces).
// Otherwise, the result is a list of every included namespace minus all excluded ones.
// Because the namespace IE filter is expanded from 1.18, there is no need to consider
// wildcard characters anymore.
func getNamespacesToList(ie *collections.NamespaceIncludesExcludes) []string {
if ie == nil {
return []string{""}
}
if ie.ShouldInclude("*") {
// "" means all namespaces
return []string{""}
}
var list []string
for _, i := range ie.GetIncludes() {
if ie.ShouldInclude(i) {
list = append(list, i)
for _, n := range ie.GetIncludes() {
if ie.ShouldInclude(n) {
list = append(list, n)
}
}
+8
View File
@@ -575,6 +575,14 @@ func (b *backupReconciler) prepareBackupRequest(ctx context.Context, backup *vel
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
}
// if included namespaces is empty, default to wildcard to include all namespaces
// This is useful for later wildcard expansion logic.
// This also align the behavior between backup creation from CLI and from API,
// as CLI will default to wildcard if included namespaces is not specified.
if request.Spec.IncludedNamespaces == nil {
request.Spec.IncludedNamespaces = []string{"*"}
}
// validate that only one exists orLabelSelector or just labelSelector (singular)
if request.Spec.OrLabelSelectors != nil && request.Spec.LabelSelector != nil {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, "encountered labelSelector as well as orLabelSelectors in backup spec, only one can be specified")
+19
View File
@@ -713,6 +713,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -752,6 +753,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -795,6 +797,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -835,6 +838,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -875,6 +879,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -916,6 +921,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -957,6 +963,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -998,6 +1005,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1039,6 +1047,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1081,6 +1090,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed,
@@ -1123,6 +1133,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed,
@@ -1165,6 +1176,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.True(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1208,6 +1220,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1251,6 +1264,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1294,6 +1308,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.True(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1338,6 +1353,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.False(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1381,6 +1397,7 @@ func TestProcessBackupCompletions(t *testing.T) {
SnapshotMoveData: boolptr.True(),
ExcludedClusterScopedResources: autoExcludeClusterScopedResources,
ExcludedNamespaceScopedResources: autoExcludeNamespaceScopedResources,
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1430,6 +1447,7 @@ func TestProcessBackupCompletions(t *testing.T) {
ExcludedClusterScopedResources: append([]string{"clusterroles"}, autoExcludeClusterScopedResources...),
IncludedNamespaceScopedResources: []string{"pods"},
ExcludedNamespaceScopedResources: append([]string{"secrets"}, autoExcludeNamespaceScopedResources...),
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
@@ -1479,6 +1497,7 @@ func TestProcessBackupCompletions(t *testing.T) {
ExcludedClusterScopedResources: append([]string{"clusterroles"}, autoExcludeClusterScopedResources...),
IncludedNamespaceScopedResources: []string{"pods"},
ExcludedNamespaceScopedResources: append([]string{"secrets"}, autoExcludeNamespaceScopedResources...),
IncludedNamespaces: []string{"*"},
},
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFinalizing,
+1 -1
View File
@@ -200,7 +200,7 @@ func (r *backupQueueReconciler) checkForEarlierRunnableBackups(backup *velerov1a
func namespacesForBackup(backup *velerov1api.Backup, clusterNamespaces []string) []string {
// Ignore error here. If a backup has invalid namespace wildcards, the backup controller
// will validate and fail it. Consider the ns list empty for conflict detection purposes.
nsList, err := collections.NewNamespaceIncludesExcludes().Includes(backup.Spec.IncludedNamespaces...).Excludes(backup.Spec.ExcludedNamespaces...).ActiveNamespaces(clusterNamespaces).ResolveNamespaceList()
nsList, err := collections.NewNamespaceIncludesExcludes().Includes(backup.Spec.IncludedNamespaces...).Excludes(backup.Spec.ExcludedNamespaces...).ActiveNamespaces(clusterNamespaces).ResolveNamespaceList(true)
if err != nil {
return []string{}
}
+1 -1
View File
@@ -2407,7 +2407,7 @@ func extractNamespacesFromBackup(backupResources map[string]*archive.ResourceIte
// expandNamespaceWildcards expands wildcard patterns in namespace includes/excludes
// and updates the restore context with the expanded patterns and status
func (ctx *restoreContext) expandNamespaceWildcards(backupResources map[string]*archive.ResourceItems) error {
if !wildcard.ShouldExpandWildcards(ctx.restore.Spec.IncludedNamespaces, ctx.restore.Spec.ExcludedNamespaces) {
if !wildcard.ShouldExpandWildcards(ctx.restore.Spec.IncludedNamespaces, ctx.restore.Spec.ExcludedNamespaces, false) {
return nil
}
-9
View File
@@ -93,15 +93,6 @@ func TestExpandNamespaceWildcards(t *testing.T) {
expectedExcludeMatches: []string{"app-test"},
expectedWildcardResult: []string{"app-dev", "app-prod"},
},
{
name: "Error: wildcard * in excludes",
includeNamespaces: []string{"test*"},
excludeNamespaces: []string{"*"},
backupResources: map[string]*archive.ResourceItems{
"namespaces": {ItemsByNamespace: map[string][]string{"test1": {}}},
},
expectedError: "wildcard '*' is not allowed in restore excludes",
},
{
name: "Empty backup - no matches",
includeNamespaces: []string{"test*"},
+9 -5
View File
@@ -17,6 +17,7 @@ limitations under the License.
package collections
import (
"slices"
"strings"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
@@ -149,15 +150,18 @@ func (nie *NamespaceIncludesExcludes) ShouldInclude(s string) bool {
// IncludeEverything returns true if the includes list is empty or '*'
// and the excludes list is empty, or false otherwise.
func (nie *NamespaceIncludesExcludes) IncludeEverything() bool {
return nie.includesExcludes.IncludeEverything()
return nie.includesExcludes.excludes.Len() == 0 &&
(nie.includesExcludes.includes.Len() == 0 ||
(nie.includesExcludes.includes.Len() == 1 && nie.includesExcludes.includes.Has("*")) ||
slices.Equal(nie.includesExcludes.includes.List(), nie.activeNamespaces))
}
// Attempts to expand wildcard patterns, if any, in the includes and excludes lists.
func (nie *NamespaceIncludesExcludes) ExpandIncludesExcludes() error {
func (nie *NamespaceIncludesExcludes) ExpandIncludesExcludes(fromBackup bool) error {
includes := nie.GetIncludes()
excludes := nie.GetExcludes()
if wildcard.ShouldExpandWildcards(includes, excludes) {
if wildcard.ShouldExpandWildcards(includes, excludes, fromBackup) {
expandedIncludes, expandedExcludes, err := wildcard.ExpandWildcards(
nie.activeNamespaces, includes, excludes)
if err != nil {
@@ -174,10 +178,10 @@ func (nie *NamespaceIncludesExcludes) ExpandIncludesExcludes() error {
// ResolveNamespaceList returns a list of all namespaces which will be backed up.
// The second return value indicates whether wildcard expansion was performed.
func (nie *NamespaceIncludesExcludes) ResolveNamespaceList() ([]string, error) {
func (nie *NamespaceIncludesExcludes) ResolveNamespaceList(fromBackup bool) ([]string, error) {
// Check if this is being called by non-backup processing e.g. backup queue controller
if !nie.wildcardExpanded {
err := nie.ExpandIncludesExcludes()
err := nie.ExpandIncludesExcludes(fromBackup)
if err != nil {
return nil, err
}
+40 -8
View File
@@ -1049,6 +1049,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes []string
expectedWildcardExpanded bool
expectError bool
fromBackup bool
}{
{
name: "no wildcards - should not expand",
@@ -1059,16 +1060,18 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{"kube-public"},
expectedWildcardExpanded: false,
expectError: false,
fromBackup: true,
},
{
name: "asterisk alone - should not expand",
name: "asterisk alone - should expand",
includes: []string{"*"},
excludes: []string{},
activeNamespaces: []string{"default", "kube-system", "test"},
expectedIncludes: []string{"*"},
expectedIncludes: []string{"default", "kube-system", "test"},
expectedExcludes: []string{},
expectedWildcardExpanded: false,
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "wildcard in includes - should expand",
@@ -1079,6 +1082,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "wildcard in excludes - should expand",
@@ -1089,6 +1093,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{"kube-test", "app-test"},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "wildcards in both includes and excludes",
@@ -1099,6 +1104,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{"kube-test", "app-test"},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "wildcard pattern matches nothing",
@@ -1109,6 +1115,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "mix of wildcards and non-wildcards in includes",
@@ -1119,6 +1126,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "question mark wildcard",
@@ -1129,6 +1137,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "empty activeNamespaces with wildcards",
@@ -1139,6 +1148,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectError: false,
fromBackup: true,
},
{
name: "invalid wildcard pattern - consecutive asterisks",
@@ -1149,6 +1159,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedExcludes: []string{},
expectedWildcardExpanded: false,
expectError: true,
fromBackup: true,
},
}
@@ -1159,7 +1170,7 @@ func TestExpandIncludesExcludes(t *testing.T) {
Includes(tc.includes...).
Excludes(tc.excludes...)
err := nie.ExpandIncludesExcludes()
err := nie.ExpandIncludesExcludes(tc.fromBackup)
if tc.expectError {
assert.Error(t, err)
@@ -1192,6 +1203,7 @@ func TestResolveNamespaceList(t *testing.T) {
activeNamespaces []string
expectedNamespaces []string
preExpandWildcards bool
fromBackup bool
}{
{
name: "no includes/excludes - all active namespaces",
@@ -1199,6 +1211,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"default", "kube-system", "test"},
expectedNamespaces: []string{"default", "kube-system", "test"},
fromBackup: true,
},
{
name: "asterisk includes - all active namespaces",
@@ -1206,6 +1219,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"default", "kube-system", "test"},
expectedNamespaces: []string{"default", "kube-system", "test"},
fromBackup: true,
},
{
name: "specific includes - only those namespaces",
@@ -1213,6 +1227,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"default", "kube-system", "test"},
expectedNamespaces: []string{"default", "test"},
fromBackup: true,
},
{
name: "includes with excludes",
@@ -1220,6 +1235,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{"kube-system"},
activeNamespaces: []string{"default", "kube-system", "test"},
expectedNamespaces: []string{"default", "test"},
fromBackup: true,
},
{
name: "wildcard includes - expands and filters",
@@ -1227,6 +1243,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"default", "kube-system", "kube-public", "test"},
expectedNamespaces: []string{"kube-system", "kube-public"},
fromBackup: true,
},
{
name: "wildcard includes with wildcard excludes",
@@ -1234,6 +1251,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{"*-test"},
activeNamespaces: []string{"app-prod", "app-dev", "app-test", "default"},
expectedNamespaces: []string{"app-prod", "app-dev"},
fromBackup: true,
},
{
name: "wildcard matches nothing - empty result",
@@ -1241,6 +1259,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"default", "kube-system"},
expectedNamespaces: []string{},
fromBackup: true,
},
{
name: "empty active namespaces",
@@ -1248,6 +1267,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{},
expectedNamespaces: []string{},
fromBackup: true,
},
{
name: "includes namespace not in active namespaces",
@@ -1255,6 +1275,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"default", "test"},
expectedNamespaces: []string{"default"},
fromBackup: true,
},
{
name: "excludes all namespaces from includes",
@@ -1262,6 +1283,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{"default", "test"},
activeNamespaces: []string{"default", "test", "prod"},
expectedNamespaces: []string{},
fromBackup: true,
},
{
name: "pre-expanded wildcards - should not expand again",
@@ -1270,6 +1292,7 @@ func TestResolveNamespaceList(t *testing.T) {
activeNamespaces: []string{"default", "kube-system", "kube-public"},
expectedNamespaces: []string{"kube-system", "kube-public"},
preExpandWildcards: true,
fromBackup: true,
},
{
name: "question mark wildcard pattern",
@@ -1277,6 +1300,7 @@ func TestResolveNamespaceList(t *testing.T) {
excludes: []string{},
activeNamespaces: []string{"ns-1", "ns-2", "ns-10", "default"},
expectedNamespaces: []string{"ns-1", "ns-2"},
fromBackup: true,
},
}
@@ -1289,11 +1313,11 @@ func TestResolveNamespaceList(t *testing.T) {
// Pre-expand wildcards if requested
if tc.preExpandWildcards {
err := nie.ExpandIncludesExcludes()
err := nie.ExpandIncludesExcludes(tc.fromBackup)
require.NoError(t, err)
}
namespaces, err := nie.ResolveNamespaceList()
namespaces, err := nie.ResolveNamespaceList(tc.fromBackup)
require.NoError(t, err)
// Convert to sets for order-independent comparison
@@ -1311,18 +1335,21 @@ func TestResolveNamespaceListError(t *testing.T) {
includes []string
excludes []string
activeNamespaces []string
fromBackup bool
}{
{
name: "invalid wildcard pattern in includes",
includes: []string{"kube-**"},
excludes: []string{},
activeNamespaces: []string{"default"},
fromBackup: true,
},
{
name: "invalid wildcard pattern in excludes",
includes: []string{"default"},
excludes: []string{"test-**"},
activeNamespaces: []string{"default"},
fromBackup: true,
},
}
@@ -1333,7 +1360,7 @@ func TestResolveNamespaceListError(t *testing.T) {
Includes(tc.includes...).
Excludes(tc.excludes...)
_, err := nie.ResolveNamespaceList()
_, err := nie.ResolveNamespaceList(tc.fromBackup)
assert.Error(t, err)
})
}
@@ -1347,6 +1374,7 @@ func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing
activeNamespaces []string
testNamespace string
expectedResult bool
fromBackup bool
}{
{
name: "wildcard expanded to empty includes - should not include anything",
@@ -1355,6 +1383,7 @@ func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing
activeNamespaces: []string{"default", "kube-system"},
testNamespace: "default",
expectedResult: false,
fromBackup: true,
},
{
name: "wildcard expanded with matches - should include matched namespace",
@@ -1363,6 +1392,7 @@ func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing
activeNamespaces: []string{"default", "kube-system", "kube-public"},
testNamespace: "kube-system",
expectedResult: true,
fromBackup: true,
},
{
name: "wildcard expanded with matches - should not include unmatched namespace",
@@ -1371,6 +1401,7 @@ func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing
activeNamespaces: []string{"default", "kube-system", "kube-public"},
testNamespace: "default",
expectedResult: false,
fromBackup: true,
},
{
name: "no wildcard expansion - empty includes means include all",
@@ -1379,6 +1410,7 @@ func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing
activeNamespaces: []string{"default", "kube-system"},
testNamespace: "default",
expectedResult: true,
fromBackup: true,
},
}
@@ -1389,7 +1421,7 @@ func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing
Includes(tc.includes...).
Excludes(tc.excludes...)
err := nie.ExpandIncludesExcludes()
err := nie.ExpandIncludesExcludes(tc.fromBackup)
require.NoError(t, err)
result := nie.ShouldInclude(tc.testNamespace)
+1 -1
View File
@@ -156,7 +156,7 @@ func TestGetVolumesByPod(t *testing.T) {
Volumes: []corev1api.Volume{
// PVB Volumes
{Name: "pvbPV1"}, {Name: "pvbPV2"}, {Name: "pvbPV3"},
/// Excluded from PVB because volume mounting default service account token
/// Excluded from PVB because column mounting default service account token
{Name: "default-token-5xq45"},
},
},
+11 -6
View File
@@ -8,14 +8,19 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
)
func ShouldExpandWildcards(includes []string, excludes []string) bool {
func ShouldExpandWildcards(includes []string, excludes []string, fromBackup bool) bool {
// Only expand wildcards if this is being called from a backup request.
// We don't want to expand wildcard patterns in restore request,
// because restore needs the IncludeEverything function to
// determine whether to restore cluster-scoped resources.
// Expand wildcards causes the IncludeEverything function to return false,
// which will cause restore to skip cluster-scoped resources.
if !fromBackup {
return false
}
wildcardFound := false
for _, include := range includes {
// Special case: "*" alone means "match all" - don't expand
if include == "*" {
return false
}
if containsWildcardPattern(include) {
wildcardFound = true
}
+100 -65
View File
@@ -9,106 +9,141 @@ import (
func TestShouldExpandWildcards(t *testing.T) {
tests := []struct {
name string
includes []string
excludes []string
expected bool
name string
includes []string
excludes []string
fromBackup bool
expected bool
}{
{
name: "no wildcards",
includes: []string{"ns1", "ns2"},
excludes: []string{"ns3", "ns4"},
expected: false,
name: "no wildcards",
includes: []string{"ns1", "ns2"},
excludes: []string{"ns3", "ns4"},
fromBackup: true,
expected: false,
},
{
name: "includes has star - should not expand",
includes: []string{"*"},
excludes: []string{"ns1"},
expected: false,
name: "includes has star - should expand",
includes: []string{"*"},
excludes: []string{"ns1"},
fromBackup: true,
expected: true,
},
{
name: "includes has star after a wildcard pattern - should not expand",
includes: []string{"ns*", "*"},
excludes: []string{"ns1"},
expected: false,
excludes: []string{"ns3", "ns4"},
fromBackup: true,
expected: false,
},
{
name: "includes has wildcard pattern",
includes: []string{"ns*"},
excludes: []string{"ns1"},
expected: true,
name: "includes has star - should expand",
includes: []string{"*"},
excludes: []string{"ns1"},
fromBackup: true,
expected: true,
},
{
name: "excludes has wildcard pattern",
includes: []string{"ns1"},
excludes: []string{"ns*"},
expected: true,
name: "includes has star after a wildcard pattern - should expand",
includes: []string{"ns*", "*"},
excludes: []string{"ns1"},
fromBackup: true,
expected: true,
},
{
name: "both have wildcard patterns",
includes: []string{"app-*"},
excludes: []string{"test-*"},
expected: true,
name: "includes has wildcard pattern",
includes: []string{"ns*"},
excludes: []string{"ns1"},
fromBackup: true,
expected: true,
},
{
name: "includes has star and wildcard - star takes precedence",
includes: []string{"*", "ns*"},
excludes: []string{},
expected: false,
name: "excludes has wildcard pattern",
includes: []string{"ns1"},
excludes: []string{"ns*"},
fromBackup: true,
expected: true,
},
{
name: "double asterisk should be detected as wildcard",
includes: []string{"**"},
excludes: []string{},
expected: true, // ** is a wildcard pattern (but will error during validation)
name: "both have wildcard patterns",
includes: []string{"app-*"},
excludes: []string{"test-*"},
fromBackup: true,
expected: true,
},
{
name: "empty slices",
includes: []string{},
excludes: []string{},
expected: false,
name: "includes has star and wildcard - should expand",
includes: []string{"*", "ns*"},
excludes: []string{},
fromBackup: true,
expected: true,
},
{
name: "complex wildcard patterns",
includes: []string{"*-prod"},
excludes: []string{"test-*-staging"},
expected: true,
name: "double asterisk should be detected as wildcard",
includes: []string{"**"},
excludes: []string{},
fromBackup: true,
expected: true, // ** is a wildcard pattern (but will error during validation)
},
{
name: "question mark wildcard",
includes: []string{"ns?"},
excludes: []string{},
expected: true, // question mark is now considered a wildcard
name: "empty slices",
includes: []string{},
excludes: []string{},
fromBackup: true,
expected: false,
},
{
name: "character class wildcard",
includes: []string{"ns[abc]"},
excludes: []string{},
expected: true, // character class is considered wildcard
name: "complex wildcard patterns",
includes: []string{"*-prod"},
excludes: []string{"test-*-staging"},
fromBackup: true,
expected: true,
},
{
name: "brace alternatives wildcard",
includes: []string{"ns{prod,staging}"},
excludes: []string{},
expected: false, // brace alternatives are not supported
name: "question mark wildcard",
includes: []string{"ns?"},
excludes: []string{},
fromBackup: true,
expected: true, // question mark is now considered a wildcard
},
{
name: "dot is literal - not wildcard",
includes: []string{"app.prod"},
excludes: []string{},
expected: false, // dot is literal, not wildcard
name: "character class wildcard",
includes: []string{"ns[abc]"},
excludes: []string{},
fromBackup: true,
expected: true, // character class is considered wildcard
},
{
name: "plus is literal - not wildcard",
includes: []string{"app+"},
excludes: []string{},
expected: false, // plus is literal, not wildcard
name: "brace alternatives wildcard",
includes: []string{"ns{prod,staging}"},
excludes: []string{},
fromBackup: true,
expected: false, // brace alternatives are not supported
},
{
name: "dot is literal - not wildcard",
includes: []string{"app.prod"},
excludes: []string{},
fromBackup: true,
expected: false, // dot is literal, not wildcard
},
{
name: "plus is literal - not wildcard",
includes: []string{"app+"},
excludes: []string{},
fromBackup: true,
expected: false, // plus is literal, not wildcard
},
{
name: "includes has a wildcard pattern from restore - should not expand",
includes: []string{"ns*"},
excludes: []string{"ns1"},
fromBackup: false,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ShouldExpandWildcards(tt.includes, tt.excludes)
result := ShouldExpandWildcards(tt.includes, tt.excludes, tt.fromBackup)
assert.Equal(t, tt.expected, result)
})
}