Merge pull request #9587 from blackpiglet/xj014661/1.18/9180_fix
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m3s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Failing after 11s
Main CI / Build (push) Has been skipped

Remove wildcard check from getNamespacesToList.
This commit is contained in:
Xun Jiang/Bruce Jiang
2026-04-08 09:33:59 +08:00
committed by GitHub
13 changed files with 210 additions and 139 deletions

View File

@@ -0,0 +1 @@
Remove wildcard check from getNamespacesToList.

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

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",
},

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)
}
}

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")

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,

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{}
}

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
}

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*"},

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
}

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)

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
}

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)
})
}