mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-28 11:56:42 +00:00
* Support all glob wildcard characters in namespace validation
Expand namespace validation to allow all valid glob pattern characters
(*, ?, {}, [], ,) by replacing them with valid characters during RFC 1123
validation. The actual glob pattern validation is handled separately by
the wildcard package.
Also add validation to reject unsupported characters (|, (), !) that are
not valid in glob patterns, and update terminology from "regex" to "glob"
for clarity since this implementation uses glob patterns, not regex.
Changes:
- Replace all glob wildcard characters in validateNamespaceName
- Add test coverage for valid glob patterns in includes/excludes
- Add test coverage for unsupported characters
- Reject exclamation mark (!) in wildcard patterns
- Clarify comments and error messages about glob vs regex
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Changelog
Signed-off-by: Joseph <jvaikath@redhat.com>
* Add documentation: glob patterns are now accepted
Signed-off-by: Joseph <jvaikath@redhat.com>
* Error message fix
Signed-off-by: Joseph <jvaikath@redhat.com>
* Remove negation glob char test
Signed-off-by: Joseph <jvaikath@redhat.com>
* Add bracket pattern validation for namespace glob patterns
Extends wildcard validation to support square bracket patterns [] used in glob character classes. Validates bracket syntax including empty brackets, unclosed brackets, and unmatched brackets. Extracts ValidateNamespaceName as a public function to enable reuse in namespace validation logic.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Reduce scope to *, ?, [ and ]
Signed-off-by: Joseph <jvaikath@redhat.com>
* Fix tests
Signed-off-by: Joseph <jvaikath@redhat.com>
* Add namespace glob patterns documentation page
Adds dedicated documentation explaining supported glob patterns
for namespace include/exclude filtering to help users understand
the wildcard syntax.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Fix build-image Dockerfile envtest download
Replace inaccessible go.kubebuilder.io URL with setup-envtest and update envtest version to 1.33.0 to match Kubernetes v0.33.3 dependencies.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* kubebuilder binaries mv
Signed-off-by: Joseph <jvaikath@redhat.com>
* Reject brace patterns and update documentation
Add {, }, and , to unsupported characters list to explicitly reject
brace expansion patterns. Remove { from wildcard detection since these
patterns are not supported in the 1.18 release.
Update all documentation to show supported patterns inline (*, ?, [abc])
with clickable links to the detailed namespace-glob-patterns page.
Simplify YAML comments by removing non-clickable URLs.
Update tests to expect errors when brace patterns are used.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Document brace expansion as unsupported
Add {} and , to the unsupported patterns section to clarify that
brace expansion patterns like {a,b,c} are not supported.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
* Update tests to expect brace pattern rejection
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Joseph <jvaikath@redhat.com>
---------
Signed-off-by: Joseph <jvaikath@redhat.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
183 lines
5.0 KiB
Go
183 lines
5.0 KiB
Go
package wildcard
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/gobwas/glob"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
)
|
|
|
|
func ShouldExpandWildcards(includes []string, excludes []string) bool {
|
|
wildcardFound := false
|
|
for _, include := range includes {
|
|
// Special case: "*" alone means "match all" - don't expand
|
|
if include == "*" {
|
|
return false
|
|
}
|
|
|
|
if containsWildcardPattern(include) {
|
|
wildcardFound = true
|
|
}
|
|
}
|
|
|
|
for _, exclude := range excludes {
|
|
if containsWildcardPattern(exclude) {
|
|
wildcardFound = true
|
|
}
|
|
}
|
|
|
|
return wildcardFound
|
|
}
|
|
|
|
// containsWildcardPattern checks if a pattern contains any wildcard symbols
|
|
// Supported patterns: *, ?, [abc]
|
|
// Note: . and + are treated as literal characters (not wildcards)
|
|
// Note: ** and consecutive asterisks are NOT supported (will cause validation error)
|
|
func containsWildcardPattern(pattern string) bool {
|
|
return strings.ContainsAny(pattern, "*?[")
|
|
}
|
|
|
|
func validateWildcardPatterns(patterns []string) error {
|
|
for _, pattern := range patterns {
|
|
if err := ValidateNamespaceName(pattern); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateNamespaceName(pattern string) error {
|
|
// Check for invalid characters that are not supported in glob patterns
|
|
if strings.ContainsAny(pattern, "|()!{},") {
|
|
return errors.New("wildcard pattern contains unsupported characters: |, (, ), !, {, }, ,")
|
|
}
|
|
|
|
// Check for consecutive asterisks (2 or more)
|
|
if strings.Contains(pattern, "**") {
|
|
return errors.New("wildcard pattern contains consecutive asterisks (only single * allowed)")
|
|
}
|
|
|
|
// Check for malformed brace patterns
|
|
if err := validateBracePatterns(pattern); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateBracePatterns checks for malformed brace patterns like unclosed braces or empty braces
|
|
// Also validates bracket patterns [] for character classes
|
|
func validateBracePatterns(pattern string) error {
|
|
bracketDepth := 0
|
|
|
|
for i := 0; i < len(pattern); i++ {
|
|
if pattern[i] == '[' {
|
|
bracketStart := i
|
|
bracketDepth++
|
|
|
|
// Scan ahead to find the matching closing bracket and validate content
|
|
for j := i + 1; j < len(pattern) && bracketDepth > 0; j++ {
|
|
if pattern[j] == ']' {
|
|
bracketDepth--
|
|
if bracketDepth == 0 {
|
|
// Found matching closing bracket - validate content
|
|
content := pattern[bracketStart+1 : j]
|
|
if content == "" {
|
|
return errors.New("wildcard pattern contains empty bracket pattern '[]'")
|
|
}
|
|
// Skip to the closing bracket
|
|
i = j
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we exited the loop without finding a match (bracketDepth > 0), bracket is unclosed
|
|
if bracketDepth > 0 {
|
|
return errors.New("wildcard pattern contains unclosed bracket '['")
|
|
}
|
|
|
|
// i is now positioned at the closing bracket; the outer loop will increment it
|
|
} else if pattern[i] == ']' {
|
|
// Found a closing bracket without a matching opening bracket
|
|
return errors.New("wildcard pattern contains unmatched closing bracket ']'")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ExpandWildcards(activeNamespaces []string, includes []string, excludes []string) ([]string, []string, error) {
|
|
expandedIncludes, err := expandWildcards(includes, activeNamespaces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
expandedExcludes, err := expandWildcards(excludes, activeNamespaces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return expandedIncludes, expandedExcludes, nil
|
|
}
|
|
|
|
// expands wildcard patterns into a list of namespaces, while normally passing non-wildcard patterns
|
|
func expandWildcards(patterns []string, activeNamespaces []string) ([]string, error) {
|
|
if len(patterns) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Validate patterns before processing
|
|
if err := validateWildcardPatterns(patterns); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
matchedSet := make(map[string]struct{})
|
|
|
|
for _, pattern := range patterns {
|
|
// If the pattern is a non-wildcard pattern, we can just add it to the result
|
|
if !containsWildcardPattern(pattern) {
|
|
matchedSet[pattern] = struct{}{}
|
|
continue
|
|
}
|
|
|
|
// Compile glob pattern
|
|
g, err := glob.Compile(pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Match against all namespaces
|
|
for _, ns := range activeNamespaces {
|
|
if g.Match(ns) {
|
|
matchedSet[ns] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert set to slice
|
|
result := make([]string, 0, len(matchedSet))
|
|
for ns := range matchedSet {
|
|
result = append(result, ns)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetWildcardResult returns the final list of namespaces after applying wildcard include/exclude logic
|
|
func GetWildcardResult(expandedIncludes []string, expandedExcludes []string) []string {
|
|
// Set check: set of expandedIncludes - set of expandedExcludes
|
|
expandedIncludesSet := sets.New(expandedIncludes...)
|
|
expandedExcludesSet := sets.New(expandedExcludes...)
|
|
selectedNamespacesSet := expandedIncludesSet.Difference(expandedExcludesSet)
|
|
|
|
// Convert the set to a slice
|
|
selectedNamespaces := make([]string, 0, selectedNamespacesSet.Len())
|
|
for ns := range selectedNamespacesSet {
|
|
selectedNamespaces = append(selectedNamespaces, ns)
|
|
}
|
|
|
|
return selectedNamespaces
|
|
}
|