Support all glob wildcard characters in namespace validation (#9502)
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m19s
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
build-image / Build (push) Failing after 16s
Main CI / get-go-version (push) Successful in 13s
Main CI / Build (push) Failing after 36s
Close stale issues and PRs / stale (push) Successful in 16s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m56s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m33s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m30s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m40s

* 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>
This commit is contained in:
Joseph Antony Vaikath
2026-02-11 12:43:55 -05:00
committed by GitHub
parent 7af688fbf5
commit 1315399f35
16 changed files with 349 additions and 306 deletions

View File

@@ -0,0 +1 @@
Support all glob wildcard characters in namespace validation

View File

@@ -21,9 +21,11 @@ ENV GO111MODULE=on
ENV GOPROXY=${GOPROXY}
# kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test.
RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/$(go env GOARCH) && \
mkdir /usr/local/kubebuilder && \
tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
# Using setup-envtest to download envtest binaries
RUN go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest && \
mkdir -p /usr/local/kubebuilder/bin && \
ENVTEST_ASSETS_DIR=$(setup-envtest use 1.33.0 --bin-dir /usr/local/kubebuilder/bin -p path) && \
cp -r ${ENVTEST_ASSETS_DIR}/* /usr/local/kubebuilder/bin/
RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_$(go env GOARCH) && \
mv kubebuilder_linux_$(go env GOARCH) /usr/local/kubebuilder/bin/kubebuilder && \

View File

@@ -666,10 +666,22 @@ func validateNamespaceName(ns string) []error {
return nil
}
// Kubernetes does not allow asterisks in namespaces but Velero uses them as
// wildcards. Replace asterisks with an arbitrary letter to pass Kubernetes
// validation.
tmpNamespace := strings.ReplaceAll(ns, "*", "x")
// Validate the namespace name to ensure it is a valid wildcard pattern
if err := wildcard.ValidateNamespaceName(ns); err != nil {
return []error{err}
}
// Kubernetes does not allow wildcard characters in namespaces but Velero uses them
// for glob patterns. Replace wildcard characters with valid characters to pass
// Kubernetes validation.
tmpNamespace := ns
// Replace glob wildcard characters with valid alphanumeric characters
// Note: Validation of wildcard patterns is handled by the wildcard package.
tmpNamespace = strings.ReplaceAll(tmpNamespace, "*", "x") // matches any sequence
tmpNamespace = strings.ReplaceAll(tmpNamespace, "?", "x") // matches single character
tmpNamespace = strings.ReplaceAll(tmpNamespace, "[", "x") // character class start
tmpNamespace = strings.ReplaceAll(tmpNamespace, "]", "x") // character class end
if errMsgs := validation.ValidateNamespaceName(tmpNamespace, false); errMsgs != nil {
for _, msg := range errMsgs {

View File

@@ -289,6 +289,54 @@ func TestValidateNamespaceIncludesExcludes(t *testing.T) {
excludes: []string{"bar"},
wantErr: true,
},
{
name: "glob characters in includes should not error",
includes: []string{"kube-*", "test-?", "ns-[0-9]"},
excludes: []string{},
wantErr: false,
},
{
name: "glob characters in excludes should not error",
includes: []string{"default"},
excludes: []string{"test-*", "app-?", "ns-[1-5]"},
wantErr: false,
},
{
name: "character class in includes should not error",
includes: []string{"ns-[abc]", "test-[0-9]"},
excludes: []string{},
wantErr: false,
},
{
name: "mixed glob patterns should not error",
includes: []string{"kube-*", "test-?"},
excludes: []string{"*-test", "debug-[0-9]"},
wantErr: false,
},
{
name: "pipe character in includes should error",
includes: []string{"namespace|other"},
excludes: []string{},
wantErr: true,
},
{
name: "parentheses in includes should error",
includes: []string{"namespace(prod)", "test-(dev)"},
excludes: []string{},
wantErr: true,
},
{
name: "exclamation mark in includes should error",
includes: []string{"!namespace", "test!"},
excludes: []string{},
wantErr: true,
},
{
name: "unsupported characters in excludes should error",
includes: []string{"default"},
excludes: []string{"test|prod", "app(staging)"},
wantErr: true,
},
}
for _, tc := range tests {
@@ -1082,16 +1130,6 @@ func TestExpandIncludesExcludes(t *testing.T) {
expectedWildcardExpanded: true,
expectError: false,
},
{
name: "brace wildcard pattern",
includes: []string{"app-{prod,dev}"},
excludes: []string{},
activeNamespaces: []string{"app-prod", "app-dev", "app-test", "default"},
expectedIncludes: []string{"app-prod", "app-dev"},
expectedExcludes: []string{},
expectedWildcardExpanded: true,
expectError: false,
},
{
name: "empty activeNamespaces with wildcards",
includes: []string{"kube-*"},
@@ -1233,13 +1271,6 @@ func TestResolveNamespaceList(t *testing.T) {
expectedNamespaces: []string{"kube-system", "kube-public"},
preExpandWildcards: true,
},
{
name: "complex wildcard pattern",
includes: []string{"app-{prod,dev}", "kube-*"},
excludes: []string{"*-test"},
activeNamespaces: []string{"app-prod", "app-dev", "app-test", "kube-system", "kube-test", "default"},
expectedNamespaces: []string{"app-prod", "app-dev", "kube-system"},
},
{
name: "question mark wildcard pattern",
includes: []string{"ns-?"},

View File

@@ -31,70 +31,77 @@ func ShouldExpandWildcards(includes []string, excludes []string) bool {
}
// containsWildcardPattern checks if a pattern contains any wildcard symbols
// Supported patterns: *, ?, [abc], {a,b,c}
// 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, "*?[{")
return strings.ContainsAny(pattern, "*?[")
}
func validateWildcardPatterns(patterns []string) error {
for _, pattern := range patterns {
// Check for invalid regex-only patterns that we don't support
if strings.ContainsAny(pattern, "|()") {
return errors.New("wildcard pattern contains unsupported regex symbols: |, (, )")
}
// 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 {
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 {
depth := 0
bracketDepth := 0
for i := 0; i < len(pattern); i++ {
if pattern[i] == '{' {
braceStart := i
depth++
if pattern[i] == '[' {
bracketStart := i
bracketDepth++
// Scan ahead to find the matching closing brace and validate content
for j := i + 1; j < len(pattern) && depth > 0; j++ {
if pattern[j] == '{' {
depth++
} else if pattern[j] == '}' {
depth--
if depth == 0 {
// Found matching closing brace - validate content
content := pattern[braceStart+1 : j]
if strings.Trim(content, ", \t") == "" {
return errors.New("wildcard pattern contains empty brace pattern '{}'")
// 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 brace
// Skip to the closing bracket
i = j
break
}
}
}
// If we exited the loop without finding a match (depth > 0), brace is unclosed
if depth > 0 {
return errors.New("wildcard pattern contains unclosed brace '{'")
// 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 brace; the outer loop will increment it
} else if pattern[i] == '}' {
// Found a closing brace without a matching opening brace
return errors.New("wildcard pattern contains unmatched closing brace '}'")
// 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 ']'")
}
}

View File

@@ -90,7 +90,7 @@ func TestShouldExpandWildcards(t *testing.T) {
name: "brace alternatives wildcard",
includes: []string{"ns{prod,staging}"},
excludes: []string{},
expected: true, // brace alternatives are considered wildcard
expected: false, // brace alternatives are not supported
},
{
name: "dot is literal - not wildcard",
@@ -237,9 +237,9 @@ func TestExpandWildcards(t *testing.T) {
activeNamespaces: []string{"app-prod", "app-staging", "app-dev", "db-prod"},
includes: []string{"app-{prod,staging}"},
excludes: []string{},
expectedIncludes: []string{"app-prod", "app-staging"}, // {prod,staging} matches either
expectedIncludes: nil,
expectedExcludes: nil,
expectError: false,
expectError: true,
},
{
name: "literal dot and plus patterns",
@@ -259,33 +259,6 @@ func TestExpandWildcards(t *testing.T) {
expectedExcludes: nil,
expectError: true, // |, (, ) are not supported
},
{
name: "unclosed brace patterns should error",
activeNamespaces: []string{"app-prod"},
includes: []string{"app-{prod,staging"},
excludes: []string{},
expectedIncludes: nil,
expectedExcludes: nil,
expectError: true, // unclosed brace
},
{
name: "empty brace patterns should error",
activeNamespaces: []string{"app-prod"},
includes: []string{"app-{}"},
excludes: []string{},
expectedIncludes: nil,
expectedExcludes: nil,
expectError: true, // empty braces
},
{
name: "unmatched closing brace should error",
activeNamespaces: []string{"app-prod"},
includes: []string{"app-prod}"},
excludes: []string{},
expectedIncludes: nil,
expectedExcludes: nil,
expectError: true, // unmatched closing brace
},
}
for _, tt := range tests {
@@ -354,13 +327,6 @@ func TestExpandWildcardsPrivate(t *testing.T) {
expected: []string{}, // returns empty slice, not nil
expectError: false,
},
{
name: "brace patterns work correctly",
patterns: []string{"app-{prod,staging}"},
activeNamespaces: []string{"app-prod", "app-staging", "app-dev", "app-{prod,staging}"},
expected: []string{"app-prod", "app-staging"}, // brace patterns do expand
expectError: false,
},
{
name: "duplicate matches from multiple patterns",
patterns: []string{"app-*", "*-prod"},
@@ -389,20 +355,6 @@ func TestExpandWildcardsPrivate(t *testing.T) {
expected: []string{"nsa", "nsb", "nsc"}, // [a-c] matches a to c
expectError: false,
},
{
name: "negated character class",
patterns: []string{"ns[!abc]"},
activeNamespaces: []string{"nsa", "nsb", "nsc", "nsd", "ns1"},
expected: []string{"nsd", "ns1"}, // [!abc] matches anything except a, b, c
expectError: false,
},
{
name: "brace alternatives",
patterns: []string{"app-{prod,test}"},
activeNamespaces: []string{"app-prod", "app-test", "app-staging", "db-prod"},
expected: []string{"app-prod", "app-test"}, // {prod,test} matches either
expectError: false,
},
{
name: "double asterisk should error",
patterns: []string{"**"},
@@ -410,13 +362,6 @@ func TestExpandWildcardsPrivate(t *testing.T) {
expected: nil,
expectError: true, // ** is not allowed
},
{
name: "literal dot and plus",
patterns: []string{"app.prod", "service+"},
activeNamespaces: []string{"app.prod", "appXprod", "service+", "service"},
expected: []string{"app.prod", "service+"}, // . and + are literal
expectError: false,
},
{
name: "unsupported regex symbols should error",
patterns: []string{"ns(1|2)"},
@@ -468,153 +413,101 @@ func TestValidateBracePatterns(t *testing.T) {
expectError bool
errorMsg string
}{
// Valid patterns
// Valid square bracket patterns
{
name: "valid single brace pattern",
pattern: "app-{prod,staging}",
name: "valid square bracket pattern",
pattern: "ns[abc]",
expectError: false,
},
{
name: "valid brace with single option",
pattern: "app-{prod}",
name: "valid square bracket pattern with range",
pattern: "ns[a-z]",
expectError: false,
},
{
name: "valid brace with three options",
pattern: "app-{prod,staging,dev}",
name: "valid square bracket pattern with numbers",
pattern: "ns[0-9]",
expectError: false,
},
{
name: "valid pattern with text before and after brace",
pattern: "prefix-{a,b}-suffix",
name: "valid square bracket pattern with mixed",
pattern: "ns[a-z0-9]",
expectError: false,
},
{
name: "valid pattern with no braces",
pattern: "app-prod",
name: "valid square bracket pattern with single character",
pattern: "ns[a]",
expectError: false,
},
{
name: "valid pattern with asterisk",
pattern: "app-*",
name: "valid square bracket pattern with text before and after",
pattern: "prefix-[abc]-suffix",
expectError: false,
},
// Unclosed opening brackets
{
name: "valid brace with spaces around content",
pattern: "app-{ prod , staging }",
expectError: false,
name: "unclosed opening bracket at end",
pattern: "ns[abc",
expectError: true,
errorMsg: "unclosed bracket",
},
{
name: "valid brace with numbers",
pattern: "ns-{1,2,3}",
expectError: false,
name: "unclosed opening bracket at start",
pattern: "[abc",
expectError: true,
errorMsg: "unclosed bracket",
},
{
name: "valid brace with hyphens in options",
pattern: "{app-prod,db-staging}",
expectError: false,
name: "unclosed opening bracket in middle",
pattern: "ns[abc-test",
expectError: true,
errorMsg: "unclosed bracket",
},
// Unclosed opening braces
// Unmatched closing brackets
{
name: "unclosed opening brace at end",
pattern: "app-{prod,staging",
name: "unmatched closing bracket at end",
pattern: "ns-abc]",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
{
name: "unclosed opening brace at start",
pattern: "{prod,staging",
name: "unmatched closing bracket at start",
pattern: "]ns-abc",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
{
name: "unclosed opening brace in middle",
pattern: "app-{prod-test",
name: "unmatched closing bracket in middle",
pattern: "ns-]abc",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
{
name: "multiple unclosed braces",
pattern: "app-{prod-{staging",
name: "extra closing bracket after valid pair",
pattern: "ns[abc]]",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
// Unmatched closing braces
// Empty bracket patterns
{
name: "unmatched closing brace at end",
pattern: "app-prod}",
name: "completely empty brackets",
pattern: "ns[]",
expectError: true,
errorMsg: "unmatched closing brace",
errorMsg: "empty bracket pattern",
},
{
name: "unmatched closing brace at start",
pattern: "}app-prod",
name: "empty brackets at start",
pattern: "[]ns",
expectError: true,
errorMsg: "unmatched closing brace",
errorMsg: "empty bracket pattern",
},
{
name: "unmatched closing brace in middle",
pattern: "app-}prod",
name: "empty brackets standalone",
pattern: "[]",
expectError: true,
errorMsg: "unmatched closing brace",
},
{
name: "extra closing brace after valid pair",
pattern: "app-{prod,staging}}",
expectError: true,
errorMsg: "unmatched closing brace",
},
// Empty brace patterns
{
name: "completely empty braces",
pattern: "app-{}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with only spaces",
pattern: "app-{ }",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with only comma",
pattern: "app-{,}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with only commas",
pattern: "app-{,,,}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with commas and spaces",
pattern: "app-{ , , }",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with tabs and commas",
pattern: "app-{\t,\t}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "empty braces at start",
pattern: "{}app-prod",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "empty braces standalone",
pattern: "{}",
expectError: true,
errorMsg: "empty brace pattern",
errorMsg: "empty bracket pattern",
},
// Edge cases
@@ -623,58 +516,6 @@ func TestValidateBracePatterns(t *testing.T) {
pattern: "",
expectError: false,
},
{
name: "pattern with only opening brace",
pattern: "{",
expectError: true,
errorMsg: "unclosed brace",
},
{
name: "pattern with only closing brace",
pattern: "}",
expectError: true,
errorMsg: "unmatched closing brace",
},
{
name: "valid brace with special characters inside",
pattern: "app-{prod-1,staging_2,dev.3}",
expectError: false,
},
{
name: "brace with asterisk inside option",
pattern: "app-{prod*,staging}",
expectError: false,
},
{
name: "multiple valid brace patterns",
pattern: "{app,db}-{prod,staging}",
expectError: false,
},
{
name: "brace with single character",
pattern: "app-{a}",
expectError: false,
},
{
name: "brace with trailing comma but has content",
pattern: "app-{prod,staging,}",
expectError: false, // Has content, so it's valid
},
{
name: "brace with leading comma but has content",
pattern: "app-{,prod,staging}",
expectError: false, // Has content, so it's valid
},
{
name: "brace with leading comma but has content",
pattern: "app-{{,prod,staging}",
expectError: true, // unclosed brace
},
{
name: "brace with leading comma but has content",
pattern: "app-{,prod,staging}}",
expectError: true, // unmatched closing brace
},
}
for _, tt := range tests {
@@ -723,20 +564,6 @@ func TestExpandWildcardsEdgeCases(t *testing.T) {
assert.ElementsMatch(t, []string{"ns-1", "ns_2", "ns.3", "ns@4"}, result)
})
t.Run("complex glob combinations", func(t *testing.T) {
activeNamespaces := []string{"app1-prod", "app2-prod", "app1-test", "db-prod", "service"}
result, err := expandWildcards([]string{"app?-{prod,test}"}, activeNamespaces)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"app1-prod", "app2-prod", "app1-test"}, result)
})
t.Run("escaped characters", func(t *testing.T) {
activeNamespaces := []string{"app*", "app-prod", "app?test", "app-test"}
result, err := expandWildcards([]string{"app\\*"}, activeNamespaces)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"app*"}, result)
})
t.Run("mixed literal and wildcard patterns", func(t *testing.T) {
activeNamespaces := []string{"app.prod", "app-prod", "app_prod", "test.ns"}
result, err := expandWildcards([]string{"app.prod", "app?prod"}, activeNamespaces)
@@ -777,12 +604,8 @@ func TestExpandWildcardsEdgeCases(t *testing.T) {
shouldError bool
}{
{"unclosed bracket", "ns[abc", true},
{"unclosed brace", "app-{prod,staging", true},
{"nested unclosed", "ns[a{bc", true},
{"valid bracket", "ns[abc]", false},
{"valid brace", "app-{prod,staging}", false},
{"empty bracket", "ns[]", true}, // empty brackets are invalid
{"empty brace", "app-{}", true}, // empty braces are invalid
}
for _, tt := range tests {

View File

@@ -16,6 +16,8 @@ Backup belongs to the API group version `velero.io/v1`.
Here is a sample `Backup` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -42,11 +44,12 @@ spec:
resourcePolicy:
kind: configmap
name: resource-policy-configmap
# Array of namespaces to include in the backup. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the backup. Accepts glob patterns (*, ?, [abc]).
# Note: '*' alone is reserved for empty fields, which means all namespaces.
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the backup. Optional.
# Array of namespaces to exclude from the backup. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the backup. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -16,6 +16,8 @@ Restore belongs to the API group version `velero.io/v1`.
Here is a sample `Restore` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -45,11 +47,11 @@ spec:
writeSparseFiles: true
# ParallelFilesDownload is the concurrency number setting for restore
parallelFilesDownload: 10
# Array of namespaces to include in the restore. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the restore. Accepts glob patterns (*, ?, [abc]).
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the restore. Optional.
# Array of namespaces to exclude from the restore. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the restore. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -0,0 +1,71 @@
---
title: "Namespace Glob Patterns"
layout: docs
---
When using `--include-namespaces` and `--exclude-namespaces` flags with backup and restore commands, you can use glob patterns to match multiple namespaces.
## Supported Patterns
Velero supports the following glob pattern characters:
- `*` - Matches any sequence of characters
```bash
velero backup create my-backup --include-namespaces "app-*"
# Matches: app-prod, app-staging, app-dev, etc.
```
- `?` - Matches exactly one character
```bash
velero backup create my-backup --include-namespaces "ns?"
# Matches: ns1, ns2, nsa, but NOT ns10
```
- `[abc]` - Matches any single character in the brackets
```bash
velero backup create my-backup --include-namespaces "ns[123]"
# Matches: ns1, ns2, ns3
```
- `[a-z]` - Matches any single character in the range
```bash
velero backup create my-backup --include-namespaces "ns[a-c]"
# Matches: nsa, nsb, nsc
```
## Unsupported Patterns
The following patterns are **not supported** and will cause validation errors:
- `**` - Consecutive asterisks
- `|` - Alternation (regex operator)
- `()` - Grouping (regex operators)
- `!` - Negation
- `{}` - Brace expansion
- `,` - Comma (used in brace expansion)
## Special Cases
- `*` alone means "all namespaces" and is not expanded
- Empty brackets `[]` are invalid
- Unmatched or unclosed brackets will cause validation errors
## Examples
Combine patterns with include and exclude flags:
```bash
# Backup all production namespaces except test
velero backup create prod-backup \
--include-namespaces "*-prod" \
--exclude-namespaces "test-*"
# Backup specific numbered namespaces
velero backup create numbered-backup \
--include-namespaces "app-[0-9]"
# Restore namespaces matching multiple patterns
velero restore create my-restore \
--from-backup my-backup \
--include-namespaces "frontend-*,backend-*"
```

View File

@@ -17,7 +17,11 @@ Wildcard takes precedence when both a wildcard and specific resource are include
### --include-namespaces
Namespaces to include. Default is `*`, all namespaces.
Namespaces to include. Accepts glob patterns (`*`, `?`, `[abc]`). Default is `*`, all namespaces.
See [Namespace Glob Patterns](namespace-glob-patterns) for more details on supported patterns.
Note: `*` alone is reserved for empty fields, which means all namespaces.
* Backup a namespace and it's objects.
@@ -158,7 +162,9 @@ Wildcard excludes are ignored.
### --exclude-namespaces
Namespaces to exclude.
Namespaces to exclude. Accepts glob patterns (`*`, `?`, `[abc]`).
See [Namespace Glob Patterns](namespace-glob-patterns.md) for more details on supported patterns.
* Exclude kube-system from the cluster backup.

View File

@@ -16,6 +16,8 @@ Backup belongs to the API group version `velero.io/v1`.
Here is a sample `Backup` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -42,11 +44,11 @@ spec:
resourcePolicy:
kind: configmap
name: resource-policy-configmap
# Array of namespaces to include in the backup. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the backup. Accepts glob patterns (*, ?, [abc]).
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the backup. Optional.
# Array of namespaces to exclude from the backup. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the backup. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -16,6 +16,8 @@ Restore belongs to the API group version `velero.io/v1`.
Here is a sample `Restore` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -45,11 +47,11 @@ spec:
writeSparseFiles: true
# ParallelFilesDownload is the concurrency number setting for restore
parallelFilesDownload: 10
# Array of namespaces to include in the restore. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the restore. Accepts glob patterns (*, ?, [abc]).
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the restore. Optional.
# Array of namespaces to exclude from the restore. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the restore. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -0,0 +1,71 @@
---
title: "Namespace Glob Patterns"
layout: docs
---
When using `--include-namespaces` and `--exclude-namespaces` flags with backup and restore commands, you can use glob patterns to match multiple namespaces.
## Supported Patterns
Velero supports the following glob pattern characters:
- `*` - Matches any sequence of characters
```bash
velero backup create my-backup --include-namespaces "app-*"
# Matches: app-prod, app-staging, app-dev, etc.
```
- `?` - Matches exactly one character
```bash
velero backup create my-backup --include-namespaces "ns?"
# Matches: ns1, ns2, nsa, but NOT ns10
```
- `[abc]` - Matches any single character in the brackets
```bash
velero backup create my-backup --include-namespaces "ns[123]"
# Matches: ns1, ns2, ns3
```
- `[a-z]` - Matches any single character in the range
```bash
velero backup create my-backup --include-namespaces "ns[a-c]"
# Matches: nsa, nsb, nsc
```
## Unsupported Patterns
The following patterns are **not supported** and will cause validation errors:
- `**` - Consecutive asterisks
- `|` - Alternation (regex operator)
- `()` - Grouping (regex operators)
- `!` - Negation
- `{}` - Brace expansion
- `,` - Comma (used in brace expansion)
## Special Cases
- `*` alone means "all namespaces" and is not expanded
- Empty brackets `[]` are invalid
- Unmatched or unclosed brackets will cause validation errors
## Examples
Combine patterns with include and exclude flags:
```bash
# Backup all production namespaces except test
velero backup create prod-backup \
--include-namespaces "*-prod" \
--exclude-namespaces "test-*"
# Backup specific numbered namespaces
velero backup create numbered-backup \
--include-namespaces "app-[0-9]"
# Restore namespaces matching multiple patterns
velero restore create my-restore \
--from-backup my-backup \
--include-namespaces "frontend-*,backend-*"
```

View File

@@ -17,7 +17,11 @@ Wildcard takes precedence when both a wildcard and specific resource are include
### --include-namespaces
Namespaces to include. Default is `*`, all namespaces.
Namespaces to include. Accepts glob patterns (`*`, `?`, `[abc]`). Default is `*`, all namespaces.
See [Namespace Glob Patterns](namespace-glob-patterns) for more details on supported patterns.
Note: `*` alone is reserved for empty fields, which means all namespaces.
* Backup a namespace and it's objects.
@@ -158,7 +162,9 @@ Wildcard excludes are ignored.
### --exclude-namespaces
Namespaces to exclude.
Namespaces to exclude. Accepts glob patterns (`*`, `?`, `[abc]`).
See [Namespace Glob Patterns](namespace-glob-patterns) for more details on supported patterns.
* Exclude kube-system from the cluster backup.

View File

@@ -33,6 +33,8 @@ toc:
url: /enable-api-group-versions-feature
- page: Resource filtering
url: /resource-filtering
- page: Namespace glob patterns
url: /namespace-glob-patterns
- page: Backup reference
url: /backup-reference
- page: Backup hooks

View File

@@ -33,6 +33,8 @@ toc:
url: /enable-api-group-versions-feature
- page: Resource filtering
url: /resource-filtering
- page: Namespace glob patterns
url: /namespace-glob-patterns
- page: Backup reference
url: /backup-reference
- page: Backup hooks