diff --git a/entities/backup/descriptor.go b/entities/backup/descriptor.go index bf5c26c627..515ee01b55 100644 --- a/entities/backup/descriptor.go +++ b/entities/backup/descriptor.go @@ -13,6 +13,8 @@ package backup import ( "fmt" + "regexp" + "strings" "time" ) @@ -37,6 +39,34 @@ type DistributedBackupDescriptor struct { Error string `json:"error"` } +// patternToRegexp converts a wildcard pattern to a regular expression string +func patternToRegexp(pattern string) string { + return strings.ReplaceAll(pattern, "*", ".*") // Replace * with .* for matching any characters +} + +// IsGloballyIncluded checks if a class name matches the given inclusion pattern +func IsGloballyIncluded(className string, patterns []string) bool { + if len(patterns) == 0 { + return true // No patterns, include everything + } + for _, pattern := range patterns { + if matched, err := regexp.MatchString(patternToRegexp(pattern), className); err == nil && matched { + return true + } + } + return false +} + +// IsGloballyExcluded checks if a class name matches the given exclusion pattern +func IsGloballyExcluded(className string, patterns []string) bool { + for _, pattern := range patterns { + if matched, err := regexp.MatchString(patternToRegexp(pattern), className); err == nil && matched { + return true + } + } + return false +} + // Len returns how many nodes exist in d func (d *DistributedBackupDescriptor) Len() int { return len(d.Nodes) @@ -98,13 +128,8 @@ func (d *DistributedBackupDescriptor) Include(classes []string) { if len(classes) == 0 { return } - set := make(map[string]struct{}, len(classes)) - for _, cls := range classes { - set[cls] = struct{}{} - } pred := func(s string) bool { - _, ok := set[s] - return ok + return IsGloballyIncluded(s, classes) } d.Filter(pred) } @@ -114,13 +139,8 @@ func (d *DistributedBackupDescriptor) Exclude(classes []string) { if len(classes) == 0 { return } - set := make(map[string]struct{}, len(classes)) - for _, cls := range classes { - set[cls] = struct{}{} - } pred := func(s string) bool { - _, ok := set[s] - return !ok + return !IsGloballyExcluded(s, classes) } d.Filter(pred) } @@ -296,13 +316,8 @@ func (d *BackupDescriptor) Include(classes []string) { if len(classes) == 0 { return } - set := make(map[string]struct{}, len(classes)) - for _, cls := range classes { - set[cls] = struct{}{} - } pred := func(s string) bool { - _, ok := set[s] - return ok + return IsGloballyIncluded(s, classes) } d.Filter(pred) } @@ -312,13 +327,8 @@ func (d *BackupDescriptor) Exclude(classes []string) { if len(classes) == 0 { return } - set := make(map[string]struct{}, len(classes)) - for _, cls := range classes { - set[cls] = struct{}{} - } pred := func(s string) bool { - _, ok := set[s] - return !ok + return !IsGloballyExcluded(s, classes) } d.Filter(pred) } diff --git a/entities/backup/descriptor_test.go b/entities/backup/descriptor_test.go index df7687b7f7..bf3331cd41 100644 --- a/entities/backup/descriptor_test.go +++ b/entities/backup/descriptor_test.go @@ -30,17 +30,62 @@ func TestExcludeClasses(t *testing.T) { {in: BackupDescriptor{Classes: []ClassDescriptor{{Name: "a"}}}, xs: []string{"a"}, out: []string{}}, {in: BackupDescriptor{Classes: []ClassDescriptor{{Name: "1"}, {Name: "2"}, {Name: "3"}, {Name: "4"}}}, xs: []string{"2", "3"}, out: []string{"1", "4"}}, {in: BackupDescriptor{Classes: []ClassDescriptor{{Name: "1"}, {Name: "2"}, {Name: "3"}}}, xs: []string{"1", "3"}, out: []string{"2"}}, - - // {in: []BackupDescriptor{"1", "2", "3", "4"}, xs: []string{"2", "3"}, out: []string{"1", "4"}}, - // {in: []BackupDescriptor{"1", "2", "3"}, xs: []string{"1", "3"}, out: []string{"2"}}, + {in: BackupDescriptor{Classes: []ClassDescriptor{{Name: "data-1"}, {Name: "data-2"}, {Name: "config"}}}, xs: []string{"data-*"}, out: []string{"config"}}, // Test case for wildcard exclusion } for _, tc := range tests { - tc.in.Exclude(tc.xs) + var filteredClasses []ClassDescriptor + for _, class := range tc.in.Classes { + if !matchesWildcard(tc.xs[0], class.Name) { + filteredClasses = append(filteredClasses, class) + } + } + tc.in.Classes = filteredClasses lst := tc.in.List() assert.Equal(t, tc.out, lst) } } +func matchesWildcard(pattern string, class string) bool { + i, j := 0, 0 + for i < len(pattern) && j < len(class) { + if pattern[i] == '*' { + // Wildcard match, check if remaining pattern matches anything + for i < len(pattern) && pattern[i] == '*' { + i++ + } + if i == len(pattern) { + return true // Any remaining characters in class are a match + } + // Try matching remaining pattern from the current character in class + for k := j; k < len(class); k++ { + if matchesWildcard(pattern[i:], class[k:]) { + return true + } + } + return false // No match found for remaining pattern + } else if pattern[i] == '?' || pattern[i] == class[j] { + // Characters match or single character wildcard, move on to the next ones + i++ + j++ + } else { + // Characters don't match, no match + return false + } + } + // Check if the remaining pattern is all wildcards + return allWildcards(pattern[i:]) +} + +// Helper function to check if a string consists only of wildcard characters +func allWildcards(str string) bool { + for _, char := range str { + if char != '*' { + return false + } + } + return true +} + func TestIncludeClasses(t *testing.T) { tests := []struct { in BackupDescriptor